diff --git a/.eslintrc.js b/.eslintrc.js index dfe779b4683523..6626a55a762d1c 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -4,21 +4,6 @@ const glob = require( 'glob' ).sync; const { join } = require( 'path' ); -/** - * Internal dependencies - */ -const { version } = require( './package' ); - -/** - * Regular expression string matching a SemVer string with equal major/minor to - * the current package version. Used in identifying deprecations. - * - * @type {string} - */ -const majorMinorRegExp = - version.replace( /\.\d+$/, '' ).replace( /[\\^$.*+?()[\]{}|]/g, '\\$&' ) + - '(\\.\\d+)?'; - /** * The list of patterns matching files used only for development purposes. * @@ -92,14 +77,6 @@ const restrictedSyntax = [ '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"]', @@ -137,6 +114,11 @@ const restrictedSyntax = [ message: 'Avoid truthy checks on length property rendering, as zero length is rendered verbatim.', }, + { + selector: + 'CallExpression[callee.name=/^(__|_x|_n|_nx)$/] > Literal[value=/^toggle\\b/i]', + message: "Avoid using the verb 'Toggle' in translatable strings", + }, ]; /** `no-restricted-syntax` rules for components. */ @@ -156,6 +138,7 @@ module.exports = { 'plugin:eslint-comments/recommended', 'plugin:storybook/recommended', ], + plugins: [ 'react-compiler' ], globals: { wp: 'off', globalThis: 'readonly', @@ -222,6 +205,15 @@ module.exports = { definedTags: [ 'jest-environment' ], }, ], + 'react-compiler/react-compiler': [ + 'error', + { + environment: { + enableTreatRefLikeIdentifiersAsRefs: true, + validateRefAccessDuringRender: false, + }, + }, + ], }, overrides: [ { @@ -236,6 +228,7 @@ module.exports = { 'import/no-unresolved': 'off', 'import/named': 'off', '@wordpress/data-no-store-string-literals': 'off', + 'react-compiler/react-compiler': 'off', }, }, { @@ -551,6 +544,7 @@ module.exports = { { files: [ 'packages/interactivity*/src/**' ], rules: { + 'react-compiler/react-compiler': 'off', 'react/react-in-jsx-scope': 'error', }, }, diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 2ec03cba722c6b..f86afa25ae4bdc 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -13,8 +13,8 @@ /packages/data-controls @nerrad # Blocks -/packages/block-library @ajitbohra -/packages/block-library/src/gallery @geriux +/packages/block-library @ajitbohra @fabiankaegy +/packages/block-library/src/gallery /packages/block-library/src/comment-template @michalczaplinski /packages/block-library/src/comments @michalczaplinski /packages/block-library/src/table-of-contents @ZebulanStanphill @@ -138,10 +138,7 @@ /lib/compat/*/html-api @dmsnell /lib/experimental/rest-api.php @timothybjacobs /lib/experimental/class-wp-rest-* @timothybjacobs -/lib/experimental/class-wp-rest-block-editor-settings-controller.php @timothybjacobs @spacedmonkey @geriux - -# Native -/packages/components/src/mobile/global-styles-context @geriux +/lib/experimental/class-wp-rest-block-editor-settings-controller.php @timothybjacobs @spacedmonkey # Native (Unowned) *.native.js diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index fd63e5e2e5312e..69fd34d709bdc5 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -20,3 +20,9 @@ https://github.com/WordPress/gutenberg/blob/trunk/CONTRIBUTING.md --> ## Screenshots or screencast + + + +|Before|After| +|-|-| +||| diff --git a/.github/workflows/gradle-wrapper-validation.yml b/.github/workflows/gradle-wrapper-validation.yml index 54378765bd26ff..c4c5eeba9c51a7 100644 --- a/.github/workflows/gradle-wrapper-validation.yml +++ b/.github/workflows/gradle-wrapper-validation.yml @@ -6,7 +6,9 @@ jobs: name: 'Validation' runs-on: ubuntu-latest steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: show-progress: ${{ runner.debug == '1' && 'true' || 'false' }} - - uses: gradle/wrapper-validation-action@v3 + - name: Validate checksums + uses: gradle/actions/wrapper-validation@0bdd871935719febd78681f197cd39af5b6e16a6 # v4.2.2 diff --git a/.github/workflows/performance.yml b/.github/workflows/performance.yml index 6c8c984602edcb..4a5b576b424b53 100644 --- a/.github/workflows/performance.yml +++ b/.github/workflows/performance.yml @@ -69,13 +69,13 @@ jobs: - name: Compare performance with base branch if: github.event_name == 'push' # The base hash used here need to be a commit that is compatible with the current WP version - # The current one is 5f4c9c853b15092ed885d5280edefb973c37d9e9 and it needs to be updated every WP major release. + # The current one is c7722262e65a3f4d0f1a2d1ad29eccb2069509e4 and it needs to be updated every WP major release. # It is used as a base comparison point to avoid fluctuation in the performance metrics. run: | WP_VERSION=$(awk -F ': ' '/^Tested up to/{print $2}' readme.txt) IFS=. read -ra WP_VERSION_ARRAY <<< "$WP_VERSION" WP_MAJOR="${WP_VERSION_ARRAY[0]}.${WP_VERSION_ARRAY[1]}" - ./bin/plugin/cli.js perf $GITHUB_SHA 5f4c9c853b15092ed885d5280edefb973c37d9e9 --tests-branch $GITHUB_SHA --wp-version "$WP_MAJOR" + ./bin/plugin/cli.js perf $GITHUB_SHA c7722262e65a3f4d0f1a2d1ad29eccb2069509e4 --tests-branch $GITHUB_SHA --wp-version "$WP_MAJOR" - name: Compare performance with custom branches if: github.event_name == 'workflow_dispatch' @@ -101,7 +101,7 @@ jobs: CODEHEALTH_PROJECT_TOKEN: ${{ secrets.CODEHEALTH_PROJECT_TOKEN }} run: | COMMITTED_AT=$(git show -s $GITHUB_SHA --format="%cI") - ./bin/log-performance-results.js $CODEHEALTH_PROJECT_TOKEN trunk $GITHUB_SHA 5f4c9c853b15092ed885d5280edefb973c37d9e9 $COMMITTED_AT + ./bin/log-performance-results.js $CODEHEALTH_PROJECT_TOKEN trunk $GITHUB_SHA c7722262e65a3f4d0f1a2d1ad29eccb2069509e4 $COMMITTED_AT - name: Archive debug artifacts (screenshots, HTML snapshots) uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 diff --git a/.github/workflows/rnmobile-android-runner.yml b/.github/workflows/rnmobile-android-runner.yml index 43a71809b5bbe7..4989239286462f 100644 --- a/.github/workflows/rnmobile-android-runner.yml +++ b/.github/workflows/rnmobile-android-runner.yml @@ -14,8 +14,9 @@ concurrency: jobs: test: - runs-on: macos-12 - if: ${{ github.repository == 'WordPress/gutenberg' || github.event_name == 'pull_request' }} + runs-on: macos-13 + if: false + #if: ${{ github.repository == 'WordPress/gutenberg' || github.event_name == 'pull_request' }} strategy: matrix: native-test-name: [gutenberg-editor-rendering] @@ -28,7 +29,7 @@ jobs: show-progress: ${{ runner.debug == '1' && 'true' || 'false' }} - name: Use desired version of Java - uses: actions/setup-java@8df1039502a15bceb9433410b1a100fbe190c53b # v4.5.0 + uses: actions/setup-java@7a6d8a8234af8eb26422e24e3006232cccaa061b # v4.6.0 with: distribution: 'corretto' java-version: '17' @@ -37,7 +38,7 @@ jobs: uses: ./.github/setup-node - name: Restore tests setup cache - uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2 + uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 with: path: | ~/.appium @@ -47,12 +48,12 @@ jobs: run: npm run native test:e2e:setup - name: Gradle cache - uses: gradle/actions/setup-gradle@473878a77f1b98e2b5ac4af93489d1656a80a5ed # v4.2.0 + uses: gradle/actions/setup-gradle@0bdd871935719febd78681f197cd39af5b6e16a6 # v4.2.2 # AVD cache disabled as it caused emulator termination to hang indefinitely. # https://github.com/ReactiveCircus/android-emulator-runner/issues/385 # - name: AVD cache - # uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2 + # uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 # id: avd-cache # with: # path: | diff --git a/.github/workflows/rnmobile-ios-runner.yml b/.github/workflows/rnmobile-ios-runner.yml index d28ee65c719e43..b6d796b1108ff0 100644 --- a/.github/workflows/rnmobile-ios-runner.yml +++ b/.github/workflows/rnmobile-ios-runner.yml @@ -14,8 +14,9 @@ concurrency: jobs: test: - runs-on: macos-12 - if: ${{ github.repository == 'WordPress/gutenberg' || github.event_name == 'pull_request' }} + runs-on: macos-13 + if: false + #if: ${{ github.repository == 'WordPress/gutenberg' || github.event_name == 'pull_request' }} strategy: matrix: xcode: ['14.2'] @@ -27,7 +28,7 @@ jobs: with: show-progress: ${{ runner.debug == '1' && 'true' || 'false' }} - - uses: ruby/setup-ruby@a2bbe5b1b236842c1cb7dd11e8e3b51e0a616acc # v1.202.0 + - uses: ruby/setup-ruby@7a6302104fbeea3c6aaa43b1b91e08f7d6623279 # v1.209.0 with: # `.ruby-version` file location working-directory: packages/react-native-editor/ios @@ -42,7 +43,7 @@ jobs: uses: ./.github/setup-node - name: Restore tests setup cache - uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2 + uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 with: path: | ~/.appium @@ -55,7 +56,7 @@ jobs: run: find package-lock.json packages/react-native-editor/ios packages/react-native-aztec/ios packages/react-native-bridge/ios -type f -print0 | sort -z | xargs -0 shasum | tee ios-checksums.txt - name: Restore build cache - uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2 + uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 with: path: | packages/react-native-editor/ios/build/GutenbergDemo/Build/Products/Release-iphonesimulator/GutenbergDemo.app @@ -63,7 +64,7 @@ jobs: key: ${{ runner.os }}-ios-build-${{ matrix.xcode }}-${{ matrix.device }}-${{ hashFiles('ios-checksums.txt') }} - name: Restore pods cache - uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2 + uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 with: path: | packages/react-native-editor/ios/Pods diff --git a/.github/workflows/storybook-check.yml b/.github/workflows/storybook-check.yml new file mode 100644 index 00000000000000..dd710f96747128 --- /dev/null +++ b/.github/workflows/storybook-check.yml @@ -0,0 +1,27 @@ +name: Check Storybook build + +on: pull_request + +# Cancels all previous workflow runs for pull requests that have not completed. +concurrency: + # The concurrency group contains the workflow name and the branch name for pull requests + # or the commit hash for any other events. + group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.head_ref || github.sha }} + cancel-in-progress: true + +jobs: + check: + runs-on: ubuntu-latest + if: ${{ github.repository == 'WordPress/gutenberg' || github.event_name == 'pull_request' }} + + steps: + - name: Checkout + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + show-progress: ${{ runner.debug == '1' && 'true' || 'false' }} + + - name: Setup Node.js and install dependencies + uses: ./.github/setup-node + + - name: Build Storybook + run: npm run storybook:build diff --git a/.github/workflows/sync-assets-to-plugin-repo.yml b/.github/workflows/sync-assets-to-plugin-repo.yml new file mode 100644 index 00000000000000..c841b3ffc79579 --- /dev/null +++ b/.github/workflows/sync-assets-to-plugin-repo.yml @@ -0,0 +1,48 @@ +name: Sync Gutenberg plugin assets to WordPress.org plugin repo + +on: + push: + branches: + - trunk + paths: + - assets/** + +jobs: + sync-assets: + name: Sync assets to WordPress.org plugin repo + runs-on: ubuntu-latest + environment: wp.org plugin + env: + PLUGIN_REPO_URL: 'https://plugins.svn.wordpress.org/gutenberg' + SVN_USERNAME: ${{ secrets.SVN_USERNAME }} + SVN_PASSWORD: ${{ secrets.SVN_PASSWORD }} + + steps: + - name: Check out Gutenberg assets folder from WP.org plugin repo + run: | + svn checkout "$PLUGIN_REPO_URL/assets" \ + --username "$SVN_USERNAME" --password "$SVN_PASSWORD" + + - name: Delete everything + run: find assets -type f -not -path 'assets/.svn/*' -delete + + - name: Checkout assets from current release + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + sparse-checkout: | + assets + show-progress: ${{ runner.debug == '1' && 'true' || 'false' }} + path: git + + - name: Copy files from git checkout to svn working copy + run: cp -R git/assets/* assets + + - name: Commit the updated assets + working-directory: ./assets + run: | + svn st | awk '/^?/ {print $2}' | xargs -r svn add + svn st | awk '/^!/ {print $2}' | xargs -r svn rm + svn commit . \ + -m "Sync assets for commit $GITHUB_SHA" \ + --no-auth-cache --non-interactive --username "$SVN_USERNAME" --password "$SVN_PASSWORD" \ + --config-option=servers:global:http-timeout=600 diff --git a/.github/workflows/unit-test.yml b/.github/workflows/unit-test.yml index 46aa109c23e658..b5c5e2255da5e2 100644 --- a/.github/workflows/unit-test.yml +++ b/.github/workflows/unit-test.yml @@ -188,7 +188,7 @@ jobs: # dependency versions are installed and cached. ## - name: Set up PHP - uses: shivammathur/setup-php@c541c155eee45413f5b09a52248675b1a2575231 # v2.31.1 + uses: shivammathur/setup-php@9e72090525849c5e82e596468b86eb55e9cc5401 # v2.32.0 with: php-version: '${{ matrix.php }}' ini-file: development @@ -283,7 +283,7 @@ jobs: show-progress: ${{ runner.debug == '1' && 'true' || 'false' }} - name: Set up PHP - uses: shivammathur/setup-php@c541c155eee45413f5b09a52248675b1a2575231 # v2.31.1 + uses: shivammathur/setup-php@9e72090525849c5e82e596468b86eb55e9cc5401 # v2.32.0 with: php-version: '7.4' coverage: none @@ -296,7 +296,7 @@ jobs: run: echo "date=$(/bin/date -u --date='last Mon' "+%F")" >> $GITHUB_OUTPUT - name: Cache PHPCS scan cache - uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2 + uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 with: path: .cache/phpcs.json key: ${{ runner.os }}-date-${{ steps.get-date.outputs.date }}-phpcs-cache-${{ hashFiles('**/composer.json', 'phpcs.xml.dist') }} diff --git a/.github/workflows/upload-release-to-plugin-repo.yml b/.github/workflows/upload-release-to-plugin-repo.yml index e866964e69b2d1..4d2b0a66a7e7d6 100644 --- a/.github/workflows/upload-release-to-plugin-repo.yml +++ b/.github/workflows/upload-release-to-plugin-repo.yml @@ -168,7 +168,9 @@ jobs: steps: - name: Check out Gutenberg trunk from WP.org plugin repo - run: svn checkout "$PLUGIN_REPO_URL/trunk" --username "$SVN_USERNAME" --password "$SVN_PASSWORD" + run: | + svn checkout "$PLUGIN_REPO_URL/trunk" --username "$SVN_USERNAME" --password "$SVN_PASSWORD" + svn checkout "$PLUGIN_REPO_URL/tags" --depth=immediates --username "$SVN_USERNAME" --password "$SVN_PASSWORD" - name: Delete everything working-directory: ./trunk @@ -182,7 +184,7 @@ jobs: unzip gutenberg.zip -d trunk rm gutenberg.zip - - name: Replace the stable tag placeholder with the existing stable tag on the SVN repository + - name: Replace the stable tag placeholder with the new version env: STABLE_TAG_PLACEHOLDER: 'Stable tag: V\.V\.V' run: | @@ -194,27 +196,16 @@ jobs: name: changelog trunk path: trunk - - name: Commit the content changes + - name: Commit the release working-directory: ./trunk run: | svn st | grep '^?' | awk '{print $2}' | xargs -r svn add svn st | grep '^!' | awk '{print $2}' | xargs -r svn rm - svn commit -m "Committing version $VERSION" \ + svn cp . "../tags/$VERSION" + svn commit . "../tags/$VERSION" \ + -m "Releasing version $VERSION" \ --no-auth-cache --non-interactive --username "$SVN_USERNAME" --password "$SVN_PASSWORD" \ - --config-option=servers:global:http-timeout=300 - - - name: Create the SVN tag - working-directory: ./trunk - run: | - svn copy "$PLUGIN_REPO_URL/trunk" "$PLUGIN_REPO_URL/tags/$VERSION" -m "Tagging version $VERSION" \ - --no-auth-cache --non-interactive --username "$SVN_USERNAME" --password "$SVN_PASSWORD" - - - name: Update the plugin's stable version - working-directory: ./trunk - run: | - sed -i "s/Stable tag: ${STABLE_VERSION_REGEX}/Stable tag: ${VERSION}/g" ./readme.txt - svn commit -m "Releasing version $VERSION" \ - --no-auth-cache --non-interactive --username "$SVN_USERNAME" --password "$SVN_PASSWORD" + --config-option=servers:global:http-timeout=600 upload-tag: name: Publish as tag diff --git a/.gitignore b/.gitignore index 4cd1d9706b7370..9e7e4333af8689 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ build build-module build-style build-types +build-wp node_modules gutenberg.zip coverage @@ -18,6 +19,7 @@ results /test/e2e/artifacts /perf-envs /composer.lock +/ts-traces # The /.cache folder is needed for phpcs to cache results between runs, while other .cache folders must be ignored # It is not possible to re-include a file if a parent directory of that file is excluded diff --git a/.wp-env.json b/.wp-env.json index 05ea05b2809f9c..d368f3ea1c72a6 100644 --- a/.wp-env.json +++ b/.wp-env.json @@ -4,6 +4,9 @@ "plugins": [ "." ], "themes": [ "./test/emptytheme" ], "env": { + "development": { + "phpmyadminPort": 9000 + }, "tests": { "mappings": { "wp-content/plugins/gutenberg": ".", diff --git a/LICENSE.md b/LICENSE.md index 983294723c4806..12a05f0c071a68 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,6 +1,6 @@ ## Gutenberg - Copyright 2016-2024 by the contributors + Copyright 2016-2025 by the contributors **License for Contributions (on and after April 15, 2021)** diff --git a/assets/README.md b/assets/README.md new file mode 100644 index 00000000000000..e437ec744d3807 --- /dev/null +++ b/assets/README.md @@ -0,0 +1,7 @@ +## Gutenberg Plugin Assets + +The contents of this directory are synced from the [`assets/` directory in the Gutenberg repository on GitHub](https://github.com/WordPress/gutenberg/tree/trunk/assets) to the [`assets/` directory of the Gutenberg WordPress.org plugin repository](https://plugins.trac.wordpress.org/browser/gutenberg/assets). **Any changes committed directly to the plugin repository on WordPress.org will be overwritten.** + +The sync is performed by a [GitHub Actions workflow](https://github.com/WordPress/gutenberg/actions/workflows/sync-assets-to-plugin-repo.yml) that is triggered whenever a file in this directory is changed. + +Since that workflow requires access to WP.org plugin repository credentials, it needs to be approved manually by a member of the Gutenberg Core team. If you don't have the necessary permissions, please ask someone in [#core-editor](https://wordpress.slack.com/archives/C02QB2JS7). diff --git a/assets/banner-1544x500.jpg b/assets/banner-1544x500.jpg new file mode 100644 index 00000000000000..12e7192dd4285e Binary files /dev/null and b/assets/banner-1544x500.jpg differ diff --git a/assets/banner-772x250.jpg b/assets/banner-772x250.jpg new file mode 100644 index 00000000000000..316f7741071cbe Binary files /dev/null and b/assets/banner-772x250.jpg differ diff --git a/assets/blueprints/blueprint.json b/assets/blueprints/blueprint.json new file mode 100644 index 00000000000000..d0626ffc15dd90 --- /dev/null +++ b/assets/blueprints/blueprint.json @@ -0,0 +1,28 @@ +{ + "$schema": "https://playground.wordpress.net/blueprint-schema.json", + "landingPage": "/wp-admin/post.php?post=1&action=edit", + "plugins": [ "gutenberg" ], + "login": true, + "features": { + "networking": true + }, + "preferredVersions": { + "php": "latest", + "wp": "latest" + }, + "steps": [ + { + "step": "setSiteOptions", + "options": { + "blogname": "Testing Gutenberg" + } + }, + { + "step": "updateUserMeta", + "meta": { + "admin_color": "modern" + }, + "userId": 1 + } + ] +} diff --git a/assets/icon-128x128.jpg b/assets/icon-128x128.jpg new file mode 100644 index 00000000000000..051af8504a919b Binary files /dev/null and b/assets/icon-128x128.jpg differ diff --git a/assets/icon-256x256.jpg b/assets/icon-256x256.jpg new file mode 100644 index 00000000000000..b7497f61652b7b Binary files /dev/null and b/assets/icon-256x256.jpg differ diff --git a/backport-changelog/6.8/7069.md b/backport-changelog/6.8/7069.md deleted file mode 100644 index ea3c717ec3c93a..00000000000000 --- a/backport-changelog/6.8/7069.md +++ /dev/null @@ -1,3 +0,0 @@ -https://github.com/WordPress/wordpress-develop/pull/7069 - -* https://github.com/WordPress/gutenberg/pull/63401 diff --git a/backport-changelog/6.8/7129.md b/backport-changelog/6.8/7129.md new file mode 100644 index 00000000000000..301f1abc45d0d7 --- /dev/null +++ b/backport-changelog/6.8/7129.md @@ -0,0 +1,4 @@ +https://github.com/WordPress/wordpress-develop/pull/7129 + +* https://github.com/WordPress/gutenberg/pull/62304 +* https://github.com/WordPress/gutenberg/pull/67879 diff --git a/backport-changelog/6.8/7687.md b/backport-changelog/6.8/7687.md index f1505645df20c6..0b5af190964df1 100644 --- a/backport-changelog/6.8/7687.md +++ b/backport-changelog/6.8/7687.md @@ -1,3 +1,4 @@ https://github.com/WordPress/wordpress-develop/pull/7687 * https://github.com/WordPress/gutenberg/pull/66488 +* https://github.com/WordPress/gutenberg/pull/67497 diff --git a/backport-changelog/6.8/7695.md b/backport-changelog/6.8/7695.md index 095c058e6fd10b..08b780e2afb0d7 100644 --- a/backport-changelog/6.8/7695.md +++ b/backport-changelog/6.8/7695.md @@ -1,3 +1,7 @@ https://github.com/WordPress/wordpress-develop/pull/7695 * https://github.com/WordPress/gutenberg/pull/66631 +* https://github.com/WordPress/gutenberg/pull/67465 +* https://github.com/WordPress/gutenberg/pull/66579 +* https://github.com/WordPress/gutenberg/pull/66654 +* https://github.com/WordPress/gutenberg/pull/67518 diff --git a/backport-changelog/6.8/7825.md b/backport-changelog/6.8/7825.md new file mode 100644 index 00000000000000..42d09c86b7f3ba --- /dev/null +++ b/backport-changelog/6.8/7825.md @@ -0,0 +1,3 @@ +https://github.com/WordPress/wordpress-develop/pull/7825 + +* https://github.com/WordPress/gutenberg/pull/67061 diff --git a/backport-changelog/6.8/7848.md b/backport-changelog/6.8/7848.md new file mode 100644 index 00000000000000..84600eb4847cdb --- /dev/null +++ b/backport-changelog/6.8/7848.md @@ -0,0 +1,3 @@ +https://github.com/WordPress/wordpress-develop/pull/7848 + +* https://github.com/WordPress/gutenberg/pull/67154 diff --git a/backport-changelog/6.8/7865.md b/backport-changelog/6.8/7865.md new file mode 100644 index 00000000000000..b5de24b8ee63d3 --- /dev/null +++ b/backport-changelog/6.8/7865.md @@ -0,0 +1,4 @@ +https://github.com/WordPress/wordpress-develop/pull/7865 + +* https://github.com/WordPress/gutenberg/pull/66851 +* https://github.com/WordPress/gutenberg/pull/68174 diff --git a/backport-changelog/6.8/7895.md b/backport-changelog/6.8/7895.md new file mode 100644 index 00000000000000..4750ab545ada3b --- /dev/null +++ b/backport-changelog/6.8/7895.md @@ -0,0 +1,3 @@ +https://github.com/WordPress/wordpress-develop/pull/7895 + +* https://github.com/WordPress/gutenberg/pull/66459 diff --git a/backport-changelog/6.8/7898.md b/backport-changelog/6.8/7898.md new file mode 100644 index 00000000000000..d824c5da82ec1b --- /dev/null +++ b/backport-changelog/6.8/7898.md @@ -0,0 +1,3 @@ +https://github.com/WordPress/wordpress-develop/pull/7898 + +* https://github.com/WordPress/gutenberg/pull/67272 diff --git a/backport-changelog/6.8/7903.md b/backport-changelog/6.8/7903.md new file mode 100644 index 00000000000000..cb20d8d2dd2b1b --- /dev/null +++ b/backport-changelog/6.8/7903.md @@ -0,0 +1,3 @@ +https://github.com/WordPress/wordpress-develop/pull/7903 + +* https://github.com/WordPress/gutenberg/pull/67199 diff --git a/backport-changelog/6.8/7909.md b/backport-changelog/6.8/7909.md new file mode 100644 index 00000000000000..32a441ef296a2d --- /dev/null +++ b/backport-changelog/6.8/7909.md @@ -0,0 +1,3 @@ +https://github.com/WordPress/wordpress-develop/pull/7909 + +* https://github.com/WordPress/gutenberg/pull/67330 diff --git a/backport-changelog/6.8/7976.md b/backport-changelog/6.8/7976.md new file mode 100644 index 00000000000000..e2942d5e4fbe15 --- /dev/null +++ b/backport-changelog/6.8/7976.md @@ -0,0 +1,3 @@ +https://github.com/WordPress/wordpress-develop/pull/7976 + +* https://github.com/WordPress/gutenberg/pull/67716 \ No newline at end of file diff --git a/backport-changelog/6.8/8014.md b/backport-changelog/6.8/8014.md new file mode 100644 index 00000000000000..3ff171d5fb367e --- /dev/null +++ b/backport-changelog/6.8/8014.md @@ -0,0 +1,3 @@ +https://github.com/WordPress/wordpress-develop/pull/8014 + +* https://github.com/WordPress/gutenberg/pull/66479 diff --git a/backport-changelog/6.8/8015.md b/backport-changelog/6.8/8015.md new file mode 100644 index 00000000000000..214705518a0e72 --- /dev/null +++ b/backport-changelog/6.8/8015.md @@ -0,0 +1,3 @@ +https://github.com/WordPress/wordpress-develop/pull/8015 + +* https://github.com/WordPress/gutenberg/pull/68058 diff --git a/backport-changelog/6.8/8031.md b/backport-changelog/6.8/8031.md new file mode 100644 index 00000000000000..864dd7562cdf36 --- /dev/null +++ b/backport-changelog/6.8/8031.md @@ -0,0 +1,4 @@ +https://github.com/WordPress/wordpress-develop/pull/8031 + +* https://github.com/WordPress/gutenberg/pull/66675 +* https://github.com/WordPress/gutenberg/pull/68243 diff --git a/backport-changelog/6.8/8032.md b/backport-changelog/6.8/8032.md new file mode 100644 index 00000000000000..4d2ad5fae5a382 --- /dev/null +++ b/backport-changelog/6.8/8032.md @@ -0,0 +1,3 @@ +https://github.com/WordPress/wordpress-develop/pull/8032 + +* https://github.com/WordPress/gutenberg/pull/68003 diff --git a/backport-changelog/6.8/8123.md b/backport-changelog/6.8/8123.md new file mode 100644 index 00000000000000..7955ec77416853 --- /dev/null +++ b/backport-changelog/6.8/8123.md @@ -0,0 +1,4 @@ +https://github.com/WordPress/wordpress-develop/pull/8123 + +* https://github.com/WordPress/gutenberg/pull/68549 +* https://github.com/WordPress/gutenberg/pull/68745 diff --git a/backport-changelog/readme.md b/backport-changelog/readme.md index 8066cc6a6fca24..02b1983dd38e19 100644 --- a/backport-changelog/readme.md +++ b/backport-changelog/readme.md @@ -20,7 +20,7 @@ The filename is the Core PR number. For example, if your Core PR number is `1234` and is slated to be part of the WordPress 6.9 release, the filename will be `1234.md`, and will be placed in the `/backport-changelog/6.9` directory. -The content of the markdown file should be the Github URL of the Core PR, followed by a list of Gutenberg PR Github URLs whose changes are backported in the Core PR. +The content of the markdown file should be the GitHub URL of the Core PR, followed by a list of Gutenberg PR GitHub URLs whose changes are backported in the Core PR. A single Core PR may contain changes from one or multiple Gutenberg PRs. @@ -51,7 +51,7 @@ For the backport changelog, Gutenberg uses individual files as opposed to a sing Some Gutenberg PRs may be flagged as needing a core backport PR when they don't, for example when the PR contains minor comment changes, or the changes already exist in Core. -For individual PRs, there are two Github labels that can be used to exclude a PR from the backport changelog CI check: +For individual PRs, there are two GitHub labels that can be used to exclude a PR from the backport changelog CI check: - `Backport from WordPress Core` - Indicates that the PR is a backport from WordPress Core and doesn't need a Core PR. - `No Core Sync Required` - Indicates that any changes do not need to be synced to WordPress Core. diff --git a/bin/api-docs/gen-block-lib-list.js b/bin/api-docs/gen-block-lib-list.js index 0c79def1989992..309a3931b12189 100644 --- a/bin/api-docs/gen-block-lib-list.js +++ b/bin/api-docs/gen-block-lib-list.js @@ -108,12 +108,12 @@ function processObjWithInnerKeys( obj ) { * not disabled. So adding { color: 'link' } support also brings along * background and text. * - * @param {Object} supports - keys supported by blokc + * @param {Object} supports - keys supported by block * @return {Object} supports augmented with defaults */ function augmentSupports( supports ) { if ( 'color' in supports ) { - // If backgroud or text is not specified (true or false) + // If background or text is not specified (true or false) // then add it as true.a if ( ! ( 'background' in supports.color ) ) { supports.color.background = true; diff --git a/bin/api-docs/gen-components-docs/get-tags-from-storybook.mjs b/bin/api-docs/gen-components-docs/get-tags-from-storybook.mjs new file mode 100644 index 00000000000000..84d7beaf1e4076 --- /dev/null +++ b/bin/api-docs/gen-components-docs/get-tags-from-storybook.mjs @@ -0,0 +1,30 @@ +/** + * External dependencies + */ +import fs from 'node:fs/promises'; +import babel from '@babel/core'; + +/** + * Returns `meta.tags` from a Storybook file. + * + * @param {string} filePath + * @return {Promise} Array of tags. + */ +export async function getTagsFromStorybook( filePath ) { + const fileContent = await fs.readFile( filePath, 'utf8' ); + const parsedFile = babel.parse( fileContent, { + filename: filePath, + } ); + + const meta = parsedFile.program.body.find( + ( node ) => + node.type === 'VariableDeclaration' && + node.declarations[ 0 ].id.name === 'meta' + ); + + return ( + meta.declarations[ 0 ].init.properties + .find( ( node ) => node.key.name === 'tags' ) + ?.value.elements.map( ( node ) => node.value ) ?? [] + ); +} diff --git a/bin/api-docs/gen-components-docs/index.mjs b/bin/api-docs/gen-components-docs/index.mjs index c7109dc4982c36..30888acf851cab 100644 --- a/bin/api-docs/gen-components-docs/index.mjs +++ b/bin/api-docs/gen-components-docs/index.mjs @@ -11,6 +11,7 @@ import path from 'path'; */ import { generateMarkdownDocs } from './markdown/index.mjs'; import { getDescriptionsForSubcomponents } from './get-subcomponent-descriptions.mjs'; +import { getTagsFromStorybook } from './get-tags-from-storybook.mjs'; const MANIFEST_GLOB = 'packages/components/src/**/docs-manifest.json'; @@ -113,9 +114,17 @@ await Promise.all( } ) ?? [] ); + const tags = await getTagsFromStorybook( + path.resolve( + path.dirname( manifestPath ), + 'stories/index.story.tsx' + ) + ); + const docs = generateMarkdownDocs( { typeDocs, subcomponentTypeDocs, + tags, } ); const outputFile = path.resolve( path.dirname( manifestPath ), diff --git a/bin/api-docs/gen-components-docs/markdown/index.mjs b/bin/api-docs/gen-components-docs/markdown/index.mjs index 126fdf0057b6e5..28e20dc3de12e0 100644 --- a/bin/api-docs/gen-components-docs/markdown/index.mjs +++ b/bin/api-docs/gen-components-docs/markdown/index.mjs @@ -8,14 +8,33 @@ import json2md from 'json2md'; */ import { generateMarkdownPropsJson } from './props.mjs'; -export function generateMarkdownDocs( { typeDocs, subcomponentTypeDocs } ) { +/** + * Converter for strings that are already formatted as Markdown. + * + * @param {string} [input] + * @return {string} The trimmed input if it is contentful, otherwise an empty string. + */ +json2md.converters.md = ( input ) => { + return input?.trim() || ''; +}; + +export function generateMarkdownDocs( { + typeDocs, + subcomponentTypeDocs, + tags, +} ) { const mainDocsJson = [ { h1: typeDocs.displayName }, '', + tags.includes( 'status-private' ) && [ + { + p: '🔒 This component is locked as a [private API](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-private-apis/). We do not yet recommend using this outside of the Gutenberg project.', + }, + ], { p: `

See the WordPress Storybook for more detailed, interactive documentation.

`, }, - typeDocs.description, + { md: typeDocs.description }, ...generateMarkdownPropsJson( typeDocs.props ), ]; @@ -26,7 +45,7 @@ export function generateMarkdownDocs( { typeDocs, subcomponentTypeDocs } ) { { h3: subcomponentTypeDoc.displayName, }, - subcomponentTypeDoc.description, + { md: subcomponentTypeDoc.description }, ...generateMarkdownPropsJson( subcomponentTypeDoc.props, { headingLevel: 4, } ), @@ -36,5 +55,5 @@ export function generateMarkdownDocs( { typeDocs, subcomponentTypeDocs } ) { return json2md( [ ...mainDocsJson, ...subcomponentDocsJson ].filter( Boolean ) - ); + ).replace( /\n+$/gm, '\n' ); // clean unnecessary consecutive newlines } diff --git a/bin/api-docs/gen-components-docs/markdown/props.mjs b/bin/api-docs/gen-components-docs/markdown/props.mjs index 9d019c4240f008..bacd86256f7e6a 100644 --- a/bin/api-docs/gen-components-docs/markdown/props.mjs +++ b/bin/api-docs/gen-components-docs/markdown/props.mjs @@ -33,7 +33,6 @@ export function generateMarkdownPropsJson( props, { headingLevel = 2 } = {} ) { return [ { [ `h${ headingLevel + 1 }` ]: `\`${ key }\`` }, - prop.description, { ul: [ `Type: \`${ renderPropType( prop.type ) }\``, @@ -42,10 +41,10 @@ export function generateMarkdownPropsJson( props, { headingLevel = 2 } = {} ) { `Default: \`${ prop.defaultValue.value }\``, ].filter( Boolean ), }, + { md: prop.description }, ]; } ) .filter( Boolean ); return [ { [ `h${ headingLevel }` ]: 'Props' }, ...propsJson ]; } - diff --git a/bin/check-licenses.mjs b/bin/check-licenses.mjs index 458590e696a9fd..b453ebd84cd3a7 100755 --- a/bin/check-licenses.mjs +++ b/bin/check-licenses.mjs @@ -10,7 +10,7 @@ import { spawnSync } from 'node:child_process'; */ import { checkDepsInTree } from '../packages/scripts/utils/license.js'; -const ignored = [ '@ampproject/remapping' ]; +const ignored = [ '@ampproject/remapping', 'webpack' ]; /* * `wp-scripts check-licenses` uses prod and dev dependencies of the package to scan for dependencies. With npm workspaces, workspace packages (the @wordpress/* packages) are not listed in the main package json and this approach does not work. diff --git a/bin/generate-gutenberg-php.php b/bin/generate-gutenberg-php.php index 4ed6b661a21c23..1dc2cf6db4ef59 100755 --- a/bin/generate-gutenberg-php.php +++ b/bin/generate-gutenberg-php.php @@ -15,6 +15,8 @@ /** * Prints `define` statements for the production version of `gutenberg.php` * (the plugin entry point). + * + * @global string $plugin_version The version number of the plugin. */ function print_production_defines() { global $plugin_version; diff --git a/bin/plugin/commands/changelog.js b/bin/plugin/commands/changelog.js index eac0f7b268d5bf..edb81aa0ca6515 100644 --- a/bin/plugin/commands/changelog.js +++ b/bin/plugin/commands/changelog.js @@ -88,7 +88,7 @@ const LABEL_TYPE_MAPPING = { }; /** - * Mapping of label names to arbitary features in the release notes. + * Mapping of label names to arbitrary features in the release notes. * * Mapping a given label to a feature will guarantee it will be categorised * under that feature name in the changelog within each section. @@ -274,7 +274,7 @@ function mapLabelsToFeatures( labels ) { * * @param {string[]} labels Label names. * - * @return {boolean} whether or not the issue's is labbeled as block specific + * @return {boolean} whether or not the issue's is labeled as block specific */ function getIsBlockSpecificIssue( labels ) { return !! labels.find( ( label ) => label.startsWith( '[Block] ' ) ); @@ -343,7 +343,7 @@ function getIssueFeature( issue ) { // 1. Prefer explicit mapping of label to feature. if ( featureCandidates.length ) { - // Get occurances of the feature labels. + // Get occurrences of the feature labels. const featureCounts = featureCandidates.reduce( /** * @param {Record} acc Accumulator @@ -941,7 +941,7 @@ function skipCreatedByBots( pullRequests ) { } /** - * Produces the formatted markdown for the contributor props seciton. + * Produces the formatted markdown for the contributor props section. * * @param {IssuesListForRepoResponseItem[]} pullRequests List of pull requests. * diff --git a/bin/plugin/commands/packages.js b/bin/plugin/commands/packages.js index d70baf4f91bfa7..8beeccde719666 100644 --- a/bin/plugin/commands/packages.js +++ b/bin/plugin/commands/packages.js @@ -6,7 +6,7 @@ const path = require( 'path' ); const glob = require( 'fast-glob' ); const fs = require( 'fs' ); const { inc: semverInc } = require( 'semver' ); -const rimraf = require( 'rimraf' ); +const { rimraf } = require( 'rimraf' ); const readline = require( 'readline' ); const SimpleGit = require( 'simple-git' ); @@ -60,17 +60,6 @@ const pluginConfig = require( '../config' ); * @property {ReleaseType} releaseType The selected release type. */ -/** - * Throws if given an error in the node.js callback style. - * - * @param {any|null} error If callback failed, this will hold a value. - */ -const rethrow = ( error ) => { - if ( error ) { - throw error; - } -}; - /** * Checks out the npm release branch. * @@ -599,7 +588,7 @@ async function runPackagesRelease( config, customMessages ) { await Promise.all( temporaryFolders .filter( ( tempDir ) => fs.existsSync( tempDir ) ) - .map( ( tempDir ) => rimraf( tempDir, rethrow ) ) + .map( ( tempDir ) => rimraf( tempDir ) ) ) ); diff --git a/bin/plugin/commands/test/changelog.js b/bin/plugin/commands/test/changelog.js index 9c9d423d18d1cb..eb7e3377fe55ba 100644 --- a/bin/plugin/commands/test/changelog.js +++ b/bin/plugin/commands/test/changelog.js @@ -260,7 +260,7 @@ describe( 'getIssueFeature', () => { name: '[Package] This package', }, { - name: '[Feature] Cool Feature', // Should have priority despite prescence of block specific label. + name: '[Feature] Cool Feature', // Should have priority despite presence of block specific label. }, { name: '[Package] Another One', diff --git a/bin/plugin/lib/utils.js b/bin/plugin/lib/utils.js index 4f57269d60c772..f4ef86c96ff081 100644 --- a/bin/plugin/lib/utils.js +++ b/bin/plugin/lib/utils.js @@ -2,7 +2,7 @@ * External dependencies */ // @ts-ignore -const inquirer = require( 'inquirer' ); +const { confirm } = require( '@inquirer/prompts' ); const fs = require( 'fs' ); const childProcess = require( 'child_process' ); const { v4: uuid } = require( 'uuid' ); @@ -97,14 +97,19 @@ async function askForConfirmation( isDefault = true, abortMessage = 'Aborting.' ) { - const { isReady } = await inquirer.prompt( [ - { - type: 'confirm', - name: 'isReady', + let isReady = false; + try { + isReady = await confirm( { default: isDefault, message, - }, - ] ); + } ); + } catch ( error ) { + if ( error instanceof Error && error.name === 'ExitPromptError' ) { + console.log( 'Cancelled.' ); + process.exit( 1 ); + } + throw error; + } if ( ! isReady ) { log( formats.error( '\n' + abortMessage ) ); diff --git a/bin/test-create-block.sh b/bin/test-create-block.sh index 99b7e8e6082604..7df3b214af042d 100755 --- a/bin/test-create-block.sh +++ b/bin/test-create-block.sh @@ -56,7 +56,7 @@ if [ "$expected" -ne "$actual" ]; then exit 1 fi expected=7 -actual=$( find src -maxdepth 1 -type f | wc -l ) +actual=$( find src -maxdepth 2 -type f | wc -l ) if [ "$expected" -ne "$actual" ]; then error "Expected $expected files in the \`src\` directory, but found $actual." exit 1 @@ -70,7 +70,7 @@ status "Building block..." status "Verifying build..." expected=9 -actual=$( find build -maxdepth 1 -type f | wc -l ) +actual=$( find build -maxdepth 2 -type f | wc -l ) if [ "$expected" -ne "$actual" ]; then error "Expected $expected files in the \`build\` directory, but found $actual." exit 1 diff --git a/bin/tsconfig.json b/bin/tsconfig.json index 3ec5d5826a045d..4baf899c9dce9e 100644 --- a/bin/tsconfig.json +++ b/bin/tsconfig.json @@ -16,6 +16,7 @@ "noEmit": true, "outDir": ".cache" }, + "include": [], "files": [ "./api-docs/update-api-docs.js", "./plugin/config.js", diff --git a/bin/validate-tsconfig.mjs b/bin/validate-tsconfig.mjs index 91d74b1bdb413f..47d6a320d7290e 100755 --- a/bin/validate-tsconfig.mjs +++ b/bin/validate-tsconfig.mjs @@ -29,14 +29,33 @@ for ( const packageName of packagesWithTypes ) { hasErrors = true; } - const packageJson = JSON.parse( - readFileSync( `packages/${ packageName }/package.json`, 'utf8' ) - ); - const tsconfigJson = JSON.parse( - stripJsonComments( - readFileSync( `packages/${ packageName }/tsconfig.json`, 'utf8' ) - ) - ); + let packageJson; + try { + packageJson = JSON.parse( + readFileSync( `packages/${ packageName }/package.json`, 'utf8' ) + ); + } catch ( e ) { + console.error( + `Error parsing package.json for package ${ packageName }` + ); + throw e; + } + let tsconfigJson; + try { + tsconfigJson = JSON.parse( + stripJsonComments( + readFileSync( + `packages/${ packageName }/tsconfig.json`, + 'utf8' + ) + ) + ); + } catch ( e ) { + console.error( + `Error parsing tsconfig.json for package ${ packageName }` + ); + throw e; + } if ( packageJson.dependencies ) { for ( const dependency of Object.keys( packageJson.dependencies ) ) { if ( dependency.startsWith( '@wordpress/' ) ) { diff --git a/changelog.txt b/changelog.txt index af07058d4ddec5..fa1e1cdcb6a0c7 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,2151 @@ == Changelog == += 20.1.0-rc.1 = + + +## Changelog + +### Enhancements + +- ESLint: Improve regex for valid-sprintf rule to handle '%%'. ([68270](https://github.com/WordPress/gutenberg/pull/68270)) +- Simplify `sprintf` translation for percentage widths. ([68587](https://github.com/WordPress/gutenberg/pull/68587)) + +#### Block Library +- Archive Title Block: Refactor settings panel to use ToolsPanel. ([67915](https://github.com/WordPress/gutenberg/pull/67915)) +- Author Name: Refactor settings panel to use ToolsPanel. ([67953](https://github.com/WordPress/gutenberg/pull/67953)) +- Block Editor: Move state logic inside 'BlockRenameModal'. ([68560](https://github.com/WordPress/gutenberg/pull/68560)) +- Details: Add allowedBlocks attributes. ([68489](https://github.com/WordPress/gutenberg/pull/68489)) +- Navigation: Add clearable option to color picker in `navigation` block. ([68454](https://github.com/WordPress/gutenberg/pull/68454)) +- Navigation: Surface `menu name` in the `List View` next to the `Navigation block`. ([68446](https://github.com/WordPress/gutenberg/pull/68446)) +- Page List: Added color support. ([66430](https://github.com/WordPress/gutenberg/pull/66430)) +- Query: Refactor settings panel to use ToolsPanel. ([68008](https://github.com/WordPress/gutenberg/pull/68008)) +- Social links block: Add Clear button for color option. ([68564](https://github.com/WordPress/gutenberg/pull/68564)) +- Table of Contents Block: Refactor settings panel to use ToolsPanel. ([67964](https://github.com/WordPress/gutenberg/pull/67964)) + +#### Design Tools +- Post Comment Link: Show Border Controls By Default. ([68506](https://github.com/WordPress/gutenberg/pull/68506)) +- Query Total: Show Border Controls By Default. ([68507](https://github.com/WordPress/gutenberg/pull/68507)) + +#### List View +- Use badge component for block anchors. ([68566](https://github.com/WordPress/gutenberg/pull/68566)) + +#### Template Editor +- Editor: New default rendering mode for editor via post type supports. ([68549](https://github.com/WordPress/gutenberg/pull/68549)) + +#### Document Settings +- Change "Swap" to "Replace" for Template Action. ([68234](https://github.com/WordPress/gutenberg/pull/68234)) + +#### DataViews +- Add: Media field changing ui to Dataviews and content preview field to posts and pages. ([67278](https://github.com/WordPress/gutenberg/pull/67278)) + + +### Bug Fixes + +- Core Data: Check post-type support before requesting autosaves. ([68680](https://github.com/WordPress/gutenberg/pull/68680)) +- Fix text direction for URL and email fields in block editor for RTL languages. ([68188](https://github.com/WordPress/gutenberg/pull/68188)) +- Try: Fix end-to-end tests 'visitSiteEditor' helper. ([68534](https://github.com/WordPress/gutenberg/pull/68534)) + +#### Block Library +- Details block: Remove auto-close behaviour. ([67766](https://github.com/WordPress/gutenberg/pull/67766)) +- Fix File block resize glitches. ([68372](https://github.com/WordPress/gutenberg/pull/68372)) +- Navigation Link UI: Remove onClose from onSelectBlock. ([67760](https://github.com/WordPress/gutenberg/pull/67760)) +- Post Featured Image: Adds control to clear the the overlay color. ([68525](https://github.com/WordPress/gutenberg/pull/68525)) +- Site Logo: Prevent uploading multiple images via drag and drop. ([68618](https://github.com/WordPress/gutenberg/pull/68618)) +- Social Links: Don't prepend URL fragments. ([68655](https://github.com/WordPress/gutenberg/pull/68655)) +- i18n: Make example label for Comments Pagination Next block translatable. ([68373](https://github.com/WordPress/gutenberg/pull/68373)) +- i18n: Make example translatable in comments-pagination-previous. ([68374](https://github.com/WordPress/gutenberg/pull/68374)) + +#### Components +- Duotone: Fix scrollbars displayed on Duotone Control. ([67187](https://github.com/WordPress/gutenberg/pull/67187)) +- Fix SCSS Media Query Compilation Issue in Storybook. ([68464](https://github.com/WordPress/gutenberg/pull/68464)) +- Fix icon condition for Badge. ([68588](https://github.com/WordPress/gutenberg/pull/68588)) +- Restore Non-Themed Text Colors for `optimizeReadabilityFor`. ([68472](https://github.com/WordPress/gutenberg/pull/68472)) +- StoryBook: Fix error that could occur when loading compiled CSS. ([68526](https://github.com/WordPress/gutenberg/pull/68526)) + +#### Site Editor +- Classic theme preview: Remove admin-bar class name. ([68519](https://github.com/WordPress/gutenberg/pull/68519)) +- Fix: Site Editor Template part is missing an icon in the sidebar. ([68653](https://github.com/WordPress/gutenberg/pull/68653)) +- Navigation: Fix typo in `history.navigation's` function call. ([68623](https://github.com/WordPress/gutenberg/pull/68623)) + +#### Block Editor +- Differentiate 'Copy' and 'Copy styles' snackbar confirmation messages. ([68167](https://github.com/WordPress/gutenberg/pull/68167)) +- Refactor: Separate input form styles to a dedicated stylesheet. ([68501](https://github.com/WordPress/gutenberg/pull/68501)) +- Update percentage strings to be translatable. ([66323](https://github.com/WordPress/gutenberg/pull/66323)) + +#### npm Packages +- Fix irregular workspace version numbers. ([68467](https://github.com/WordPress/gutenberg/pull/68467)) +- Packages: Fix published * workspace dependencies. ([68240](https://github.com/WordPress/gutenberg/pull/68240)) + +#### Post Editor +- Inline Commenting: Avoid querying comments when the experiment is disabled. ([68632](https://github.com/WordPress/gutenberg/pull/68632)) + +#### Colors +- Color Gradients: Adjust `max-width` for color gradient swatch to accommodate `reset` button size. ([68626](https://github.com/WordPress/gutenberg/pull/68626)) + +#### Block Directory +- Fix : Block Inserter Search Infinity Spinner. ([68600](https://github.com/WordPress/gutenberg/pull/68600)) + +#### Interactivity API +- iAPI: Fix the logic path that merges plain objects. ([68579](https://github.com/WordPress/gutenberg/pull/68579)) + + +### Accessibility + +- Nux: Standardize reduced motion handling using media queries. ([68423](https://github.com/WordPress/gutenberg/pull/68423)) + +#### Block Library +- Fix Inconsistent Labels for Lightbox Feature. ([68261](https://github.com/WordPress/gutenberg/pull/68261)) +- Site Logo: Prevent focus loss when updating media from the sidebar. ([68621](https://github.com/WordPress/gutenberg/pull/68621)) +- Standardize reduced motion handling with media queries. ([68315](https://github.com/WordPress/gutenberg/pull/68315)) + +#### Components +- Fix usage of tooltip in the Circular option picker. ([68602](https://github.com/WordPress/gutenberg/pull/68602)) + +#### Post Editor +- Edit Post: Standardize reduced motion handling using media queries. ([68426](https://github.com/WordPress/gutenberg/pull/68426)) + +#### Widgets Editor +- Customize Widgets: Standardize reduced motion handling using media queries. ([68425](https://github.com/WordPress/gutenberg/pull/68425)) + +#### Block Editor +- Remove unnecessary CSS order property for the contrast checker in the Color hook. ([68055](https://github.com/WordPress/gutenberg/pull/68055)) + +#### DataViews +- Remove label from dataview checkbox. ([67868](https://github.com/WordPress/gutenberg/pull/67868)) + + +### Performance + +- Add npm script to profile TypeScript builds. ([68533](https://github.com/WordPress/gutenberg/pull/68533)) + +#### Post Editor +- Editor: Update data selector in 'PostPreviewButton'. ([68678](https://github.com/WordPress/gutenberg/pull/68678)) + + +### Documentation + +- Storybook: Add UnitControl story. ([67346](https://github.com/WordPress/gutenberg/pull/67346)) +- Add BlockIcon Storybook stories. ([67186](https://github.com/WordPress/gutenberg/pull/67186)) +- Added Global Documentation in inline documentation. ([68613](https://github.com/WordPress/gutenberg/pull/68613)) +- Block Card: Remove storybook for internal BlockCard component. ([68556](https://github.com/WordPress/gutenberg/pull/68556)) +- BlockInspector: Add showNoBlockSelectedMessage prop documentation. ([68444](https://github.com/WordPress/gutenberg/pull/68444)) +- Docs: Interactivity API - missing styles in the sample code. ([66253](https://github.com/WordPress/gutenberg/pull/66253)) +- Menu: Auto-generate README. ([68249](https://github.com/WordPress/gutenberg/pull/68249)) +- StoryBook: Add Story for ResolutionTool. ([68292](https://github.com/WordPress/gutenberg/pull/68292)) +- Storybook: Add TabbedSidebar stories and improve documentation. ([68118](https://github.com/WordPress/gutenberg/pull/68118)) +- Tabs: Remove unnecessary stories. ([68463](https://github.com/WordPress/gutenberg/pull/68463)) +- Updated Inline Document order. ([68650](https://github.com/WordPress/gutenberg/pull/68650)) +- Updated Small Typo in Feature-flags.md file. ([68612](https://github.com/WordPress/gutenberg/pull/68612)) +- Updated Typo in Document file. ([68477](https://github.com/WordPress/gutenberg/pull/68477)) +- docs: Fix type syntax in `_gutenberg_add_block_template_plugin_attribute()`. ([68391](https://github.com/WordPress/gutenberg/pull/68391)) + + +### Code Quality + +- Data Views: Standardize reduced motion handling using media queries. ([68422](https://github.com/WordPress/gutenberg/pull/68422)) +- Fix typos. ([67304](https://github.com/WordPress/gutenberg/pull/67304)) +- [core-data] Document and add types for dynamic actions and selectors. ([67668](https://github.com/WordPress/gutenberg/pull/67668)) +- docs: Fix param name in `gutenberg_add_can_update_block_bindings_editor_setting()`. ([68390](https://github.com/WordPress/gutenberg/pull/68390)) +- docs: Fix return type on `gutenberg_register_block_module_id()`. ([68393](https://github.com/WordPress/gutenberg/pull/68393)) + +#### Block Library +- Query Loop: Remove unused styles. ([68615](https://github.com/WordPress/gutenberg/pull/68615)) +- Site Logo: Remove unused argument for 'mediaUpload' function. ([68617](https://github.com/WordPress/gutenberg/pull/68617)) + +#### Global Styles +- Remove unused prop for 'BackgroundImageControls'. ([68616](https://github.com/WordPress/gutenberg/pull/68616)) + +#### Post Editor +- Document Outline: Use block client ID as unique 'key'. ([68502](https://github.com/WordPress/gutenberg/pull/68502)) + +#### Block Editor +- BlockInspector: Remove unused 'showNoBlockSelectedMessage' prop. ([68487](https://github.com/WordPress/gutenberg/pull/68487)) + +#### Widgets Editor +- Edit Widgets and Base Styles: Standardize reduced motion handling using media queries. ([68427](https://github.com/WordPress/gutenberg/pull/68427)) + +#### Components +- Standardize reduced motion handling using media queries. ([68421](https://github.com/WordPress/gutenberg/pull/68421)) + +#### Block Directory +- Standardize reduced motion handling using media queries #68419. ([68420](https://github.com/WordPress/gutenberg/pull/68420)) + +#### Site Editor +- Edit Site: Add tsconfig.json validation for package. ([67406](https://github.com/WordPress/gutenberg/pull/67406)) + + +### Tools + +#### Testing +- Check Storybook build on CI for PRs. ([68466](https://github.com/WordPress/gutenberg/pull/68466)) +- Fix flaky DataViews list arraow nav end-to-end tests. ([68503](https://github.com/WordPress/gutenberg/pull/68503)) +- Fix flaky navigation-frontend-interactivity end-to-end tests. ([68667](https://github.com/WordPress/gutenberg/pull/68667)) +- Site Editor: Fix "Quick Edit Mode" end-to-end tests. ([68484](https://github.com/WordPress/gutenberg/pull/68484)) +- Upgrade Playwright to v1.49. ([68504](https://github.com/WordPress/gutenberg/pull/68504)) + +#### Build Tooling +- Plugin: Remove ESLint rule for deprecated functions. ([68590](https://github.com/WordPress/gutenberg/pull/68590)) + + +## First-time contributors + +The following PRs were merged by first-time contributors: + +- @AhmarZaidi: Update percentage strings to be translatable. ([66323](https://github.com/WordPress/gutenberg/pull/66323)) +- @benazeer-ben: Page List: Added color support. ([66430](https://github.com/WordPress/gutenberg/pull/66430)) +- @himanipanchal: Updated Typo in Document file. ([68477](https://github.com/WordPress/gutenberg/pull/68477)) +- @im3dabasia: Storybook: Add UnitControl story. ([67346](https://github.com/WordPress/gutenberg/pull/67346)) +- @szepeviktor: Fix typos. ([67304](https://github.com/WordPress/gutenberg/pull/67304)) + + +## Contributors + +The following contributors merged PRs in this release: + +@afercia @AhmarZaidi @atachibana @benazeer-ben @carolinan @ciampo @dhananjaykuber @ellatrix @geriux @himanipanchal @himanshupathak95 @im3dabasia @Infinite-Null @jeryj @jorgefilipecosta @juanfra @justlevine @karthick-murugan @luisherranz @Mamaduka @manzoorwanijk @mirka @ramonjd @SainathPoojary @shail-mehta @shimotmk @sirreal @stokesman @Sukhendu2002 @szepeviktor @t-hamano @vipul0425 @Vrishabhsk @yogeshbhutkar + + += 20.0.0 = + +## Changelog + +### Features + +#### Interactivity API +- Prevent each directive errors and allow any iterable. ([67798](https://github.com/WordPress/gutenberg/pull/67798)) + + +### Enhancements + +- Add dropdown menu props to ToolsPanel component. ([68019](https://github.com/WordPress/gutenberg/pull/68019)) +- Create Block: Allow external templates to customize more fields. ([68193](https://github.com/WordPress/gutenberg/pull/68193)) +- Create Block: Optimize the default template for multiple blocks case. ([68175](https://github.com/WordPress/gutenberg/pull/68175)) +- DOM: Support class wildcard matcher in 'cleanNodeList'. ([67830](https://github.com/WordPress/gutenberg/pull/67830)) +- Scripts: Recommend passing JS entry points with paths. ([68251](https://github.com/WordPress/gutenberg/pull/68251)) +- Upgrade sass to version 1.54.0. ([68380](https://github.com/WordPress/gutenberg/pull/68380)) +- Use Badge component in dataview grids. ([68062](https://github.com/WordPress/gutenberg/pull/68062)) +- Use Badge component in page markers. ([68103](https://github.com/WordPress/gutenberg/pull/68103)) +- postcss-plugins-preset: Bump autoprefixer to 10.4.20. ([68237](https://github.com/WordPress/gutenberg/pull/68237)) +- wp-env: Add multisite support. ([67845](https://github.com/WordPress/gutenberg/pull/67845)) + +#### Block Library +- Add Tools Panel dropdown menu props to More block. ([68039](https://github.com/WordPress/gutenberg/pull/68039)) +- Add block example attribute for Comments Form block. ([68267](https://github.com/WordPress/gutenberg/pull/68267)) +- Add block example attribute for Comments block. ([68266](https://github.com/WordPress/gutenberg/pull/68266)) +- Archive: Add dropdown menu props to ToolsPanel component. ([68010](https://github.com/WordPress/gutenberg/pull/68010)) +- Archives Block: Refactor setting panel. ([67841](https://github.com/WordPress/gutenberg/pull/67841)) +- Button Block: Refactor setting panel. ([67887](https://github.com/WordPress/gutenberg/pull/67887)) +- Button: Update Settings text labels. ([68265](https://github.com/WordPress/gutenberg/pull/68265)) +- Date Block: Add dropdown menu props to ToolsPanel component. ([68018](https://github.com/WordPress/gutenberg/pull/68018)) +- Date Block: Refactor settings panel to use ToolsPanel. ([67906](https://github.com/WordPress/gutenberg/pull/67906)) +- Details Block: Migrate to Toolspanel. ([67966](https://github.com/WordPress/gutenberg/pull/67966)) +- Excerpt Block: Refactor settings panel to use ToolsPanel. ([67908](https://github.com/WordPress/gutenberg/pull/67908)) +- Featured Image Block: Refactor setting panel. ([67456](https://github.com/WordPress/gutenberg/pull/67456)) +- Introduce new filter "render_block_core_navigation_link_allowed_post_status". ([63181](https://github.com/WordPress/gutenberg/pull/63181)) +- Latest Posts Border Block Support. ([66353](https://github.com/WordPress/gutenberg/pull/66353)) +- Login/Logout: Add dropdown menu props to ToolsPanel component. ([68009](https://github.com/WordPress/gutenberg/pull/68009)) +- Login/Logout: Refactor settings panel to use ToolsPanel. ([67909](https://github.com/WordPress/gutenberg/pull/67909)) +- More Block: Refactor settings panel to use ToolsPanel. ([67905](https://github.com/WordPress/gutenberg/pull/67905)) +- Navigation Submenu Block: Refactor settings panel to use ToolsPanel. ([67969](https://github.com/WordPress/gutenberg/pull/67969)) +- Page List Block: Add dropdown menu props to ToolsPanel component. ([68012](https://github.com/WordPress/gutenberg/pull/68012)) +- Page List Block: Refactor settings panel to use ToolsPanel. ([67903](https://github.com/WordPress/gutenberg/pull/67903)) +- Post Featured Image: Use the 'ResolutionTool' component. ([68294](https://github.com/WordPress/gutenberg/pull/68294)) +- Query Page Numbers Block: Refactor settings panel to use ToolsPanel. ([67958](https://github.com/WordPress/gutenberg/pull/67958)) +- Query Page Numbers: Add dropdown menu props to ToolsPanel component. ([68013](https://github.com/WordPress/gutenberg/pull/68013)) +- Query Pagination: Refactor settings panel to use ToolsPanel. ([67914](https://github.com/WordPress/gutenberg/pull/67914)) +- Query Pagination: Update 'showLabel' help text. ([68105](https://github.com/WordPress/gutenberg/pull/68105)) +- Query Total block: Reduce concatenation in the output text. ([68150](https://github.com/WordPress/gutenberg/pull/68150)) +- Read More: Add example preview. ([68288](https://github.com/WordPress/gutenberg/pull/68288)) +- Refactor "Settings" panel of Navigation Item block to use ToolsPanel instead of PanelBody. ([67973](https://github.com/WordPress/gutenberg/pull/67973)) +- Replace PanelBody with ToolsPanel and ToolsPanelItem in column block. ([67913](https://github.com/WordPress/gutenberg/pull/67913)) +- Replace PanelBody with ToolsPanel and ToolsPanelItem in spacer block. ([67981](https://github.com/WordPress/gutenberg/pull/67981)) +- Replace PanelBody with ToolsPanel in columns block. ([67910](https://github.com/WordPress/gutenberg/pull/67910)) +- Site Title Block: Add dropdown menu props to ToolsPanel component. ([68017](https://github.com/WordPress/gutenberg/pull/68017)) +- Site Title Block: Refactor settings panel to use ToolsPanel. ([67898](https://github.com/WordPress/gutenberg/pull/67898)) +- Social Icon: Migrate to Toolspanel. ([67974](https://github.com/WordPress/gutenberg/pull/67974)) +- Social Icons: Migrate to Toolspanel. ([67975](https://github.com/WordPress/gutenberg/pull/67975)) +- Table Block: Refactor settings panel to use ToolsPanel. ([67896](https://github.com/WordPress/gutenberg/pull/67896)) +- Tag Cloud Block: Refactor settings panel to use ToolsPanel. ([67911](https://github.com/WordPress/gutenberg/pull/67911)) +- Video Block: Refactor setting panel. ([67044](https://github.com/WordPress/gutenberg/pull/67044)) + +#### Components +- : Badge Component. ([66555](https://github.com/WordPress/gutenberg/pull/66555)) +- Badge: Support text truncation. ([68107](https://github.com/WordPress/gutenberg/pull/68107)) +- Button: Add hover style to `secondary` variant. ([67325](https://github.com/WordPress/gutenberg/pull/67325)) +- CreateTemplatePartModalContents: Use native radio inputs. ([67702](https://github.com/WordPress/gutenberg/pull/67702)) +- Menu: More granular sub-components. ([67422](https://github.com/WordPress/gutenberg/pull/67422)) +- RangeControl: Animate thumb and track only when using marks. ([67836](https://github.com/WordPress/gutenberg/pull/67836)) +- Storybook: Add more `max-width` containers. ([68080](https://github.com/WordPress/gutenberg/pull/68080)) +- Storybook: Upgrade to the latest version (v8.4.7). ([67863](https://github.com/WordPress/gutenberg/pull/67863)) +- Storybook: Upgrade to v8.0.x. ([67574](https://github.com/WordPress/gutenberg/pull/67574)) +- Unite inline Ariakit imports. ([67818](https://github.com/WordPress/gutenberg/pull/67818)) + +#### Style Book +- Give style book its own route so it can be linked to directly. ([67811](https://github.com/WordPress/gutenberg/pull/67811)) +- Stylebook: Add the Appearance -> Design submenu through `admin_menu` action. ([68174](https://github.com/WordPress/gutenberg/pull/68174)) +- Try splitting style book into sections. ([68071](https://github.com/WordPress/gutenberg/pull/68071)) +- Try toggle instead of dropdown to show stylebook. ([67810](https://github.com/WordPress/gutenberg/pull/67810)) + +#### Design Tools +- Post Comments Link: Add Border Support. ([68450](https://github.com/WordPress/gutenberg/pull/68450)) +- Post Template: Add Border and Spacing Support. ([64425](https://github.com/WordPress/gutenberg/pull/64425)) +- Query Total: Add Border Support. ([68323](https://github.com/WordPress/gutenberg/pull/68323)) + +#### Block Editor +- Add reset button to ColorGradientSettingsDropdown. ([67800](https://github.com/WordPress/gutenberg/pull/67800)) +- ChildLayoutControl: Use units defined in theme.json. ([67784](https://github.com/WordPress/gutenberg/pull/67784)) +- KeyboardShortcuts: Update delete shortcut to use `shift + Backspace`. ([68164](https://github.com/WordPress/gutenberg/pull/68164)) + +#### Block hooks +- Apply to Post Content (on frontend and in editor). ([67272](https://github.com/WordPress/gutenberg/pull/67272)) +- Synced Patterns: Apply Block Hooks. ([68058](https://github.com/WordPress/gutenberg/pull/68058)) + +#### Media +- Split upload into verbs and nouns. ([68227](https://github.com/WordPress/gutenberg/pull/68227)) + +#### Zoom Out +- Remove placeholder of default paragraph when it's the only block and canvas is zoomed out. ([68106](https://github.com/WordPress/gutenberg/pull/68106)) + +#### Interactivity API +- iAPI Router: Handle styles assets on region-based navigation. ([67826](https://github.com/WordPress/gutenberg/pull/67826)) + +#### Plugin +- Add a Playground blueprint json to the /assets/blueprints folder of Plugin Repo. ([67742](https://github.com/WordPress/gutenberg/pull/67742)) + +#### Site Editor +- Pages: Add "Set as posts page" action. ([67650](https://github.com/WordPress/gutenberg/pull/67650)) + +#### Write mode +- Allow template part editing in write mode. ([67372](https://github.com/WordPress/gutenberg/pull/67372)) + +#### Patterns +- Replace Starter Content modal with inserter panel. ([66836](https://github.com/WordPress/gutenberg/pull/66836)) + +#### Commands +- Add command to navigate to site editor. ([66722](https://github.com/WordPress/gutenberg/pull/66722)) + +#### Inspector Controls +- Use custom name in block sidebar if available (retaining block type information). ([65641](https://github.com/WordPress/gutenberg/pull/65641)) + + +### New APIs + +#### Components +- BoxControl: Add support for presets. ([67688](https://github.com/WordPress/gutenberg/pull/67688)) + + +### Bug Fixes + +- Add duotone and dimensions to the block level for translation. ([68243](https://github.com/WordPress/gutenberg/pull/68243)) +- Add text domain option while scaffolding the block in create-block. ([57197](https://github.com/WordPress/gutenberg/pull/57197)) +- Added `is-focus-mode` class on all viewports. ([67377](https://github.com/WordPress/gutenberg/pull/67377)) +- Editor: Fix initial edits applied again after saving the post. ([68273](https://github.com/WordPress/gutenberg/pull/68273)) +- Fix dataviews commonjs export. ([67962](https://github.com/WordPress/gutenberg/pull/67962)) +- Get active element within the iframe when restoring focus. ([68060](https://github.com/WordPress/gutenberg/pull/68060)) +- Make strings in theme.json translatable. ([66675](https://github.com/WordPress/gutenberg/pull/66675)) +- Scripts: Use fork of `rtlcss-webpack-plugin` to fix issues with deps. ([68201](https://github.com/WordPress/gutenberg/pull/68201)) + +#### Block Library +- Columns: Add space above notice text. ([68259](https://github.com/WordPress/gutenberg/pull/68259)) +- Enhance: Improve pagination logic in core/query-pagination-previous block. ([68070](https://github.com/WordPress/gutenberg/pull/68070)) +- Fix author information leakage by author blocks for Custom Post Types without author support & display notice to user. ([67136](https://github.com/WordPress/gutenberg/pull/67136)) +- Media & Text: Correctly reset the 'useFeaturedImage' attribute. ([68247](https://github.com/WordPress/gutenberg/pull/68247)) +- Navigation Submenu Block: Add dropdown menu props to ToolsPanel component. ([68015](https://github.com/WordPress/gutenberg/pull/68015)) +- Page List Block: Fix critical error when converting to link. ([68076](https://github.com/WordPress/gutenberg/pull/68076)) +- Page List block: Don't wrap Edit button with ToolsPanelItem component. ([68248](https://github.com/WordPress/gutenberg/pull/68248)) +- Query Total: Remove nested element. ([68304](https://github.com/WordPress/gutenberg/pull/68304)) +- Table Block: Fix margin/padding to include caption in spacing. ([68281](https://github.com/WordPress/gutenberg/pull/68281)) +- Update SiteTitle block to Fix `isLink` Toggle Behavior. ([68295](https://github.com/WordPress/gutenberg/pull/68295)) +- i18n: Make example and variations translatable in `post-navigation-link`. ([68375](https://github.com/WordPress/gutenberg/pull/68375)) +- i18n: Make example translatable in `query-no-results`. ([68376](https://github.com/WordPress/gutenberg/pull/68376)) +- i18n: Make example translatable in `table-of-contents`. ([68377](https://github.com/WordPress/gutenberg/pull/68377)) + +#### Components +- Block Editor: Fix the 'Reset all' bug for the 'ResolutionTool' component. ([68296](https://github.com/WordPress/gutenberg/pull/68296)) +- BoxControl: Better minimum value support. ([67819](https://github.com/WordPress/gutenberg/pull/67819)) +- BoxControl: Fix `aria-valuetext` value. ([68362](https://github.com/WordPress/gutenberg/pull/68362)) +- Fix end-to-end storybook. ([68307](https://github.com/WordPress/gutenberg/pull/68307)) +- Fixing Text Contrast for Dark Mode. ([68349](https://github.com/WordPress/gutenberg/pull/68349)) +- FontSizePicker: Add `display: Contents` rule to custom size select. ([68280](https://github.com/WordPress/gutenberg/pull/68280)) +- Storybook: Fix `emotion/is-prop-valid` warning. ([68202](https://github.com/WordPress/gutenberg/pull/68202)) +- Storybook: Fix a few editor styles warnings. ([68198](https://github.com/WordPress/gutenberg/pull/68198)) +- Storybook: Fix warnings in Layout document. ([67865](https://github.com/WordPress/gutenberg/pull/67865)) +- Use default value in `useMediaUploadSettings`. ([68100](https://github.com/WordPress/gutenberg/pull/68100)) + +#### Block Editor +- Media Replace Flow: Add custom toggle support and fix button height. ([68084](https://github.com/WordPress/gutenberg/pull/68084)) +- BlockCard: Fix title alignment. ([68115](https://github.com/WordPress/gutenberg/pull/68115)) +- DateFormatPicker: Fix styles & spacing. ([68079](https://github.com/WordPress/gutenberg/pull/68079)) +- Fix Iframe error for links without 'href'. ([68024](https://github.com/WordPress/gutenberg/pull/68024)) +- Grid Visualizer: Improve observation logic. ([68230](https://github.com/WordPress/gutenberg/pull/68230)) +- List View: Fix appender size. ([68221](https://github.com/WordPress/gutenberg/pull/68221)) +- MediaReplaceFlow: Remove store subscription in favor of modern CSS. ([68276](https://github.com/WordPress/gutenberg/pull/68276)) +- Remove patterns from the Quick Inserter to prevent misuse in block-specific contexts. ([67738](https://github.com/WordPress/gutenberg/pull/67738)) +- Revert 'Warning' component autofocus. ([68133](https://github.com/WordPress/gutenberg/pull/68133)) + +#### Post Editor +- DataViews: Fix text in action for setting site home page. ([67787](https://github.com/WordPress/gutenberg/pull/67787)) +- Edit post: Fix meta box pane’s pointer capture. ([68252](https://github.com/WordPress/gutenberg/pull/68252)) +- Editor: Remove HTML from the post title in the document bar. ([68358](https://github.com/WordPress/gutenberg/pull/68358)) +- Fix: Some 403 errors for editor roles. ([68146](https://github.com/WordPress/gutenberg/pull/68146)) +- Improve logic to show entities saved panel description. ([67971](https://github.com/WordPress/gutenberg/pull/67971)) + +#### DataViews +- Don't render actions dropdown when all eligible ones are `primary`. ([68168](https://github.com/WordPress/gutenberg/pull/68168)) +- Handle `grid` preview size based on container width. ([68078](https://github.com/WordPress/gutenberg/pull/68078)) +- Hide actions related UI in `grid` when no actions or bulk actions are passed. ([68033](https://github.com/WordPress/gutenberg/pull/68033)) +- Pages: Update layout-specific configuration when the view is updated. ([67881](https://github.com/WordPress/gutenberg/pull/67881)) +- Use `action.disabled` state to disable actions (primary and secondary). ([68275](https://github.com/WordPress/gutenberg/pull/68275)) + +#### Site Editor +- Add CSS classname to fix the negative margins not appearing in the Navigation Screen. ([67825](https://github.com/WordPress/gutenberg/pull/67825)) +- Fix obsolete `getLocationWithParams` usage. ([68388](https://github.com/WordPress/gutenberg/pull/68388)) +- Pages: Remove unnecessary padding for items. ([67977](https://github.com/WordPress/gutenberg/pull/67977)) +- Update active menu item appearance. ([68147](https://github.com/WordPress/gutenberg/pull/68147)) + +#### Style Book +- Fix global styles updating in style book. ([68111](https://github.com/WordPress/gutenberg/pull/68111)) +- Fix style book background color. ([68088](https://github.com/WordPress/gutenberg/pull/68088)) +- Fix uploading background images in stylebook view. ([68159](https://github.com/WordPress/gutenberg/pull/68159)) +- Stylebook: Avoid double line in subcategory titles. ([67752](https://github.com/WordPress/gutenberg/pull/67752)) + +#### Zoom Out +- Allow replace operation on empty default block in Zoom Out. ([68026](https://github.com/WordPress/gutenberg/pull/68026)) +- Fix don't show inserter in Zoom Out dropzone when the text is visible. ([68031](https://github.com/WordPress/gutenberg/pull/68031)) +- Hide separators for currently dragged section in Zoom Out. ([67638](https://github.com/WordPress/gutenberg/pull/67638)) +- Make Write mode and Zoom out block options menus consistent. ([67749](https://github.com/WordPress/gutenberg/pull/67749)) + +#### Design Tools +- Background supports: Add default controls supports. ([68085](https://github.com/WordPress/gutenberg/pull/68085)) +- Block supports: Show selected item in font family select control. ([68254](https://github.com/WordPress/gutenberg/pull/68254)) +- Fix: Ensure consistency in editor tools for navigation buttons and delete options. ([67253](https://github.com/WordPress/gutenberg/pull/67253)) + +#### Template Editor +- Fix: Editing "Page" is broken for low capability users. ([68110](https://github.com/WordPress/gutenberg/pull/68110)) +- Plugin: Fix eligibility check for post types' default rendering mode. ([67879](https://github.com/WordPress/gutenberg/pull/67879)) + +#### Widgets Editor +- Customizer Widgets: Fix inserter button size and animation. ([67880](https://github.com/WordPress/gutenberg/pull/67880)) +- Widget Editor: Fix: Close button is not working. ([65443](https://github.com/WordPress/gutenberg/pull/65443)) + +#### Meta Boxes +- Show metabox when pattern is accessed directly. ([68255](https://github.com/WordPress/gutenberg/pull/68255)) + +#### Typography +- Button Block: Set proper typography for inner elements. ([68023](https://github.com/WordPress/gutenberg/pull/68023)) + +#### History +- Query Pagination: Fix 'undo' trap. ([68022](https://github.com/WordPress/gutenberg/pull/68022)) + +#### npm Packages +- Add --glob argument to rimraf cli scripts. ([67829](https://github.com/WordPress/gutenberg/pull/67829)) + +#### Paste +- Image: Avoid link class loss when pasting for raw transformation. ([67803](https://github.com/WordPress/gutenberg/pull/67803)) + +#### Extensibility +- Make Block Bindings work with `editor.BlockEdit` hook (2nd try). ([67523](https://github.com/WordPress/gutenberg/pull/67523)) + + +### Accessibility + +- Dataviews List layout: Do not use grid role on a `ul` element. ([67849](https://github.com/WordPress/gutenberg/pull/67849)) +- Fix: Templates and patterns are nesting two elements with the button role. ([67801](https://github.com/WordPress/gutenberg/pull/67801)) +- [Dataviews] Fix: Media item focus style is not visible on Grid. ([67789](https://github.com/WordPress/gutenberg/pull/67789)) + +#### Block Editor +- Fix: Inserter category tabs: Avoid unnecessary aria-label. ([68160](https://github.com/WordPress/gutenberg/pull/68160)) +- Improve accessibility of the Warning component in the block editor. ([67433](https://github.com/WordPress/gutenberg/pull/67433)) + +#### Global Styles +- Shadows: Always show reset button if hover is not supported. ([68122](https://github.com/WordPress/gutenberg/pull/68122)) +- Visual Refactor: Add Chevron Icon for Shadows in Global Styles. ([67720](https://github.com/WordPress/gutenberg/pull/67720)) + +#### Block Library +- Button: Replace ButtonGroup usage with ToggleGroupControl. ([65346](https://github.com/WordPress/gutenberg/pull/65346)) +- Fix Choose menu label when a menu has been deleted. ([67009](https://github.com/WordPress/gutenberg/pull/67009)) + +#### DataViews +- Add confirm dialog before Permanently delete. ([67824](https://github.com/WordPress/gutenberg/pull/67824)) + +#### Site Editor +- Make sure the sidebar navigation item focus style is fully visible. ([67817](https://github.com/WordPress/gutenberg/pull/67817)) + +#### Components +- CustomSelectControl: Refactor to use Ariakit store state for current value. ([67815](https://github.com/WordPress/gutenberg/pull/67815)) + + +### Performance + +#### Block Library +- Don't fetch media details if the block doesn't use a featured image. ([68299](https://github.com/WordPress/gutenberg/pull/68299)) +- Media & Text: Optimize block editor store subscriptions. ([68290](https://github.com/WordPress/gutenberg/pull/68290)) + + +### Experiments + +#### DataViews +- Proof of concept: Visualize hierarchical data. ([66479](https://github.com/WordPress/gutenberg/pull/66479)) + + +### Documentation + +- .wp-env.json schema: Add `testsPort` field. ([68220](https://github.com/WordPress/gutenberg/pull/68220)) +- Add README for TextAlignmentControl component. ([68126](https://github.com/WordPress/gutenberg/pull/68126)) +- Add layout related updates to the DataForm README. ([68050](https://github.com/WordPress/gutenberg/pull/68050)) +- Added Global Documentation in load.php. ([68325](https://github.com/WordPress/gutenberg/pull/68325)) +- Badge component: Fix Storybook URL link. ([68077](https://github.com/WordPress/gutenberg/pull/68077)) +- Badge: Fix up extra newline in readme. ([68359](https://github.com/WordPress/gutenberg/pull/68359)) +- Block Editor Storybook: Restructure the directory and add badges to private components. ([68352](https://github.com/WordPress/gutenberg/pull/68352)) +- Clarify template property behavior in InnerBlocks documentation to specify prefill when empty. ([66911](https://github.com/WordPress/gutenberg/pull/66911)) +- Components: Normalize newlines in auto-generated READMEs. ([68208](https://github.com/WordPress/gutenberg/pull/68208)) +- Components: Prevent broken lists in auto-generated readmes. ([68301](https://github.com/WordPress/gutenberg/pull/68301)) +- Components: Warn private API in auto-generated readmes. ([68317](https://github.com/WordPress/gutenberg/pull/68317)) +- Create a catalog list of private APIs. ([66558](https://github.com/WordPress/gutenberg/pull/66558)) +- DateFormatPicker: Improve line breaks in JSDoc and README. ([68006](https://github.com/WordPress/gutenberg/pull/68006)) +- Doc: Add JSDoc and update README for BlockCard component. ([68114](https://github.com/WordPress/gutenberg/pull/68114)) +- Docs: Fix some typos on reference-guide data-core-block-editor.md. ([68066](https://github.com/WordPress/gutenberg/pull/68066)) +- Documenting innerBlocks in save function. ([66689](https://github.com/WordPress/gutenberg/pull/66689)) +- Fix reference to `wp-env start` in documentation. ([68034](https://github.com/WordPress/gutenberg/pull/68034)) +- Fix wrong `npm start` command. ([65221](https://github.com/WordPress/gutenberg/pull/65221)) +- Fix: Fix link to minimal-block example plugin code. ([67888](https://github.com/WordPress/gutenberg/pull/67888)) +- Fixed typo in README of TextTransformControl. ([68443](https://github.com/WordPress/gutenberg/pull/68443)) +- Section Styles: Update block style variation documentation. ([68169](https://github.com/WordPress/gutenberg/pull/68169)) +- Storybook : Add TextTransformControl stories. ([67365](https://github.com/WordPress/gutenberg/pull/67365)) +- Storybook: Add BorderRadiusControl story. ([67383](https://github.com/WordPress/gutenberg/pull/67383)) +- Storybook: Add PlainText Storybook stories. ([67341](https://github.com/WordPress/gutenberg/pull/67341)) +- Storybook: Add stories for BlockCard component. ([67191](https://github.com/WordPress/gutenberg/pull/67191)) +- Storybook: Add stories for BlockTitle Component. ([67234](https://github.com/WordPress/gutenberg/pull/67234)) +- Storybook: Add stories for DateFormatPicker Component. ([67290](https://github.com/WordPress/gutenberg/pull/67290)) +- Storybook: Add stories for the ContrastChecker component. ([68120](https://github.com/WordPress/gutenberg/pull/68120)) +- Storybook: Add stories for the TextAlignmentControl component. ([67371](https://github.com/WordPress/gutenberg/pull/67371)) +- Storybook: Add stories for the TextDecorationControl component. ([67337](https://github.com/WordPress/gutenberg/pull/67337)) +- Storybook: Add story for the Warning component. ([68124](https://github.com/WordPress/gutenberg/pull/68124)) +- Storybook: Make prop sort order consistent. ([68152](https://github.com/WordPress/gutenberg/pull/68152)) +- Tabs: Auto-generate README. ([68209](https://github.com/WordPress/gutenberg/pull/68209)) +- Update platform documentation intro. ([61341](https://github.com/WordPress/gutenberg/pull/61341)) +- Update the copyright license to 2025. ([68440](https://github.com/WordPress/gutenberg/pull/68440)) +- Updated @since Doc Order in Inline documentation. ([68003](https://github.com/WordPress/gutenberg/pull/68003)) +- Updated Document URL in Documentation. ([67990](https://github.com/WordPress/gutenberg/pull/67990)) +- Updated Small Typo in documentation in docs/getting-started/faq.md file. ([68357](https://github.com/WordPress/gutenberg/pull/68357)) +- [Docs] Fix: Two broken links to the packages reference API and to blocks documentation. ([67889](https://github.com/WordPress/gutenberg/pull/67889)) +- env: Fix changelog entry. ([68219](https://github.com/WordPress/gutenberg/pull/68219)) +- theme.json schema: Fix block list. ([68343](https://github.com/WordPress/gutenberg/pull/68343)) + + +### Code Quality + +- Adding myself as a code owner of the block library package. ([67891](https://github.com/WordPress/gutenberg/pull/67891)) +- Create Block: Migrate Inquirer.js dependency to the new API. ([67877](https://github.com/WordPress/gutenberg/pull/67877)) +- Fix indentation in the upload-media tsconfig. ([68083](https://github.com/WordPress/gutenberg/pull/68083)) +- Fix indentation in upload-media package.json. ([68037](https://github.com/WordPress/gutenberg/pull/68037)) +- Fix: Invalid JSDoc syntax for optional object. ([68061](https://github.com/WordPress/gutenberg/pull/68061)) +- Remove some obsolete stylelint `at-rule-no-unknown` disable rules. ([68087](https://github.com/WordPress/gutenberg/pull/68087)) + +#### Components +- DatePicker: Prepare day buttons for 40px default size. ([68156](https://github.com/WordPress/gutenberg/pull/68156)) +- DropZone: Make the drop zone in Storybook the same size as the item. ([68231](https://github.com/WordPress/gutenberg/pull/68231)) +- Fix Button size violations in misc. unit tests. ([68154](https://github.com/WordPress/gutenberg/pull/68154)) +- Fix: Add soft deperecation notice for the ButtonGroup component. ([65429](https://github.com/WordPress/gutenberg/pull/65429)) +- InputControl : Deprecate 36px default size. ([66897](https://github.com/WordPress/gutenberg/pull/66897)) +- Menu: Migrate Storybook examples to CSF3. ([68204](https://github.com/WordPress/gutenberg/pull/68204)) +- Menu: Use ariakit types. ([68206](https://github.com/WordPress/gutenberg/pull/68206)) +- Navigation: Prepare for hard deprecation. ([68158](https://github.com/WordPress/gutenberg/pull/68158)) +- Navigation: Upsize back buttons. ([68157](https://github.com/WordPress/gutenberg/pull/68157)) +- RadioGroup: Log deprecation warning. ([68067](https://github.com/WordPress/gutenberg/pull/68067)) +- SelectControl : Deprecate 36px default size. ([66898](https://github.com/WordPress/gutenberg/pull/66898)) +- Slot: Use layout effect and update Cover block unit tests. ([68176](https://github.com/WordPress/gutenberg/pull/68176)) +- SlotFill: Use observableMap everywhere, remove manual rerendering. ([67400](https://github.com/WordPress/gutenberg/pull/67400)) +- Tabs: Use correct ariakit type for root component. ([68207](https://github.com/WordPress/gutenberg/pull/68207)) +- TreeSelect: Deprecate 36px default size. ([67855](https://github.com/WordPress/gutenberg/pull/67855)) + +#### Plugin +- chore: fix return type for `WP_Duotone_Gutenberg::Get_selector()`. ([66695](https://github.com/WordPress/gutenberg/pull/66695)) +- fix: Deprecated `WP_Webfonts()` constructor takes no arguments. ([66700](https://github.com/WordPress/gutenberg/pull/66700)) +- fix: Remove extraneous arg from `gutenberg_url()` call in `gutenberg_posts_dashboard()`. ([66699](https://github.com/WordPress/gutenberg/pull/66699)) +- fix: Remove extraneous param from `remove_filter()` calls. ([66697](https://github.com/WordPress/gutenberg/pull/66697)) +- fix: Wrong number of `$accepted_args` on `add_filter()` calls. ([66694](https://github.com/WordPress/gutenberg/pull/66694)) +- fix: explicitly return false in `WP_Theme_JSON_Gutenberg::Should_override_preset()`. ([66696](https://github.com/WordPress/gutenberg/pull/66696)) + +#### Block Editor +- Fix ESLint warnings for the 'useInnerBlockTemplateSync' hook. ([68355](https://github.com/WordPress/gutenberg/pull/68355)) +- FontAppearanceControl: Deprecate 36px default size. ([67854](https://github.com/WordPress/gutenberg/pull/67854)) +- FontFamilyControl: Deprecate 36px default size. ([67853](https://github.com/WordPress/gutenberg/pull/67853)) +- Inserter: Use 40px default size for toggle button. ([68155](https://github.com/WordPress/gutenberg/pull/68155)) +- LineHeightControl: Deprecate 36px default size. ([67850](https://github.com/WordPress/gutenberg/pull/67850)) + +#### Post Editor +- DocumentTools: Use standard ToolbarButton for inserter. ([68332](https://github.com/WordPress/gutenberg/pull/68332)) +- Editor: Remove constants for notices. ([68361](https://github.com/WordPress/gutenberg/pull/68361)) +- Editor: Remove the 'content-only' check from 'TemplatePartConverterMenuItem'. ([67961](https://github.com/WordPress/gutenberg/pull/67961)) + +#### DataViews +- DataForm: Add unit tests. ([68054](https://github.com/WordPress/gutenberg/pull/68054)) +- DataForm: Remove `FormFieldVisibility`. ([68203](https://github.com/WordPress/gutenberg/pull/68203)) +- [DataView] Initial list of unit tests for the DataView component. ([68205](https://github.com/WordPress/gutenberg/pull/68205)) + +#### Block Library +- Columns: Replace some store selectors with 'getBlockOrder'. ([67991](https://github.com/WordPress/gutenberg/pull/67991)) +- Fix trailing spaces in navigation block classnames. ([68161](https://github.com/WordPress/gutenberg/pull/68161)) + +#### Site Editor +- Edit Site: Standardize reduced motion handling using media queries. ([68419](https://github.com/WordPress/gutenberg/pull/68419)) + +#### Design Tools +- Block Supports: Revert stabilization of typography, border, skip serialization and default controls supports. ([68163](https://github.com/WordPress/gutenberg/pull/68163)) + +#### Zoom Out +- Correct spelling in Zoom Out Inserters comment. ([68051](https://github.com/WordPress/gutenberg/pull/68051)) + +#### Block API +- Fail gracefully when block in `createBlock` function is not registered. ([68043](https://github.com/WordPress/gutenberg/pull/68043)) + +#### Icons +- Deprecate `warning` and rename to `cautionFilled`. ([67895](https://github.com/WordPress/gutenberg/pull/67895)) + + +### Tools + +#### Build Tooling +- Add new private `upload-media` package. ([66290](https://github.com/WordPress/gutenberg/pull/66290)) +- Build: Simplify tsconfig.json files. ([68326](https://github.com/WordPress/gutenberg/pull/68326)) +- Clean script: Use braces instead of @-pattern for glob. ([67833](https://github.com/WordPress/gutenberg/pull/67833)) +- Fix VS Code performance. ([68347](https://github.com/WordPress/gutenberg/pull/68347)) +- Fix tsconfig for test/ directory. ([68346](https://github.com/WordPress/gutenberg/pull/68346)) +- Fix: Script with glob option doesn't work on Windows. ([67862](https://github.com/WordPress/gutenberg/pull/67862)) + +#### Testing +- Page - Quick Edit: Add end-to-end tests. ([68151](https://github.com/WordPress/gutenberg/pull/68151)) +- Add ESLint rule to prevent usage of the verb 'toggle' in translatable strings. ([67741](https://github.com/WordPress/gutenberg/pull/67741)) +- Enhance template registration end-to-end tests to handle welcome dialog visibility. ([68059](https://github.com/WordPress/gutenberg/pull/68059)) + + +### Various + +- ActionItem.Slot: Render as `MenuGroup` by default. ([67985](https://github.com/WordPress/gutenberg/pull/67985)) +- Storybook: Add BlockAlignmentMatrixControl Stories and update README. ([68007](https://github.com/WordPress/gutenberg/pull/68007)) +- Update "Call to Action" to "Call to action". ([67876](https://github.com/WordPress/gutenberg/pull/67876)) + +#### Plugin +- Assets: Add README.md about syncing. ([68128](https://github.com/WordPress/gutenberg/pull/68128)) +- Workflows: Sync assets to plugin repo upon change in trunk. ([68052](https://github.com/WordPress/gutenberg/pull/68052)) + + +## First-time contributors + +The following PRs were merged by first-time contributors: + +- @benazeer-ben: Add command to navigate to site editor. ([66722](https://github.com/WordPress/gutenberg/pull/66722)) +- @dhruvikpatel18: Fixed typo in README of TextTransformControl. ([68443](https://github.com/WordPress/gutenberg/pull/68443)) +- @fushar: Stylebook: Add the Appearance -> Design submenu through `admin_menu` action. ([68174](https://github.com/WordPress/gutenberg/pull/68174)) +- @im3dabasia: Storybook : Add TextTransformControl stories. ([67365](https://github.com/WordPress/gutenberg/pull/67365)) +- @justlevine: fix: Deprecated `WP_Webfonts()` constructor takes no arguments. ([66700](https://github.com/WordPress/gutenberg/pull/66700)) +- @karthick-murugan: Latest Posts Border Block Support. ([66353](https://github.com/WordPress/gutenberg/pull/66353)) +- @mayurprajapatii: Updated Document URL in Documentation. ([67990](https://github.com/WordPress/gutenberg/pull/67990)) +- @PARTHVATALIYA: Widget Editor: Fix: Close button is not working. ([65443](https://github.com/WordPress/gutenberg/pull/65443)) +- @prasadkarmalkar: Replace PanelBody with ToolsPanel and ToolsPanelItem in column block. ([67913](https://github.com/WordPress/gutenberg/pull/67913)) +- @rilwis: Fix wrong `npm start` command. ([65221](https://github.com/WordPress/gutenberg/pull/65221)) +- @sarthaknagoshe2002: Clarify template property behavior in InnerBlocks documentation to specify prefill when empty. ([66911](https://github.com/WordPress/gutenberg/pull/66911)) +- @timse201: Split upload into verbs and nouns. ([68227](https://github.com/WordPress/gutenberg/pull/68227)) +- @vampdroid: Add text domain option while scaffolding the block in create-block. ([57197](https://github.com/WordPress/gutenberg/pull/57197)) + + +## Contributors + +The following contributors merged PRs in this release: + +@aaronrobertshaw @afercia @akasunil @benazeer-ben @bph @Chrico @ciampo @d-alleyne @DAreRodz @dhruvikpatel18 @draganescu @ellatrix @fabiankaegy @fushar @getdave @gigitux @gziolo @hbhalodia @himanshupathak95 @im3dabasia @Infinite-Null @jameskoster @jasmussen @jeryj @jorgefilipecosta @jsnajdr @juanfra @justlevine @karthick-murugan @kmanijak @louwie17 @Lovor01 @Mamaduka @manzoorwanijk @matiasbenedetto @Mayank-Tripathi32 @mayurprajapatii @mcsf @michalczaplinski @mikachan @mirka @ntsekouras @oandregal @ockham @PARTHVATALIYA @prasadkarmalkar @ramonjd @rilwis @rinkalpagdar @Rishit30G @rohitmathur-7 @SainathPoojary @sarthaknagoshe2002 @SH4LIN @shail-mehta @shimotmk @sirreal @stokesman @Sukhendu2002 @swissspidy @t-hamano @talldan @tellthemachines @timse201 @tyxla @up1512001 @vampdroid @Vrishabhsk @yogeshbhutkar @youknowriad + + + + += 19.9.0 = + +## Changelog + +### Enhancements + +- Feature: Add `navigation.isLoading` state to core/router store. ([67680](https://github.com/WordPress/gutenberg/pull/67680)) +- Update the title, description, and order of Experiments page. ([67762](https://github.com/WordPress/gutenberg/pull/67762)) +- wp-env: Add phpMyAdmin support. ([67588](https://github.com/WordPress/gutenberg/pull/67588)) + +#### Components +- Added enableAlpha prop to CustomGradientPicker and GradientPicker components. ([66974](https://github.com/WordPress/gutenberg/pull/66974)) +- BorderBoxControl: Reduce gap value when unlinked. ([67049](https://github.com/WordPress/gutenberg/pull/67049)) +- DateTime: Add default date/time to stories. ([67678](https://github.com/WordPress/gutenberg/pull/67678)) +- Deprecate `COLORS.white`. ([67649](https://github.com/WordPress/gutenberg/pull/67649)) +- Disabled: Suppress `contentEditable` warning in story. ([67679](https://github.com/WordPress/gutenberg/pull/67679)) +- Document layout in Storybook. ([67628](https://github.com/WordPress/gutenberg/pull/67628)) +- DropdownMenu: Increase option height to 40px. ([67435](https://github.com/WordPress/gutenberg/pull/67435)) +- DuotonePicker: Simplify Button styles. ([66641](https://github.com/WordPress/gutenberg/pull/66641)) +- Menu: Throw when subcomponents are not rendered inside top level Menu. ([67411](https://github.com/WordPress/gutenberg/pull/67411)) +- Popover: Use `anchor` instead of `anchorRef` in story. ([67674](https://github.com/WordPress/gutenberg/pull/67674)) +- Storybook: Remove unnecessary feature flags. ([67576](https://github.com/WordPress/gutenberg/pull/67576)) +- Storybook: Update `ArgsTable` to `Controls` in preview. ([67582](https://github.com/WordPress/gutenberg/pull/67582)) +- Storybook: Update control types from `null` to `undefined`. ([67581](https://github.com/WordPress/gutenberg/pull/67581)) +- Storybook: Use manager-api instead of addons package. ([67578](https://github.com/WordPress/gutenberg/pull/67578)) +- Update @ariakit/react to 0.4.13. ([65907](https://github.com/WordPress/gutenberg/pull/65907)) +- Update @ariakit/react to 0.4.15 and @ariakit/test to 0.4.7. ([67404](https://github.com/WordPress/gutenberg/pull/67404)) + +#### Block Library +- Cover Block: Image size option for featured image. ([67273](https://github.com/WordPress/gutenberg/pull/67273)) +- Feature: Allow Post Template block to get deeply nested within Query Block. ([67657](https://github.com/WordPress/gutenberg/pull/67657)) +- Image Block: Change how the Image's overlay styles are applied. ([67788](https://github.com/WordPress/gutenberg/pull/67788)) +- Navigation: Enable all non-interactive formats. ([67585](https://github.com/WordPress/gutenberg/pull/67585)) +- Query block: Move patterns modal to dropdown on block toolbar. ([66993](https://github.com/WordPress/gutenberg/pull/66993)) +- Separator block: Allow divs to be used as separators. ([67530](https://github.com/WordPress/gutenberg/pull/67530)) +- New Block: Add Query Total block for displaying total query results or ranges. ([67629](https://github.com/WordPress/gutenberg/pull/67629)) +- Block Library: Update the relationship of `No results` block to `ancestor`. ([48348](https://github.com/WordPress/gutenberg/pull/48348)) + +#### DataViews +- Add header to the quick edit when bulk editing. ([67390](https://github.com/WordPress/gutenberg/pull/67390)) +- Data views: Expand configuration drop down on mobile. ([67715](https://github.com/WordPress/gutenberg/pull/67715)) +- Quick Edit: Add Template field. ([66591](https://github.com/WordPress/gutenberg/pull/66591)) +- Refactor actions to render modal outside of the menu. ([67664](https://github.com/WordPress/gutenberg/pull/67664)) +- Renders `DataForm` component only when data has been fetched. ([67694](https://github.com/WordPress/gutenberg/pull/67694)) +- Unify layout configuration. ([67477](https://github.com/WordPress/gutenberg/pull/67477)) +- Update bulk header with actions. ([67743](https://github.com/WordPress/gutenberg/pull/67743)) + +#### Style Book +- Add stylebook screen for classic themes. ([66851](https://github.com/WordPress/gutenberg/pull/66851)) +- Scroll to top at styles root. ([67605](https://github.com/WordPress/gutenberg/pull/67605)) +- Stylebook: Render overview colors in 4 columns. ([67597](https://github.com/WordPress/gutenberg/pull/67597)) +- Update style book headings to new design. ([67546](https://github.com/WordPress/gutenberg/pull/67546)) + +#### Post Editor +- Inline Commenting: Added new sidebar as extension of the canvas. ([67347](https://github.com/WordPress/gutenberg/pull/67347)) +- Inline Commenting: Re-order the comments in sidebar in which blocks are listed. ([66927](https://github.com/WordPress/gutenberg/pull/66927)) +- Inline commenting: UX Enhancements for Comments. ([67385](https://github.com/WordPress/gutenberg/pull/67385)) + +#### Site Editor +- Data Views: Add action for pages to set site homepage. ([65426](https://github.com/WordPress/gutenberg/pull/65426)) +- Sidebar: Update appearance of active items. ([67318](https://github.com/WordPress/gutenberg/pull/67318)) +- Style the selected template pattern. ([65917](https://github.com/WordPress/gutenberg/pull/65917)) + +#### Data Layer +- Data: Expose 'useSelect' warning to third-party consumers. ([67735](https://github.com/WordPress/gutenberg/pull/67735)) +- Data: Include more details when shallow equality fails in 'useSelect'. ([67713](https://github.com/WordPress/gutenberg/pull/67713)) + +#### Global Styles +- Controls in grid should match between sidebar panel and editor. ([67602](https://github.com/WordPress/gutenberg/pull/67602)) +- Shadows: Improve design and a11y of remove button. ([67705](https://github.com/WordPress/gutenberg/pull/67705)) + +#### Block Editor +- Prefer exact matches in Link Search results sorting. ([67367](https://github.com/WordPress/gutenberg/pull/67367)) +- Try direct drag (outside text editable). ([67305](https://github.com/WordPress/gutenberg/pull/67305)) + +#### Zoom Out +- Keep only copy, duplicate and delete in the zoom out more block toolbar menu item. ([67279](https://github.com/WordPress/gutenberg/pull/67279)) + +#### Font Library +- FontCollection: Update pagination controls. ([67143](https://github.com/WordPress/gutenberg/pull/67143)) + +#### Colors +- Add reset button to color control. ([67116](https://github.com/WordPress/gutenberg/pull/67116)) + + +### Bug Fixes + +- Exclude Set instance methods from polyfills. ([67230](https://github.com/WordPress/gutenberg/pull/67230)) +- Preload: Fix settings fields order. ([67450](https://github.com/WordPress/gutenberg/pull/67450)) +- Scripts: Make React Fast Refresh work with multiple blocks. ([64924](https://github.com/WordPress/gutenberg/pull/64924)) +- WP Scripts: Update webpack dependencies related to styling. ([67572](https://github.com/WordPress/gutenberg/pull/67572)) + +#### Site Editor +- Allow access to quick edit. ([67469](https://github.com/WordPress/gutenberg/pull/67469)) +- Edit Site: Fix sidebar template author navigation. ([67382](https://github.com/WordPress/gutenberg/pull/67382)) +- Fix Site editor navigation menu items alignment visual regression. ([67321](https://github.com/WordPress/gutenberg/pull/67321)) +- Fix sidebar item animation regression. ([67771](https://github.com/WordPress/gutenberg/pull/67771)) +- Fix sidebar plugins. ([67557](https://github.com/WordPress/gutenberg/pull/67557)) +- Fix the templates route on mobile. ([67547](https://github.com/WordPress/gutenberg/pull/67547)) +- Fix: Fixed site-editor crashing when added front-page template and clicking more option. ([67500](https://github.com/WordPress/gutenberg/pull/67500)) +- Fix: Fixed styling tab not opening on themes without style variations on mobile & desktop. ([67537](https://github.com/WordPress/gutenberg/pull/67537)) +- Preload: Parse post ID from p (path). ([67465](https://github.com/WordPress/gutenberg/pull/67465)) +- Remove default page slug. ([67673](https://github.com/WordPress/gutenberg/pull/67673)) +- Router: Fix addition and removal of empty classnames. ([67378](https://github.com/WordPress/gutenberg/pull/67378)) +- Wrap each router area in 'ErrorBoundary'. ([64245](https://github.com/WordPress/gutenberg/pull/64245)) +- useEditorTitle: Fix wrong request without ID. ([67475](https://github.com/WordPress/gutenberg/pull/67475)) + +#### Block Editor +- Animate `useScaleCanvas()` only when toggling zoomed out mode. ([67481](https://github.com/WordPress/gutenberg/pull/67481)) +- Drag and drop: Fix drop zones on block drag. ([67317](https://github.com/WordPress/gutenberg/pull/67317)) +- Drag and drop: Fix firefox compat logic. ([67439](https://github.com/WordPress/gutenberg/pull/67439)) +- Fix JS error in the 'useTabNav' hook. ([67102](https://github.com/WordPress/gutenberg/pull/67102)) +- FontFamilyControl: Restore margin bottom. ([67424](https://github.com/WordPress/gutenberg/pull/67424)) +- Inserter: Hide child blocks from the inserter when needed. ([67734](https://github.com/WordPress/gutenberg/pull/67734)) +- Inserter: Patterns: Remove loading indicator. ([67072](https://github.com/WordPress/gutenberg/pull/67072)) +- Inserter: Should receive focus on open. ([67754](https://github.com/WordPress/gutenberg/pull/67754)) +- Remove words count in the multi-selection inspector. ([67624](https://github.com/WordPress/gutenberg/pull/67624)) +- Storybook: Fix `BlockPatternsList` fixtures. ([67672](https://github.com/WordPress/gutenberg/pull/67672)) +- Drag and drop: Fix misplaced drop indicator. ([67434](https://github.com/WordPress/gutenberg/pull/67434)) +- Drag and drop: Fix scroll disorientation after drop. ([67405](https://github.com/WordPress/gutenberg/pull/67405)) +- Drag and drop: Restore moving animation. ([67417](https://github.com/WordPress/gutenberg/pull/67417)) + +#### Block Library +- Align Submenu block and Nav Link block by including description and wrapping span. ([67198](https://github.com/WordPress/gutenberg/pull/67198)) +- CommentsPagination: Set font-size to inherit for pagination items. ([67296](https://github.com/WordPress/gutenberg/pull/67296)) +- Fix latest post block spacing issue. ([66442](https://github.com/WordPress/gutenberg/pull/66442)) +- Fix: Caption with Link in Wide-Width and Full-Width Images Appears on two lines. ([67392](https://github.com/WordPress/gutenberg/pull/67392)) +- Fix: Don't show `aria-label` when its value is empty. ([67381](https://github.com/WordPress/gutenberg/pull/67381)) +- Navigation Block: Fix issue with double-clicking "Create a new menu" causing duplicate menus. ([67488](https://github.com/WordPress/gutenberg/pull/67488)) +- Pullquote block having design issue when text-decoration is choosen strikethrough. ([66707](https://github.com/WordPress/gutenberg/pull/66707)) +- Remove inline-block display from image anchor in style.scss. ([67368](https://github.com/WordPress/gutenberg/pull/67368)) +- Search block: Add space between attributes when using "Button only" option. ([61399](https://github.com/WordPress/gutenberg/pull/61399)) +- Updated 'Set featured image' text in dropdown. ([67775](https://github.com/WordPress/gutenberg/pull/67775)) + +#### DataViews +- Avoid double click handler on primary fields. ([67393](https://github.com/WordPress/gutenberg/pull/67393)) +- Better handling of missing onClickItem prop. ([67402](https://github.com/WordPress/gutenberg/pull/67402)) +- Fix filters lost when switching layouts. ([67740](https://github.com/WordPress/gutenberg/pull/67740)) +- Fix hidden List layout actions dropdown. ([67778](https://github.com/WordPress/gutenberg/pull/67778)) +- Fix reordering fields in list and grid layouts. ([67777](https://github.com/WordPress/gutenberg/pull/67777)) +- Fix: Duplicate template part refers to original name instead of duplicated name. ([67329](https://github.com/WordPress/gutenberg/pull/67329)) +- Preserve filters when switching layouts in templates dataviews. ([67744](https://github.com/WordPress/gutenberg/pull/67744)) +- QuickEdit: Prevent site-editor from crashing when slug is not an object. ([67577](https://github.com/WordPress/gutenberg/pull/67577)) +- Site Editor: Fix featured image not appearing in pages dataviews. ([67562](https://github.com/WordPress/gutenberg/pull/67562)) + +#### Components +- CustomSelectControl: Update Value from Fresh State. ([67733](https://github.com/WordPress/gutenberg/pull/67733)) +- Fix the 'ClipboardButton' effect cleanup. ([67399](https://github.com/WordPress/gutenberg/pull/67399)) +- Navigation: Fix active item hover color. ([67732](https://github.com/WordPress/gutenberg/pull/67732)) +- Scrollable: Fix story by declaring field as readonly. ([67683](https://github.com/WordPress/gutenberg/pull/67683)) +- Storybook: Fix control types. ([67646](https://github.com/WordPress/gutenberg/pull/67646)) +- Storybook: Fix storybook blocks imports. ([67684](https://github.com/WordPress/gutenberg/pull/67684)) +- Storybook: Fix table markup in Design Language - Radius documentation. ([67686](https://github.com/WordPress/gutenberg/pull/67686)) +- Theme: Fix contrast in nested story. ([67681](https://github.com/WordPress/gutenberg/pull/67681)) + +#### Post Editor +- Fix Meta boxes saving when they’re not present. ([67254](https://github.com/WordPress/gutenberg/pull/67254)) +- Fix hiding and showing of meta boxes. ([67504](https://github.com/WordPress/gutenberg/pull/67504)) +- Fix: Header layout spacing in Firefox. ([67074](https://github.com/WordPress/gutenberg/pull/67074)) +- Make sure Document Bar doesn’t go missing. ([67322](https://github.com/WordPress/gutenberg/pull/67322)) +- Update pre-publish panel wording to accurately describe the review process. ([67328](https://github.com/WordPress/gutenberg/pull/67328)) + +#### Zoom Out +- Fix for inserter. ([67495](https://github.com/WordPress/gutenberg/pull/67495)) +- Fix useZoomOut inserter behavior. ([67591](https://github.com/WordPress/gutenberg/pull/67591)) +- Fix zoom animation scrollbar. ([67536](https://github.com/WordPress/gutenberg/pull/67536)) +- UseScaleCanvas performance improvements. ([67496](https://github.com/WordPress/gutenberg/pull/67496)) + +#### Write mode +- Fix color of disabled buttons in dark toolbar. ([67348](https://github.com/WordPress/gutenberg/pull/67348)) +- Fix synced pattern editing in write mode and refactor block editing mode to reducer. ([67026](https://github.com/WordPress/gutenberg/pull/67026)) +- Fix: Remove parent block selector while in Write mode. ([67395](https://github.com/WordPress/gutenberg/pull/67395)) +- Fix: Write Mode mode persists as enabled in widget editor. ([67587](https://github.com/WordPress/gutenberg/pull/67587)) + +#### Global Styles +- Edit site: Remove empty preview border and redirect to editor in global styles navigation. ([67548](https://github.com/WordPress/gutenberg/pull/67548)) +- Fix: Styles section does not moves stylebook to typography. ([67423](https://github.com/WordPress/gutenberg/pull/67423)) +- Global Styles Preview: Don't use iframe component. ([67682](https://github.com/WordPress/gutenberg/pull/67682)) + +#### Style Book +- Fix critical error when blocks are not registered. ([67703](https://github.com/WordPress/gutenberg/pull/67703)) + +#### Design Tools +- Global Styles: Fix handling of booleans when stabilizing block supports. ([67552](https://github.com/WordPress/gutenberg/pull/67552)) + +#### Block bindings +- Revert "Extensibility: Make Block Bindings work with `editor.BlockEdit` hook". ([67516](https://github.com/WordPress/gutenberg/pull/67516)) + +#### Patterns +- Site Editor: Fix the patterns route on mobile. ([67467](https://github.com/WordPress/gutenberg/pull/67467)) + +#### Focus Mode +- Site Editor: Fix focus mode navigation. ([67458](https://github.com/WordPress/gutenberg/pull/67458)) + +#### List View +- Fix List View not updating when switching editor modes. ([67379](https://github.com/WordPress/gutenberg/pull/67379)) + +#### Extensibility +- Make Block Bindings work with `editor.BlockEdit` hook. ([67370](https://github.com/WordPress/gutenberg/pull/67370)) + +#### Synced Patterns +- Remove use of `contentOnly` block editing mode for synced patterns. ([67364](https://github.com/WordPress/gutenberg/pull/67364)) + +#### Widgets Editor +- Block Bindings: Remove client core sources registration in widgets. ([67349](https://github.com/WordPress/gutenberg/pull/67349)) + +#### REST API +- Support search_columns argument in the user endpoint. ([67330](https://github.com/WordPress/gutenberg/pull/67330)) + + +### Accessibility + +- [Dataviews] Fix: Space does not triggers the media button on grid view. ([67791](https://github.com/WordPress/gutenberg/pull/67791)) + +#### Block Editor +- BlockSwitcher: Refactor to use Button layout properly. ([67502](https://github.com/WordPress/gutenberg/pull/67502)) +- Remove one occurrence of incorrect usage of ItemGroup. ([67427](https://github.com/WordPress/gutenberg/pull/67427)) + +#### DataViews +- [a11y] Fix: Media button on the page view grid does not have an accessible name. ([67690](https://github.com/WordPress/gutenberg/pull/67690)) + +#### Components +- Fix incorrect usage of ItemGroup in the Image block filters panel. ([67513](https://github.com/WordPress/gutenberg/pull/67513)) + +#### Post Editor +- Fix EntitiesSavedStates panel dialog props. ([67351](https://github.com/WordPress/gutenberg/pull/67351)) + + +### Performance + +- Fix re-renders caused by `getEntityRecordsPermissions` after #67667. ([67770](https://github.com/WordPress/gutenberg/pull/67770)) +- Preload: Fix end-to-end test. ([67497](https://github.com/WordPress/gutenberg/pull/67497)) +- Site Editor: Pages: Preload template lookup. ([66654](https://github.com/WordPress/gutenberg/pull/66654)) +- [mini] Preload: Add post type. ([67518](https://github.com/WordPress/gutenberg/pull/67518)) + + +### Experiments + +- Move `duplicateTemplatePart` action to the `@wordpress/fields` package. ([65390](https://github.com/WordPress/gutenberg/pull/65390)) + + +### Documentation + +- Button: Revise documentation. ([66617](https://github.com/WordPress/gutenberg/pull/66617)) +- Docs: Fix Playwright Page Object Model link. ([67652](https://github.com/WordPress/gutenberg/pull/67652)) +- Docs: Include the strategy for setting `engines` for WordPress packages. ([67727](https://github.com/WordPress/gutenberg/pull/67727)) +- Docs: Remove invalid key projects links on the documentation. ([67491](https://github.com/WordPress/gutenberg/pull/67491)) +- Improve documentation for fields package. ([67580](https://github.com/WordPress/gutenberg/pull/67580)) +- Refine `getServerState()` & `getServerContext()` documentation. ([67499](https://github.com/WordPress/gutenberg/pull/67499)) +- Storybook: Add WritingModeControl story. ([67343](https://github.com/WordPress/gutenberg/pull/67343)) +- Storybook: Add stories for AlignmentToolbar and AlignmentControl components. ([67046](https://github.com/WordPress/gutenberg/pull/67046)) +- Storybook: Add stories for HeadingLevelDropdown component. ([67294](https://github.com/WordPress/gutenberg/pull/67294)) +- Storybook: Revert "Preview: ArgsTable => Controls (#67582)". ([67656](https://github.com/WordPress/gutenberg/pull/67656)) +- Storybook: Support keyword search in Icon Library. ([67442](https://github.com/WordPress/gutenberg/pull/67442)) +- Switch Several Links to https in Document Files. ([67706](https://github.com/WordPress/gutenberg/pull/67706)) +- Update README.md. ([67711](https://github.com/WordPress/gutenberg/pull/67711)) +- Update extending-the-query-loop-block.md. ([67529](https://github.com/WordPress/gutenberg/pull/67529)) +- Update global stylesheet docblocks with `custom-css` parameter. ([67716](https://github.com/WordPress/gutenberg/pull/67716)) +- Updated old URL in Documentation. ([67446](https://github.com/WordPress/gutenberg/pull/67446)) + + +### Code Quality + +- Convert lock unlock to generics. ([66682](https://github.com/WordPress/gutenberg/pull/66682)) +- CreateTemplatePartModal: Avoid identity warning in useSelect. ([67786](https://github.com/WordPress/gutenberg/pull/67786)) +- CreateTemplatePartModal: Replace `ts-ignore` with `ts-expect-error`. ([67709](https://github.com/WordPress/gutenberg/pull/67709)) +- Fix misc type compilation errors in editor and block editor packages. ([67410](https://github.com/WordPress/gutenberg/pull/67410)) +- Fix: Invalid JSDoc for optional string parameter and return value. ([67489](https://github.com/WordPress/gutenberg/pull/67489)) +- Fix: Remove unused test code on tools panel. ([67589](https://github.com/WordPress/gutenberg/pull/67589)) +- Removed trailing space in "Color randomizer ". ([67457](https://github.com/WordPress/gutenberg/pull/67457)) +- Update misc types and revert WPCompleter export from components. ([67599](https://github.com/WordPress/gutenberg/pull/67599)) + +#### Components +- BoxControl: Deprecate 36px default size. ([66704](https://github.com/WordPress/gutenberg/pull/66704)) +- BoxControl: Passive deprecate `onMouseOver`/`onMouseOut`. ([67332](https://github.com/WordPress/gutenberg/pull/67332)) +- BoxControl: Refactor and unify the different sides implementation. ([67626](https://github.com/WordPress/gutenberg/pull/67626)) +- CustomSelectControl: Deprecate 36px default size. ([67441](https://github.com/WordPress/gutenberg/pull/67441)) +- FormFileUpload: Deprecate 36px default size. ([67438](https://github.com/WordPress/gutenberg/pull/67438)) +- FormTokenField: Deprecate 36px default size. ([67454](https://github.com/WordPress/gutenberg/pull/67454)) +- NumberControl: Deprecate 36px default size. ([66730](https://github.com/WordPress/gutenberg/pull/66730)) +- RangeControl: Update the default marks styles to match the padding/margin control. ([67611](https://github.com/WordPress/gutenberg/pull/67611)) +- Remove `__unstableMotionContext` from `@wordpress/components`. ([67623](https://github.com/WordPress/gutenberg/pull/67623)) +- SlotFill: Remove explicit rerender from portal version. ([67471](https://github.com/WordPress/gutenberg/pull/67471)) +- Tabs: Overhaul unit tests. ([66140](https://github.com/WordPress/gutenberg/pull/66140)) +- ToolbarButton: Set size to "compact". ([67440](https://github.com/WordPress/gutenberg/pull/67440)) +- UnitControl : Deprecate 36px default size. ([66791](https://github.com/WordPress/gutenberg/pull/66791)) + +#### Block Editor +- Group 'onRemove' callback with other public APIs. ([67551](https://github.com/WordPress/gutenberg/pull/67551)) +- InspectorControlsSlot: Remove unused framer motion context forwarding. ([67522](https://github.com/WordPress/gutenberg/pull/67522)) +- LetteringSpacingControl: Deprecate 36px default size. ([67429](https://github.com/WordPress/gutenberg/pull/67429)) +- Reduce the 'isZoomOut' selector calls in the block toolbar. ([67594](https://github.com/WordPress/gutenberg/pull/67594)) +- Remove 'React.Children' legacy API in 'Warning' component. ([67675](https://github.com/WordPress/gutenberg/pull/67675)) +- Replace remaining custom deep cloning with 'structuredClone'. ([67707](https://github.com/WordPress/gutenberg/pull/67707)) +- Stabilize `LinkControl` Component. ([56384](https://github.com/WordPress/gutenberg/pull/56384)) + +#### Site Editor +- Remove .components-item-group selector in edit-site components[2]. ([67575](https://github.com/WordPress/gutenberg/pull/67575)) +- Site Editor Sidebar: Remove `hasGlobalStyleVariations` condition for the Styles nav item. ([67545](https://github.com/WordPress/gutenberg/pull/67545)) +- Unify layout with posts dataviews. ([67162](https://github.com/WordPress/gutenberg/pull/67162)) +- Use path based routing instead of query args and site-editor.php routes. ([67199](https://github.com/WordPress/gutenberg/pull/67199)) + +#### Post Editor +- Editor: Refactor 'PostPublishPanelPostpublish' to function component. ([67398](https://github.com/WordPress/gutenberg/pull/67398)) +- Editor: Use hooks instead of HOC in 'PostPublishButtonOrToggle'. ([67413](https://github.com/WordPress/gutenberg/pull/67413)) +- Remove PostSlugCheck and PostSlug unused components. ([67414](https://github.com/WordPress/gutenberg/pull/67414)) + +#### DataViews +- Create a single component for rendering the actions list. ([67558](https://github.com/WordPress/gutenberg/pull/67558)) +- Fix: Dataviews remove primary field concept from some classes. ([67689](https://github.com/WordPress/gutenberg/pull/67689)) + +#### Data Layer +- TypeScript: Convert factory utils in data package to TS. ([67667](https://github.com/WordPress/gutenberg/pull/67667)) + +#### Shortcodes +- Add types for shortcode package. ([67416](https://github.com/WordPress/gutenberg/pull/67416)) + +#### Block bindings +- Remove fallback for `context.postType` in post meta. ([67345](https://github.com/WordPress/gutenberg/pull/67345)) + +#### Block hooks +- Navigation block: Remove more obsolete Block Hooks helpers. ([67193](https://github.com/WordPress/gutenberg/pull/67193)) + + +### Tools + +- PR template: Add before/after table. ([62739](https://github.com/WordPress/gutenberg/pull/62739)) + +#### Build Tooling +- Build: Stop generating unused legacy scripts for core blocks. ([65268](https://github.com/WordPress/gutenberg/pull/65268)) +- CI: Skip native jobs. ([67799](https://github.com/WordPress/gutenberg/pull/67799)) +- DataViews build-wp: Don't bundle singleton WordPress packages. ([67590](https://github.com/WordPress/gutenberg/pull/67590)) +- DataViews build-wp: Don't bundle the date package. ([67612](https://github.com/WordPress/gutenberg/pull/67612)) +- Keycodes: Improve tree shaking by annotating exports as pure. ([67615](https://github.com/WordPress/gutenberg/pull/67615)) +- Upgrade TypeScript to 5.7 and fix types. ([67461](https://github.com/WordPress/gutenberg/pull/67461)) +- Combine the release steps to ensure that releases are tagged. ([65591](https://github.com/WordPress/gutenberg/pull/65591)) + +#### Testing +- e2e-test-utils-playwright: Increase timeout of site-editor selector. ([66672](https://github.com/WordPress/gutenberg/pull/66672)) + + +### Security + +#### npm Packages +- Update npm dependencies to fix issues reported by audit. ([67708](https://github.com/WordPress/gutenberg/pull/67708)) + + +### Various + +#### Extensibility +- Add ability to show drop cap setting in paragraph block by default. ([45994](https://github.com/WordPress/gutenberg/pull/45994)) +- DataViews: Move template and pattern title fields. ([67449](https://github.com/WordPress/gutenberg/pull/67449)) +- DataViews: Update `usePostFields` to accept postType. ([67380](https://github.com/WordPress/gutenberg/pull/67380)) + +#### Plugin +- Only override REST server for older WP versions. ([67779](https://github.com/WordPress/gutenberg/pull/67779)) + +#### NUX +- Welcome guide headline update. ([67654](https://github.com/WordPress/gutenberg/pull/67654)) + +#### Block Locking +- Simplify description and option names in the Lock modal dialog. ([67437](https://github.com/WordPress/gutenberg/pull/67437)) + + +## First-time contributors + +The following PRs were merged by first-time contributors: + +- @alexflorisca: e2e-test-utils-playwright: Increase timeout of site-editor selector. ([66672](https://github.com/WordPress/gutenberg/pull/66672)) +- @benazeer-ben: Site editor: Style the selected template pattern. ([65917](https://github.com/WordPress/gutenberg/pull/65917)) +- @creador-dev: Navigation Block: Fix issue with double-clicking "Create a new menu" causing duplicate menus. ([67488](https://github.com/WordPress/gutenberg/pull/67488)) +- @dknauss: Update README.md. ([67711](https://github.com/WordPress/gutenberg/pull/67711)) +- @im3dabasia: Removed trailing space in "Color randomizer ". ([67457](https://github.com/WordPress/gutenberg/pull/67457)) +- @Mayank-Tripathi32: Fix: Header layout spacing in Firefox. ([67074](https://github.com/WordPress/gutenberg/pull/67074)) +- @subodhr258: CustomSelectControl: Update Value from Fresh State. ([67733](https://github.com/WordPress/gutenberg/pull/67733)) +- @wwdes: Added enableAlpha prop to CustomGradientPicker and GradientPicker components. ([66974](https://github.com/WordPress/gutenberg/pull/66974)) + + +## Contributors + +The following contributors merged PRs in this release: + +@aaronrobertshaw @afercia @akasunil @alexflorisca @annezazu @benazeer-ben @ciampo @creador-dev @creativecoder @DAreRodz @dcalhoun @dd32 @dknauss @draganescu @ellatrix @fabiankaegy @getdave @gigitux @gvgvgvijayan @gziolo @hbhalodia @im3dabasia @imrraaj @jameskoster @jeryj @jorgefilipecosta @jsnajdr @juanfra @louwie17 @Mamaduka @manzoorwanijk @matiasbenedetto @Mayank-Tripathi32 @mcsf @michalczaplinski @miminari @mirka @ntsekouras @oandregal @ockham @prajapatisagar @ramonjd @sabernhardt @SantosGuillamot @sarthaknagoshe2002 @sgomes @shail-mehta @stokesman @subodhr258 @Sukhendu2002 @t-hamano @talldan @tellthemachines @tyxla @viralsampat-multidots @wwdes @yogeshbhutkar @youknowriad + + + + += 19.8.0 = + +## Changelog + +### Enhancements + +#### Block Library +- Details block: Use summary content as default label. ([67217](https://github.com/WordPress/gutenberg/pull/67217)) +- Make social icon navigation one arrow keypress. ([64883](https://github.com/WordPress/gutenberg/pull/64883)) +- Page List : Add border and spacing support. ([66385](https://github.com/WordPress/gutenberg/pull/66385)) +- Query Loop block: Remove 'add new post' prompt in the sidebar. ([67189](https://github.com/WordPress/gutenberg/pull/67189)) +- Query block: Update Enhanced Pagination help text. ([67173](https://github.com/WordPress/gutenberg/pull/67173)) +- Social Link: Add contentOnly editing support. ([66622](https://github.com/WordPress/gutenberg/pull/66622)) + +#### Components +- Autocomplete: Increase option height. ([67214](https://github.com/WordPress/gutenberg/pull/67214)) +- CircularOptionPicker: Update Button sizes. ([67285](https://github.com/WordPress/gutenberg/pull/67285)) +- ColorPalette: Disable `Clear` button if there's no color value. ([67108](https://github.com/WordPress/gutenberg/pull/67108)) +- ColorPicker: Update sizes of format select and copy button. ([67093](https://github.com/WordPress/gutenberg/pull/67093)) +- ComboboxControl: Update reset button size. ([67215](https://github.com/WordPress/gutenberg/pull/67215)) + +#### DataViews +- Add density option to `table` layout. ([67170](https://github.com/WordPress/gutenberg/pull/67170)) +- DataForm: Enable fields to declare a different layout. ([66531](https://github.com/WordPress/gutenberg/pull/66531)) +- DataViews list layout: Hide actions menu when there is only one action and is primary. ([67015](https://github.com/WordPress/gutenberg/pull/67015)) +- DataViews table layout: Hide actions menu when there is only one action and is primary. ([67020](https://github.com/WordPress/gutenberg/pull/67020)) +- Reduce the size of action button in Grid layout. ([67032](https://github.com/WordPress/gutenberg/pull/67032)) +- DataViews: Allow register/unregister fields. ([67175](https://github.com/WordPress/gutenberg/pull/67175)) + +#### Global Styles +- Block Supports: Extend stabilization to common experimental block support flags. ([67018](https://github.com/WordPress/gutenberg/pull/67018)) +- Borders: Stabilize border block supports within block processing. ([66918](https://github.com/WordPress/gutenberg/pull/66918)) +- Site Editor > Styles: Open styles inspector when clicking preview canvas. ([66996](https://github.com/WordPress/gutenberg/pull/66996)) + +#### Media +- Block Editor: Add notice action to revert image to original after cropping. ([67314](https://github.com/WordPress/gutenberg/pull/67314)) +- Block Editor: Add success notices for image editing. ([67312](https://github.com/WordPress/gutenberg/pull/67312)) + +#### REST API +- Feature: Set editor rendering mode by post type. ([62304](https://github.com/WordPress/gutenberg/pull/62304)) +- Terms: Respect order specified by register_taxonomy(). ([67154](https://github.com/WordPress/gutenberg/pull/67154)) + +#### Zoom Out +- Leave help text regardless of zoom state. ([67132](https://github.com/WordPress/gutenberg/pull/67132)) +- Preserve footer template bar in zoom out. ([67135](https://github.com/WordPress/gutenberg/pull/67135)) +- Add section styles switch button in block toolbar in zoom out mode. ([67140](https://github.com/WordPress/gutenberg/pull/67140)) + +#### Post Editor +- Move default template types and template part areas to REST API. ([66459](https://github.com/WordPress/gutenberg/pull/66459)) +- Move `usePostFields` to `wordpress/editor` package. ([67024](https://github.com/WordPress/gutenberg/pull/67024)) + +#### Site Editor +- Blocks: Adds check for parent before showing convert to pattern button. ([66158](https://github.com/WordPress/gutenberg/pull/66158)) +- Try dark toolbar for the write mode. ([66116](https://github.com/WordPress/gutenberg/pull/66116)) + +#### Design Tools +- Heading: Hide border controls by default. ([67105](https://github.com/WordPress/gutenberg/pull/67105)) +- Font family preview in the font family picker. ([67118](https://github.com/WordPress/gutenberg/pull/67118)) + +### Bug Fixes + +#### Block Library +- Fix block mover clickable area. ([67261](https://github.com/WordPress/gutenberg/pull/67261)) +- Fix dropping media from inserter into Cover block. ([67056](https://github.com/WordPress/gutenberg/pull/67056)) +- Fix: Preserve Display Preview State in File Block. ([67263](https://github.com/WordPress/gutenberg/pull/67263)) +- Paragraph: Update condition for rendering Drop Cap for a selected block. ([67111](https://github.com/WordPress/gutenberg/pull/67111)) +- RSS block: Check for description field before rendering excerpt. ([66985](https://github.com/WordPress/gutenberg/pull/66985)) +- Resolve search block button text overlapping issue. ([66868](https://github.com/WordPress/gutenberg/pull/66868)) +- Social Links: Fix font family and weight inconsistency in editor. ([67204](https://github.com/WordPress/gutenberg/pull/67204)) + +#### Components +- Composite: Restore `Hover` and `Typeahead` functionality. ([67212](https://github.com/WordPress/gutenberg/pull/67212)) +- Menu.ItemHelpText: Better line breaking. ([67011](https://github.com/WordPress/gutenberg/pull/67011)) +- SlotFill: Fix a bug with storing stale fillProps. ([67000](https://github.com/WordPress/gutenberg/pull/67000)) +- Storybook: Fix DataViews layout. ([66999](https://github.com/WordPress/gutenberg/pull/66999)) +- `FormFileUpload`: Prevent HEIC and HEIF files from always being uploaded on Safari. ([67139](https://github.com/WordPress/gutenberg/pull/67139)) + +#### Block Editor +- Add all color palettes to select from editor panel. ([65148](https://github.com/WordPress/gutenberg/pull/65148)) +- Correctly mark Block Comment SlotFills private. ([67271](https://github.com/WordPress/gutenberg/pull/67271)) +- Fix media placeholder to only activate for media objects. ([66986](https://github.com/WordPress/gutenberg/pull/66986)) +- Rich text: Preserve comments. ([62128](https://github.com/WordPress/gutenberg/pull/62128)) +- Fix TS types for the editor package. ([67196](https://github.com/WordPress/gutenberg/pull/67196)) +- PostTitle: Exit early when post type doesn't support titles. ([67086](https://github.com/WordPress/gutenberg/pull/67086)) +- Split view with meta boxes even with legacy canvas. ([66706](https://github.com/WordPress/gutenberg/pull/66706)) +- Edit Site: Styles nav item does not open on mobile for themes without style variations. ([67550](https://github.com/WordPress/gutenberg/pull/67550)) + +#### Global Styles +- Avoid zooming out when browsing styles if the preview mode is active. ([67190](https://github.com/WordPress/gutenberg/pull/67190)) +- Remove styles from blocks' previews. ([67144](https://github.com/WordPress/gutenberg/pull/67144)) +- Style panel: Use correct revisions count. ([67180](https://github.com/WordPress/gutenberg/pull/67180)) +- Theme JSON: Include block style variations in path only output of get_block_nodes. ([66948](https://github.com/WordPress/gutenberg/pull/66948)) +- Fix: Logic for Highlight/text-color format availability. ([65530](https://github.com/WordPress/gutenberg/pull/65530)) +- Fix complex variation selectors when using selectors API. ([67061](https://github.com/WordPress/gutenberg/pull/67061)) +#### Site Editor +- Prevent Pre-Publish Panel from Displaying Incorrect Information After Navigating away. ([67010](https://github.com/WordPress/gutenberg/pull/67010)) +- Site Editor Sidebar: Fixed focus/hover style for navigation item buttons. ([67251](https://github.com/WordPress/gutenberg/pull/67251)) +- Site Hub: Fix height in mobile layout. ([67110](https://github.com/WordPress/gutenberg/pull/67110)) +- Site Editor: Styles: Fix inspector opening. ([67004](https://github.com/WordPress/gutenberg/pull/67004)) +- Improve accessibility and consistency of the 'Last modified' Revisions button. ([66606](https://github.com/WordPress/gutenberg/pull/66606)) +- Remove styles from examples. ([67098](https://github.com/WordPress/gutenberg/pull/67098)) +- Editor: Correctly select post title support in 'DocumentOutline'. ([67109](https://github.com/WordPress/gutenberg/pull/67109)) + + +#### DataViews +- Fix action visibility logic. ([67197](https://github.com/WordPress/gutenberg/pull/67197)) +- Fix primary field misalignment in grid layout. ([66995](https://github.com/WordPress/gutenberg/pull/66995)) +- Fix spacing when combining combined fields. ([67226](https://github.com/WordPress/gutenberg/pull/67226)) + +#### Zoom Out +- Zoom In/Out to correct canvas location. ([66917](https://github.com/WordPress/gutenberg/pull/66917)) +- Zoom in/out to correct location. ([67126](https://github.com/WordPress/gutenberg/pull/67126)) +- Zoom Out: Disable zooming out when Distraction Free mode is activated. ([67028](https://github.com/WordPress/gutenberg/pull/67028)) +- Disable Zoom Out if no section root to allow for Theme opt in. ([67232](https://github.com/WordPress/gutenberg/pull/67232)) + +#### Layout +- Allow flex justification controls to be disabled at the block level. ([67059](https://github.com/WordPress/gutenberg/pull/67059)) +- Show vertical alignment toolbar with allowSwitching enabled. ([67022](https://github.com/WordPress/gutenberg/pull/67022)) + +#### Patterns +- Fix: JavaScript error when pattern category is unregistered. ([67063](https://github.com/WordPress/gutenberg/pull/67063)) +- Block Locking: Remove edit locking for Synced Patterns. ([67021](https://github.com/WordPress/gutenberg/pull/67021)) + +### Accessibility + +#### Components +- ColorPicker: Add accessible label for copy button. ([67094](https://github.com/WordPress/gutenberg/pull/67094)) +- Modal: Increase size of the Close button. ([66792](https://github.com/WordPress/gutenberg/pull/66792)) +- DataViews: Fix focus loss when removing all filters or resetting. ([67003](https://github.com/WordPress/gutenberg/pull/67003)) + +#### Block Library +- Improve accessibility of the video track editor. ([66832](https://github.com/WordPress/gutenberg/pull/66832)) +- Navigation: Fix 'ariaLabel' block support. ([66943](https://github.com/WordPress/gutenberg/pull/66943)) + +#### Post Editor +- Improve the featured image UI when it cannot retrieve the image file and data. ([66936](https://github.com/WordPress/gutenberg/pull/66936)) + +### Experiments + +- Inline Commenting: Update placement of reply input and add author info header. ([66580](https://github.com/WordPress/gutenberg/pull/66580)) +- Place "Write mode" functionality behind a Gutenberg experiment. ([67008](https://github.com/WordPress/gutenberg/pull/67008)) + +### Documentation + +- Add documentation about required Core changes when updating minimum WordPress version. ([67167](https://github.com/WordPress/gutenberg/pull/67167)) +- BoxControl: Auto-generate readme. ([67284](https://github.com/WordPress/gutenberg/pull/67284)) +- Components contributing guide: Fix relative links. ([67323](https://github.com/WordPress/gutenberg/pull/67323)) +- DataViews: Reorganize documentation for actions. ([67159](https://github.com/WordPress/gutenberg/pull/67159)) +- Docs: Correct `@return` type in `block_core_query_disable_enhanced_pagination()`. ([67128](https://github.com/WordPress/gutenberg/pull/67128)) +- Feat: Storybook: Improve component organisation - Layout Category - Issue #66275. ([66659](https://github.com/WordPress/gutenberg/pull/66659)) +- Feat: Storybook: Improve component organisation - Selection & Input Category - Issue #66275. ([66635](https://github.com/WordPress/gutenberg/pull/66635)) +- GradientPicker: Auto-generate readme. ([67250](https://github.com/WordPress/gutenberg/pull/67250)) +- Icon: Auto-generate readme. ([67282](https://github.com/WordPress/gutenberg/pull/67282)) +- Icon: Improve `icon` prop usage documentation in Storybook. ([67280](https://github.com/WordPress/gutenberg/pull/67280)) +- Storybook: Restore stable components back into categories. ([67216](https://github.com/WordPress/gutenberg/pull/67216)) +- Update BlockMover Stories and README. ([66519](https://github.com/WordPress/gutenberg/pull/66519)) +- Update custom store readme to use thunks instead of controls. ([67006](https://github.com/WordPress/gutenberg/pull/67006)) +- Update versions-in-wordpress.md. ([67298](https://github.com/WordPress/gutenberg/pull/67298)) + +### Code Quality + +- ESLint: Enable `eslint-plugin-react-compiler`. ([61788](https://github.com/WordPress/gutenberg/pull/61788)) +- Extract selectors from useResolveEditedEntity hook. ([67031](https://github.com/WordPress/gutenberg/pull/67031)) +- Pattern: Remove backward compatibility code for WordPress < 6.4. ([67131](https://github.com/WordPress/gutenberg/pull/67131)) +- Post fields: Move `author` from `edit-site` to `fields` package. ([66939](https://github.com/WordPress/gutenberg/pull/66939)) +- Posts DataViews: Refactor the router to use route registration. ([67160](https://github.com/WordPress/gutenberg/pull/67160)) +- Comments controller: Fix issue where comments are allowed when closed. ([66976](https://github.com/WordPress/gutenberg/pull/66976)) +- Fix fatal error in in_array call in post_type_default_rendering_mode. ([67225](https://github.com/WordPress/gutenberg/pull/67225)) +- Data: Add changelog for Redux update. ([66968](https://github.com/WordPress/gutenberg/pull/66968)) + +#### Components +- BorderBoxControl: Suppress redundant warnings for deprecated 36px size. ([67213](https://github.com/WordPress/gutenberg/pull/67213)) +- ComboboxControl : Deprecate 36px default size. ([66900](https://github.com/WordPress/gutenberg/pull/66900)) +- CustomGradientPicker: Prepare `Button`s for 40px default size. ([67286](https://github.com/WordPress/gutenberg/pull/67286)) +- Dashicons: Remove non-existent icons from type. ([67235](https://github.com/WordPress/gutenberg/pull/67235)) +- DimensionControl: Deprecate 36px default size. ([66705](https://github.com/WordPress/gutenberg/pull/66705)) +- Feat: Adds the deprecation warning for 36px default size in range control. ([66721](https://github.com/WordPress/gutenberg/pull/66721)) +- FontSizePicker : Deprecate 36px default size. ([66920](https://github.com/WordPress/gutenberg/pull/66920)) +- Remove createPrivateSlotFill function. ([67238](https://github.com/WordPress/gutenberg/pull/67238)) +- SlotFill: Fix dependencies of registration effects, deduplicate code. ([67071](https://github.com/WordPress/gutenberg/pull/67071)) +- SlotFill: Remove registration API from useSlot result. ([67070](https://github.com/WordPress/gutenberg/pull/67070)) +- SlotFill: Rewrite base Slot to functional, unify rerenderable refs. ([67153](https://github.com/WordPress/gutenberg/pull/67153)) +- TextControl: Deprecate 36px default size. ([66745](https://github.com/WordPress/gutenberg/pull/66745)) +- ToggleGroupControl : Deprecate 36px default size. ([66747](https://github.com/WordPress/gutenberg/pull/66747)) + +#### Post Editor +- ESLint: Bump `eslint-plugin-react-compiler` to latest beta. ([67106](https://github.com/WordPress/gutenberg/pull/67106)) +- Edit Post: Refactor 'MetaBoxVisibility' component. ([67265](https://github.com/WordPress/gutenberg/pull/67265)) +- Edit Post: Remove unused 'hasHistory' flag. ([67293](https://github.com/WordPress/gutenberg/pull/67293)) +- Editor: Update focus return handler for the Featured Image. ([67236](https://github.com/WordPress/gutenberg/pull/67236)) +- Make `BlockManager` component reusable. ([67052](https://github.com/WordPress/gutenberg/pull/67052)) +- Preferences: Use hooks instead of HoC in 'EnableCustomFieldsOption'. ([67023](https://github.com/WordPress/gutenberg/pull/67023)) +- Preferences: Use hooks instead of HoC in 'EnablePanelOption'. ([66994](https://github.com/WordPress/gutenberg/pull/66994)) +- Preferences: Use hooks instead of HoC in 'EnablePublishSidebarOption'. ([67002](https://github.com/WordPress/gutenberg/pull/67002)) + +#### Block Library +- Fix React Compiler error for shortcuts. ([67019](https://github.com/WordPress/gutenberg/pull/67019)) +- Home Link: Remove label attribute synchronization. ([67151](https://github.com/WordPress/gutenberg/pull/67151)) +- Use rems for Nav overlay left padding. ([67168](https://github.com/WordPress/gutenberg/pull/67168)) +- useBlockNameForPatterns: Refactor as a single useSelect call. ([67171](https://github.com/WordPress/gutenberg/pull/67171)) +- Navigation Block: Remove obsolete Block Hooks filters. ([64676](https://github.com/WordPress/gutenberg/pull/64676)) +- [mini] 🧹 remove obsolete rich text css. ([67264](https://github.com/WordPress/gutenberg/pull/67264)) + +#### Global Styles +- Don't call store actions during the render. ([67146](https://github.com/WordPress/gutenberg/pull/67146)) +- Edit Site: Fix settings mutation in `ScreenBlock`. ([67085](https://github.com/WordPress/gutenberg/pull/67085)) +- Remove unused 'Fragment' import. ([67104](https://github.com/WordPress/gutenberg/pull/67104)) + +#### Block Editor +- Block Manager: Make it a private component in the block editor package. ([67255](https://github.com/WordPress/gutenberg/pull/67255)) +- Inserter: Set initial active tab ID during render. ([67103](https://github.com/WordPress/gutenberg/pull/67103)) + +#### Site Editor +- Deprecate edited entity state. ([66965](https://github.com/WordPress/gutenberg/pull/66965)) +- Remove redundant style-edit route. ([67057](https://github.com/WordPress/gutenberg/pull/67057)) + +### Tools + +#### Testing +- Fix ESLint Jest reporting entire body of the test function rather than the identifier. ([67222](https://github.com/WordPress/gutenberg/pull/67222)) +- Fix typo in use-block-sync tests. ([67145](https://github.com/WordPress/gutenberg/pull/67145)) +- Migrate Gradle wrapper validation action. ([66602](https://github.com/WordPress/gutenberg/pull/66602)) + +#### Plugin +- Bump minimum required WordPress version to 6.6. ([67117](https://github.com/WordPress/gutenberg/pull/67117)) +- Add #7895 Core Backport PR to the changelog. ([67319](https://github.com/WordPress/gutenberg/pull/67319)) +- WP Scripts: Revert changes that inline CSS imports early in the build process. ([66975](https://github.com/WordPress/gutenberg/pull/66975)) + +## First-time contributors + +The following PRs were merged by first-time contributors: + +- @AKSHAT2802: Add all color palettes to select from editor panel. ([65148](https://github.com/WordPress/gutenberg/pull/65148)) +- @benazeer-ben: Page List : Add border and spacing support. ([66385](https://github.com/WordPress/gutenberg/pull/66385)) +- @himanshupathak95: Menu.ItemHelpText: Better line breaking. ([67011](https://github.com/WordPress/gutenberg/pull/67011)) +- @SainathPoojary: Social Links: Fix font family and weight inconsistency in editor. ([67204](https://github.com/WordPress/gutenberg/pull/67204)) +- @sarthaknagoshe2002: Prevent Pre-Publish Panel from Displaying Incorrect Information After Navigating away. ([67010](https://github.com/WordPress/gutenberg/pull/67010)) +- @Sukhendu2002: Fix: Preserve Display Preview State in File Block. ([67263](https://github.com/WordPress/gutenberg/pull/67263)) + + +## Contributors + +The following contributors merged PRs in this release: + +@aaronrobertshaw @afercia @ajlende @akasunil @AKSHAT2802 @benazeer-ben @benniledl @carolinan @cbravobernal @desrosj @dhruvang21 @dougwollison @ellatrix @getdave @gigitux @gziolo @hbhalodia @himanshupathak95 @Infinite-Null @jeryj @jsnajdr @juanfra @louwie17 @Mamaduka @manzoorwanijk @matiasbenedetto @mcsf @michalczaplinski @miminari @mirka @ndiego @ntsekouras @oandregal @ockham @PARTHVATALIYA @ramonjd @SainathPoojary @SantosGuillamot @sarthaknagoshe2002 @snehapatil2001 @Soean @stokesman @Sukhendu2002 @t-hamano @talldan @tellthemachines @TylerB24890 @tyxla @up1512001 @vipul0425 @yogeshbhutkar @youknowriad + + + + += 19.7.0 = + +## Changelog + +### Enhancements + +- Add "show template" to preview dropdown. ([66514](https://github.com/WordPress/gutenberg/pull/66514)) +- Iframe: Always enable for block themes, in core too. ([66800](https://github.com/WordPress/gutenberg/pull/66800)) +- Media Utils: Add experimental `sideloadMedia`. ([66378](https://github.com/WordPress/gutenberg/pull/66378)) +- Post fields: Clean up. ([66941](https://github.com/WordPress/gutenberg/pull/66941)) +- Post fields: Extract `title` from `edit-site` to `fields` package. ([66940](https://github.com/WordPress/gutenberg/pull/66940)) +- Post fields: Move `comment_status` from edit-site to fields package. ([66934](https://github.com/WordPress/gutenberg/pull/66934)) +- Post fields: Move `date` fields from `edit-site` to `fields` package. ([66938](https://github.com/WordPress/gutenberg/pull/66938)) +- Post fields: Move `status` from `edit-site` to `fields`. ([66937](https://github.com/WordPress/gutenberg/pull/66937)) +- Relocate “View” external link to end of editor header controls. ([66785](https://github.com/WordPress/gutenberg/pull/66785)) + +#### Block Library +- Added toggle control to set any image as feature image if no feature image is set for post. ([65896](https://github.com/WordPress/gutenberg/pull/65896)) +- Improve cover z-index solution. ([66249](https://github.com/WordPress/gutenberg/pull/66249)) +- Post Content: Add border and spacing support. ([66366](https://github.com/WordPress/gutenberg/pull/66366)) +- Query Loop: Use templateSlug and postType for more context. ([65820](https://github.com/WordPress/gutenberg/pull/65820)) +- Update text case of "Starter Content". ([66954](https://github.com/WordPress/gutenberg/pull/66954)) +- [Details Block]: Adds anchor support in details block. ([66734](https://github.com/WordPress/gutenberg/pull/66734)) + +#### Components +- Guide: Use small size button for page controls. ([66607](https://github.com/WordPress/gutenberg/pull/66607)) +- MenuItem: Add 40px size prop on Button. ([66596](https://github.com/WordPress/gutenberg/pull/66596)) +- Notice: Add appropriate size props to Buttons. ([66593](https://github.com/WordPress/gutenberg/pull/66593)) +- PaletteEdit: Add appropriate size props to Buttons. ([66590](https://github.com/WordPress/gutenberg/pull/66590)) +- Popover: Add small size prop to close button. ([66587](https://github.com/WordPress/gutenberg/pull/66587)) + +#### Global Styles +- Global styles revisions: Move focus and active state to list item. ([66780](https://github.com/WordPress/gutenberg/pull/66780)) +- Site editor: Integrate global styles controls and style book preview into the styles panel. ([65619](https://github.com/WordPress/gutenberg/pull/65619)) + +#### DataViews +- DataViews Fields API: Default getValueFromId supports nested objects. ([66890](https://github.com/WordPress/gutenberg/pull/66890)) + +#### Block Editor +- Inserter: Add 'Starter Content' category to the inserter. ([66819](https://github.com/WordPress/gutenberg/pull/66819)) + +#### Zoom Out +- Enable zoom out mode for non-iframe editor. ([66789](https://github.com/WordPress/gutenberg/pull/66789)) + +#### Themes +- Theme JSON Resolver: Remove theme json merge in resolve_theme_file_uris. ([66662](https://github.com/WordPress/gutenberg/pull/66662)) + +#### Edit Mode +- Image block: Add support for "more" dropdown for additional tools in Write mode. ([66605](https://github.com/WordPress/gutenberg/pull/66605)) + +#### Style Book +- Add a landing section to stylebook tabs. ([66545](https://github.com/WordPress/gutenberg/pull/66545)) + +#### Media +- Media Library: Expose filters dropdown for individual images, such as with the Image block. ([65965](https://github.com/WordPress/gutenberg/pull/65965)) + + +### Bug Fixes + +- Block toolbar: Restrict visible child calculation to known blocks. ([66702](https://github.com/WordPress/gutenberg/pull/66702)) +- ComplementaryArea: Fix button position. ([66677](https://github.com/WordPress/gutenberg/pull/66677)) +- Fix Paragraph appender layout shift (building on 66061). ([66779](https://github.com/WordPress/gutenberg/pull/66779)) +- Fix: Set the `fit-content` width for images that are not `.svg`. ([66643](https://github.com/WordPress/gutenberg/pull/66643)) +- Preference modal: Avoid fetching all reusable blocks when the site editor loads. ([66621](https://github.com/WordPress/gutenberg/pull/66621)) +- Revert "Set image width to `fit-content` to solve aspect ratio problems in Firefox. (#66217)". ([66804](https://github.com/WordPress/gutenberg/pull/66804)) +- Safari: Fix site editor template error. ([66647](https://github.com/WordPress/gutenberg/pull/66647)) +- Safari: Prevent focus capturing caused by flex display. ([66402](https://github.com/WordPress/gutenberg/pull/66402)) +- Select Mode: Hide tool selector in the post editor and force design mode. ([66784](https://github.com/WordPress/gutenberg/pull/66784)) +- Shadow panel: Make the delete modal text translatable. ([66712](https://github.com/WordPress/gutenberg/pull/66712)) +- Site Editor: Fix template for page-on-front option. ([66739](https://github.com/WordPress/gutenberg/pull/66739)) +- WP Scripts: Make watch mode more resilient for developer errors. ([66752](https://github.com/WordPress/gutenberg/pull/66752)) +- getDefaultTemplateId: Ensure entity configuration is loaded. ([66650](https://github.com/WordPress/gutenberg/pull/66650)) +- Comments controller: fix issue where comments are allowed when closed (https://github.com/WordPress/gutenberg/pull/66976) + +#### Block Library +- Cover: Fix media library image selection. ([66782](https://github.com/WordPress/gutenberg/pull/66782)) +- Cover: Show DropZone only when dragging withing the block. ([66912](https://github.com/WordPress/gutenberg/pull/66912)) +- Media & Text: Set `.wp-block-media-text__media a` display to block. ([66915](https://github.com/WordPress/gutenberg/pull/66915)) +- Prevent duplicate post format taxonomy queries. ([66627](https://github.com/WordPress/gutenberg/pull/66627)) +- Query Loop: Check for postTypeFromContext before using it. ([66655](https://github.com/WordPress/gutenberg/pull/66655)) +- Query Loop: Remove postTypeFromContext. ([66681](https://github.com/WordPress/gutenberg/pull/66681)) + +#### Block Editor +- Appender: Fix initial position. ([66711](https://github.com/WordPress/gutenberg/pull/66711)) +- Appender: Fix outside canvas styles. ([66630](https://github.com/WordPress/gutenberg/pull/66630)) +- Block Inspector: Restore bottom margin for RadioControl. ([66688](https://github.com/WordPress/gutenberg/pull/66688)) +- Iframed editor: Fix relative wp-content URLs. ([66751](https://github.com/WordPress/gutenberg/pull/66751)) + +#### Global Styles +- Section Styles: Fix insecure properties removal for inner block types and elements. ([66896](https://github.com/WordPress/gutenberg/pull/66896)) +- Style book: Reduce margin selector specificity so that it doesn't override global block styles. ([66895](https://github.com/WordPress/gutenberg/pull/66895)) +- Theme JSON: Replace top-level background style objects on merge. ([66656](https://github.com/WordPress/gutenberg/pull/66656)) + +#### Components +- FormTokenField: Fix token styles. ([66640](https://github.com/WordPress/gutenberg/pull/66640)) +- Storybook: Fix DataViews action modals. ([66727](https://github.com/WordPress/gutenberg/pull/66727)) +- ToggleGroupControl: Fix active background for `zero` value. ([66855](https://github.com/WordPress/gutenberg/pull/66855)) + +#### Post Editor +- Disable device preview button in pattern/template part/navitation editor. ([65970](https://github.com/WordPress/gutenberg/pull/65970)) +- PostTaxonomiesFlatTermSelector: Abstract wrapper component. ([66625](https://github.com/WordPress/gutenberg/pull/66625)) +- VisualEditor: Always output has-global-padding classname when in post only mode. ([66626](https://github.com/WordPress/gutenberg/pull/66626)) + +#### DataViews +- Fix TypeError when duplicating uncategorized theme patterns. ([66889](https://github.com/WordPress/gutenberg/pull/66889)) +- Tweak primary field in patterns grid layout. ([66733](https://github.com/WordPress/gutenberg/pull/66733)) + +#### Meta Boxes +- Fix: Show Meta Boxes at the bottom of the screen regardless of the current rendering mode. ([66508](https://github.com/WordPress/gutenberg/pull/66508)) +- Hide metaboxes in Zoom Out. ([66886](https://github.com/WordPress/gutenberg/pull/66886)) + +#### Site Editor +- DataViews: Fix 'aria-label' for pattern preview element. ([66601](https://github.com/WordPress/gutenberg/pull/66601)) +- Site Hub: Fixed navigation redirect on mobile devices for classic themes. ([66867](https://github.com/WordPress/gutenberg/pull/66867)) + +#### Media +- Add `x-wav` mime type for wav files in Firefox. ([66850](https://github.com/WordPress/gutenberg/pull/66850)) +- Ensure HEIC files selectable from “Upload” button. ([66292](https://github.com/WordPress/gutenberg/pull/66292)) + +#### Patterns +- Fix uncategorized pattern browsing when pattern has no categories. ([66945](https://github.com/WordPress/gutenberg/pull/66945)) + +#### Interactivity API +- Fix property modification from inherited context two or more levels above. ([66872](https://github.com/WordPress/gutenberg/pull/66872)) + +#### Block API +- Process Block Type: Copy deprecation to a new object instead of mutating when stabilizing supports. ([66849](https://github.com/WordPress/gutenberg/pull/66849)) + +#### Design Tools +- Block Gap: Fix block spacing control for axial gap supported blocks. ([66783](https://github.com/WordPress/gutenberg/pull/66783)) + +#### Document Settings +- Editor: Restore the 'PluginPostStatusInfo' slot position. ([66665](https://github.com/WordPress/gutenberg/pull/66665)) + +#### Templates API +- Fix flash when clicking template name in the editor when a plugin registered template matches a default WP theme template. ([66359](https://github.com/WordPress/gutenberg/pull/66359)) + +#### Block bindings +- Fix unset array key warning in block-bindings.php. ([66337](https://github.com/WordPress/gutenberg/pull/66337)) + + +### Accessibility + +- Fix : Snackbar Notice Inconsistency. ([66405](https://github.com/WordPress/gutenberg/pull/66405)) +- Image: Add `aria-haspopup` prop write mode `more` tools menu items. ([66815](https://github.com/WordPress/gutenberg/pull/66815)) +- Site Icon Focus fix. ([66952](https://github.com/WordPress/gutenberg/pull/66952)) + +#### Components +- Popover: Fix missing label of the headerTitle Close button. ([66813](https://github.com/WordPress/gutenberg/pull/66813)) + +#### Post Editor +- Fix inconsistent sidebars close buttons sizes. ([66756](https://github.com/WordPress/gutenberg/pull/66756)) + +#### Block Library +- Remove unnecessary tooltip from Video block Text tracks button. ([66716](https://github.com/WordPress/gutenberg/pull/66716)) + +#### Block Editor +- Speak 'Block moved up/down' after using keyboard actions to move up/down. ([64966](https://github.com/WordPress/gutenberg/pull/64966)) + +#### Patterns +- Block Patterns List: Fix visual title and tooltip inconsistencies. ([64815](https://github.com/WordPress/gutenberg/pull/64815)) + + +### Performance + +- Inline Commenting: Avoid querying comments on editor load. ([66670](https://github.com/WordPress/gutenberg/pull/66670)) +- Patterns: Receive intermediate responses while unbound request is resolving. ([66713](https://github.com/WordPress/gutenberg/pull/66713)) +- Perf metrics: Update select and other metrics to use non-empty paragraphs. ([66762](https://github.com/WordPress/gutenberg/pull/66762)) +- Site Editor: Preload settings requests. ([66488](https://github.com/WordPress/gutenberg/pull/66488)) +- Site Editor: Speed up load by preloading home and front-page templates. ([66579](https://github.com/WordPress/gutenberg/pull/66579)) +- Site editor: Preload post if needed. ([66631](https://github.com/WordPress/gutenberg/pull/66631)) + +#### Global Styles +- Preload user global styles based on user caps. ([66541](https://github.com/WordPress/gutenberg/pull/66541)) + + +### Experiments + +- Add `isVisible` option to fields within DataForm. ([65826](https://github.com/WordPress/gutenberg/pull/65826)) +- DataViews: Implement `isItemClickable` and `onClickItem` props. ([66365](https://github.com/WordPress/gutenberg/pull/66365)) + +#### DataViews +- Quick Edit - Slug Field: Improve slug preview. ([66559](https://github.com/WordPress/gutenberg/pull/66559)) +- QuickEdit: Add password field data to the pages quick edit. ([66567](https://github.com/WordPress/gutenberg/pull/66567)) + + +### Documentation + +- Add 6.6.2 to Version in WordPress. ([66870](https://github.com/WordPress/gutenberg/pull/66870)) +- Add missing properties for DataViews/DataForm components. ([66749](https://github.com/WordPress/gutenberg/pull/66749)) +- Add section about the Fields API. ([66761](https://github.com/WordPress/gutenberg/pull/66761)) +- Block Bindings: Documentation API reference. ([66251](https://github.com/WordPress/gutenberg/pull/66251)) +- Docs: Include a note about supported licenses in WordPress packages. ([66562](https://github.com/WordPress/gutenberg/pull/66562)) +- Document `filterSortAndPaginate` & `isItemValid` utilities. ([66738](https://github.com/WordPress/gutenberg/pull/66738)) +- Feat: Storybook: Improve component organisation - Navigation Category - Issue #66275. ([66658](https://github.com/WordPress/gutenberg/pull/66658)) +- Feat: Storybook: Improve component organisation - Overlays Category - Issue #66275. ([66657](https://github.com/WordPress/gutenberg/pull/66657)) +- Feat: Storybook: Improve component organisation - Selection & Input Category - Issue #66275. ([66660](https://github.com/WordPress/gutenberg/pull/66660)) +- Feat: Storybook: Improve component organisation - Typography - Issue #66275. ([66633](https://github.com/WordPress/gutenberg/pull/66633)) +- Improve readability of DataViews documentation. ([66766](https://github.com/WordPress/gutenberg/pull/66766)) +- Move documentation for filter operators to proper place. ([66743](https://github.com/WordPress/gutenberg/pull/66743)) +- Reorganize to bootstrap DataForm API section. ([66729](https://github.com/WordPress/gutenberg/pull/66729)) +- Storybook: Improve component organisation - Actions. ([66680](https://github.com/WordPress/gutenberg/pull/66680)) +- Storybook: Log `warning()` when in dev mode. ([66568](https://github.com/WordPress/gutenberg/pull/66568)) +- Update Commands documentation with the existing contexts. ([66860](https://github.com/WordPress/gutenberg/pull/66860)) + + +### Code Quality + +- BlockPatternsList: Use the Async component. ([66744](https://github.com/WordPress/gutenberg/pull/66744)) +- Core Commands: Fix add new post URL assignment. ([66830](https://github.com/WordPress/gutenberg/pull/66830)) +- Inline Commenting: Optimize store selector and misc changes. ([66592](https://github.com/WordPress/gutenberg/pull/66592)) +- Remove unnecessary boolean assignments. ([66857](https://github.com/WordPress/gutenberg/pull/66857)) +- TypeScript: Fix and improve types for private-apis. ([66667](https://github.com/WordPress/gutenberg/pull/66667)) + +#### Block Editor +- Fix 'useSelect' dependencies for the 'RichText' component. ([66964](https://github.com/WordPress/gutenberg/pull/66964)) +- Fix ESLint warning for 'useBlockTypesState' hook. ([66757](https://github.com/WordPress/gutenberg/pull/66757)) +- Fix React Compiler error for 'BlockProps' util component. ([66809](https://github.com/WordPress/gutenberg/pull/66809)) +- Optimize `getVisibleElementBounds` in scrollable cases. ([66546](https://github.com/WordPress/gutenberg/pull/66546)) +- Revert: Fix unable to remove empty blocks on merge (#65262) + alternative. ([66564](https://github.com/WordPress/gutenberg/pull/66564)) +- URLInput: Fix incorrect classname for suggestions. ([66714](https://github.com/WordPress/gutenberg/pull/66714)) + +#### Site Editor +- Avoid using edited entity state in site editor loading hook. ([66924](https://github.com/WordPress/gutenberg/pull/66924)) +- Avoid using edited post selectors in welcome guide. ([66926](https://github.com/WordPress/gutenberg/pull/66926)) +- Edit Site: Refactor to remove usage of edited entity state. ([66922](https://github.com/WordPress/gutenberg/pull/66922)) +- Edit Site: Remove leftover 'priority-queue' dependency. ([66773](https://github.com/WordPress/gutenberg/pull/66773)) +- Remove useEditedEntityRecord hook. ([66955](https://github.com/WordPress/gutenberg/pull/66955)) + +#### Components +- Fix React Compiler error for 'useScrollRectIntoView'. ([66498](https://github.com/WordPress/gutenberg/pull/66498)) +- Panel: Add 40px size prop to Button. ([66589](https://github.com/WordPress/gutenberg/pull/66589)) +- Radio: Deprecate 36px default size. ([66572](https://github.com/WordPress/gutenberg/pull/66572)) +- Snackbar: Use `link` variant for action Button. ([66560](https://github.com/WordPress/gutenberg/pull/66560)) + +#### Data Layer +- Convert the emitter module in data package to TS. ([66669](https://github.com/WordPress/gutenberg/pull/66669)) +- Data: Rename useSelect internals to fix React Compiler violations. ([66807](https://github.com/WordPress/gutenberg/pull/66807)) +- Data: Upgrade Redux to v5.0.1. ([66966](https://github.com/WordPress/gutenberg/pull/66966)) + +#### Post Editor +- ESLint: Fix React Compiler violations in various commands. ([66787](https://github.com/WordPress/gutenberg/pull/66787)) +- Fix TS types for editor package. ([66754](https://github.com/WordPress/gutenberg/pull/66754)) + +#### Zoom Out +- Zoom-out: Move default background to the iframe component. ([66284](https://github.com/WordPress/gutenberg/pull/66284)) + +#### Design Tools +- Typography: Stabilize typography block supports within block processing. ([63401](https://github.com/WordPress/gutenberg/pull/63401)) + + +### Tools + +#### Testing +- Media: Check for `wav` mime type using isset. ([66947](https://github.com/WordPress/gutenberg/pull/66947)) + +#### Build Tooling +- Enforce the same order of fields in `package.json` files. ([66239](https://github.com/WordPress/gutenberg/pull/66239)) +- Introduce React Scanner for component usage stats. ([65463](https://github.com/WordPress/gutenberg/pull/65463)) + + +### Various + +- Style engine: Wrap array_merge in conditionals to prevent unnecessary merging. ([66661](https://github.com/WordPress/gutenberg/pull/66661)) + +#### Block Library +- Update placeholder text for blocks that support drag and drop. ([66842](https://github.com/WordPress/gutenberg/pull/66842)) +- update: Add Media to Add media in cover block. ([66835](https://github.com/WordPress/gutenberg/pull/66835)) + + +## First-time contributors + +The following PRs were merged by first-time contributors: + +- @benharri: Fix unset array key warning in block-bindings.php. ([66337](https://github.com/WordPress/gutenberg/pull/66337)) +- @benniledl: Add 6.6.2 to Version in WordPress. ([66870](https://github.com/WordPress/gutenberg/pull/66870)) +- @Infinite-Null: Media & Text: Set `.wp-block-media-text__media a` display to block. ([66915](https://github.com/WordPress/gutenberg/pull/66915)) +- @karthick-murugan: Site Icon Focus fix. ([66952](https://github.com/WordPress/gutenberg/pull/66952)) +- @rinkalpagdar: Post Content: Add border and spacing support. ([66366](https://github.com/WordPress/gutenberg/pull/66366)) +- @yogeshbhutkar: Site Hub: Fixed navigation redirect on mobile devices for classic themes. ([66867](https://github.com/WordPress/gutenberg/pull/66867)) + + +## Contributors + +The following contributors merged PRs in this release: + +@aaronrobertshaw @adamsilverstein @afercia @Aljullu @amitraj2203 @andrewserong @benharri @benniledl @carolinan @cbravobernal @DAreRodz @dcalhoun @ellatrix @fabiankaegy @gigitux @gziolo @hbhalodia @Infinite-Null @jasmussen @jorgefilipecosta @jsnajdr @juanfra @karthick-murugan @kevin940726 @louwie17 @Mamaduka @manzoorwanijk @matiasbenedetto @mikachan @mirka @n2erjo00 @ntsekouras @oandregal @ramonjd @renatho @rinkalpagdar @Soean @stokesman @swissspidy @t-hamano @tellthemachines @tyxla @up1512001 @Vrishabhsk @yogeshbhutkar @youknowriad + + + + += 19.8.0-rc.1 = + + +## Changelog + +### Enhancements + +#### Block Library +- Details block: Use summary content as default label. ([67217](https://github.com/WordPress/gutenberg/pull/67217)) +- Make social icon navigation one arrow keypress. ([64883](https://github.com/WordPress/gutenberg/pull/64883)) +- Page List : Add border and spacing support. ([66385](https://github.com/WordPress/gutenberg/pull/66385)) +- Query Loop block: Remove 'add new post' prompt in the sidebar. ([67189](https://github.com/WordPress/gutenberg/pull/67189)) +- Query block: Update Enhanced Pagination help text. ([67173](https://github.com/WordPress/gutenberg/pull/67173)) +- Social Link: Add contentOnly editing support. ([66622](https://github.com/WordPress/gutenberg/pull/66622)) + +#### Components +- Autocomplete: Increase option height. ([67214](https://github.com/WordPress/gutenberg/pull/67214)) +- CircularOptionPicker: Update Button sizes. ([67285](https://github.com/WordPress/gutenberg/pull/67285)) +- ColorPalette: Disable `Clear` button if there's no color value. ([67108](https://github.com/WordPress/gutenberg/pull/67108)) +- ColorPicker: Update sizes of format select and copy button. ([67093](https://github.com/WordPress/gutenberg/pull/67093)) +- ComboboxControl: Update reset button size. ([67215](https://github.com/WordPress/gutenberg/pull/67215)) + +#### DataViews +- Add density option to `table` layout. ([67170](https://github.com/WordPress/gutenberg/pull/67170)) +- DataForm: Enable fields to declare a different layout. ([66531](https://github.com/WordPress/gutenberg/pull/66531)) +- DataViews list layout: Hide actions menu when there is only one action and is primary. ([67015](https://github.com/WordPress/gutenberg/pull/67015)) +- DataViews table layout: Hide actions menu when there is only one action and is primary. ([67020](https://github.com/WordPress/gutenberg/pull/67020)) +- Reduce the size of action button in Grid layout. ([67032](https://github.com/WordPress/gutenberg/pull/67032)) +- DataViews: Allow register/unregister fields. ([67175](https://github.com/WordPress/gutenberg/pull/67175)) + +#### Global Styles +- Block Supports: Extend stabilization to common experimental block support flags. ([67018](https://github.com/WordPress/gutenberg/pull/67018)) +- Borders: Stabilize border block supports within block processing. ([66918](https://github.com/WordPress/gutenberg/pull/66918)) +- Site Editor > Styles: Open styles inspector when clicking preview canvas. ([66996](https://github.com/WordPress/gutenberg/pull/66996)) + +#### Media +- Block Editor: Add notice action to revert image to original after cropping. ([67314](https://github.com/WordPress/gutenberg/pull/67314)) +- Block Editor: Add success notices for image editing. ([67312](https://github.com/WordPress/gutenberg/pull/67312)) + +#### REST API +- Feature: Set editor rendering mode by post type. ([62304](https://github.com/WordPress/gutenberg/pull/62304)) +- Terms: Respect order specified by register_taxonomy(). ([67154](https://github.com/WordPress/gutenberg/pull/67154)) + +#### Zoom Out +- Leave help text regardless of zoom state. ([67132](https://github.com/WordPress/gutenberg/pull/67132)) +- Preserve footer template bar in zoom out. ([67135](https://github.com/WordPress/gutenberg/pull/67135)) +- Add section styles switch button in block toolbar in zoom out mode. ([67140](https://github.com/WordPress/gutenberg/pull/67140)) + +#### Post Editor +- Move default template types and template part areas to REST API. ([66459](https://github.com/WordPress/gutenberg/pull/66459)) +- Move `usePostFields` to `wordpress/editor` package. ([67024](https://github.com/WordPress/gutenberg/pull/67024)) + +#### Site Editor +- Blocks: Adds check for parent before showing convert to pattern button. ([66158](https://github.com/WordPress/gutenberg/pull/66158)) +- Try dark toolbar for the write mode. ([66116](https://github.com/WordPress/gutenberg/pull/66116)) + +#### Design Tools +- Heading: Hide border controls by default. ([67105](https://github.com/WordPress/gutenberg/pull/67105)) +- Font family preview in the font family picker. ([67118](https://github.com/WordPress/gutenberg/pull/67118)) + +### Bug Fixes + + + + +#### Block Library +- Fix block mover clickable area. ([67261](https://github.com/WordPress/gutenberg/pull/67261)) +- Fix dropping media from inserter into Cover block. ([67056](https://github.com/WordPress/gutenberg/pull/67056)) +- Fix: Preserve Display Preview State in File Block. ([67263](https://github.com/WordPress/gutenberg/pull/67263)) +- Paragraph: Update condition for rendering Drop Cap for a selected block. ([67111](https://github.com/WordPress/gutenberg/pull/67111)) +- RSS block: Check for description field before rendering excerpt. ([66985](https://github.com/WordPress/gutenberg/pull/66985)) +- Resolve search block button text overlapping issue. ([66868](https://github.com/WordPress/gutenberg/pull/66868)) +- Social Links: Fix font family and weight inconsistency in editor. ([67204](https://github.com/WordPress/gutenberg/pull/67204)) + +#### Components +- Composite: Restore `Hover` and `Typeahead` functionality. ([67212](https://github.com/WordPress/gutenberg/pull/67212)) +- Menu.ItemHelpText: Better line breaking. ([67011](https://github.com/WordPress/gutenberg/pull/67011)) +- SlotFill: Fix a bug with storing stale fillProps. ([67000](https://github.com/WordPress/gutenberg/pull/67000)) +- Storybook: Fix DataViews layout. ([66999](https://github.com/WordPress/gutenberg/pull/66999)) +- `FormFileUpload`: Prevent HEIC and HEIF files from always being uploaded on Safari. ([67139](https://github.com/WordPress/gutenberg/pull/67139)) + +#### Block Editor +- Add all color palettes to select from editor panel. ([65148](https://github.com/WordPress/gutenberg/pull/65148)) +- Correctly mark Block Comment SlotFills private. ([67271](https://github.com/WordPress/gutenberg/pull/67271)) +- Fix media placeholder to only activate for media objects. ([66986](https://github.com/WordPress/gutenberg/pull/66986)) +- Rich text: Preserve comments. ([62128](https://github.com/WordPress/gutenberg/pull/62128)) +- Fix TS types for the editor package. ([67196](https://github.com/WordPress/gutenberg/pull/67196)) +- PostTitle: Exit early when post type doesn't support titles. ([67086](https://github.com/WordPress/gutenberg/pull/67086)) + +#### Global Styles +- Avoid zooming out when browsing styles if the preview mode is active. ([67190](https://github.com/WordPress/gutenberg/pull/67190)) +- Remove styles from blocks' previews. ([67144](https://github.com/WordPress/gutenberg/pull/67144)) +- Style panel: Use correct revisions count. ([67180](https://github.com/WordPress/gutenberg/pull/67180)) +- Theme JSON: Include block style variations in path only output of get_block_nodes. ([66948](https://github.com/WordPress/gutenberg/pull/66948)) +- Fix: Logic for Highlight/text-color format availability. ([65530](https://github.com/WordPress/gutenberg/pull/65530)) +- Fix complex variation selectors when using selectors API. ([67061](https://github.com/WordPress/gutenberg/pull/67061)) +#### Site Editor +- Prevent Pre-Publish Panel from Displaying Incorrect Information After Navigating away. ([67010](https://github.com/WordPress/gutenberg/pull/67010)) +- Site Editor Sidebar: Fixed focus/hover style for navigation item buttons. ([67251](https://github.com/WordPress/gutenberg/pull/67251)) +- Site Hub: Fix height in mobile layout. ([67110](https://github.com/WordPress/gutenberg/pull/67110)) +- Site Editor: Styles: Fix inspector opening. ([67004](https://github.com/WordPress/gutenberg/pull/67004)) +- Improve accessibility and consistency of the 'Last modified' Revisions button. ([66606](https://github.com/WordPress/gutenberg/pull/66606)) +- Remove styles from examples. ([67098](https://github.com/WordPress/gutenberg/pull/67098)) +- Editor: Correctly select post title support in 'DocumentOutline'. ([67109](https://github.com/WordPress/gutenberg/pull/67109)) + + +#### DataViews +- Fix action visibility logic. ([67197](https://github.com/WordPress/gutenberg/pull/67197)) +- Fix primary field misalignment in grid layout. ([66995](https://github.com/WordPress/gutenberg/pull/66995)) +- Fix spacing when combining combined fields. ([67226](https://github.com/WordPress/gutenberg/pull/67226)) + +#### Zoom Out +- Zoom In/Out to correct canvas location. ([66917](https://github.com/WordPress/gutenberg/pull/66917)) +- Zoom in/out to correct location. ([67126](https://github.com/WordPress/gutenberg/pull/67126)) +- Zoom Out: Disable zooming out when Distraction Free mode is activated. ([67028](https://github.com/WordPress/gutenberg/pull/67028)) + +#### Layout +- Allow flex justification controls to be disabled at the block level. ([67059](https://github.com/WordPress/gutenberg/pull/67059)) +- Show vertical alignment toolbar with allowSwitching enabled. ([67022](https://github.com/WordPress/gutenberg/pull/67022)) + +#### Patterns +- Fix: JavaScript error when pattern category is unregistered. ([67063](https://github.com/WordPress/gutenberg/pull/67063)) +- Block Locking: Remove edit locking for Synced Patterns. ([67021](https://github.com/WordPress/gutenberg/pull/67021)) + +### Accessibility + +#### Components +- ColorPicker: Add accessible label for copy button. ([67094](https://github.com/WordPress/gutenberg/pull/67094)) +- Modal: Increase size of the Close button. ([66792](https://github.com/WordPress/gutenberg/pull/66792)) +- DataViews: Fix focus loss when removing all filters or resetting. ([67003](https://github.com/WordPress/gutenberg/pull/67003)) + +#### Block Library +- Improve accessibility of the video track editor. ([66832](https://github.com/WordPress/gutenberg/pull/66832)) +- Navigation: Fix 'ariaLabel' block support. ([66943](https://github.com/WordPress/gutenberg/pull/66943)) + +#### Post Editor +- Improve the featured image UI when it cannot retrieve the image file and data. ([66936](https://github.com/WordPress/gutenberg/pull/66936)) + +### Experiments + +- Inline Commenting: Update placement of reply input and add author info header. ([66580](https://github.com/WordPress/gutenberg/pull/66580)) +- Place "Write mode" functionality behind a Gutenberg experiment. ([67008](https://github.com/WordPress/gutenberg/pull/67008)) + +### Documentation + +- Add documentation about required Core changes when updating minimum WordPress version. ([67167](https://github.com/WordPress/gutenberg/pull/67167)) +- BoxControl: Auto-generate readme. ([67284](https://github.com/WordPress/gutenberg/pull/67284)) +- Components contributing guide: Fix relative links. ([67323](https://github.com/WordPress/gutenberg/pull/67323)) +- DataViews: Reorganize documentation for actions. ([67159](https://github.com/WordPress/gutenberg/pull/67159)) +- Docs: Correct `@return` type in `block_core_query_disable_enhanced_pagination()`. ([67128](https://github.com/WordPress/gutenberg/pull/67128)) +- Feat: Storybook: Improve component organisation - Layout Category - Issue #66275. ([66659](https://github.com/WordPress/gutenberg/pull/66659)) +- Feat: Storybook: Improve component organisation - Selection & Input Category - Issue #66275. ([66635](https://github.com/WordPress/gutenberg/pull/66635)) +- GradientPicker: Auto-generate readme. ([67250](https://github.com/WordPress/gutenberg/pull/67250)) +- Icon: Auto-generate readme. ([67282](https://github.com/WordPress/gutenberg/pull/67282)) +- Icon: Improve `icon` prop usage documentation in Storybook. ([67280](https://github.com/WordPress/gutenberg/pull/67280)) +- Storybook: Restore stable components back into categories. ([67216](https://github.com/WordPress/gutenberg/pull/67216)) +- Update BlockMover Stories and README. ([66519](https://github.com/WordPress/gutenberg/pull/66519)) +- Update custom store readme to use thunks instead of controls. ([67006](https://github.com/WordPress/gutenberg/pull/67006)) +- Update versions-in-wordpress.md. ([67298](https://github.com/WordPress/gutenberg/pull/67298)) + +### Code Quality + +- ESLint: Enable `eslint-plugin-react-compiler`. ([61788](https://github.com/WordPress/gutenberg/pull/61788)) +- Extract selectors from useResolveEditedEntity hook. ([67031](https://github.com/WordPress/gutenberg/pull/67031)) +- Pattern: Remove backward compatibility code for WordPress < 6.4. ([67131](https://github.com/WordPress/gutenberg/pull/67131)) +- Post fields: Move `author` from `edit-site` to `fields` package. ([66939](https://github.com/WordPress/gutenberg/pull/66939)) +- Posts DataViews: Refactor the router to use route registration. ([67160](https://github.com/WordPress/gutenberg/pull/67160)) +- Comments controller: Fix issue where comments are allowed when closed. ([66976](https://github.com/WordPress/gutenberg/pull/66976)) +- Fix fatal error in in_array call in post_type_default_rendering_mode. ([67225](https://github.com/WordPress/gutenberg/pull/67225)) +- Data: Add changelog for Redux update. ([66968](https://github.com/WordPress/gutenberg/pull/66968)) + +#### Components +- BorderBoxControl: Suppress redundant warnings for deprecated 36px size. ([67213](https://github.com/WordPress/gutenberg/pull/67213)) +- ComboboxControl : Deprecate 36px default size. ([66900](https://github.com/WordPress/gutenberg/pull/66900)) +- CustomGradientPicker: Prepare `Button`s for 40px default size. ([67286](https://github.com/WordPress/gutenberg/pull/67286)) +- Dashicons: Remove non-existent icons from type. ([67235](https://github.com/WordPress/gutenberg/pull/67235)) +- DimensionControl: Deprecate 36px default size. ([66705](https://github.com/WordPress/gutenberg/pull/66705)) +- Feat: Adds the deprecation warning for 36px default size in range control. ([66721](https://github.com/WordPress/gutenberg/pull/66721)) +- FontSizePicker : Deprecate 36px default size. ([66920](https://github.com/WordPress/gutenberg/pull/66920)) +- Remove createPrivateSlotFill function. ([67238](https://github.com/WordPress/gutenberg/pull/67238)) +- SlotFill: Fix dependencies of registration effects, deduplicate code. ([67071](https://github.com/WordPress/gutenberg/pull/67071)) +- SlotFill: Remove registration API from useSlot result. ([67070](https://github.com/WordPress/gutenberg/pull/67070)) +- SlotFill: Rewrite base Slot to functional, unify rerenderable refs. ([67153](https://github.com/WordPress/gutenberg/pull/67153)) +- TextControl: Deprecate 36px default size. ([66745](https://github.com/WordPress/gutenberg/pull/66745)) +- ToggleGroupControl : Deprecate 36px default size. ([66747](https://github.com/WordPress/gutenberg/pull/66747)) + +#### Post Editor +- ESLint: Bump `eslint-plugin-react-compiler` to latest beta. ([67106](https://github.com/WordPress/gutenberg/pull/67106)) +- Edit Post: Refactor 'MetaBoxVisibility' component. ([67265](https://github.com/WordPress/gutenberg/pull/67265)) +- Edit Post: Remove unused 'hasHistory' flag. ([67293](https://github.com/WordPress/gutenberg/pull/67293)) +- Editor: Update focus return handler for the Featured Image. ([67236](https://github.com/WordPress/gutenberg/pull/67236)) +- Make `BlockManager` component reusable. ([67052](https://github.com/WordPress/gutenberg/pull/67052)) +- Preferences: Use hooks instead of HoC in 'EnableCustomFieldsOption'. ([67023](https://github.com/WordPress/gutenberg/pull/67023)) +- Preferences: Use hooks instead of HoC in 'EnablePanelOption'. ([66994](https://github.com/WordPress/gutenberg/pull/66994)) +- Preferences: Use hooks instead of HoC in 'EnablePublishSidebarOption'. ([67002](https://github.com/WordPress/gutenberg/pull/67002)) + +#### Block Library +- Fix React Compiler error for shortcuts. ([67019](https://github.com/WordPress/gutenberg/pull/67019)) +- Home Link: Remove label attribute synchronization. ([67151](https://github.com/WordPress/gutenberg/pull/67151)) +- Use rems for Nav overlay left padding. ([67168](https://github.com/WordPress/gutenberg/pull/67168)) +- useBlockNameForPatterns: Refactor as a single useSelect call. ([67171](https://github.com/WordPress/gutenberg/pull/67171)) +- Navigation Block: Remove obsolete Block Hooks filters. ([64676](https://github.com/WordPress/gutenberg/pull/64676)) +- [mini] 🧹 remove obsolete rich text css. ([67264](https://github.com/WordPress/gutenberg/pull/67264)) + +#### Global Styles +- Don't call store actions during the render. ([67146](https://github.com/WordPress/gutenberg/pull/67146)) +- Edit Site: Fix settings mutation in `ScreenBlock`. ([67085](https://github.com/WordPress/gutenberg/pull/67085)) +- Remove unused 'Fragment' import. ([67104](https://github.com/WordPress/gutenberg/pull/67104)) + +#### Block Editor +- Block Manager: Make it a private component in the block editor package. ([67255](https://github.com/WordPress/gutenberg/pull/67255)) +- Inserter: Set initial active tab ID during render. ([67103](https://github.com/WordPress/gutenberg/pull/67103)) + +#### Site Editor +- Deprecate edited entity state. ([66965](https://github.com/WordPress/gutenberg/pull/66965)) +- Remove redundant style-edit route. ([67057](https://github.com/WordPress/gutenberg/pull/67057)) + +### Tools + +#### Testing +- Fix ESLint Jest reporting entire body of the test function rather than the identifier. ([67222](https://github.com/WordPress/gutenberg/pull/67222)) +- Fix typo in use-block-sync tests. ([67145](https://github.com/WordPress/gutenberg/pull/67145)) +- Migrate Gradle wrapper validation action. ([66602](https://github.com/WordPress/gutenberg/pull/66602)) + +#### Plugin +- Bump minimum required WordPress version to 6.6. ([67117](https://github.com/WordPress/gutenberg/pull/67117)) +- Add #7895 Core Backport PR to the changelog. ([67319](https://github.com/WordPress/gutenberg/pull/67319)) +- WP Scripts: Revert changes that inline CSS imports early in the build process. ([66975](https://github.com/WordPress/gutenberg/pull/66975)) + +## First-time contributors + +The following PRs were merged by first-time contributors: + +- @AKSHAT2802: Add all color palettes to select from editor panel. ([65148](https://github.com/WordPress/gutenberg/pull/65148)) +- @benazeer-ben: Page List : Add border and spacing support. ([66385](https://github.com/WordPress/gutenberg/pull/66385)) +- @himanshupathak95: Menu.ItemHelpText: Better line breaking. ([67011](https://github.com/WordPress/gutenberg/pull/67011)) +- @SainathPoojary: Social Links: Fix font family and weight inconsistency in editor. ([67204](https://github.com/WordPress/gutenberg/pull/67204)) +- @sarthaknagoshe2002: Prevent Pre-Publish Panel from Displaying Incorrect Information After Navigating away. ([67010](https://github.com/WordPress/gutenberg/pull/67010)) +- @Sukhendu2002: Fix: Preserve Display Preview State in File Block. ([67263](https://github.com/WordPress/gutenberg/pull/67263)) + + +## Contributors + +The following contributors merged PRs in this release: + +@aaronrobertshaw @afercia @ajlende @akasunil @AKSHAT2802 @benazeer-ben @benniledl @carolinan @cbravobernal @desrosj @dhruvang21 @dougwollison @ellatrix @getdave @gigitux @gziolo @hbhalodia @himanshupathak95 @Infinite-Null @jeryj @jsnajdr @juanfra @louwie17 @Mamaduka @manzoorwanijk @matiasbenedetto @mcsf @michalczaplinski @miminari @mirka @ndiego @ntsekouras @oandregal @ockham @PARTHVATALIYA @ramonjd @SainathPoojary @SantosGuillamot @sarthaknagoshe2002 @snehapatil2001 @Soean @Sukhendu2002 @t-hamano @talldan @tellthemachines @TylerB24890 @tyxla @up1512001 @vipul0425 @yogeshbhutkar @youknowriad + + += 19.7.0 = + +## Changelog + +### Enhancements + +- Add "show template" to preview dropdown. ([66514](https://github.com/WordPress/gutenberg/pull/66514)) +- Iframe: Always enable for block themes, in core too. ([66800](https://github.com/WordPress/gutenberg/pull/66800)) +- Media Utils: Add experimental `sideloadMedia`. ([66378](https://github.com/WordPress/gutenberg/pull/66378)) +- Post fields: Clean up. ([66941](https://github.com/WordPress/gutenberg/pull/66941)) +- Post fields: Extract `title` from `edit-site` to `fields` package. ([66940](https://github.com/WordPress/gutenberg/pull/66940)) +- Post fields: Move `comment_status` from edit-site to fields package. ([66934](https://github.com/WordPress/gutenberg/pull/66934)) +- Post fields: Move `date` fields from `edit-site` to `fields` package. ([66938](https://github.com/WordPress/gutenberg/pull/66938)) +- Post fields: Move `status` from `edit-site` to `fields`. ([66937](https://github.com/WordPress/gutenberg/pull/66937)) +- Relocate “View” external link to end of editor header controls. ([66785](https://github.com/WordPress/gutenberg/pull/66785)) + +#### Block Library +- Added toggle control to set any image as feature image if no feature image is set for post. ([65896](https://github.com/WordPress/gutenberg/pull/65896)) +- Improve cover z-index solution. ([66249](https://github.com/WordPress/gutenberg/pull/66249)) +- Post Content: Add border and spacing support. ([66366](https://github.com/WordPress/gutenberg/pull/66366)) +- Query Loop: Use templateSlug and postType for more context. ([65820](https://github.com/WordPress/gutenberg/pull/65820)) +- Update text case of "Starter Content". ([66954](https://github.com/WordPress/gutenberg/pull/66954)) +- [Details Block]: Adds anchor support in details block. ([66734](https://github.com/WordPress/gutenberg/pull/66734)) + +#### Components +- Guide: Use small size button for page controls. ([66607](https://github.com/WordPress/gutenberg/pull/66607)) +- MenuItem: Add 40px size prop on Button. ([66596](https://github.com/WordPress/gutenberg/pull/66596)) +- Notice: Add appropriate size props to Buttons. ([66593](https://github.com/WordPress/gutenberg/pull/66593)) +- PaletteEdit: Add appropriate size props to Buttons. ([66590](https://github.com/WordPress/gutenberg/pull/66590)) +- Popover: Add small size prop to close button. ([66587](https://github.com/WordPress/gutenberg/pull/66587)) + +#### Global Styles +- Global styles revisions: Move focus and active state to list item. ([66780](https://github.com/WordPress/gutenberg/pull/66780)) +- Site editor: Integrate global styles controls and style book preview into the styles panel. ([65619](https://github.com/WordPress/gutenberg/pull/65619)) + +#### DataViews +- DataViews Fields API: Default getValueFromId supports nested objects. ([66890](https://github.com/WordPress/gutenberg/pull/66890)) + +#### Block Editor +- Inserter: Add 'Starter Content' category to the inserter. ([66819](https://github.com/WordPress/gutenberg/pull/66819)) + +#### Zoom Out +- Enable zoom out mode for non-iframe editor. ([66789](https://github.com/WordPress/gutenberg/pull/66789)) + +#### Themes +- Theme JSON Resolver: Remove theme json merge in resolve_theme_file_uris. ([66662](https://github.com/WordPress/gutenberg/pull/66662)) + +#### Edit Mode +- Image block: Add support for "more" dropdown for additional tools in Write mode. ([66605](https://github.com/WordPress/gutenberg/pull/66605)) + +#### Style Book +- Add a landing section to stylebook tabs. ([66545](https://github.com/WordPress/gutenberg/pull/66545)) + +#### Media +- Media Library: Expose filters dropdown for individual images, such as with the Image block. ([65965](https://github.com/WordPress/gutenberg/pull/65965)) + + +### Bug Fixes + +- Block toolbar: Restrict visible child calculation to known blocks. ([66702](https://github.com/WordPress/gutenberg/pull/66702)) +- ComplementaryArea: Fix button position. ([66677](https://github.com/WordPress/gutenberg/pull/66677)) +- Fix Paragraph appender layout shift (building on 66061). ([66779](https://github.com/WordPress/gutenberg/pull/66779)) +- Fix: Set the `fit-content` width for images that are not `.svg`. ([66643](https://github.com/WordPress/gutenberg/pull/66643)) +- Preference modal: Avoid fetching all reusable blocks when the site editor loads. ([66621](https://github.com/WordPress/gutenberg/pull/66621)) +- Revert "Set image width to `fit-content` to solve aspect ratio problems in Firefox. (#66217)". ([66804](https://github.com/WordPress/gutenberg/pull/66804)) +- Safari: Fix site editor template error. ([66647](https://github.com/WordPress/gutenberg/pull/66647)) +- Safari: Prevent focus capturing caused by flex display. ([66402](https://github.com/WordPress/gutenberg/pull/66402)) +- Select Mode: Hide tool selector in the post editor and force design mode. ([66784](https://github.com/WordPress/gutenberg/pull/66784)) +- Shadow panel: Make the delete modal text translatable. ([66712](https://github.com/WordPress/gutenberg/pull/66712)) +- Site Editor: Fix template for page-on-front option. ([66739](https://github.com/WordPress/gutenberg/pull/66739)) +- WP Scripts: Make watch mode more resilient for developer errors. ([66752](https://github.com/WordPress/gutenberg/pull/66752)) +- getDefaultTemplateId: Ensure entity configuration is loaded. ([66650](https://github.com/WordPress/gutenberg/pull/66650)) +- Comments controller: fix issue where comments are allowed when closed (https://github.com/WordPress/gutenberg/pull/66976) + +#### Block Library +- Cover: Fix media library image selection. ([66782](https://github.com/WordPress/gutenberg/pull/66782)) +- Cover: Show DropZone only when dragging withing the block. ([66912](https://github.com/WordPress/gutenberg/pull/66912)) +- Media & Text: Set `.wp-block-media-text__media a` display to block. ([66915](https://github.com/WordPress/gutenberg/pull/66915)) +- Prevent duplicate post format taxonomy queries. ([66627](https://github.com/WordPress/gutenberg/pull/66627)) +- Query Loop: Check for postTypeFromContext before using it. ([66655](https://github.com/WordPress/gutenberg/pull/66655)) +- Query Loop: Remove postTypeFromContext. ([66681](https://github.com/WordPress/gutenberg/pull/66681)) + +#### Block Editor +- Appender: Fix initial position. ([66711](https://github.com/WordPress/gutenberg/pull/66711)) +- Appender: Fix outside canvas styles. ([66630](https://github.com/WordPress/gutenberg/pull/66630)) +- Block Inspector: Restore bottom margin for RadioControl. ([66688](https://github.com/WordPress/gutenberg/pull/66688)) +- Iframed editor: Fix relative wp-content URLs. ([66751](https://github.com/WordPress/gutenberg/pull/66751)) + +#### Global Styles +- Section Styles: Fix insecure properties removal for inner block types and elements. ([66896](https://github.com/WordPress/gutenberg/pull/66896)) +- Style book: Reduce margin selector specificity so that it doesn't override global block styles. ([66895](https://github.com/WordPress/gutenberg/pull/66895)) +- Theme JSON: Replace top-level background style objects on merge. ([66656](https://github.com/WordPress/gutenberg/pull/66656)) + +#### Components +- FormTokenField: Fix token styles. ([66640](https://github.com/WordPress/gutenberg/pull/66640)) +- Storybook: Fix DataViews action modals. ([66727](https://github.com/WordPress/gutenberg/pull/66727)) +- ToggleGroupControl: Fix active background for `zero` value. ([66855](https://github.com/WordPress/gutenberg/pull/66855)) + +#### Post Editor +- Disable device preview button in pattern/template part/navitation editor. ([65970](https://github.com/WordPress/gutenberg/pull/65970)) +- PostTaxonomiesFlatTermSelector: Abstract wrapper component. ([66625](https://github.com/WordPress/gutenberg/pull/66625)) +- VisualEditor: Always output has-global-padding classname when in post only mode. ([66626](https://github.com/WordPress/gutenberg/pull/66626)) + +#### DataViews +- Fix TypeError when duplicating uncategorized theme patterns. ([66889](https://github.com/WordPress/gutenberg/pull/66889)) +- Tweak primary field in patterns grid layout. ([66733](https://github.com/WordPress/gutenberg/pull/66733)) + +#### Meta Boxes +- Fix: Show Meta Boxes at the bottom of the screen regardless of the current rendering mode. ([66508](https://github.com/WordPress/gutenberg/pull/66508)) +- Hide metaboxes in Zoom Out. ([66886](https://github.com/WordPress/gutenberg/pull/66886)) + +#### Site Editor +- DataViews: Fix 'aria-label' for pattern preview element. ([66601](https://github.com/WordPress/gutenberg/pull/66601)) +- Site Hub: Fixed navigation redirect on mobile devices for classic themes. ([66867](https://github.com/WordPress/gutenberg/pull/66867)) + +#### Media +- Add `x-wav` mime type for wav files in Firefox. ([66850](https://github.com/WordPress/gutenberg/pull/66850)) +- Ensure HEIC files selectable from “Upload” button. ([66292](https://github.com/WordPress/gutenberg/pull/66292)) + +#### Patterns +- Fix uncategorized pattern browsing when pattern has no categories. ([66945](https://github.com/WordPress/gutenberg/pull/66945)) + +#### Interactivity API +- Fix property modification from inherited context two or more levels above. ([66872](https://github.com/WordPress/gutenberg/pull/66872)) + +#### Block API +- Process Block Type: Copy deprecation to a new object instead of mutating when stabilizing supports. ([66849](https://github.com/WordPress/gutenberg/pull/66849)) + +#### Design Tools +- Block Gap: Fix block spacing control for axial gap supported blocks. ([66783](https://github.com/WordPress/gutenberg/pull/66783)) + +#### Document Settings +- Editor: Restore the 'PluginPostStatusInfo' slot position. ([66665](https://github.com/WordPress/gutenberg/pull/66665)) + +#### Templates API +- Fix flash when clicking template name in the editor when a plugin registered template matches a default WP theme template. ([66359](https://github.com/WordPress/gutenberg/pull/66359)) + +#### Block bindings +- Fix unset array key warning in block-bindings.php. ([66337](https://github.com/WordPress/gutenberg/pull/66337)) + + +### Accessibility + +- Fix : Snackbar Notice Inconsistency. ([66405](https://github.com/WordPress/gutenberg/pull/66405)) +- Image: Add `aria-haspopup` prop write mode `more` tools menu items. ([66815](https://github.com/WordPress/gutenberg/pull/66815)) +- Site Icon Focus fix. ([66952](https://github.com/WordPress/gutenberg/pull/66952)) + +#### Components +- Popover: Fix missing label of the headerTitle Close button. ([66813](https://github.com/WordPress/gutenberg/pull/66813)) + +#### Post Editor +- Fix inconsistent sidebars close buttons sizes. ([66756](https://github.com/WordPress/gutenberg/pull/66756)) + +#### Block Library +- Remove unnecessary tooltip from Video block Text tracks button. ([66716](https://github.com/WordPress/gutenberg/pull/66716)) + +#### Block Editor +- Speak 'Block moved up/down' after using keyboard actions to move up/down. ([64966](https://github.com/WordPress/gutenberg/pull/64966)) + +#### Patterns +- Block Patterns List: Fix visual title and tooltip inconsistencies. ([64815](https://github.com/WordPress/gutenberg/pull/64815)) + + +### Performance + +- Inline Commenting: Avoid querying comments on editor load. ([66670](https://github.com/WordPress/gutenberg/pull/66670)) +- Patterns: Receive intermediate responses while unbound request is resolving. ([66713](https://github.com/WordPress/gutenberg/pull/66713)) +- Perf metrics: Update select and other metrics to use non-empty paragraphs. ([66762](https://github.com/WordPress/gutenberg/pull/66762)) +- Site Editor: Preload settings requests. ([66488](https://github.com/WordPress/gutenberg/pull/66488)) +- Site Editor: Speed up load by preloading home and front-page templates. ([66579](https://github.com/WordPress/gutenberg/pull/66579)) +- Site editor: Preload post if needed. ([66631](https://github.com/WordPress/gutenberg/pull/66631)) + +#### Global Styles +- Preload user global styles based on user caps. ([66541](https://github.com/WordPress/gutenberg/pull/66541)) + + +### Experiments + +- Add `isVisible` option to fields within DataForm. ([65826](https://github.com/WordPress/gutenberg/pull/65826)) +- DataViews: Implement `isItemClickable` and `onClickItem` props. ([66365](https://github.com/WordPress/gutenberg/pull/66365)) + +#### DataViews +- Quick Edit - Slug Field: Improve slug preview. ([66559](https://github.com/WordPress/gutenberg/pull/66559)) +- QuickEdit: Add password field data to the pages quick edit. ([66567](https://github.com/WordPress/gutenberg/pull/66567)) + + +### Documentation + +- Add 6.6.2 to Version in WordPress. ([66870](https://github.com/WordPress/gutenberg/pull/66870)) +- Add missing properties for DataViews/DataForm components. ([66749](https://github.com/WordPress/gutenberg/pull/66749)) +- Add section about the Fields API. ([66761](https://github.com/WordPress/gutenberg/pull/66761)) +- Block Bindings: Documentation API reference. ([66251](https://github.com/WordPress/gutenberg/pull/66251)) +- Docs: Include a note about supported licenses in WordPress packages. ([66562](https://github.com/WordPress/gutenberg/pull/66562)) +- Document `filterSortAndPaginate` & `isItemValid` utilities. ([66738](https://github.com/WordPress/gutenberg/pull/66738)) +- Feat: Storybook: Improve component organisation - Navigation Category - Issue #66275. ([66658](https://github.com/WordPress/gutenberg/pull/66658)) +- Feat: Storybook: Improve component organisation - Overlays Category - Issue #66275. ([66657](https://github.com/WordPress/gutenberg/pull/66657)) +- Feat: Storybook: Improve component organisation - Selection & Input Category - Issue #66275. ([66660](https://github.com/WordPress/gutenberg/pull/66660)) +- Feat: Storybook: Improve component organisation - Typography - Issue #66275. ([66633](https://github.com/WordPress/gutenberg/pull/66633)) +- Improve readability of DataViews documentation. ([66766](https://github.com/WordPress/gutenberg/pull/66766)) +- Move documentation for filter operators to proper place. ([66743](https://github.com/WordPress/gutenberg/pull/66743)) +- Reorganize to bootstrap DataForm API section. ([66729](https://github.com/WordPress/gutenberg/pull/66729)) +- Storybook: Improve component organisation - Actions. ([66680](https://github.com/WordPress/gutenberg/pull/66680)) +- Storybook: Log `warning()` when in dev mode. ([66568](https://github.com/WordPress/gutenberg/pull/66568)) +- Update Commands documentation with the existing contexts. ([66860](https://github.com/WordPress/gutenberg/pull/66860)) + + +### Code Quality + +- BlockPatternsList: Use the Async component. ([66744](https://github.com/WordPress/gutenberg/pull/66744)) +- Core Commands: Fix add new post URL assignment. ([66830](https://github.com/WordPress/gutenberg/pull/66830)) +- Inline Commenting: Optimize store selector and misc changes. ([66592](https://github.com/WordPress/gutenberg/pull/66592)) +- Remove unnecessary boolean assignments. ([66857](https://github.com/WordPress/gutenberg/pull/66857)) +- TypeScript: Fix and improve types for private-apis. ([66667](https://github.com/WordPress/gutenberg/pull/66667)) + +#### Block Editor +- Fix 'useSelect' dependencies for the 'RichText' component. ([66964](https://github.com/WordPress/gutenberg/pull/66964)) +- Fix ESLint warning for 'useBlockTypesState' hook. ([66757](https://github.com/WordPress/gutenberg/pull/66757)) +- Fix React Compiler error for 'BlockProps' util component. ([66809](https://github.com/WordPress/gutenberg/pull/66809)) +- Optimize `getVisibleElementBounds` in scrollable cases. ([66546](https://github.com/WordPress/gutenberg/pull/66546)) +- Revert: Fix unable to remove empty blocks on merge (#65262) + alternative. ([66564](https://github.com/WordPress/gutenberg/pull/66564)) +- URLInput: Fix incorrect classname for suggestions. ([66714](https://github.com/WordPress/gutenberg/pull/66714)) + +#### Site Editor +- Avoid using edited entity state in site editor loading hook. ([66924](https://github.com/WordPress/gutenberg/pull/66924)) +- Avoid using edited post selectors in welcome guide. ([66926](https://github.com/WordPress/gutenberg/pull/66926)) +- Edit Site: Refactor to remove usage of edited entity state. ([66922](https://github.com/WordPress/gutenberg/pull/66922)) +- Edit Site: Remove leftover 'priority-queue' dependency. ([66773](https://github.com/WordPress/gutenberg/pull/66773)) +- Remove useEditedEntityRecord hook. ([66955](https://github.com/WordPress/gutenberg/pull/66955)) + +#### Components +- Fix React Compiler error for 'useScrollRectIntoView'. ([66498](https://github.com/WordPress/gutenberg/pull/66498)) +- Panel: Add 40px size prop to Button. ([66589](https://github.com/WordPress/gutenberg/pull/66589)) +- Radio: Deprecate 36px default size. ([66572](https://github.com/WordPress/gutenberg/pull/66572)) +- Snackbar: Use `link` variant for action Button. ([66560](https://github.com/WordPress/gutenberg/pull/66560)) + +#### Data Layer +- Convert the emitter module in data package to TS. ([66669](https://github.com/WordPress/gutenberg/pull/66669)) +- Data: Rename useSelect internals to fix React Compiler violations. ([66807](https://github.com/WordPress/gutenberg/pull/66807)) +- Data: Upgrade Redux to v5.0.1. ([66966](https://github.com/WordPress/gutenberg/pull/66966)) + +#### Post Editor +- ESLint: Fix React Compiler violations in various commands. ([66787](https://github.com/WordPress/gutenberg/pull/66787)) +- Fix TS types for editor package. ([66754](https://github.com/WordPress/gutenberg/pull/66754)) + +#### Zoom Out +- Zoom-out: Move default background to the iframe component. ([66284](https://github.com/WordPress/gutenberg/pull/66284)) + +#### Design Tools +- Typography: Stabilize typography block supports within block processing. ([63401](https://github.com/WordPress/gutenberg/pull/63401)) + + +### Tools + +#### Testing +- Media: Check for `wav` mime type using isset. ([66947](https://github.com/WordPress/gutenberg/pull/66947)) + +#### Build Tooling +- Enforce the same order of fields in `package.json` files. ([66239](https://github.com/WordPress/gutenberg/pull/66239)) +- Introduce React Scanner for component usage stats. ([65463](https://github.com/WordPress/gutenberg/pull/65463)) + + +### Various + +- Style engine: Wrap array_merge in conditionals to prevent unnecessary merging. ([66661](https://github.com/WordPress/gutenberg/pull/66661)) + +#### Block Library +- Update placeholder text for blocks that support drag and drop. ([66842](https://github.com/WordPress/gutenberg/pull/66842)) +- update: Add Media to Add media in cover block. ([66835](https://github.com/WordPress/gutenberg/pull/66835)) + + +## First-time contributors + +The following PRs were merged by first-time contributors: + +- @benharri: Fix unset array key warning in block-bindings.php. ([66337](https://github.com/WordPress/gutenberg/pull/66337)) +- @benniledl: Add 6.6.2 to Version in WordPress. ([66870](https://github.com/WordPress/gutenberg/pull/66870)) +- @Infinite-Null: Media & Text: Set `.wp-block-media-text__media a` display to block. ([66915](https://github.com/WordPress/gutenberg/pull/66915)) +- @karthick-murugan: Site Icon Focus fix. ([66952](https://github.com/WordPress/gutenberg/pull/66952)) +- @rinkalpagdar: Post Content: Add border and spacing support. ([66366](https://github.com/WordPress/gutenberg/pull/66366)) +- @yogeshbhutkar: Site Hub: Fixed navigation redirect on mobile devices for classic themes. ([66867](https://github.com/WordPress/gutenberg/pull/66867)) + + +## Contributors + +The following contributors merged PRs in this release: + +@aaronrobertshaw @adamsilverstein @afercia @Aljullu @amitraj2203 @andrewserong @benharri @benniledl @carolinan @cbravobernal @DAreRodz @dcalhoun @ellatrix @fabiankaegy @gigitux @gziolo @hbhalodia @Infinite-Null @jasmussen @jorgefilipecosta @jsnajdr @juanfra @karthick-murugan @kevin940726 @louwie17 @Mamaduka @manzoorwanijk @matiasbenedetto @mikachan @mirka @n2erjo00 @ntsekouras @oandregal @ramonjd @renatho @rinkalpagdar @Soean @stokesman @swissspidy @t-hamano @tellthemachines @tyxla @up1512001 @Vrishabhsk @yogeshbhutkar @youknowriad + + + + += 19.6.4 = + +- PostTaxonomiesFlatTermSelector: abstract wrapper component (#66625) + + += 19.6.3 = + +- Revert "Set image width to fit-content to solve aspect ratio problems in Firefox. ([#66217](https://github.com/WordPress/gutenberg/pull/66804)) + + += 19.7.0-rc.2 = + + +- Comments controller: fix issue where comments are allowed when closed (https://github.com/WordPress/gutenberg/pull/66976) + + += 19.6.2 = + +- Comments controller: fix issue where comments are allowed when closed [#66976](https://github.com/WordPress/gutenberg/pull/66976) + + = 19.7.0-rc.1 = diff --git a/docs/README.md b/docs/README.md index 31471a9928b2cf..4fd7d16595e133 100644 --- a/docs/README.md +++ b/docs/README.md @@ -48,7 +48,7 @@ This handbook should be considered the canonical resource for all things related ## Are you in the right place? -The Block Editor Handbook is designed for those looking to create and develop for the Block Editor. However, it's important to note that there are multiple other handbooks available within the [Developer Resources](http://developer.wordpress.org/) that you may find beneficial: +The Block Editor Handbook is designed for those looking to create and develop for the Block Editor. However, it's important to note that there are multiple other handbooks available within the [Developer Resources](https://developer.wordpress.org/) that you may find beneficial: - [Theme Handbook](https://developer.wordpress.org/themes) - [Plugin Handbook](https://developer.wordpress.org/plugins) diff --git a/docs/contributors/code/e2e/README.md b/docs/contributors/code/e2e/README.md index 3a123cc2988b7a..5aa4e21be909fe 100644 --- a/docs/contributors/code/e2e/README.md +++ b/docs/contributors/code/e2e/README.md @@ -75,7 +75,7 @@ To encourage better practices for querying elements, selectors are [strict](http ### Favor Page Object Model over utils -As mentioned above, [Page Object Model](https://playwright.dev/docs/test-pom) is the preferred way to create reusable utility functions on a certain page. +As mentioned above, [Page Object Model](https://playwright.dev/docs/pom) is the preferred way to create reusable utility functions on a certain page. The rationale behind using a POM is to group utils under namespaces to be easier to discover and use. In fact, `PageUtils` in the `e2e-test-utils-playwright` package is also a POM, which avoids the need for global variables, and utils can reference each other with `this`. diff --git a/docs/contributors/code/getting-started-with-code-contribution.md b/docs/contributors/code/getting-started-with-code-contribution.md index df6b305f35983e..9afcc3e46ca1bd 100644 --- a/docs/contributors/code/getting-started-with-code-contribution.md +++ b/docs/contributors/code/getting-started-with-code-contribution.md @@ -104,7 +104,9 @@ You can access the Dashboard at: `http://localhost:8888/wp-admin/` using **Usern #### Accessing the MySQL Database -To access the MySQL database on the `wp-env` instance you will first need the connection details. To do this: +phpMyAdmin is available by default for the Gutenberg project. You can access the MySQL Database at: `http://localhost:9000/`. + +If you want to access the database through another tool, you will first need the connection details. To do this: 1. In a terminal, navigate to your local Gutenberg repo. 2. Run `npm run wp-env start` - various information about the `wp-env` environment should be logged into the terminal. diff --git a/docs/contributors/code/release.md b/docs/contributors/code/release.md index ec3af0d7bd4cb2..1a3af716f5848a 100644 --- a/docs/contributors/code/release.md +++ b/docs/contributors/code/release.md @@ -5,7 +5,7 @@ The [Gutenberg repository](https://github.com/WordPress/gutenberg) on GitHub is Before you begin, there are some requirements that must be met in order to successfully release a stable version of the Gutenberg plugin. You will need to: - Be a member of the [Gutenberg development team](https://developer.wordpress.org/block-editor/block-editor/contributors/repository-management/#teams). This gives you the ability to launch the GitHub actions that are related to the release process and to backport pull requests (PRs) to the release branch. -- Have write permissions on the [Make WordPress Core](http://make.wordpress.org/core) blog. This allows you to draft the release post. +- Have write permissions on the [Make WordPress Core](https://make.wordpress.org/core) blog. This allows you to draft the release post. - Obtain approval from a member of the Gutenberg Core team in order to upload the new version Gutenberg to the WordPress.org plugin directory. Similar requirements apply to releasing WordPress's [npm packages](https://developer.wordpress.org/block-editor/contributors/code/release/#packages-releases-to-npm-and-wordpress-core-updates). @@ -234,7 +234,7 @@ It's important to check that: - the plugin from the directory works as expected - the ZIP contents (see [Downloads](https://plugins.trac.wordpress.org/browser/gutenberg/)) looks correct (doesn't have anything obvious missing) - the [Gutenberg SVN repo](https://plugins.trac.wordpress.org/browser/gutenberg/) has two new commits (see [the log](https://plugins.trac.wordpress.org/browser/gutenberg/)): - - the `trunk` folder should have "Commiting version X.Y.Z" + - the `trunk` folder should have "Committing version X.Y.Z" - there is a new `tags/X.Y.Z` folder with the same contents as `trunk` whose latest commit is "Tagging version X.Y.Z" Most likely, the tag folder couldn't be created. This is a [known issue](https://plugins.trac.wordpress.org/browser/gutenberg/) that [can be fixed manually](https://github.com/WordPress/gutenberg/issues/55295#issuecomment-1759292978). @@ -344,7 +344,7 @@ To do this, when running the Workflow, select the appropriate `release/` branch It is possible to create a minor release for any release branch even after a more recent stable release has been published. This can be done for _any_ previous release branches, allowing more flexibility in delivering updates to users. In the past, users had to wait for the next stable release, potentially taking days. Now, fixes can be swiftly shipped to any previous release branches as required. -The process is identical to the one documented above when an RC is already out: choose a previous release branch, type `stable`, and click "Run workflow". The release will be published on the GitHub releases page for Gutenberg and to the WordPress core repository SVN as a `tag` under http://plugins.svn.wordpress.org/gutenberg/tags/. The SVN `trunk` directory will not be touched. +The process is identical to the one documented above when an RC is already out: choose a previous release branch, type `stable`, and click "Run workflow". The release will be published on the GitHub releases page for Gutenberg and to the WordPress core repository SVN as a `tag` under https://plugins.svn.wordpress.org/gutenberg/tags/. The SVN `trunk` directory will not be touched. **IMPORTANT:** When publishing the draft created by the ["Build Plugin Zip" workflow](https://github.com/WordPress/gutenberg/actions/workflows/build-plugin-zip.yml), make sure to leave the "Set as last release" checkbox unchecked. If it is left checked by accident, the ["Upload Gutenberg plugin to WordPress.org plugin" workflow](https://github.com/WordPress/gutenberg/actions/workflows/upload-release-to-plugin-repo.yml) will still correctly upload it **as a tag (and will _not_ replace the `trunk` version)** to the WordPress plugin repository SVN - the workflow will perform some version arithmetic to determine how the plugin should be shipped - but you'll still need to fix the state on GitHub by setting the right release as `latest` on the [releases](https://github.com/WordPress/gutenberg/releases/) page! diff --git a/docs/contributors/design/README.md b/docs/contributors/design/README.md index abc077b8fdc072..0a50be2bf4ae3f 100644 --- a/docs/contributors/design/README.md +++ b/docs/contributors/design/README.md @@ -14,7 +14,7 @@ The Gutenberg project uses GitHub for managing code and tracking issues. The mai If you'd like to contribute to the design or front-end, feel free to contribute to tickets labeled [Needs Design](https://github.com/WordPress/gutenberg/issues?q=is%3Aissue+is%3Aopen+label%3A%22Needs+Design%22) or [Needs Design Feedback](https://github.com/WordPress/gutenberg/issues?q=is%3Aissue+is%3Aopen+label%3A"Needs+Design+Feedback%22). We could use your thoughtful replies, mockups, animatics, sketches, doodles. Proposed changes are best done as minimal and specific iterations on the work that precedes it so we can compare. -The [WordPress Design team](http://make.wordpress.org/design/) uses [Figma](https://www.figma.com/) to collaborate and share work. If you'd like to contribute, join the [#design channel](http://wordpress.slack.com/messages/design/) in [Slack](https://make.wordpress.org/chat/) and ask the team to set you up with a free Figma account. This will give you access to a helpful [library of components](https://www.figma.com/file/ZtN5xslEVYgzU7Dd5CxgGZwq/WordPress-Components?node-id=0%3A1) used in WordPress. +The [WordPress Design team](https://make.wordpress.org/design/) uses [Figma](https://www.figma.com/) to collaborate and share work. If you'd like to contribute, join the [#design channel](https://wordpress.slack.com/messages/design/) in [Slack](https://make.wordpress.org/chat/) and ask the team to set you up with a free Figma account. This will give you access to a helpful [library of components](https://www.figma.com/file/ZtN5xslEVYgzU7Dd5CxgGZwq/WordPress-Components?node-id=0%3A1) used in WordPress. ## Principles diff --git a/docs/contributors/repository-management.md b/docs/contributors/repository-management.md index e57f762a605394..5bb971bfaf2efc 100644 --- a/docs/contributors/repository-management.md +++ b/docs/contributors/repository-management.md @@ -165,9 +165,3 @@ If you meet this criterion of several meaningful contributions having been accep ## Projects We use [GitHub projects](https://github.com/WordPress/gutenberg/projects) to keep track of details that aren't immediately actionable, but that we want to keep around for future reference. - -Some key projects include: - -- [Phase 2](https://github.com/WordPress/gutenberg/projects/13) - Development tasks needed for Phase 2 of Gutenberg. -- [Phase 2 design](https://github.com/WordPress/gutenberg/projects/21) - Tasks for design in Phase 2. Note: specific projects may have their own boards. -- [Ideas](https://github.com/WordPress/gutenberg/projects/8) - Project containing tickets that, while closed for the time being, can be revisited in the future. diff --git a/docs/contributors/triage.md b/docs/contributors/triage.md index 33275b8d3df014..5c4cf7c161f03d 100644 --- a/docs/contributors/triage.md +++ b/docs/contributors/triage.md @@ -9,7 +9,7 @@ To keep the repository healthy, it needs to be triaged regularly. **Triage is th The triage team is an open group of people with a particular role of making sure triage is done consistently across the Gutenberg repo. There are various types of triage which happen: - Regular self triage sessions done by members on their own time. -- Organised triage sessions done as a group at a set time. You can [review the meetings page](https://make.wordpress.org/meetings/) to find these triage sessions and appropriate slack channels. +- Organized triage sessions done as a group at a set time. You can [review the meetings page](https://make.wordpress.org/meetings/) to find these triage sessions and appropriate slack channels. - Focused triage sessions on a specific board, label or feature. These are the expectations of being a triage team member: diff --git a/docs/contributors/versions-in-wordpress.md b/docs/contributors/versions-in-wordpress.md index 4ba7b34da15552..c2be7e3fa4e884 100644 --- a/docs/contributors/versions-in-wordpress.md +++ b/docs/contributors/versions-in-wordpress.md @@ -6,6 +6,7 @@ If anything looks incorrect here, please bring it up in #core-editor in [WordPre | Gutenberg Versions | WordPress Version | | ------------------ | ----------------- | +| 18.6-19.3 | 6.7.1 | | 18.6-19.3 | 6.7 | | 17.8-18.5 | 6.6.2 | | 17.8-18.5 | 6.6.1 | diff --git a/docs/explanations/architecture/performance.md b/docs/explanations/architecture/performance.md index 4c8b6386b9263b..8c1034ad9de331 100644 --- a/docs/explanations/architecture/performance.md +++ b/docs/explanations/architecture/performance.md @@ -84,6 +84,12 @@ The new reference commit hash that is chosen needs to meet the following require - Be compatible with the new WP version used in the "Tested up to" flag. - Is already tracked on "codevitals.run" for all existing metrics. +When releasing a plugin update with changes to the minimum WordPress version requirements, the end-to-end test GitHub Action workflow in Core SVN will need to be updated for any branch losing support. Otherwise the first run of that workflow on that branch following the release will fail. + +The version of the plugin used in the workflow can be pinned by adding the `gutenberg-version` input to the test matrix. [Core-59221](https://core.trac.wordpress.org/changeset/59221) is an example of this change for the 6.4 branch. + +**Note:** Always use the final release including bug fixes (ie. `x.y.2` or `x.y.3`). If the final release is not yet known, create a [Trac ticket](https://core.trac.wordpress.org/ticket/62488) so it's not forgotten. + **A simple way to choose commit is to pick a very recent commit on trunk with a passing performance job.** ## Going further diff --git a/docs/getting-started/devenv/get-started-with-wp-env.md b/docs/getting-started/devenv/get-started-with-wp-env.md index 74942ea3ee93bf..a6427deb863b7e 100644 --- a/docs/getting-started/devenv/get-started-with-wp-env.md +++ b/docs/getting-started/devenv/get-started-with-wp-env.md @@ -47,7 +47,7 @@ wp-env start Once the script completes, you can access the local environment at: http://localhost:8888. Log into the WordPress dashboard using username `admin` and password `password`.
- Some projects, like Gutenberg, include their own specific wp-env configurations, and the documentation might prompt you to run npm run start wp-env instead. + Some projects, like Gutenberg, include their own specific wp-env configurations, and the documentation might prompt you to run npm run wp-env start instead.
For more information on controlling the Docker environment, see the [@wordpress/env package](/packages/env/README.md) readme. diff --git a/docs/getting-started/faq.md b/docs/getting-started/faq.md index 8ac489e3c154a2..d9120cc58197e9 100644 --- a/docs/getting-started/faq.md +++ b/docs/getting-started/faq.md @@ -8,7 +8,7 @@ What follows is a set of questions that have come up from the last few years of “Gutenberg” is the name of the project to create a new editor experience for WordPress — contributors have been working on it since January 2017 and it’s one of the most significant changes to WordPress in years. It’s built on the idea of using “blocks” to write and design posts and pages. This will serve as the foundation for future improvements to WordPress, including blocks as a way not just to design posts and pages, but also entire sites. The overall goal is to simplify the first-time user experience of WordPress — for those who are writing, editing, publishing, and designing web pages. The editing experience is intended to give users a better visual representation of what their post or page will look like when they hit publish. Originally, this was the kickoff goal: -> The editor will endeavour to create a new page and post building experience that makes writing rich posts effortless, and has “blocks” to make it easy what today might take shortcodes, custom HTML, or “mystery meat” embed discovery. +> The editor will endeavor to create a new page and post building experience that makes writing rich posts effortless, and has “blocks” to make it easy what today might take shortcodes, custom HTML, or “mystery meat” embed discovery. Key takeaways include the following points: diff --git a/docs/getting-started/fundamentals/block-in-the-editor.md b/docs/getting-started/fundamentals/block-in-the-editor.md index d1f2a25063e6c6..1d51239907d8b5 100644 --- a/docs/getting-started/fundamentals/block-in-the-editor.md +++ b/docs/getting-started/fundamentals/block-in-the-editor.md @@ -139,6 +139,8 @@ export default function Edit( { attributes, setAttributes } ) { setAttributes( { message: val } ) } style={ { 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 348b95ba88da3c..4cd7c0b36fe86a 100644 --- a/docs/getting-started/fundamentals/javascript-in-the-block-editor.md +++ b/docs/getting-started/fundamentals/javascript-in-the-block-editor.md @@ -26,7 +26,7 @@ The diagram below provides an overview of the build process when using the `wp-s - **Production Mode (`npm run build`):** In this mode, `wp-scripts` compiles your JavaScript, minifying the output to reduce file size and improve loading times in the browser. This is ideal for deploying your code to a live site. -- **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. +- **Development Mode (`npm 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. @@ -38,9 +38,9 @@ The `wp-scripts` package also facilitates the use of JavaScript modules, allowin 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.md) 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. +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`](/packages/blocks/README.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. diff --git a/docs/getting-started/fundamentals/registration-of-a-block.md b/docs/getting-started/fundamentals/registration-of-a-block.md index 5c80422f6f8574..63a7a9031f72a7 100644 --- a/docs/getting-started/fundamentals/registration-of-a-block.md +++ b/docs/getting-started/fundamentals/registration-of-a-block.md @@ -42,7 +42,7 @@ function minimal_block_ca6eda___register_block() { add_action( 'init', 'minimal_block_ca6eda___register_block' ); ``` -_See the [full block example](https://github.com/WordPress/block-development-examples/tree/trunk/plugins/minimal-block-ca6eda) of the [code above](https://github.com/WordPress/block-development-examples/blob/trunk/plugins/minimal-block-ca6eda/index.php)_ +_See the [full block example](https://github.com/WordPress/block-development-examples/tree/trunk/plugins/minimal-block-ca6eda) of the [code above](https://github.com/WordPress/block-development-examples/blob/trunk/plugins/minimal-block-ca6eda/plugin.php)_ ## Registering a block with JavaScript (client-side) diff --git a/docs/getting-started/tutorial.md b/docs/getting-started/tutorial.md index 4e43241f63fb16..2a5dd979d3a569 100644 --- a/docs/getting-started/tutorial.md +++ b/docs/getting-started/tutorial.md @@ -480,6 +480,8 @@ export default function Edit( { attributes, setAttributes } ) { { showStartingYear && ( @@ -139,6 +141,8 @@ function EditPageForm( { pageId, onCancel, onSaveFinished } ) { return (
@@ -164,6 +168,8 @@ function VanillaReactForm({ initialTitle }) { const [title, setTitle] = useState( initialTitle ); return ( @@ -233,6 +239,8 @@ function EditPageForm( { pageId, onCancel, onSaveFinished } ) { return (
'green', + 'label' => __( 'Green' ), + 'style_data' => array( + 'color' => array( + 'background' => '#4f6f52', + 'text' => '#d2e3c8', + ), + 'blocks' => array( + 'core/group' => array( + 'color' => array( + 'background' => '#739072', + 'text' => '#e3eedd', + ), + ), + ), + 'elements' => array( + 'link' => array( + 'color' => array( + 'text' => '#ead196', + ), + ':hover' => array( + 'color' => array( + 'text' => '#ebd9b4', + ), + ), + ), + ), + ), + ) +); +``` + ### customTemplates
Supported in WordPress from version 5.9.
diff --git a/docs/manifest.json b/docs/manifest.json index 8f267e79ef4feb..94610061e430b5 100644 --- a/docs/manifest.json +++ b/docs/manifest.json @@ -1967,6 +1967,12 @@ "markdown_source": "../packages/undo-manager/README.md", "parent": "packages" }, + { + "title": "@wordpress/upload-media", + "slug": "packages-upload-media", + "markdown_source": "../packages/upload-media/README.md", + "parent": "packages" + }, { "title": "@wordpress/url", "slug": "packages-url", diff --git a/docs/private-apis.md b/docs/private-apis.md new file mode 100644 index 00000000000000..14c1a4aa22472b --- /dev/null +++ b/docs/private-apis.md @@ -0,0 +1,340 @@ +# Gutenberg Private APIs + +This is an overview of private APIs exposed by Gutenberg packages. These APIs are used to implement parts of the Gutenberg editor (Post Editor, Site Editor, Core blocks and others) but are not exposed publicly to plugin and theme authors or authors of custom Gutenberg integrations. + +The purpose of this document is to present a picture of how many private APIs we have and how they are used to build the Gutenberg editor apps with the libraries and frameworks provided by the family of `@wordpress/*` packages. + +## data + +The registry has two private methods: +- `privateActionsOf` +- `privateSelectorsOf` + +Every store has a private API for registering private selectors/actions: +- `privateActions` +- `registerPrivateActions` +- `privateSelectors` +- `registerPrivateSelectors` + +## blocks + +### `core/blocks` store + +Private actions: +- `addBlockBindingsSource` +- `removeBlockBindingsSource` +- `addBootstrappedBlockType` +- `addUnprocessedBlockType` + +Private selectors: +- `getAllBlockBindingsSources` +- `getBlockBindingsSource` +- `getBootstrappedBlockType` +- `getSupportedStyles` +- `getUnprocessedBlockTypes` +- `hasContentRoleAttribute` + +## components + +Private exports: +- `__experimentalPopoverLegacyPositionToPlacement` +- `ComponentsContext` +- `Tabs` +- `Theme` +- `Menu` +- `kebabCase` + +## commands + +Private exports: +- `useCommandContext` (added May 2023 in #50543) + +### `core/commands` store + +Private actions: +- `setContext` (added together with `useCommandContext`) + +## preferences + +Private exports: (added in Jan 2024 in #57639) +- `PreferenceBaseOption` +- `PreferenceToggleControl` +- `PreferencesModal` +- `PreferencesModalSection` +- `PreferencesModalTabs` + +There is only one publicly exported component! +- `PreferenceToggleMenuItem` + +## block-editor + +Private exports: +- `AdvancedPanel` +- `BackgroundPanel` +- `BorderPanel` +- `ColorPanel` +- `DimensionsPanel` +- `FiltersPanel` +- `GlobalStylesContext` +- `ImageSettingsPanel` +- `TypographyPanel` +- `areGlobalStyleConfigsEqual` +- `getBlockCSSSelector` +- `getBlockSelectors` +- `getGlobalStylesChanges` +- `getLayoutStyles` +- `toStyles` +- `useGlobalSetting` +- `useGlobalStyle` +- `useGlobalStylesOutput` +- `useGlobalStylesOutputWithConfig` +- `useGlobalStylesReset` +- `useHasBackgroundPanel` +- `useHasBorderPanel` +- `useHasBorderPanelControls` +- `useHasColorPanel` +- `useHasDimensionsPanel` +- `useHasFiltersPanel` +- `useHasImageSettingsPanel` +- `useHasTypographyPanel` +- `useSettingsForBlockElement` +- `ExperimentalBlockCanvas`: version of public `BlockCanvas` that has several extra props: `contentRef`, `shouldIframe`, `iframeProps`. +- `ExperimentalBlockEditorProvider`: version of public `BlockEditorProvider` that filters out several private/experimental settings. See also `__experimentalUpdateSettings`. +- `getDuotoneFilter` +- `getRichTextValues` +- `PrivateQuickInserter` +- `extractWords` +- `getNormalizedSearchTerms` +- `normalizeString` +- `PrivateListView` +- `ResizableBoxPopover` +- `BlockInfo` +- `useHasBlockToolbar` +- `cleanEmptyObject` +- `BlockQuickNavigation` +- `LayoutStyle` +- `BlockRemovalWarningModal` +- `useLayoutClasses` +- `useLayoutStyles` +- `DimensionsTool` +- `ResolutionTool` +- `TabbedSidebar` +- `TextAlignmentControl` +- `usesContextKey` +- `useFlashEditableBlocks` +- `useZoomOut` +- `globalStylesDataKey` +- `globalStylesLinksDataKey` +- `selectBlockPatternsKey` +- `requiresWrapperOnCopy` +- `PrivateRichText`: has an extra prop `readOnly` added in #58916 and #60327 (Feb and Mar 2024). +- `PrivateInserterLibrary`: has an extra prop `onPatternCategorySelection` added in #62130 (May 2024). +- `reusableBlocksSelectKey` +- `PrivateBlockPopover`: has two extra props, `__unstableContentRef` and `__unstablePopoverSlot`. +- `PrivatePublishDateTimePicker`: version of public `PublishDateTimePicker` that has two extra props: `isCompact` and `showPopoverHeaderActions`. +- `useSpacingSizes` +- `useBlockDisplayTitle` +- `__unstableBlockStyleVariationOverridesWithConfig` +- `setBackgroundStyleDefaults` +- `sectionRootClientIdKey` +- `__unstableCommentIconFill` +- `__unstableCommentIconToolbarFill` + +### `core/block-editor` store + +Private actions: +- `__experimentalUpdateSettings`: version of public `updateSettings` action that filters out some private/experimental settings. +- `clearBlockRemovalPrompt` +- `deleteStyleOverride` +- `ensureDefaultBlock` +- `expandBlock` +- `hideBlockInterface` +- `modifyContentLockBlock` +- `privateRemoveBlocks` +- `resetZoomLevel` +- `setBlockRemovalRules` +- `setInsertionPoint` +- `setLastFocus` +- `setOpenedBlockSettingsMenu` +- `setStyleOverride` +- `setZoomLevel` +- `showBlockInterface` +- `startDragging` +- `stopDragging` +- `stopEditingAsBlocks` + +Private selectors: +- `getAllPatterns` +- `getBlockRemovalRules` +- `getBlockSettings` +- `getBlockStyles` +- `getBlockWithoutAttributes` +- `getClosestAllowedInsertionPoint` +- `getClosestAllowedInsertionPointForPattern` +- `getContentLockingParent` +- `getEnabledBlockParents` +- `getEnabledClientIdsTree` +- `getExpandedBlock` +- `getInserterMediaCategories` +- `getInsertionPoint` +- `getLastFocus` +- `getLastInsertedBlocksClientIds` +- `getOpenedBlockSettingsMenu` +- `getParentSectionBlock` +- `getPatternBySlug` +- `getRegisteredInserterMediaCategories` +- `getRemovalPromptData` +- `getReusableBlocks` +- `getSectionRootClientId` +- `getStyleOverrides` +- `getTemporarilyEditingAsBlocks` +- `getTemporarilyEditingFocusModeToRevert` +- `getZoomLevel` +- `hasAllowedPatterns` +- `isBlockInterfaceHidden` +- `isBlockSubtreeDisabled` +- `isDragging` +- `isResolvingPatterns` +- `isSectionBlock` +- `isZoomOut` + +## core-data + +Private exports: +- `useEntityRecordsWithPermissions` + +### `core` store + +Private actions: +- `receiveRegisteredPostMeta` + +Private selectors: +- `getBlockPatternsForPostType` +- `getEntityRecordPermissions` +- `getEntityRecordsPermissions` +- `getNavigationFallbackId` +- `getRegisteredPostMeta` +- `getUndoManager` + +## patterns (package created in Aug 2023 and has no public exports, everything is private) + +Private exports: +- `OverridesPanel` +- `CreatePatternModal` +- `CreatePatternModalContents` +- `DuplicatePatternModal` +- `isOverridableBlock` +- `hasOverridableBlocks` +- `useDuplicatePatternProps` +- `RenamePatternModal` +- `PatternsMenuItems` +- `RenamePatternCategoryModal` +- `PatternOverridesControls` +- `ResetOverridesControl` +- `PatternOverridesBlockControls` +- `useAddPatternCategory` +- `PATTERN_TYPES` +- `PATTERN_DEFAULT_CATEGORY` +- `PATTERN_USER_CATEGORY` +- `EXCLUDED_PATTERN_SOURCES` +- `PATTERN_SYNC_TYPES` +- `PARTIAL_SYNCING_SUPPORTED_BLOCKS` + +### `core/patterns` store + +Private actions: +- `convertSyncedPatternToStatic` +- `createPattern` +- `createPatternFromFile` +- `setEditingPattern` + +Private selectors: +- `isEditingPattern` + +## block-library + +Private exports: +- `BlockKeyboardShortcuts` + +## router (private exports only) + +Private exports: +- `useHistory` +- `useLocation` +- `RouterProvider` + +## core-commands (private exports only) + +Private exports: +- `useCommands` + +## editor + +Private exports: +- `CreateTemplatePartModal` +- `BackButton` +- `EntitiesSavedStatesExtensible` +- `Editor` +- `EditorContentSlotFill` +- `GlobalStylesProvider` +- `mergeBaseAndUserConfigs` +- `PluginPostExcerpt` +- `PostCardPanel` +- `PreferencesModal` +- `usePostActions` +- `ToolsMoreMenuGroup` +- `ViewMoreMenuGroup` +- `ResizableEditor` +- `registerCoreBlockBindingsSources` +- `interfaceStore` +- `ActionItem` +- `ComplementaryArea` +- `ComplementaryAreaMoreMenuItem` +- `FullscreenMode` +- `InterfaceSkeleton` +- `NavigableRegion` +- `PinnedItems` + +### `core/editor` store + +Private actions: +- `createTemplate` +- `hideBlockTypes` +- `registerEntityAction` +- `registerPostTypeActions` +- `removeTemplates` +- `revertTemplate` +- `saveDirtyEntities` +- `setCurrentTemplateId` +- `setIsReady` +- `showBlockTypes` +- `unregisterEntityAction` + +Private selectors: +- `getEntityActions` +- `getInserter` +- `getInserterSidebarToggleRef` +- `getListViewToggleRef` +- `getPostBlocksByName` +- `getPostIcon` +- `hasPostMetaChanges` +- `isEntityReady` + +## edit-post + +### `core/edit-post` store + +Private selectors: +- `getEditedPostTemplateId` + +## edit-site + +### `core/edit-site` store + +Private actions: +- `registerRoute` +- `setEditorCanvasContainerView` + +Private selectors: +- `getRoutes` +- `getEditorCanvasContainerView` diff --git a/docs/reference-guides/block-api/block-bindings.md b/docs/reference-guides/block-api/block-bindings.md index 479396abc13c9c..c26ade45e8b5e3 100644 --- a/docs/reference-guides/block-api/block-bindings.md +++ b/docs/reference-guides/block-api/block-bindings.md @@ -148,7 +148,7 @@ The function to register a custom source is `registerBlockBindingsSource( args ) - `args`: `object` with the following structure: - `name`: `string` with the unique and machine-readable name. - - `label`: `string` with the human readable name of the custom source. In case it was defined already on the server, the server label will be overriden by this one, in that case, it is not recommended to be defined here. (optional) + - `label`: `string` with the human readable name of the custom source. In case it was defined already on the server, the server label will be overridden by this one, in that case, it is not recommended to be defined here. (optional) - `usesContext`: `array` with the block context that the custom source may need. In case it was defined already on the server, it should not be defined here. (optional) - `getValues`: `function` that retrieves the values from the source. (optional) - `setValues`: `function` that allows updating the values connected to the source. (optional) diff --git a/docs/reference-guides/block-api/block-context.md b/docs/reference-guides/block-api/block-context.md index 09c33dfb71b7c3..c5fcfaedbae349 100644 --- a/docs/reference-guides/block-api/block-context.md +++ b/docs/reference-guides/block-api/block-context.md @@ -141,6 +141,8 @@ export default function Edit( props ) { return (
diff --git a/docs/reference-guides/block-api/block-edit-save.md b/docs/reference-guides/block-api/block-edit-save.md index 9e4dd3d1a916bd..a50a17b75cb54d 100644 --- a/docs/reference-guides/block-api/block-edit-save.md +++ b/docs/reference-guides/block-api/block-edit-save.md @@ -183,9 +183,34 @@ save: ( { attributes } ) => { ``` - When saving your block, you want to save the attributes in the same format specified by the attribute source definition. If no attribute source is specified, the attribute will be saved to the block's comment delimiter. See the [Block Attributes documentation](/docs/reference-guides/block-api/block-attributes.md) for more details. +### innerBlocks + +There is a second property in the props passed to the `save` function, `innerBlocks`. This property is typically used for internal operations, and there are very few scenarios where you would need to use it. + +`innerBlocks`, when initialized, is an array containing object representations of nested blocks. In those rare cases where you might use this property, +it can help you adjust how a block is rendered. For example, you could render a block differently based on the number of nested blocks or if a specific block type is present.. + + +```jsx +save: ( { attributes, innerBlocks } ) => { + const { className, ...rest } = useBlockProps.save(); + + // innerBlocks could also be an object - react element during initialization + const numberOfInnerBlocks = innerBlocks?.length; + if ( numberOfInnerBlocks > 1 ) { + className = className + ( className ? ' ' : '' ) + 'more-than-one'; + }; + const blockProps = { ...rest, className }; + + return
{ attributes.content }
; +}; +``` + + +Here, an additional class is added to the block if number of inner blocks is greater than one, allowing for different styling of the block. + ## Examples Here are a couple examples of using attributes, edit, and save all together. @@ -210,6 +235,8 @@ edit: ( { attributes, setAttributes } ) => { return (
{ return (
{ diff --git a/docs/reference-guides/block-api/block-transforms.md b/docs/reference-guides/block-api/block-transforms.md index c2c5ed49d1b19c..9055ed0a3b45b3 100644 --- a/docs/reference-guides/block-api/block-transforms.md +++ b/docs/reference-guides/block-api/block-transforms.md @@ -44,7 +44,7 @@ A transformation of type `block` is an object that takes the following parameter - **transform** _(function)_: a callback that receives the attributes and inner blocks of the block being processed. It should return a block object or an array of block objects. - **isMatch** _(function, optional)_: a callback that receives the block attributes as the first argument and the block object as the second argument and should return a boolean. Returning `false` from this function will prevent the transform from being available and displayed as an option to the user. - **isMultiBlock** _(boolean, optional)_: whether the transformation can be applied when multiple blocks are selected. If true, the `transform` function's first parameter will be an array containing each selected block's attributes, and the second an array of each selected block's inner blocks. False by default. -- **priority** _(number, optional)_: controls the priority with which a transformation is applied, where a lower value will take precedence over higher values. This behaves much like a [WordPress hook](https://codex.wordpress.org/Plugin_API#Hook_to_WordPress). Like hooks, the default priority is `10` when not otherwise set. +- **priority** _(number, optional)_: controls the priority with which a transformation is applied, where a lower value will take precedence over higher values. This behaves much like a [WordPress hook](https://developer.wordpress.org/reference/#Hook_to_WordPress). Like hooks, the default priority is `10` when not otherwise set. **Example: from Paragraph block to Heading block** @@ -97,7 +97,7 @@ A transformation of type `enter` is an object that takes the following parameter - **type** _(string)_: the value `enter`. - **regExp** _(RegExp)_: the Regular Expression to use as a matcher. If the value matches, the transformation will be applied. - **transform** _(function)_: a callback that receives an object with a `content` field containing the value that has been entered. It should return a block object or an array of block objects. -- **priority** _(number, optional)_: controls the priority with which a transform is applied, where a lower value will take precedence over higher values. This behaves much like a [WordPress hook](https://codex.wordpress.org/Plugin_API#Hook_to_WordPress). Like hooks, the default priority is `10` when not otherwise set. +- **priority** _(number, optional)_: controls the priority with which a transform is applied, where a lower value will take precedence over higher values. This behaves much like a [WordPress hook](https://developer.wordpress.org/reference/#Hook_to_WordPress). Like hooks, the default priority is `10` when not otherwise set. **Example: from --- to Separator block** @@ -124,7 +124,7 @@ A transformation of type `files` is an object that takes the following parameter - **type** _(string)_: the value `files`. - **transform** _(function)_: a callback that receives the array of files being processed. It should return a block object or an array of block objects. - **isMatch** _(function, optional)_: a callback that receives the array of files being processed and should return a boolean. Returning `false` from this function will prevent the transform from being applied. -- **priority** _(number, optional)_: controls the priority with which a transform is applied, where a lower value will take precedence over higher values. This behaves much like a [WordPress hook](https://codex.wordpress.org/Plugin_API#Hook_to_WordPress). Like hooks, the default priority is `10` when not otherwise set. +- **priority** _(number, optional)_: controls the priority with which a transform is applied, where a lower value will take precedence over higher values. This behaves much like a [WordPress hook](https://developer.wordpress.org/reference/#Hook_to_WordPress). Like hooks, the default priority is `10` when not otherwise set. **Example: from file to File block** @@ -164,7 +164,7 @@ A transformation of type `prefix` is an object that takes the following paramete - **type** _(string)_: the value `prefix`. - **prefix** _(string)_: the character or sequence of characters that match this transform. - **transform** _(function)_: a callback that receives the content introduced. It should return a block object or an array of block objects. -- **priority** _(number, optional)_: controls the priority with which a transform is applied, where a lower value will take precedence over higher values. This behaves much like a [WordPress hook](https://codex.wordpress.org/Plugin_API#Hook_to_WordPress). Like hooks, the default priority is `10` when not otherwise set. +- **priority** _(number, optional)_: controls the priority with which a transform is applied, where a lower value will take precedence over higher values. This behaves much like a [WordPress hook](https://developer.wordpress.org/reference/#Hook_to_WordPress). Like hooks, the default priority is `10` when not otherwise set. **Example: from text to custom block** @@ -197,7 +197,7 @@ A transformation of type `raw` is an object that takes the following parameters: - **schema** _(object|function, optional)_: defines an [HTML content model](https://html.spec.whatwg.org/multipage/dom.html#content-models) used to detect and process pasted contents. See [below](#schemas-and-content-models). - **selector** _(string, optional)_: a CSS selector string to determine whether the element matches according to the [element.matches](https://developer.mozilla.org/en-US/docs/Web/API/Element/matches) method. The transform won't be executed if the element doesn't match. This is a shorthand and alternative to using `isMatch`, which, if present, will take precedence. - **isMatch** _(function, optional)_: a callback that receives the node being processed and should return a boolean. Returning `false` from this function will prevent the transform from being applied. -- **priority** _(number, optional)_: controls the priority with which a transform is applied, where a lower value will take precedence over higher values. This behaves much like a [WordPress hook](https://codex.wordpress.org/Plugin_API#Hook_to_WordPress). Like hooks, the default priority is `10` when not otherwise set. +- **priority** _(number, optional)_: controls the priority with which a transform is applied, where a lower value will take precedence over higher values. This behaves much like a [WordPress hook](https://developer.wordpress.org/reference/#Hook_to_WordPress). Like hooks, the default priority is `10` when not otherwise set. **Example: from URLs to Embed block** @@ -273,7 +273,7 @@ A transformation of type `shortcode` is an object that takes the following param - **transform** _(function, optional)_: a callback that receives the shortcode attributes as the first argument and the [WPShortcodeMatch](/packages/shortcode/README.md#next) as the second. It should return a block object or an array of block objects. When this parameter is defined, it will take precedence over the `attributes` parameter. - **attributes** _(object, optional)_: object representing where the block attributes should be sourced from, according to the attributes shape defined by the [block configuration object](./block-registration.md). If a particular attribute contains a `shortcode` key, it should be a function that receives the shortcode attributes as the first arguments and the [WPShortcodeMatch](/packages/shortcode/README.md#next) as second, and returns a value for the attribute that will be sourced in the block's comment. - **isMatch** _(function, optional)_: a callback that receives the shortcode attributes per the [Shortcode API](https://codex.wordpress.org/Shortcode_API) and should return a boolean. Returning `false` from this function will prevent the shortcode to be transformed into this block. -- **priority** _(number, optional)_: controls the priority with which a transform is applied, where a lower value will take precedence over higher values. This behaves much like a [WordPress hook](https://codex.wordpress.org/Plugin_API#Hook_to_WordPress). Like hooks, the default priority is `10` when not otherwise set. +- **priority** _(number, optional)_: controls the priority with which a transform is applied, where a lower value will take precedence over higher values. This behaves much like a [WordPress hook](https://developer.wordpress.org/reference/#Hook_to_WordPress). Like hooks, the default priority is `10` when not otherwise set. **Example: from shortcode to block using `transform`** diff --git a/docs/reference-guides/core-blocks.md b/docs/reference-guides/core-blocks.md index a27de8211c824a..10ceb797e28c0f 100644 --- a/docs/reference-guides/core-blocks.md +++ b/docs/reference-guides/core-blocks.md @@ -255,7 +255,7 @@ Hide and show additional content. ([Source](https://github.com/WordPress/gutenbe - **Name:** core/details - **Category:** text - **Supports:** align (full, wide), anchor, color (background, gradients, link, text), interactivity (clientNavigation), layout (~~allowEditing~~), spacing (blockGap, margin, padding), typography (fontSize, lineHeight), ~~html~~ -- **Attributes:** showContent, summary +- **Attributes:** allowedBlocks, name, showContent, summary ## Embed @@ -418,7 +418,7 @@ An organized collection of items displayed in a specific order. ([Source](https: - **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 +## List Item An individual item within a list. ([Source](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/list-item)) @@ -512,7 +512,7 @@ Display a list of all pages. ([Source](https://github.com/WordPress/gutenberg/tr - **Name:** core/page-list - **Category:** widgets - **Allowed Blocks:** core/page-list-item -- **Supports:** interactivity (clientNavigation), typography (fontSize, lineHeight), ~~html~~, ~~reusable~~ +- **Supports:** color (background, gradients, link, text), interactivity (clientNavigation), spacing (margin, padding), typography (fontSize, lineHeight), ~~html~~, ~~reusable~~ - **Attributes:** isNested, parentPageID ## Page List Item @@ -534,7 +534,7 @@ Start with the basic building block of all narrative. ([Source](https://github.c - **Supports:** __unstablePasteTextInline, anchor, color (background, gradients, link, text), interactivity (clientNavigation), spacing (margin, padding), splitting, typography (fontSize, lineHeight), ~~className~~ - **Attributes:** align, content, direction, dropCap, placeholder -## Pattern placeholder +## Pattern Placeholder Show a block pattern. ([Source](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/pattern)) @@ -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), background (backgroundImage, backgroundSize), color (background, gradients, link, text), dimensions (minHeight), layout, spacing (blockGap, margin, padding), typography (fontSize, lineHeight), ~~html~~ +- **Supports:** align (full, wide), background (backgroundImage, backgroundSize), color (background, gradients, heading, link, text), dimensions (minHeight), layout, spacing (blockGap, margin, padding), typography (fontSize, lineHeight), ~~html~~ ## Date @@ -660,8 +660,8 @@ Contains the block elements used to render a post, like the title, date, feature - **Name:** core/post-template - **Category:** theme -- **Parent:** core/query -- **Supports:** align (full, wide), color (background, gradients, link, text), interactivity (clientNavigation), layout, spacing (blockGap), typography (fontSize, lineHeight), ~~html~~, ~~reusable~~ +- **Ancestor:** core/query +- **Supports:** align (full, wide), color (background, gradients, link, text), interactivity (clientNavigation), layout, spacing (blockGap, margin, padding), typography (fontSize, lineHeight), ~~html~~, ~~reusable~~ ## Post Terms @@ -672,7 +672,7 @@ Post terms. ([Source](https://github.com/WordPress/gutenberg/tree/trunk/packages - **Supports:** color (background, gradients, link, text), interactivity (clientNavigation), spacing (margin, padding), typography (fontSize, lineHeight), ~~html~~ - **Attributes:** prefix, separator, suffix, term, textAlign -## Time To Read +## Time to Read Show minutes required to finish reading the post. ([Source](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/post-time-to-read)) @@ -718,13 +718,13 @@ An advanced block that allows displaying post types based on different query par - **Supports:** align (full, wide), interactivity, layout, ~~html~~ - **Attributes:** enhancedPagination, namespace, query, queryId, tagName -## No results +## No Results Contains the block elements used to render content when no query results are found. ([Source](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/query-no-results)) - **Name:** core/query-no-results - **Category:** theme -- **Parent:** core/query +- **Ancestor:** core/query - **Supports:** align, color (background, gradients, link, text), interactivity (clientNavigation), typography (fontSize, lineHeight), ~~html~~, ~~reusable~~ ## Pagination @@ -777,6 +777,16 @@ Display the query title. ([Source](https://github.com/WordPress/gutenberg/tree/t - **Supports:** align (full, wide), color (background, gradients, text), interactivity (clientNavigation), spacing (margin, padding), typography (fontSize, lineHeight), ~~html~~ - **Attributes:** level, levelOptions, showPrefix, showSearchTerm, textAlign, type +## Query Total + +Display the total number of results in a query. ([Source](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/query-total)) + +- **Name:** core/query-total +- **Category:** theme +- **Ancestor:** core/query +- **Supports:** align (full, wide), color (background, gradients, text), spacing (margin, padding), typography (fontSize, lineHeight), ~~html~~ +- **Attributes:** displayType + ## Quote Give quoted text visual emphasis. "In quoting others, we cite ourselves." — Julio Cortázar ([Source](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/quote)) @@ -801,7 +811,7 @@ Display entries from any RSS or Atom feed. ([Source](https://github.com/WordPres - **Name:** core/rss - **Category:** widgets -- **Supports:** align, interactivity (clientNavigation), ~~html~~ +- **Supports:** align, color (background, gradients, link, text), interactivity (clientNavigation), ~~html~~ - **Attributes:** blockLayout, columns, displayAuthor, displayDate, displayExcerpt, excerptLength, feedURL, itemsToShow ## Search @@ -820,7 +830,7 @@ Create a break between ideas or sections with a horizontal separator. ([Source]( - **Name:** core/separator - **Category:** design - **Supports:** align (center, full, wide), anchor, color (background, gradients, ~~enableContrastChecker~~, ~~text~~), interactivity (clientNavigation), spacing (margin) -- **Attributes:** opacity +- **Attributes:** opacity, tagName ## Shortcode diff --git a/docs/reference-guides/data/data-core-block-editor.md b/docs/reference-guides/data/data-core-block-editor.md index 437f7be20f7705..c7ea40d3a6ff11 100644 --- a/docs/reference-guides/data/data-core-block-editor.md +++ b/docs/reference-guides/data/data-core-block-editor.md @@ -190,7 +190,7 @@ _Parameters_ _Returns_ -- `Object?`: Block attributes. +- `?Object`: Block attributes. ### getBlockCount @@ -448,7 +448,7 @@ Determines the items that appear in the available block transforms list. Each item object contains what's necessary to display a menu item in the transform list and handle its selection. -The 'frecency' property is a heuristic () that combines block usage frequenty and recency. +The 'frecency' property is a heuristic () that combines block usage frequency and recency. Items are returned ordered descendingly by their 'frecency'. @@ -521,7 +521,7 @@ _Properties_ - _name_ `string`: The type of block. - _attributes_ `?Object`: Attributes to pass to the newly created block. -- _attributesToCopy_ `?Array`: Attributes to be copied from adjecent blocks when inserted. +- _attributesToCopy_ `?Array`: Attributes to be copied from adjacent blocks when inserted. ### getDraggedBlockClientIds @@ -580,7 +580,7 @@ Determines the items that appear in the inserter. Includes both static items (e. Each item object contains what's necessary to display a button in the inserter and handle its selection. -The 'frecency' property is a heuristic () that combines block usage frequenty and recency. +The 'frecency' property is a heuristic () that combines block usage frequency and recency. Items are returned ordered descendingly by their 'utility' and 'frecency'. @@ -714,7 +714,7 @@ Returns the list of patterns based on their declared `blockTypes` and a block's _Parameters_ - _state_ `Object`: Editor state. -- _blockNames_ `string|string[]`: Block's name or array of block names to find matching pattens. +- _blockNames_ `string|string[]`: Block's name or array of block names to find matching patterns. - _rootClientId_ `?string`: Optional target root client ID. _Returns_ diff --git a/docs/reference-guides/data/data-core-blocks.md b/docs/reference-guides/data/data-core-blocks.md index 084c9c1d7a5fbc..04292135aca51b 100644 --- a/docs/reference-guides/data/data-core-blocks.md +++ b/docs/reference-guides/data/data-core-blocks.md @@ -172,7 +172,7 @@ _Parameters_ _Returns_ -- `Object?`: Block Type. +- `?Object`: Block Type. ### getBlockTypes @@ -385,7 +385,7 @@ _Parameters_ _Returns_ -- `string?`: Default block name. +- `?string`: Default block name. ### getDefaultBlockVariation @@ -464,7 +464,7 @@ _Parameters_ _Returns_ -- `string?`: Name of the block for handling non-block content. +- `?string`: Name of the block for handling non-block content. ### getGroupingBlockName @@ -502,7 +502,7 @@ _Parameters_ _Returns_ -- `string?`: Name of the block for handling the grouping of blocks. +- `?string`: Name of the block for handling the grouping of blocks. ### getUnregisteredFallbackBlockName @@ -540,7 +540,7 @@ _Parameters_ _Returns_ -- `string?`: Name of the block for handling unregistered blocks. +- `?string`: Name of the block for handling unregistered blocks. ### hasBlockSupport diff --git a/docs/reference-guides/data/data-core-edit-post.md b/docs/reference-guides/data/data-core-edit-post.md index 06fe5fc30420ae..c316a9266af98a 100644 --- a/docs/reference-guides/data/data-core-edit-post.md +++ b/docs/reference-guides/data/data-core-edit-post.md @@ -65,7 +65,7 @@ Retrieves the template of the currently edited post. _Returns_ -- `Object?`: Post Template. +- `?Object`: Post Template. ### getEditorMode diff --git a/docs/reference-guides/data/data-core-editor.md b/docs/reference-guides/data/data-core-editor.md index 9567d8e4b954fa..a9f07104bd3b6e 100644 --- a/docs/reference-guides/data/data-core-editor.md +++ b/docs/reference-guides/data/data-core-editor.md @@ -272,7 +272,7 @@ _Parameters_ _Returns_ -- `string?`: Template ID. +- `?string`: Template ID. ### getDeviceType @@ -1148,7 +1148,8 @@ Action that autosaves the current post. This includes server-side autosaving (de _Parameters_ -- _options_ `Object?`: Extra flags to identify the autosave. +- _options_ `[Object]`: Extra flags to identify the autosave. +- _options.local_ `[boolean]`: Whether to perform a local autosave. ### clearSelectedBlock @@ -1204,7 +1205,7 @@ const getFeaturedMediaUrl = useSelect( ( select ) => { _Parameters_ - _edits_ `Object`: Post attributes to edit. -- _options_ `Object`: Options for the edit. +- _options_ `[Object]`: Options for the edit. _Returns_ @@ -1417,7 +1418,7 @@ Returns an action object used to signal that the blocks have been updated. _Parameters_ - _blocks_ `Array`: Block Array. -- _options_ `?Object`: Optional options. +- _options_ `[Object]`: Optional options. ### resetPost @@ -1431,7 +1432,7 @@ Action for saving the current post in the editor. _Parameters_ -- _options_ `Object`: +- _options_ `[Object]`: ### selectBlock @@ -1519,7 +1520,7 @@ _Parameters_ - _post_ `Object`: Post object. - _edits_ `Object`: Initial edited attributes object. -- _template_ `Array?`: Block Template. +- _template_ `[Array]`: Block Template. ### setupEditorState diff --git a/docs/reference-guides/data/data-core-keyboard-shortcuts.md b/docs/reference-guides/data/data-core-keyboard-shortcuts.md index d7d5cf853f7865..426fb316021a42 100644 --- a/docs/reference-guides/data/data-core-keyboard-shortcuts.md +++ b/docs/reference-guides/data/data-core-keyboard-shortcuts.md @@ -239,7 +239,7 @@ _Parameters_ _Returns_ -- `string?`: Shortcut description. +- `?string`: Shortcut description. ### getShortcutKeyCombination @@ -335,7 +335,7 @@ _Parameters_ _Returns_ -- `string?`: Shortcut representation. +- `?string`: Shortcut representation. diff --git a/docs/reference-guides/data/data-core-rich-text.md b/docs/reference-guides/data/data-core-rich-text.md index 55220b3ca9c5d9..8c213ee9c69ec4 100644 --- a/docs/reference-guides/data/data-core-rich-text.md +++ b/docs/reference-guides/data/data-core-rich-text.md @@ -46,7 +46,7 @@ _Parameters_ _Returns_ -- `Object?`: Format type. +- `?Object`: Format type. ### getFormatTypeForBareElement diff --git a/docs/reference-guides/data/data-core.md b/docs/reference-guides/data/data-core.md index 474207aa20460f..4aee9d5051909b 100644 --- a/docs/reference-guides/data/data-core.md +++ b/docs/reference-guides/data/data-core.md @@ -359,7 +359,7 @@ _Parameters_ - _state_ `State`: State tree - _kind_ `string`: Entity kind. - _name_ `string`: Entity name. -- _key_ `EntityRecordKey`: Record's key +- _key_ `EntityRecordKey`: Optional record's key. If requesting a global record (e.g. site settings), the key can be omitted. If requesting a specific item, the key must always be included. - _query_ `GetRecordsHttpQuery`: Optional query. If requesting specific fields, fields must always include the ID. For valid query parameters see the [Reference](https://developer.wordpress.org/rest-api/reference/) in the REST API Handbook and select the entity kind. Then see the arguments available "Retrieve a [Entity kind]". _Returns_ @@ -878,7 +878,7 @@ _Returns_ ### redo -Action triggered to redo the last undoed edit to an entity record, if any. +Action triggered to redo the last undone edit to an entity record, if any. ### saveEditedEntityRecord diff --git a/docs/reference-guides/interactivity-api/core-concepts/the-reactive-and-declarative-mindset.md b/docs/reference-guides/interactivity-api/core-concepts/the-reactive-and-declarative-mindset.md index 3760fdf3867802..4f167d4e37de8a 100644 --- a/docs/reference-guides/interactivity-api/core-concepts/the-reactive-and-declarative-mindset.md +++ b/docs/reference-guides/interactivity-api/core-concepts/the-reactive-and-declarative-mindset.md @@ -28,6 +28,15 @@ Take, for example, this interactive block with two buttons and a paragraph:
+ + ` inside an HTML comment. - * - STYLE content is raw text. - * - TITLE content is plain text but character references are decoded. - * - TEXTAREA content is plain text but character references are decoded. - * - XMP (deprecated) content is raw text. - * - * ### Modifying HTML attributes for a found tag - * - * Once you've found the start of an opening tag you can modify - * any number of the attributes on that tag. You can set a new - * value for an attribute, remove the entire attribute, or do - * nothing and move on to the next opening tag. - * - * Example: - * - * if ( $tags->next_tag( array( 'class_name' => 'wp-group-block' ) ) ) { - * $tags->set_attribute( 'title', 'This groups the contained content.' ); - * $tags->remove_attribute( 'data-test-id' ); - * } - * - * If `set_attribute()` is called for an existing attribute it will - * overwrite the existing value. Similarly, calling `remove_attribute()` - * for a non-existing attribute has no effect on the document. Both - * of these methods are safe to call without knowing if a given attribute - * exists beforehand. - * - * ### Modifying CSS classes for a found tag - * - * The tag processor treats the `class` attribute as a special case. - * Because it's a common operation to add or remove CSS classes, this - * interface adds helper methods to make that easier. - * - * As with attribute values, adding or removing CSS classes is a safe - * operation that doesn't require checking if the attribute or class - * exists before making changes. If removing the only class then the - * entire `class` attribute will be removed. - * - * Example: - * - * // from `Yippee!` - * // to `Yippee!` - * $tags->add_class( 'is-active' ); - * - * // from `Yippee!` - * // to `Yippee!` - * $tags->add_class( 'is-active' ); - * - * // from `Yippee!` - * // to `Yippee!` - * $tags->add_class( 'is-active' ); - * - * // from `` - * // to ` - * $tags->remove_class( 'rugby' ); - * - * // from `` - * // to ` - * $tags->remove_class( 'rugby' ); - * - * // from `` - * // to ` - * $tags->remove_class( 'rugby' ); - * - * When class changes are enqueued but a direct change to `class` is made via - * `set_attribute` then the changes to `set_attribute` (or `remove_attribute`) - * will take precedence over those made through `add_class` and `remove_class`. - * - * ### Bookmarks - * - * While scanning through the input HTMl document it's possible to set - * a named bookmark when a particular tag is found. Later on, after - * continuing to scan other tags, it's possible to `seek` to one of - * the set bookmarks and then proceed again from that point forward. - * - * Because bookmarks create processing overhead one should avoid - * creating too many of them. As a rule, create only bookmarks - * of known string literal names; avoid creating "mark_{$index}" - * and so on. It's fine from a performance standpoint to create a - * bookmark and update it frequently, such as within a loop. - * - * $total_todos = 0; - * while ( $p->next_tag( array( 'tag_name' => 'UL', 'class_name' => 'todo' ) ) ) { - * $p->set_bookmark( 'list-start' ); - * while ( $p->next_tag( array( 'tag_closers' => 'visit' ) ) ) { - * if ( 'UL' === $p->get_tag() && $p->is_tag_closer() ) { - * $p->set_bookmark( 'list-end' ); - * $p->seek( 'list-start' ); - * $p->set_attribute( 'data-contained-todos', (string) $total_todos ); - * $total_todos = 0; - * $p->seek( 'list-end' ); - * break; - * } - * - * if ( 'LI' === $p->get_tag() && ! $p->is_tag_closer() ) { - * $total_todos++; - * } - * } - * } - * - * ## Tokens and finer-grained processing. - * - * It's possible to scan through every lexical token in the - * HTML document using the `next_token()` function. This - * alternative form takes no argument and provides no built-in - * query syntax. - * - * Example: - * - * $title = '(untitled)'; - * $text = ''; - * while ( $processor->next_token() ) { - * switch ( $processor->get_token_name() ) { - * case '#text': - * $text .= $processor->get_modifiable_text(); - * break; - * - * case 'BR': - * $text .= "\n"; - * break; - * - * case 'TITLE': - * $title = $processor->get_modifiable_text(); - * break; - * } - * } - * return trim( "# {$title}\n\n{$text}" ); - * - * ### Tokens and _modifiable text_. - * - * #### Special "atomic" HTML elements. - * - * Not all HTML elements are able to contain other elements inside of them. - * For instance, the contents inside a TITLE element are plaintext (except - * that character references like & will be decoded). This means that - * if the string `` appears inside a TITLE element, then it's not an - * image tag, but rather it's text describing an image tag. Likewise, the - * contents of a SCRIPT or STYLE element are handled entirely separately in - * a browser than the contents of other elements because they represent a - * different language than HTML. - * - * For these elements the Tag Processor treats the entire sequence as one, - * from the opening tag, including its contents, through its closing tag. - * This means that the it's not possible to match the closing tag for a - * SCRIPT element unless it's unexpected; the Tag Processor already matched - * it when it found the opening tag. - * - * The inner contents of these elements are that element's _modifiable text_. - * - * The special elements are: - * - `SCRIPT` whose contents are treated as raw plaintext but supports a legacy - * style of including JavaScript inside of HTML comments to avoid accidentally - * closing the SCRIPT from inside a JavaScript string. E.g. `console.log( '' )`. - * - `TITLE` and `TEXTAREA` whose contents are treated as plaintext and then any - * character references are decoded. E.g. `1 < 2 < 3` becomes `1 < 2 < 3`. - * - `IFRAME`, `NOSCRIPT`, `NOEMBED`, `NOFRAME`, `STYLE` whose contents are treated as - * raw plaintext and left as-is. E.g. `1 < 2 < 3` remains `1 < 2 < 3`. - * - * #### Other tokens with modifiable text. - * - * There are also non-elements which are void/self-closing in nature and contain - * modifiable text that is part of that individual syntax token itself. - * - * - `#text` nodes, whose entire token _is_ the modifiable text. - * - HTML comments and tokens that become comments due to some syntax error. The - * text for these tokens is the portion of the comment inside of the syntax. - * E.g. for `` the text is `" comment "` (note the spaces are included). - * - `CDATA` sections, whose text is the content inside of the section itself. E.g. for - * `` the text is `"some content"` (with restrictions [1]). - * - "Funky comments," which are a special case of invalid closing tags whose name is - * invalid. The text for these nodes is the text that a browser would transform into - * an HTML comment when parsing. E.g. for `` the text is `%post_author`. - * - `DOCTYPE` declarations like `` which have no closing tag. - * - XML Processing instruction nodes like `` (with restrictions [2]). - * - The empty end tag `` which is ignored in the browser and DOM. - * - * [1]: There are no CDATA sections in HTML. When encountering `` becomes a bogus HTML comment, meaning there can be no CDATA - * section in an HTML document containing `>`. The Tag Processor will first find - * all valid and bogus HTML comments, and then if the comment _would_ have been a - * CDATA section _were they to exist_, it will indicate this as the type of comment. - * - * [2]: XML allows a broader range of characters in a processing instruction's target name - * and disallows "xml" as a name, since it's special. The Tag Processor only recognizes - * target names with an ASCII-representable subset of characters. It also exhibits the - * same constraint as with CDATA sections, in that `>` cannot exist within the token - * since Processing Instructions do no exist within HTML and their syntax transforms - * into a bogus comment in the DOM. - * - * ## Design and limitations - * - * The Tag Processor is designed to linearly scan HTML documents and tokenize - * HTML tags and their attributes. It's designed to do this as efficiently as - * possible without compromising parsing integrity. Therefore it will be - * slower than some methods of modifying HTML, such as those incorporating - * over-simplified PCRE patterns, but will not introduce the defects and - * failures that those methods bring in, which lead to broken page renders - * and often to security vulnerabilities. On the other hand, it will be faster - * than full-blown HTML parsers such as DOMDocument and use considerably - * less memory. It requires a negligible memory overhead, enough to consider - * it a zero-overhead system. - * - * The performance characteristics are maintained by avoiding tree construction - * and semantic cleanups which are specified in HTML5. Because of this, for - * example, it's not possible for the Tag Processor to associate any given - * opening tag with its corresponding closing tag, or to return the inner markup - * inside an element. Systems may be built on top of the Tag Processor to do - * this, but the Tag Processor is and should be constrained so it can remain an - * efficient, low-level, and reliable HTML scanner. - * - * The Tag Processor's design incorporates a "garbage-in-garbage-out" philosophy. - * HTML5 specifies that certain invalid content be transformed into different forms - * for display, such as removing null bytes from an input document and replacing - * invalid characters with the Unicode replacement character `U+FFFD` (visually "�"). - * Where errors or transformations exist within the HTML5 specification, the Tag Processor - * leaves those invalid inputs untouched, passing them through to the final browser - * to handle. While this implies that certain operations will be non-spec-compliant, - * such as reading the value of an attribute with invalid content, it also preserves a - * simplicity and efficiency for handling those error cases. - * - * Most operations within the Tag Processor are designed to minimize the difference - * between an input and output document for any given change. For example, the - * `add_class` and `remove_class` methods preserve whitespace and the class ordering - * within the `class` attribute; and when encountering tags with duplicated attributes, - * the Tag Processor will leave those invalid duplicate attributes where they are but - * update the proper attribute which the browser will read for parsing its value. An - * exception to this rule is that all attribute updates store their values as - * double-quoted strings, meaning that attributes on input with single-quoted or - * unquoted values will appear in the output with double-quotes. - * - * ### Scripting Flag - * - * The Tag Processor parses HTML with the "scripting flag" disabled. This means - * that it doesn't run any scripts while parsing the page. In a browser with - * JavaScript enabled, for example, the script can change the parse of the - * document as it loads. On the server, however, evaluating JavaScript is not - * only impractical, but also unwanted. - * - * Practically this means that the Tag Processor will descend into NOSCRIPT - * elements and process its child tags. Were the scripting flag enabled, such - * as in a typical browser, the contents of NOSCRIPT are skipped entirely. - * - * This allows the HTML API to process the content that will be presented in - * a browser when scripting is disabled, but it offers a different view of a - * page than most browser sessions will experience. E.g. the tags inside the - * NOSCRIPT disappear. - * - * ### Text Encoding - * - * The Tag Processor assumes that the input HTML document is encoded with a - * text encoding compatible with 7-bit ASCII's '<', '>', '&', ';', '/', '=', - * "'", '"', 'a' - 'z', 'A' - 'Z', and the whitespace characters ' ', tab, - * carriage-return, newline, and form-feed. - * - * In practice, this includes almost every single-byte encoding as well as - * UTF-8. Notably, however, it does not include UTF-16. If providing input - * that's incompatible, then convert the encoding beforehand. - * - * @since 6.2.0 - * @since 6.2.1 Fix: Support for various invalid comments; attribute updates are case-insensitive. - * @since 6.3.2 Fix: Skip HTML-like content inside rawtext elements such as STYLE. - * @since 6.5.0 Pauses processor when input ends in an incomplete syntax token. - * Introduces "special" elements which act like void elements, e.g. TITLE, STYLE. - * Allows scanning through all tokens and processing modifiable text, where applicable. - */ -class Gutenberg_HTML_Tag_Processor_6_6 { - /** - * The maximum number of bookmarks allowed to exist at - * any given time. - * - * @since 6.2.0 - * @var int - * - * @see WP_HTML_Tag_Processor::set_bookmark() - */ - const MAX_BOOKMARKS = 10; - - /** - * Maximum number of times seek() can be called. - * Prevents accidental infinite loops. - * - * @since 6.2.0 - * @var int - * - * @see WP_HTML_Tag_Processor::seek() - */ - const MAX_SEEK_OPS = 1000; - - /** - * The HTML document to parse. - * - * @since 6.2.0 - * @var string - */ - protected $html; - - /** - * The last query passed to next_tag(). - * - * @since 6.2.0 - * @var array|null - */ - private $last_query; - - /** - * The tag name this processor currently scans for. - * - * @since 6.2.0 - * @var string|null - */ - private $sought_tag_name; - - /** - * The CSS class name this processor currently scans for. - * - * @since 6.2.0 - * @var string|null - */ - private $sought_class_name; - - /** - * The match offset this processor currently scans for. - * - * @since 6.2.0 - * @var int|null - */ - private $sought_match_offset; - - /** - * Whether to visit tag closers, e.g.
, when walking an input document. - * - * @since 6.2.0 - * @var bool - */ - private $stop_on_tag_closers; - - /** - * Specifies mode of operation of the parser at any given time. - * - * | State | Meaning | - * | ----------------|----------------------------------------------------------------------| - * | *Ready* | The parser is ready to run. | - * | *Complete* | There is nothing left to parse. | - * | *Incomplete* | The HTML ended in the middle of a token; nothing more can be parsed. | - * | *Matched tag* | Found an HTML tag; it's possible to modify its attributes. | - * | *Text node* | Found a #text node; this is plaintext and modifiable. | - * | *CDATA node* | Found a CDATA section; this is modifiable. | - * | *Comment* | Found a comment or bogus comment; this is modifiable. | - * | *Presumptuous* | Found an empty tag closer: ``. | - * | *Funky comment* | Found a tag closer with an invalid tag name; this is modifiable. | - * - * @since 6.5.0 - * - * @see WP_HTML_Tag_Processor::STATE_READY - * @see WP_HTML_Tag_Processor::STATE_COMPLETE - * @see WP_HTML_Tag_Processor::STATE_INCOMPLETE_INPUT - * @see WP_HTML_Tag_Processor::STATE_MATCHED_TAG - * @see WP_HTML_Tag_Processor::STATE_TEXT_NODE - * @see WP_HTML_Tag_Processor::STATE_CDATA_NODE - * @see WP_HTML_Tag_Processor::STATE_COMMENT - * @see WP_HTML_Tag_Processor::STATE_DOCTYPE - * @see WP_HTML_Tag_Processor::STATE_PRESUMPTUOUS_TAG - * @see WP_HTML_Tag_Processor::STATE_FUNKY_COMMENT - * - * @var string - */ - protected $parser_state = self::STATE_READY; - - /** - * What kind of syntax token became an HTML comment. - * - * Since there are many ways in which HTML syntax can create an HTML comment, - * this indicates which of those caused it. This allows the Tag Processor to - * represent more from the original input document than would appear in the DOM. - * - * @since 6.5.0 - * - * @var string|null - */ - protected $comment_type = null; - - /** - * How many bytes from the original HTML document have been read and parsed. - * - * This value points to the latest byte offset in the input document which - * has been already parsed. It is the internal cursor for the Tag Processor - * and updates while scanning through the HTML tokens. - * - * @since 6.2.0 - * @var int - */ - private $bytes_already_parsed = 0; - - /** - * Byte offset in input document where current token starts. - * - * Example: - * - *
... - * 01234 - * - token starts at 0 - * - * @since 6.5.0 - * - * @var int|null - */ - private $token_starts_at; - - /** - * Byte length of current token. - * - * Example: - * - *
... - * 012345678901234 - * - token length is 14 - 0 = 14 - * - * a is a token. - * 0123456789 123456789 123456789 - * - token length is 17 - 2 = 15 - * - * @since 6.5.0 - * - * @var int|null - */ - private $token_length; - - /** - * Byte offset in input document where current tag name starts. - * - * Example: - * - *
... - * 01234 - * - tag name starts at 1 - * - * @since 6.2.0 - * - * @var int|null - */ - private $tag_name_starts_at; - - /** - * Byte length of current tag name. - * - * Example: - * - *
... - * 01234 - * --- tag name length is 3 - * - * @since 6.2.0 - * - * @var int|null - */ - private $tag_name_length; - - /** - * Byte offset into input document where current modifiable text starts. - * - * @since 6.5.0 - * - * @var int - */ - private $text_starts_at; - - /** - * Byte length of modifiable text. - * - * @since 6.5.0 - * - * @var string - */ - private $text_length; - - /** - * Whether the current tag is an opening tag, e.g.
, or a closing tag, e.g.
. - * - * @var bool - */ - private $is_closing_tag; - - /** - * Lazily-built index of attributes found within an HTML tag, keyed by the attribute name. - * - * Example: - * - * // Supposing the parser is working through this content - * // and stops after recognizing the `id` attribute. - * //
- * // ^ parsing will continue from this point. - * $this->attributes = array( - * 'id' => new WP_HTML_Attribute_Token( 'id', 9, 6, 5, 11, false ) - * ); - * - * // When picking up parsing again, or when asking to find the - * // `class` attribute we will continue and add to this array. - * $this->attributes = array( - * 'id' => new WP_HTML_Attribute_Token( 'id', 9, 6, 5, 11, false ), - * 'class' => new WP_HTML_Attribute_Token( 'class', 23, 7, 17, 13, false ) - * ); - * - * // Note that only the `class` attribute value is stored in the index. - * // That's because it is the only value used by this class at the moment. - * - * @since 6.2.0 - * @var WP_HTML_Attribute_Token[] - */ - private $attributes = array(); - - /** - * Tracks spans of duplicate attributes on a given tag, used for removing - * all copies of an attribute when calling `remove_attribute()`. - * - * @since 6.3.2 - * - * @var (WP_HTML_Span[])[]|null - */ - private $duplicate_attributes = null; - - /** - * Which class names to add or remove from a tag. - * - * These are tracked separately from attribute updates because they are - * semantically distinct, whereas this interface exists for the common - * case of adding and removing class names while other attributes are - * generally modified as with DOM `setAttribute` calls. - * - * When modifying an HTML document these will eventually be collapsed - * into a single `set_attribute( 'class', $changes )` call. - * - * Example: - * - * // Add the `wp-block-group` class, remove the `wp-group` class. - * $classname_updates = array( - * // Indexed by a comparable class name. - * 'wp-block-group' => WP_HTML_Tag_Processor::ADD_CLASS, - * 'wp-group' => WP_HTML_Tag_Processor::REMOVE_CLASS - * ); - * - * @since 6.2.0 - * @var bool[] - */ - private $classname_updates = array(); - - /** - * Tracks a semantic location in the original HTML which - * shifts with updates as they are applied to the document. - * - * @since 6.2.0 - * @var WP_HTML_Span[] - */ - protected $bookmarks = array(); - - const ADD_CLASS = true; - const REMOVE_CLASS = false; - const SKIP_CLASS = null; - - /** - * Lexical replacements to apply to input HTML document. - * - * "Lexical" in this class refers to the part of this class which - * operates on pure text _as text_ and not as HTML. There's a line - * between the public interface, with HTML-semantic methods like - * `set_attribute` and `add_class`, and an internal state that tracks - * text offsets in the input document. - * - * When higher-level HTML methods are called, those have to transform their - * operations (such as setting an attribute's value) into text diffing - * operations (such as replacing the sub-string from indices A to B with - * some given new string). These text-diffing operations are the lexical - * updates. - * - * As new higher-level methods are added they need to collapse their - * operations into these lower-level lexical updates since that's the - * Tag Processor's internal language of change. Any code which creates - * these lexical updates must ensure that they do not cross HTML syntax - * boundaries, however, so these should never be exposed outside of this - * class or any classes which intentionally expand its functionality. - * - * These are enqueued while editing the document instead of being immediately - * applied to avoid processing overhead, string allocations, and string - * copies when applying many updates to a single document. - * - * Example: - * - * // Replace an attribute stored with a new value, indices - * // sourced from the lazily-parsed HTML recognizer. - * $start = $attributes['src']->start; - * $length = $attributes['src']->length; - * $modifications[] = new WP_HTML_Text_Replacement( $start, $length, $new_value ); - * - * // Correspondingly, something like this will appear in this array. - * $lexical_updates = array( - * WP_HTML_Text_Replacement( 14, 28, 'https://my-site.my-domain/wp-content/uploads/2014/08/kittens.jpg' ) - * ); - * - * @since 6.2.0 - * @var WP_HTML_Text_Replacement[] - */ - protected $lexical_updates = array(); - - /** - * Tracks and limits `seek()` calls to prevent accidental infinite loops. - * - * @since 6.2.0 - * @var int - * - * @see WP_HTML_Tag_Processor::seek() - */ - protected $seek_count = 0; - - /** - * Constructor. - * - * @since 6.2.0 - * - * @param string $html HTML to process. - */ - public function __construct( $html ) { - $this->html = $html; - } - - /** - * Finds the next tag matching the $query. - * - * @since 6.2.0 - * @since 6.5.0 No longer processes incomplete tokens at end of document; pauses the processor at start of token. - * - * @param array|string|null $query { - * Optional. Which tag name to find, having which class, etc. Default is to find any tag. - * - * @type string|null $tag_name Which tag to find, or `null` for "any tag." - * @type int|null $match_offset Find the Nth tag matching all search criteria. - * 1 for "first" tag, 3 for "third," etc. - * Defaults to first tag. - * @type string|null $class_name Tag must contain this whole class name to match. - * @type string|null $tag_closers "visit" or "skip": whether to stop on tag closers, e.g.
. - * } - * @return bool Whether a tag was matched. - */ - public function next_tag( $query = null ) { - $this->parse_query( $query ); - $already_found = 0; - - do { - if ( false === $this->next_token() ) { - return false; - } - - if ( self::STATE_MATCHED_TAG !== $this->parser_state ) { - continue; - } - - if ( $this->matches() ) { - ++$already_found; - } - } while ( $already_found < $this->sought_match_offset ); - - return true; - } - - /** - * Finds the next token in the HTML document. - * - * An HTML document can be viewed as a stream of tokens, - * where tokens are things like HTML tags, HTML comments, - * text nodes, etc. This method finds the next token in - * the HTML document and returns whether it found one. - * - * If it starts parsing a token and reaches the end of the - * document then it will seek to the start of the last - * token and pause, returning `false` to indicate that it - * failed to find a complete token. - * - * Possible token types, based on the HTML specification: - * - * - an HTML tag, whether opening, closing, or void. - * - a text node - the plaintext inside tags. - * - an HTML comment. - * - a DOCTYPE declaration. - * - a processing instruction, e.g. ``. - * - * The Tag Processor currently only supports the tag token. - * - * @since 6.5.0 - * - * @return bool Whether a token was parsed. - */ - public function next_token() { - return $this->base_class_next_token(); - } - - /** - * Internal method which finds the next token in the HTML document. - * - * This method is a protected internal function which implements the logic for - * finding the next token in a document. It exists so that the parser can update - * its state without affecting the location of the cursor in the document and - * without triggering subclass methods for things like `next_token()`, e.g. when - * applying patches before searching for the next token. - * - * @since 6.5.0 - * - * @access private - * - * @return bool Whether a token was parsed. - */ - private function base_class_next_token() { - $was_at = $this->bytes_already_parsed; - $this->after_tag(); - - // Don't proceed if there's nothing more to scan. - if ( - self::STATE_COMPLETE === $this->parser_state || - self::STATE_INCOMPLETE_INPUT === $this->parser_state - ) { - return false; - } - - /* - * The next step in the parsing loop determines the parsing state; - * clear it so that state doesn't linger from the previous step. - */ - $this->parser_state = self::STATE_READY; - - if ( $this->bytes_already_parsed >= strlen( $this->html ) ) { - $this->parser_state = self::STATE_COMPLETE; - return false; - } - - // Find the next tag if it exists. - if ( false === $this->parse_next_tag() ) { - if ( self::STATE_INCOMPLETE_INPUT === $this->parser_state ) { - $this->bytes_already_parsed = $was_at; - } - - return false; - } - - /* - * For legacy reasons the rest of this function handles tags and their - * attributes. If the processor has reached the end of the document - * or if it matched any other token then it should return here to avoid - * attempting to process tag-specific syntax. - */ - if ( - self::STATE_INCOMPLETE_INPUT !== $this->parser_state && - self::STATE_COMPLETE !== $this->parser_state && - self::STATE_MATCHED_TAG !== $this->parser_state - ) { - return true; - } - - // Parse all of its attributes. - while ( $this->parse_next_attribute() ) { - continue; - } - - // Ensure that the tag closes before the end of the document. - if ( - self::STATE_INCOMPLETE_INPUT === $this->parser_state || - $this->bytes_already_parsed >= strlen( $this->html ) - ) { - // Does this appropriately clear state (parsed attributes)? - $this->parser_state = self::STATE_INCOMPLETE_INPUT; - $this->bytes_already_parsed = $was_at; - - return false; - } - - $tag_ends_at = strpos( $this->html, '>', $this->bytes_already_parsed ); - if ( false === $tag_ends_at ) { - $this->parser_state = self::STATE_INCOMPLETE_INPUT; - $this->bytes_already_parsed = $was_at; - - return false; - } - $this->parser_state = self::STATE_MATCHED_TAG; - $this->bytes_already_parsed = $tag_ends_at + 1; - $this->token_length = $this->bytes_already_parsed - $this->token_starts_at; - - /* - * For non-DATA sections which might contain text that looks like HTML tags but - * isn't, scan with the appropriate alternative mode. Looking at the first letter - * of the tag name as a pre-check avoids a string allocation when it's not needed. - */ - $t = $this->html[ $this->tag_name_starts_at ]; - if ( - $this->is_closing_tag || - ! ( - 'i' === $t || 'I' === $t || - 'n' === $t || 'N' === $t || - 's' === $t || 'S' === $t || - 't' === $t || 'T' === $t || - 'x' === $t || 'X' === $t - ) - ) { - return true; - } - - $tag_name = $this->get_tag(); - - /* - * Preserve the opening tag pointers, as these will be overwritten - * when finding the closing tag. They will be reset after finding - * the closing to tag to point to the opening of the special atomic - * tag sequence. - */ - $tag_name_starts_at = $this->tag_name_starts_at; - $tag_name_length = $this->tag_name_length; - $tag_ends_at = $this->token_starts_at + $this->token_length; - $attributes = $this->attributes; - $duplicate_attributes = $this->duplicate_attributes; - - // Find the closing tag if necessary. - $found_closer = false; - switch ( $tag_name ) { - case 'SCRIPT': - $found_closer = $this->skip_script_data(); - break; - - case 'TEXTAREA': - case 'TITLE': - $found_closer = $this->skip_rcdata( $tag_name ); - break; - - /* - * In the browser this list would include the NOSCRIPT element, - * but the Tag Processor is an environment with the scripting - * flag disabled, meaning that it needs to descend into the - * NOSCRIPT element to be able to properly process what will be - * sent to a browser. - * - * Note that this rule makes HTML5 syntax incompatible with XML, - * because the parsing of this token depends on client application. - * The NOSCRIPT element cannot be represented in the XHTML syntax. - */ - case 'IFRAME': - case 'NOEMBED': - case 'NOFRAMES': - case 'STYLE': - case 'XMP': - $found_closer = $this->skip_rawtext( $tag_name ); - break; - - // No other tags should be treated in their entirety here. - default: - return true; - } - - if ( ! $found_closer ) { - $this->parser_state = self::STATE_INCOMPLETE_INPUT; - $this->bytes_already_parsed = $was_at; - return false; - } - - /* - * The values here look like they reference the opening tag but they reference - * the closing tag instead. This is why the opening tag values were stored - * above in a variable. It reads confusingly here, but that's because the - * functions that skip the contents have moved all the internal cursors past - * the inner content of the tag. - */ - $this->token_starts_at = $was_at; - $this->token_length = $this->bytes_already_parsed - $this->token_starts_at; - $this->text_starts_at = $tag_ends_at; - $this->text_length = $this->tag_name_starts_at - $this->text_starts_at; - $this->tag_name_starts_at = $tag_name_starts_at; - $this->tag_name_length = $tag_name_length; - $this->attributes = $attributes; - $this->duplicate_attributes = $duplicate_attributes; - - return true; - } - - /** - * Whether the processor paused because the input HTML document ended - * in the middle of a syntax element, such as in the middle of a tag. - * - * Example: - * - * $processor = new WP_HTML_Tag_Processor( '" ); - * $p->next_tag(); - * foreach ( $p->class_list() as $class_name ) { - * echo "{$class_name} "; - * } - * // Outputs: "free lang-en " - * - * @since 6.4.0 - */ - public function class_list() { - if ( self::STATE_MATCHED_TAG !== $this->parser_state ) { - return; - } - - /** @var string $class contains the string value of the class attribute, with character references decoded. */ - $class = $this->get_attribute( 'class' ); - - if ( ! is_string( $class ) ) { - return; - } - - $seen = array(); - - $at = 0; - while ( $at < strlen( $class ) ) { - // Skip past any initial boundary characters. - $at += strspn( $class, " \t\f\r\n", $at ); - if ( $at >= strlen( $class ) ) { - return; - } - - // Find the byte length until the next boundary. - $length = strcspn( $class, " \t\f\r\n", $at ); - if ( 0 === $length ) { - return; - } - - /* - * CSS class names are case-insensitive in the ASCII range. - * - * @see https://www.w3.org/TR/CSS2/syndata.html#x1 - */ - $name = strtolower( substr( $class, $at, $length ) ); - $at += $length; - - /* - * It's expected that the number of class names for a given tag is relatively small. - * Given this, it is probably faster overall to scan an array for a value rather - * than to use the class name as a key and check if it's a key of $seen. - */ - if ( in_array( $name, $seen, true ) ) { - continue; - } - - $seen[] = $name; - yield $name; - } - } - - - /** - * Returns if a matched tag contains the given ASCII case-insensitive class name. - * - * @since 6.4.0 - * - * @param string $wanted_class Look for this CSS class name, ASCII case-insensitive. - * @return bool|null Whether the matched tag contains the given class name, or null if not matched. - */ - public function has_class( $wanted_class ) { - if ( self::STATE_MATCHED_TAG !== $this->parser_state ) { - return null; - } - - $wanted_class = strtolower( $wanted_class ); - - foreach ( $this->class_list() as $class_name ) { - if ( $class_name === $wanted_class ) { - return true; - } - } - - return false; - } - - - /** - * Sets a bookmark in the HTML document. - * - * Bookmarks represent specific places or tokens in the HTML - * document, such as a tag opener or closer. When applying - * edits to a document, such as setting an attribute, the - * text offsets of that token may shift; the bookmark is - * kept updated with those shifts and remains stable unless - * the entire span of text in which the token sits is removed. - * - * Release bookmarks when they are no longer needed. - * - * Example: - * - *

Surprising fact you may not know!

- * ^ ^ - * \-|-- this `H2` opener bookmark tracks the token - * - *

Surprising fact you may no… - * ^ ^ - * \-|-- it shifts with edits - * - * Bookmarks provide the ability to seek to a previously-scanned - * place in the HTML document. This avoids the need to re-scan - * the entire document. - * - * Example: - * - *
  • One
  • Two
  • Three
- * ^^^^ - * want to note this last item - * - * $p = new WP_HTML_Tag_Processor( $html ); - * $in_list = false; - * while ( $p->next_tag( array( 'tag_closers' => $in_list ? 'visit' : 'skip' ) ) ) { - * if ( 'UL' === $p->get_tag() ) { - * if ( $p->is_tag_closer() ) { - * $in_list = false; - * $p->set_bookmark( 'resume' ); - * if ( $p->seek( 'last-li' ) ) { - * $p->add_class( 'last-li' ); - * } - * $p->seek( 'resume' ); - * $p->release_bookmark( 'last-li' ); - * $p->release_bookmark( 'resume' ); - * } else { - * $in_list = true; - * } - * } - * - * if ( 'LI' === $p->get_tag() ) { - * $p->set_bookmark( 'last-li' ); - * } - * } - * - * Bookmarks intentionally hide the internal string offsets - * to which they refer. They are maintained internally as - * updates are applied to the HTML document and therefore - * retain their "position" - the location to which they - * originally pointed. The inability to use bookmarks with - * functions like `substr` is therefore intentional to guard - * against accidentally breaking the HTML. - * - * Because bookmarks allocate memory and require processing - * for every applied update, they are limited and require - * a name. They should not be created with programmatically-made - * names, such as "li_{$index}" with some loop. As a general - * rule they should only be created with string-literal names - * like "start-of-section" or "last-paragraph". - * - * Bookmarks are a powerful tool to enable complicated behavior. - * Consider double-checking that you need this tool if you are - * reaching for it, as inappropriate use could lead to broken - * HTML structure or unwanted processing overhead. - * - * @since 6.2.0 - * - * @param string $name Identifies this particular bookmark. - * @return bool Whether the bookmark was successfully created. - */ - public function set_bookmark( $name ) { - // It only makes sense to set a bookmark if the parser has paused on a concrete token. - if ( - self::STATE_COMPLETE === $this->parser_state || - self::STATE_INCOMPLETE_INPUT === $this->parser_state - ) { - return false; - } - - if ( ! array_key_exists( $name, $this->bookmarks ) && count( $this->bookmarks ) >= static::MAX_BOOKMARKS ) { - _doing_it_wrong( - __METHOD__, - __( 'Too many bookmarks: cannot create any more.' ), - '6.2.0' - ); - return false; - } - - $this->bookmarks[ $name ] = new WP_HTML_Span( $this->token_starts_at, $this->token_length ); - - return true; - } - - - /** - * Removes a bookmark that is no longer needed. - * - * Releasing a bookmark frees up the small - * performance overhead it requires. - * - * @param string $name Name of the bookmark to remove. - * @return bool Whether the bookmark already existed before removal. - */ - public function release_bookmark( $name ) { - if ( ! array_key_exists( $name, $this->bookmarks ) ) { - return false; - } - - unset( $this->bookmarks[ $name ] ); - - return true; - } - - /** - * Skips contents of generic rawtext elements. - * - * @since 6.3.2 - * - * @see https://html.spec.whatwg.org/#generic-raw-text-element-parsing-algorithm - * - * @param string $tag_name The uppercase tag name which will close the RAWTEXT region. - * @return bool Whether an end to the RAWTEXT region was found before the end of the document. - */ - private function skip_rawtext( $tag_name ) { - /* - * These two functions distinguish themselves on whether character references are - * decoded, and since functionality to read the inner markup isn't supported, it's - * not necessary to implement these two functions separately. - */ - return $this->skip_rcdata( $tag_name ); - } - - /** - * Skips contents of RCDATA elements, namely title and textarea tags. - * - * @since 6.2.0 - * - * @see https://html.spec.whatwg.org/multipage/parsing.html#rcdata-state - * - * @param string $tag_name The uppercase tag name which will close the RCDATA region. - * @return bool Whether an end to the RCDATA region was found before the end of the document. - */ - private function skip_rcdata( $tag_name ) { - $html = $this->html; - $doc_length = strlen( $html ); - $tag_length = strlen( $tag_name ); - - $at = $this->bytes_already_parsed; - - while ( false !== $at && $at < $doc_length ) { - $at = strpos( $this->html, 'tag_name_starts_at = $at; - - // Fail if there is no possible tag closer. - if ( false === $at || ( $at + $tag_length ) >= $doc_length ) { - return false; - } - - $at += 2; - - /* - * Find a case-insensitive match to the tag name. - * - * Because tag names are limited to US-ASCII there is no - * need to perform any kind of Unicode normalization when - * comparing; any character which could be impacted by such - * normalization could not be part of a tag name. - */ - for ( $i = 0; $i < $tag_length; $i++ ) { - $tag_char = $tag_name[ $i ]; - $html_char = $html[ $at + $i ]; - - if ( $html_char !== $tag_char && strtoupper( $html_char ) !== $tag_char ) { - $at += $i; - continue 2; - } - } - - $at += $tag_length; - $this->bytes_already_parsed = $at; - - if ( $at >= strlen( $html ) ) { - return false; - } - - /* - * Ensure that the tag name terminates to avoid matching on - * substrings of a longer tag name. For example, the sequence - * "' !== $c ) { - continue; - } - - while ( $this->parse_next_attribute() ) { - continue; - } - - $at = $this->bytes_already_parsed; - if ( $at >= strlen( $this->html ) ) { - return false; - } - - if ( '>' === $html[ $at ] ) { - $this->bytes_already_parsed = $at + 1; - return true; - } - - if ( $at + 1 >= strlen( $this->html ) ) { - return false; - } - - if ( '/' === $html[ $at ] && '>' === $html[ $at + 1 ] ) { - $this->bytes_already_parsed = $at + 2; - return true; - } - } - - return false; - } - - /** - * Skips contents of script tags. - * - * @since 6.2.0 - * - * @return bool Whether the script tag was closed before the end of the document. - */ - private function skip_script_data() { - $state = 'unescaped'; - $html = $this->html; - $doc_length = strlen( $html ); - $at = $this->bytes_already_parsed; - - while ( false !== $at && $at < $doc_length ) { - $at += strcspn( $html, '-<', $at ); - - /* - * For all script states a "-->" transitions - * back into the normal unescaped script mode, - * even if that's the current state. - */ - if ( - $at + 2 < $doc_length && - '-' === $html[ $at ] && - '-' === $html[ $at + 1 ] && - '>' === $html[ $at + 2 ] - ) { - $at += 3; - $state = 'unescaped'; - continue; - } - - // Everything of interest past here starts with "<". - if ( $at + 1 >= $doc_length || '<' !== $html[ $at++ ] ) { - continue; - } - - /* - * Unlike with "-->", the "`. Unlike other comment - * and bogus comment syntax, these leave no clear insertion point for text and - * they need to be modified specially in order to contain text. E.g. to store - * `?` as the modifiable text, the `` needs to become ``, which - * involves inserting an additional `-` into the token after the modifiable text. - */ - $this->parser_state = self::STATE_COMMENT; - $this->comment_type = self::COMMENT_AS_ABRUPTLY_CLOSED_COMMENT; - $this->token_length = $closer_at + $span_of_dashes + 1 - $this->token_starts_at; - - // Only provide modifiable text if the token is long enough to contain it. - if ( $span_of_dashes >= 2 ) { - $this->comment_type = self::COMMENT_AS_HTML_COMMENT; - $this->text_starts_at = $this->token_starts_at + 4; - $this->text_length = $span_of_dashes - 2; - } - - $this->bytes_already_parsed = $closer_at + $span_of_dashes + 1; - return true; - } - - /* - * Comments may be closed by either a --> or an invalid --!>. - * The first occurrence closes the comment. - * - * See https://html.spec.whatwg.org/#parse-error-incorrectly-closed-comment - */ - --$closer_at; // Pre-increment inside condition below reduces risk of accidental infinite looping. - while ( ++$closer_at < $doc_length ) { - $closer_at = strpos( $html, '--', $closer_at ); - if ( false === $closer_at ) { - $this->parser_state = self::STATE_INCOMPLETE_INPUT; - - return false; - } - - if ( $closer_at + 2 < $doc_length && '>' === $html[ $closer_at + 2 ] ) { - $this->parser_state = self::STATE_COMMENT; - $this->comment_type = self::COMMENT_AS_HTML_COMMENT; - $this->token_length = $closer_at + 3 - $this->token_starts_at; - $this->text_starts_at = $this->token_starts_at + 4; - $this->text_length = $closer_at - $this->text_starts_at; - $this->bytes_already_parsed = $closer_at + 3; - return true; - } - - if ( - $closer_at + 3 < $doc_length && - '!' === $html[ $closer_at + 2 ] && - '>' === $html[ $closer_at + 3 ] - ) { - $this->parser_state = self::STATE_COMMENT; - $this->comment_type = self::COMMENT_AS_HTML_COMMENT; - $this->token_length = $closer_at + 4 - $this->token_starts_at; - $this->text_starts_at = $this->token_starts_at + 4; - $this->text_length = $closer_at - $this->text_starts_at; - $this->bytes_already_parsed = $closer_at + 4; - return true; - } - } - } - - /* - * ` - * These are ASCII-case-insensitive. - * https://html.spec.whatwg.org/multipage/parsing.html#tag-open-state - */ - if ( - $doc_length > $at + 8 && - ( 'D' === $html[ $at + 2 ] || 'd' === $html[ $at + 2 ] ) && - ( 'O' === $html[ $at + 3 ] || 'o' === $html[ $at + 3 ] ) && - ( 'C' === $html[ $at + 4 ] || 'c' === $html[ $at + 4 ] ) && - ( 'T' === $html[ $at + 5 ] || 't' === $html[ $at + 5 ] ) && - ( 'Y' === $html[ $at + 6 ] || 'y' === $html[ $at + 6 ] ) && - ( 'P' === $html[ $at + 7 ] || 'p' === $html[ $at + 7 ] ) && - ( 'E' === $html[ $at + 8 ] || 'e' === $html[ $at + 8 ] ) - ) { - $closer_at = strpos( $html, '>', $at + 9 ); - if ( false === $closer_at ) { - $this->parser_state = self::STATE_INCOMPLETE_INPUT; - - return false; - } - - $this->parser_state = self::STATE_DOCTYPE; - $this->token_length = $closer_at + 1 - $this->token_starts_at; - $this->text_starts_at = $this->token_starts_at + 9; - $this->text_length = $closer_at - $this->text_starts_at; - $this->bytes_already_parsed = $closer_at + 1; - return true; - } - - /* - * Anything else here is an incorrectly-opened comment and transitions - * to the bogus comment state - skip to the nearest >. If no closer is - * found then the HTML was truncated inside the markup declaration. - */ - $closer_at = strpos( $html, '>', $at + 1 ); - if ( false === $closer_at ) { - $this->parser_state = self::STATE_INCOMPLETE_INPUT; - - return false; - } - - $this->parser_state = self::STATE_COMMENT; - $this->comment_type = self::COMMENT_AS_INVALID_HTML; - $this->token_length = $closer_at + 1 - $this->token_starts_at; - $this->text_starts_at = $this->token_starts_at + 2; - $this->text_length = $closer_at - $this->text_starts_at; - $this->bytes_already_parsed = $closer_at + 1; - - /* - * Identify nodes that would be CDATA if HTML had CDATA sections. - * - * This section must occur after identifying the bogus comment end - * because in an HTML parser it will span to the nearest `>`, even - * if there's no `]]>` as would be required in an XML document. It - * is therefore not possible to parse a CDATA section containing - * a `>` in the HTML syntax. - * - * Inside foreign elements there is a discrepancy between browsers - * and the specification on this. - * - * @todo Track whether the Tag Processor is inside a foreign element - * and require the proper closing `]]>` in those cases. - */ - if ( - $this->token_length >= 10 && - '[' === $html[ $this->token_starts_at + 2 ] && - 'C' === $html[ $this->token_starts_at + 3 ] && - 'D' === $html[ $this->token_starts_at + 4 ] && - 'A' === $html[ $this->token_starts_at + 5 ] && - 'T' === $html[ $this->token_starts_at + 6 ] && - 'A' === $html[ $this->token_starts_at + 7 ] && - '[' === $html[ $this->token_starts_at + 8 ] && - ']' === $html[ $closer_at - 1 ] && - ']' === $html[ $closer_at - 2 ] - ) { - $this->parser_state = self::STATE_COMMENT; - $this->comment_type = self::COMMENT_AS_CDATA_LOOKALIKE; - $this->text_starts_at += 7; - $this->text_length -= 9; - } - - return true; - } - - /* - * is a missing end tag name, which is ignored. - * - * This was also known as the "presumptuous empty tag" - * in early discussions as it was proposed to close - * the nearest previous opening tag. - * - * See https://html.spec.whatwg.org/#parse-error-missing-end-tag-name - */ - if ( '>' === $html[ $at + 1 ] ) { - // `<>` is interpreted as plaintext. - if ( ! $this->is_closing_tag ) { - ++$at; - continue; - } - - $this->parser_state = self::STATE_PRESUMPTUOUS_TAG; - $this->token_length = $at + 2 - $this->token_starts_at; - $this->bytes_already_parsed = $at + 2; - return true; - } - - /* - * ` - * See https://html.spec.whatwg.org/multipage/parsing.html#tag-open-state - */ - if ( ! $this->is_closing_tag && '?' === $html[ $at + 1 ] ) { - $closer_at = strpos( $html, '>', $at + 2 ); - if ( false === $closer_at ) { - $this->parser_state = self::STATE_INCOMPLETE_INPUT; - - return false; - } - - $this->parser_state = self::STATE_COMMENT; - $this->comment_type = self::COMMENT_AS_INVALID_HTML; - $this->token_length = $closer_at + 1 - $this->token_starts_at; - $this->text_starts_at = $this->token_starts_at + 2; - $this->text_length = $closer_at - $this->text_starts_at; - $this->bytes_already_parsed = $closer_at + 1; - - /* - * Identify a Processing Instruction node were HTML to have them. - * - * This section must occur after identifying the bogus comment end - * because in an HTML parser it will span to the nearest `>`, even - * if there's no `?>` as would be required in an XML document. It - * is therefore not possible to parse a Processing Instruction node - * containing a `>` in the HTML syntax. - * - * XML allows for more target names, but this code only identifies - * those with ASCII-representable target names. This means that it - * may identify some Processing Instruction nodes as bogus comments, - * but it will not misinterpret the HTML structure. By limiting the - * identification to these target names the Tag Processor can avoid - * the need to start parsing UTF-8 sequences. - * - * > NameStartChar ::= ":" | [A-Z] | "_" | [a-z] | [#xC0-#xD6] | [#xD8-#xF6] | [#xF8-#x2FF] | - * [#x370-#x37D] | [#x37F-#x1FFF] | [#x200C-#x200D] | [#x2070-#x218F] | - * [#x2C00-#x2FEF] | [#x3001-#xD7FF] | [#xF900-#xFDCF] | [#xFDF0-#xFFFD] | - * [#x10000-#xEFFFF] - * > NameChar ::= NameStartChar | "-" | "." | [0-9] | #xB7 | [#x0300-#x036F] | [#x203F-#x2040] - * - * @see https://www.w3.org/TR/2006/REC-xml11-20060816/#NT-PITarget - */ - if ( $this->token_length >= 5 && '?' === $html[ $closer_at - 1 ] ) { - $comment_text = substr( $html, $this->token_starts_at + 2, $this->token_length - 4 ); - $pi_target_length = strspn( $comment_text, 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ:_' ); - - if ( 0 < $pi_target_length ) { - $pi_target_length += strspn( $comment_text, 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789:_-.', $pi_target_length ); - - $this->comment_type = self::COMMENT_AS_PI_NODE_LOOKALIKE; - $this->tag_name_starts_at = $this->token_starts_at + 2; - $this->tag_name_length = $pi_target_length; - $this->text_starts_at += $pi_target_length; - $this->text_length -= $pi_target_length + 1; - } - } - - return true; - } - - /* - * If a non-alpha starts the tag name in a tag closer it's a comment. - * Find the first `>`, which closes the comment. - * - * This parser classifies these particular comments as special "funky comments" - * which are made available for further processing. - * - * See https://html.spec.whatwg.org/#parse-error-invalid-first-character-of-tag-name - */ - if ( $this->is_closing_tag ) { - // No chance of finding a closer. - if ( $at + 3 > $doc_length ) { - return false; - } - - $closer_at = strpos( $html, '>', $at + 2 ); - if ( false === $closer_at ) { - $this->parser_state = self::STATE_INCOMPLETE_INPUT; - - return false; - } - - $this->parser_state = self::STATE_FUNKY_COMMENT; - $this->token_length = $closer_at + 1 - $this->token_starts_at; - $this->text_starts_at = $this->token_starts_at + 2; - $this->text_length = $closer_at - $this->text_starts_at; - $this->bytes_already_parsed = $closer_at + 1; - return true; - } - - ++$at; - } - - return false; - } - - /** - * Parses the next attribute. - * - * @since 6.2.0 - * - * @return bool Whether an attribute was found before the end of the document. - */ - private function parse_next_attribute() { - // Skip whitespace and slashes. - $this->bytes_already_parsed += strspn( $this->html, " \t\f\r\n/", $this->bytes_already_parsed ); - if ( $this->bytes_already_parsed >= strlen( $this->html ) ) { - $this->parser_state = self::STATE_INCOMPLETE_INPUT; - - return false; - } - - /* - * Treat the equal sign as a part of the attribute - * name if it is the first encountered byte. - * - * @see https://html.spec.whatwg.org/multipage/parsing.html#before-attribute-name-state - */ - $name_length = '=' === $this->html[ $this->bytes_already_parsed ] - ? 1 + strcspn( $this->html, "=/> \t\f\r\n", $this->bytes_already_parsed + 1 ) - : strcspn( $this->html, "=/> \t\f\r\n", $this->bytes_already_parsed ); - - // No attribute, just tag closer. - if ( 0 === $name_length || $this->bytes_already_parsed + $name_length >= strlen( $this->html ) ) { - return false; - } - - $attribute_start = $this->bytes_already_parsed; - $attribute_name = substr( $this->html, $attribute_start, $name_length ); - $this->bytes_already_parsed += $name_length; - if ( $this->bytes_already_parsed >= strlen( $this->html ) ) { - $this->parser_state = self::STATE_INCOMPLETE_INPUT; - - return false; - } - - $this->skip_whitespace(); - if ( $this->bytes_already_parsed >= strlen( $this->html ) ) { - $this->parser_state = self::STATE_INCOMPLETE_INPUT; - - return false; - } - - $has_value = '=' === $this->html[ $this->bytes_already_parsed ]; - if ( $has_value ) { - ++$this->bytes_already_parsed; - $this->skip_whitespace(); - if ( $this->bytes_already_parsed >= strlen( $this->html ) ) { - $this->parser_state = self::STATE_INCOMPLETE_INPUT; - - return false; - } - - switch ( $this->html[ $this->bytes_already_parsed ] ) { - case "'": - case '"': - $quote = $this->html[ $this->bytes_already_parsed ]; - $value_start = $this->bytes_already_parsed + 1; - $value_length = strcspn( $this->html, $quote, $value_start ); - $attribute_end = $value_start + $value_length + 1; - $this->bytes_already_parsed = $attribute_end; - break; - - default: - $value_start = $this->bytes_already_parsed; - $value_length = strcspn( $this->html, "> \t\f\r\n", $value_start ); - $attribute_end = $value_start + $value_length; - $this->bytes_already_parsed = $attribute_end; - } - } else { - $value_start = $this->bytes_already_parsed; - $value_length = 0; - $attribute_end = $attribute_start + $name_length; - } - - if ( $attribute_end >= strlen( $this->html ) ) { - $this->parser_state = self::STATE_INCOMPLETE_INPUT; - - return false; - } - - if ( $this->is_closing_tag ) { - return true; - } - - /* - * > There must never be two or more attributes on - * > the same start tag whose names are an ASCII - * > case-insensitive match for each other. - * - HTML 5 spec - * - * @see https://html.spec.whatwg.org/multipage/syntax.html#attributes-2:ascii-case-insensitive - */ - $comparable_name = strtolower( $attribute_name ); - - // If an attribute is listed many times, only use the first declaration and ignore the rest. - if ( ! array_key_exists( $comparable_name, $this->attributes ) ) { - $this->attributes[ $comparable_name ] = new WP_HTML_Attribute_Token( - $attribute_name, - $value_start, - $value_length, - $attribute_start, - $attribute_end - $attribute_start, - ! $has_value - ); - - return true; - } - - /* - * Track the duplicate attributes so if we remove it, all disappear together. - * - * While `$this->duplicated_attributes` could always be stored as an `array()`, - * which would simplify the logic here, storing a `null` and only allocating - * an array when encountering duplicates avoids needless allocations in the - * normative case of parsing tags with no duplicate attributes. - */ - $duplicate_span = new WP_HTML_Span( $attribute_start, $attribute_end - $attribute_start ); - if ( null === $this->duplicate_attributes ) { - $this->duplicate_attributes = array( $comparable_name => array( $duplicate_span ) ); - } elseif ( ! array_key_exists( $comparable_name, $this->duplicate_attributes ) ) { - $this->duplicate_attributes[ $comparable_name ] = array( $duplicate_span ); - } else { - $this->duplicate_attributes[ $comparable_name ][] = $duplicate_span; - } - - return true; - } - - /** - * Move the internal cursor past any immediate successive whitespace. - * - * @since 6.2.0 - */ - private function skip_whitespace() { - $this->bytes_already_parsed += strspn( $this->html, " \t\f\r\n", $this->bytes_already_parsed ); - } - - /** - * Applies attribute updates and cleans up once a tag is fully parsed. - * - * @since 6.2.0 - */ - private function after_tag() { - /* - * There could be lexical updates enqueued for an attribute that - * also exists on the next tag. In order to avoid conflating the - * attributes across the two tags, lexical updates with names - * need to be flushed to raw lexical updates. - */ - $this->class_name_updates_to_attributes_updates(); - - /* - * Purge updates if there are too many. The actual count isn't - * scientific, but a few values from 100 to a few thousand were - * tests to find a practically-useful limit. - * - * If the update queue grows too big, then the Tag Processor - * will spend more time iterating through them and lose the - * efficiency gains of deferring applying them. - */ - if ( 1000 < count( $this->lexical_updates ) ) { - $this->get_updated_html(); - } - - foreach ( $this->lexical_updates as $name => $update ) { - /* - * Any updates appearing after the cursor should be applied - * before proceeding, otherwise they may be overlooked. - */ - if ( $update->start >= $this->bytes_already_parsed ) { - $this->get_updated_html(); - break; - } - - if ( is_int( $name ) ) { - continue; - } - - $this->lexical_updates[] = $update; - unset( $this->lexical_updates[ $name ] ); - } - - $this->token_starts_at = null; - $this->token_length = null; - $this->tag_name_starts_at = null; - $this->tag_name_length = null; - $this->text_starts_at = 0; - $this->text_length = 0; - $this->is_closing_tag = null; - $this->attributes = array(); - $this->comment_type = null; - $this->duplicate_attributes = null; - } - - /** - * Converts class name updates into tag attributes updates - * (they are accumulated in different data formats for performance). - * - * @since 6.2.0 - * - * @see WP_HTML_Tag_Processor::$lexical_updates - * @see WP_HTML_Tag_Processor::$classname_updates - */ - private function class_name_updates_to_attributes_updates() { - if ( count( $this->classname_updates ) === 0 ) { - return; - } - - $existing_class = $this->get_enqueued_attribute_value( 'class' ); - if ( null === $existing_class || true === $existing_class ) { - $existing_class = ''; - } - - if ( false === $existing_class && isset( $this->attributes['class'] ) ) { - $existing_class = substr( - $this->html, - $this->attributes['class']->value_starts_at, - $this->attributes['class']->value_length - ); - } - - if ( false === $existing_class ) { - $existing_class = ''; - } - - /** - * Updated "class" attribute value. - * - * This is incrementally built while scanning through the existing class - * attribute, skipping removed classes on the way, and then appending - * added classes at the end. Only when finished processing will the - * value contain the final new value. - - * @var string $class - */ - $class = ''; - - /** - * Tracks the cursor position in the existing - * class attribute value while parsing. - * - * @var int $at - */ - $at = 0; - - /** - * Indicates if there's any need to modify the existing class attribute. - * - * If a call to `add_class()` and `remove_class()` wouldn't impact - * the `class` attribute value then there's no need to rebuild it. - * For example, when adding a class that's already present or - * removing one that isn't. - * - * This flag enables a performance optimization when none of the enqueued - * class updates would impact the `class` attribute; namely, that the - * processor can continue without modifying the input document, as if - * none of the `add_class()` or `remove_class()` calls had been made. - * - * This flag is set upon the first change that requires a string update. - * - * @var bool $modified - */ - $modified = false; - - // Remove unwanted classes by only copying the new ones. - $existing_class_length = strlen( $existing_class ); - while ( $at < $existing_class_length ) { - // Skip to the first non-whitespace character. - $ws_at = $at; - $ws_length = strspn( $existing_class, " \t\f\r\n", $ws_at ); - $at += $ws_length; - - // Capture the class name – it's everything until the next whitespace. - $name_length = strcspn( $existing_class, " \t\f\r\n", $at ); - if ( 0 === $name_length ) { - // If no more class names are found then that's the end. - break; - } - - $name = substr( $existing_class, $at, $name_length ); - $at += $name_length; - - // If this class is marked for removal, start processing the next one. - $remove_class = ( - isset( $this->classname_updates[ $name ] ) && - self::REMOVE_CLASS === $this->classname_updates[ $name ] - ); - - // If a class has already been seen then skip it; it should not be added twice. - if ( ! $remove_class ) { - $this->classname_updates[ $name ] = self::SKIP_CLASS; - } - - if ( $remove_class ) { - $modified = true; - continue; - } - - /* - * Otherwise, append it to the new "class" attribute value. - * - * There are options for handling whitespace between tags. - * Preserving the existing whitespace produces fewer changes - * to the HTML content and should clarify the before/after - * content when debugging the modified output. - * - * This approach contrasts normalizing the inter-class - * whitespace to a single space, which might appear cleaner - * in the output HTML but produce a noisier change. - */ - $class .= substr( $existing_class, $ws_at, $ws_length ); - $class .= $name; - } - - // Add new classes by appending those which haven't already been seen. - foreach ( $this->classname_updates as $name => $operation ) { - if ( self::ADD_CLASS === $operation ) { - $modified = true; - - $class .= strlen( $class ) > 0 ? ' ' : ''; - $class .= $name; - } - } - - $this->classname_updates = array(); - if ( ! $modified ) { - return; - } - - if ( strlen( $class ) > 0 ) { - $this->set_attribute( 'class', $class ); - } else { - $this->remove_attribute( 'class' ); - } - } - - /** - * Applies attribute updates to HTML document. - * - * @since 6.2.0 - * @since 6.2.1 Accumulates shift for internal cursor and passed pointer. - * @since 6.3.0 Invalidate any bookmarks whose targets are overwritten. - * - * @param int $shift_this_point Accumulate and return shift for this position. - * @return int How many bytes the given pointer moved in response to the updates. - */ - private function apply_attributes_updates( $shift_this_point ) { - if ( ! count( $this->lexical_updates ) ) { - return 0; - } - - $accumulated_shift_for_given_point = 0; - - /* - * Attribute updates can be enqueued in any order but updates - * to the document must occur in lexical order; that is, each - * replacement must be made before all others which follow it - * at later string indices in the input document. - * - * Sorting avoid making out-of-order replacements which - * can lead to mangled output, partially-duplicated - * attributes, and overwritten attributes. - */ - usort( $this->lexical_updates, array( self::class, 'sort_start_ascending' ) ); - - $bytes_already_copied = 0; - $output_buffer = ''; - foreach ( $this->lexical_updates as $diff ) { - $shift = strlen( $diff->text ) - $diff->length; - - // Adjust the cursor position by however much an update affects it. - if ( $diff->start < $this->bytes_already_parsed ) { - $this->bytes_already_parsed += $shift; - } - - // Accumulate shift of the given pointer within this function call. - if ( $diff->start <= $shift_this_point ) { - $accumulated_shift_for_given_point += $shift; - } - - $output_buffer .= substr( $this->html, $bytes_already_copied, $diff->start - $bytes_already_copied ); - $output_buffer .= $diff->text; - $bytes_already_copied = $diff->start + $diff->length; - } - - $this->html = $output_buffer . substr( $this->html, $bytes_already_copied ); - - /* - * Adjust bookmark locations to account for how the text - * replacements adjust offsets in the input document. - */ - foreach ( $this->bookmarks as $bookmark_name => $bookmark ) { - $bookmark_end = $bookmark->start + $bookmark->length; - - /* - * Each lexical update which appears before the bookmark's endpoints - * might shift the offsets for those endpoints. Loop through each change - * and accumulate the total shift for each bookmark, then apply that - * shift after tallying the full delta. - */ - $head_delta = 0; - $tail_delta = 0; - - foreach ( $this->lexical_updates as $diff ) { - $diff_end = $diff->start + $diff->length; - - if ( $bookmark->start < $diff->start && $bookmark_end < $diff->start ) { - break; - } - - if ( $bookmark->start >= $diff->start && $bookmark_end < $diff_end ) { - $this->release_bookmark( $bookmark_name ); - continue 2; - } - - $delta = strlen( $diff->text ) - $diff->length; - - if ( $bookmark->start >= $diff->start ) { - $head_delta += $delta; - } - - if ( $bookmark_end >= $diff_end ) { - $tail_delta += $delta; - } - } - - $bookmark->start += $head_delta; - $bookmark->length += $tail_delta - $head_delta; - } - - $this->lexical_updates = array(); - - return $accumulated_shift_for_given_point; - } - - /** - * Checks whether a bookmark with the given name exists. - * - * @since 6.3.0 - * - * @param string $bookmark_name Name to identify a bookmark that potentially exists. - * @return bool Whether that bookmark exists. - */ - public function has_bookmark( $bookmark_name ) { - return array_key_exists( $bookmark_name, $this->bookmarks ); - } - - /** - * Move the internal cursor in the Tag Processor to a given bookmark's location. - * - * In order to prevent accidental infinite loops, there's a - * maximum limit on the number of times seek() can be called. - * - * @since 6.2.0 - * - * @param string $bookmark_name Jump to the place in the document identified by this bookmark name. - * @return bool Whether the internal cursor was successfully moved to the bookmark's location. - */ - public function seek( $bookmark_name ) { - if ( ! array_key_exists( $bookmark_name, $this->bookmarks ) ) { - _doing_it_wrong( - __METHOD__, - __( 'Unknown bookmark name.' ), - '6.2.0' - ); - return false; - } - - if ( ++$this->seek_count > static::MAX_SEEK_OPS ) { - _doing_it_wrong( - __METHOD__, - __( 'Too many calls to seek() - this can lead to performance issues.' ), - '6.2.0' - ); - return false; - } - - // Flush out any pending updates to the document. - $this->get_updated_html(); - - // Point this tag processor before the sought tag opener and consume it. - $this->bytes_already_parsed = $this->bookmarks[ $bookmark_name ]->start; - $this->parser_state = self::STATE_READY; - return $this->next_token(); - } - - /** - * Compare two WP_HTML_Text_Replacement objects. - * - * @since 6.2.0 - * - * @param WP_HTML_Text_Replacement $a First attribute update. - * @param WP_HTML_Text_Replacement $b Second attribute update. - * @return int Comparison value for string order. - */ - private static function sort_start_ascending( $a, $b ) { - $by_start = $a->start - $b->start; - if ( 0 !== $by_start ) { - return $by_start; - } - - $by_text = isset( $a->text, $b->text ) ? strcmp( $a->text, $b->text ) : 0; - if ( 0 !== $by_text ) { - return $by_text; - } - - /* - * This code should be unreachable, because it implies the two replacements - * start at the same location and contain the same text. - */ - return $a->length - $b->length; - } - - /** - * Return the enqueued value for a given attribute, if one exists. - * - * Enqueued updates can take different data types: - * - If an update is enqueued and is boolean, the return will be `true` - * - If an update is otherwise enqueued, the return will be the string value of that update. - * - If an attribute is enqueued to be removed, the return will be `null` to indicate that. - * - If no updates are enqueued, the return will be `false` to differentiate from "removed." - * - * @since 6.2.0 - * - * @param string $comparable_name The attribute name in its comparable form. - * @return string|boolean|null Value of enqueued update if present, otherwise false. - */ - private function get_enqueued_attribute_value( $comparable_name ) { - if ( self::STATE_MATCHED_TAG !== $this->parser_state ) { - return false; - } - - if ( ! isset( $this->lexical_updates[ $comparable_name ] ) ) { - return false; - } - - $enqueued_text = $this->lexical_updates[ $comparable_name ]->text; - - // Removed attributes erase the entire span. - if ( '' === $enqueued_text ) { - return null; - } - - /* - * Boolean attribute updates are just the attribute name without a corresponding value. - * - * This value might differ from the given comparable name in that there could be leading - * or trailing whitespace, and that the casing follows the name given in `set_attribute`. - * - * Example: - * - * $p->set_attribute( 'data-TEST-id', 'update' ); - * 'update' === $p->get_enqueued_attribute_value( 'data-test-id' ); - * - * Detect this difference based on the absence of the `=`, which _must_ exist in any - * attribute containing a value, e.g. ``. - * ¹ ² - * 1. Attribute with a string value. - * 2. Boolean attribute whose value is `true`. - */ - $equals_at = strpos( $enqueued_text, '=' ); - if ( false === $equals_at ) { - return true; - } - - /* - * Finally, a normal update's value will appear after the `=` and - * be double-quoted, as performed incidentally by `set_attribute`. - * - * e.g. `type="text"` - * ¹² ³ - * 1. Equals is here. - * 2. Double-quoting starts one after the equals sign. - * 3. Double-quoting ends at the last character in the update. - */ - $enqueued_value = substr( $enqueued_text, $equals_at + 2, -1 ); - return Gutenberg_HTML_Decoder_6_6::decode_attribute( $enqueued_value ); - } - - /** - * Returns the value of a requested attribute from a matched tag opener if that attribute exists. - * - * Example: - * - * $p = new WP_HTML_Tag_Processor( '
Test
' ); - * $p->next_tag( array( 'class_name' => 'test' ) ) === true; - * $p->get_attribute( 'data-test-id' ) === '14'; - * $p->get_attribute( 'enabled' ) === true; - * $p->get_attribute( 'aria-label' ) === null; - * - * $p->next_tag() === false; - * $p->get_attribute( 'class' ) === null; - * - * @since 6.2.0 - * - * @param string $name Name of attribute whose value is requested. - * @return string|true|null Value of attribute or `null` if not available. Boolean attributes return `true`. - */ - public function get_attribute( $name ) { - if ( self::STATE_MATCHED_TAG !== $this->parser_state ) { - return null; - } - - $comparable = strtolower( $name ); - - /* - * For every attribute other than `class` it's possible to perform a quick check if - * there's an enqueued lexical update whose value takes priority over what's found in - * the input document. - * - * The `class` attribute is special though because of the exposed helpers `add_class` - * and `remove_class`. These form a builder for the `class` attribute, so an additional - * check for enqueued class changes is required in addition to the check for any enqueued - * attribute values. If any exist, those enqueued class changes must first be flushed out - * into an attribute value update. - */ - if ( 'class' === $name ) { - $this->class_name_updates_to_attributes_updates(); - } - - // Return any enqueued attribute value updates if they exist. - $enqueued_value = $this->get_enqueued_attribute_value( $comparable ); - if ( false !== $enqueued_value ) { - return $enqueued_value; - } - - if ( ! isset( $this->attributes[ $comparable ] ) ) { - return null; - } - - $attribute = $this->attributes[ $comparable ]; - - /* - * This flag distinguishes an attribute with no value - * from an attribute with an empty string value. For - * unquoted attributes this could look very similar. - * It refers to whether an `=` follows the name. - * - * e.g.
- * ¹ ² - * 1. Attribute `boolean-attribute` is `true`. - * 2. Attribute `empty-attribute` is `""`. - */ - if ( true === $attribute->is_true ) { - return true; - } - - $raw_value = substr( $this->html, $attribute->value_starts_at, $attribute->value_length ); - - return Gutenberg_HTML_Decoder_6_6::decode_attribute( $raw_value ); - } - - /** - * Gets lowercase names of all attributes matching a given prefix in the current tag. - * - * Note that matching is case-insensitive. This is in accordance with the spec: - * - * > There must never be two or more attributes on - * > the same start tag whose names are an ASCII - * > case-insensitive match for each other. - * - HTML 5 spec - * - * Example: - * - * $p = new WP_HTML_Tag_Processor( '
Test
' ); - * $p->next_tag( array( 'class_name' => 'test' ) ) === true; - * $p->get_attribute_names_with_prefix( 'data-' ) === array( 'data-enabled', 'data-test-id' ); - * - * $p->next_tag() === false; - * $p->get_attribute_names_with_prefix( 'data-' ) === null; - * - * @since 6.2.0 - * - * @see https://html.spec.whatwg.org/multipage/syntax.html#attributes-2:ascii-case-insensitive - * - * @param string $prefix Prefix of requested attribute names. - * @return array|null List of attribute names, or `null` when no tag opener is matched. - */ - public function get_attribute_names_with_prefix( $prefix ) { - if ( - self::STATE_MATCHED_TAG !== $this->parser_state || - $this->is_closing_tag - ) { - return null; - } - - $comparable = strtolower( $prefix ); - - $matches = array(); - foreach ( array_keys( $this->attributes ) as $attr_name ) { - if ( str_starts_with( $attr_name, $comparable ) ) { - $matches[] = $attr_name; - } - } - return $matches; - } - - /** - * Returns the uppercase name of the matched tag. - * - * Example: - * - * $p = new WP_HTML_Tag_Processor( '
Test
' ); - * $p->next_tag() === true; - * $p->get_tag() === 'DIV'; - * - * $p->next_tag() === false; - * $p->get_tag() === null; - * - * @since 6.2.0 - * - * @return string|null Name of currently matched tag in input HTML, or `null` if none found. - */ - public function get_tag() { - if ( null === $this->tag_name_starts_at ) { - return null; - } - - $tag_name = substr( $this->html, $this->tag_name_starts_at, $this->tag_name_length ); - - if ( self::STATE_MATCHED_TAG === $this->parser_state ) { - return strtoupper( $tag_name ); - } - - if ( - self::STATE_COMMENT === $this->parser_state && - self::COMMENT_AS_PI_NODE_LOOKALIKE === $this->get_comment_type() - ) { - return $tag_name; - } - - return null; - } - - /** - * Indicates if the currently matched tag contains the self-closing flag. - * - * No HTML elements ought to have the self-closing flag and for those, the self-closing - * flag will be ignored. For void elements this is benign because they "self close" - * automatically. For non-void HTML elements though problems will appear if someone - * intends to use a self-closing element in place of that element with an empty body. - * For HTML foreign elements and custom elements the self-closing flag determines if - * they self-close or not. - * - * This function does not determine if a tag is self-closing, - * but only if the self-closing flag is present in the syntax. - * - * @since 6.3.0 - * - * @return bool Whether the currently matched tag contains the self-closing flag. - */ - public function has_self_closing_flag() { - if ( self::STATE_MATCHED_TAG !== $this->parser_state ) { - return false; - } - - /* - * The self-closing flag is the solidus at the _end_ of the tag, not the beginning. - * - * Example: - * - *
- * ^ this appears one character before the end of the closing ">". - */ - return '/' === $this->html[ $this->token_starts_at + $this->token_length - 2 ]; - } - - /** - * Indicates if the current tag token is a tag closer. - * - * Example: - * - * $p = new WP_HTML_Tag_Processor( '
' ); - * $p->next_tag( array( 'tag_name' => 'div', 'tag_closers' => 'visit' ) ); - * $p->is_tag_closer() === false; - * - * $p->next_tag( array( 'tag_name' => 'div', 'tag_closers' => 'visit' ) ); - * $p->is_tag_closer() === true; - * - * @since 6.2.0 - * - * @return bool Whether the current tag is a tag closer. - */ - public function is_tag_closer() { - return ( - self::STATE_MATCHED_TAG === $this->parser_state && - $this->is_closing_tag - ); - } - - /** - * Indicates the kind of matched token, if any. - * - * This differs from `get_token_name()` in that it always - * returns a static string indicating the type, whereas - * `get_token_name()` may return values derived from the - * token itself, such as a tag name or processing - * instruction tag. - * - * Possible values: - * - `#tag` when matched on a tag. - * - `#text` when matched on a text node. - * - `#cdata-section` when matched on a CDATA node. - * - `#comment` when matched on a comment. - * - `#doctype` when matched on a DOCTYPE declaration. - * - `#presumptuous-tag` when matched on an empty tag closer. - * - `#funky-comment` when matched on a funky comment. - * - * @since 6.5.0 - * - * @return string|null What kind of token is matched, or null. - */ - public function get_token_type() { - switch ( $this->parser_state ) { - case self::STATE_MATCHED_TAG: - return '#tag'; - - case self::STATE_DOCTYPE: - return '#doctype'; - - default: - return $this->get_token_name(); - } - } - - /** - * Returns the node name represented by the token. - * - * This matches the DOM API value `nodeName`. Some values - * are static, such as `#text` for a text node, while others - * are dynamically generated from the token itself. - * - * Dynamic names: - * - Uppercase tag name for tag matches. - * - `html` for DOCTYPE declarations. - * - * Note that if the Tag Processor is not matched on a token - * then this function will return `null`, either because it - * hasn't yet found a token or because it reached the end - * of the document without matching a token. - * - * @since 6.5.0 - * - * @return string|null Name of the matched token. - */ - public function get_token_name() { - switch ( $this->parser_state ) { - case self::STATE_MATCHED_TAG: - return $this->get_tag(); - - case self::STATE_TEXT_NODE: - return '#text'; - - case self::STATE_CDATA_NODE: - return '#cdata-section'; - - case self::STATE_COMMENT: - return '#comment'; - - case self::STATE_DOCTYPE: - return 'html'; - - case self::STATE_PRESUMPTUOUS_TAG: - return '#presumptuous-tag'; - - case self::STATE_FUNKY_COMMENT: - return '#funky-comment'; - } - - return null; - } - - /** - * Indicates what kind of comment produced the comment node. - * - * Because there are different kinds of HTML syntax which produce - * comments, the Tag Processor tracks and exposes this as a type - * for the comment. Nominally only regular HTML comments exist as - * they are commonly known, but a number of unrelated syntax errors - * also produce comments. - * - * @see self::COMMENT_AS_ABRUPTLY_CLOSED_COMMENT - * @see self::COMMENT_AS_CDATA_LOOKALIKE - * @see self::COMMENT_AS_INVALID_HTML - * @see self::COMMENT_AS_HTML_COMMENT - * @see self::COMMENT_AS_PI_NODE_LOOKALIKE - * - * @since 6.5.0 - * - * @return string|null - */ - public function get_comment_type() { - if ( self::STATE_COMMENT !== $this->parser_state ) { - return null; - } - - return $this->comment_type; - } - - /** - * Returns the modifiable text for a matched token, or an empty string. - * - * Modifiable text is text content that may be read and changed without - * changing the HTML structure of the document around it. This includes - * the contents of `#text` nodes in the HTML as well as the inner - * contents of HTML comments, Processing Instructions, and others, even - * though these nodes aren't part of a parsed DOM tree. They also contain - * the contents of SCRIPT and STYLE tags, of TEXTAREA tags, and of any - * other section in an HTML document which cannot contain HTML markup (DATA). - * - * If a token has no modifiable text then an empty string is returned to - * avoid needless crashing or type errors. An empty string does not mean - * that a token has modifiable text, and a token with modifiable text may - * have an empty string (e.g. a comment with no contents). - * - * @since 6.5.0 - * - * @return string - */ - public function get_modifiable_text() { - if ( null === $this->text_starts_at ) { - return ''; - } - - $text = substr( $this->html, $this->text_starts_at, $this->text_length ); - - // Comment data is not decoded. - if ( - self::STATE_CDATA_NODE === $this->parser_state || - self::STATE_COMMENT === $this->parser_state || - self::STATE_DOCTYPE === $this->parser_state || - self::STATE_FUNKY_COMMENT === $this->parser_state - ) { - return $text; - } - - $tag_name = $this->get_tag(); - if ( - // Script data is not decoded. - 'SCRIPT' === $tag_name || - - // RAWTEXT data is not decoded. - 'IFRAME' === $tag_name || - 'NOEMBED' === $tag_name || - 'NOFRAMES' === $tag_name || - 'STYLE' === $tag_name || - 'XMP' === $tag_name - ) { - return $text; - } - - $decoded = Gutenberg_HTML_Decoder_6_6::decode_text_node( $text ); - - /* - * TEXTAREA skips a leading newline, but this newline may appear not only as the - * literal character `\n`, but also as a character reference, such as in the - * following markup: ``. - * - * For these cases it's important to first decode the text content before checking - * for a leading newline and removing it. - */ - if ( - self::STATE_MATCHED_TAG === $this->parser_state && - 'TEXTAREA' === $tag_name && - strlen( $decoded ) > 0 && - "\n" === $decoded[0] - ) { - return substr( $decoded, 1 ); - } - - return $decoded; - } - - /** - * Updates or creates a new attribute on the currently matched tag with the passed value. - * - * For boolean attributes special handling is provided: - * - When `true` is passed as the value, then only the attribute name is added to the tag. - * - When `false` is passed, the attribute gets removed if it existed before. - * - * For string attributes, the value is escaped using the `esc_attr` function. - * - * @since 6.2.0 - * @since 6.2.1 Fix: Only create a single update for multiple calls with case-variant attribute names. - * - * @param string $name The attribute name to target. - * @param string|bool $value The new attribute value. - * @return bool Whether an attribute value was set. - */ - public function set_attribute( $name, $value ) { - if ( - self::STATE_MATCHED_TAG !== $this->parser_state || - $this->is_closing_tag - ) { - return false; - } - - /* - * WordPress rejects more characters than are strictly forbidden - * in HTML5. This is to prevent additional security risks deeper - * in the WordPress and plugin stack. Specifically the - * less-than (<) greater-than (>) and ampersand (&) aren't allowed. - * - * The use of a PCRE match enables looking for specific Unicode - * code points without writing a UTF-8 decoder. Whereas scanning - * for one-byte characters is trivial (with `strcspn`), scanning - * for the longer byte sequences would be more complicated. Given - * that this shouldn't be in the hot path for execution, it's a - * reasonable compromise in efficiency without introducing a - * noticeable impact on the overall system. - * - * @see https://html.spec.whatwg.org/#attributes-2 - * - * @todo As the only regex pattern maybe we should take it out? - * Are Unicode patterns available broadly in Core? - */ - if ( preg_match( - '~[' . - // Syntax-like characters. - '"\'>& The values "true" and "false" are not allowed on boolean attributes. - * > To represent a false value, the attribute has to be omitted altogether. - * - HTML5 spec, https://html.spec.whatwg.org/#boolean-attributes - */ - if ( false === $value ) { - return $this->remove_attribute( $name ); - } - - if ( true === $value ) { - $updated_attribute = $name; - } else { - $comparable_name = strtolower( $name ); - - /* - * Escape URL attributes. - * - * @see https://html.spec.whatwg.org/#attributes-3 - */ - $escaped_new_value = in_array( $comparable_name, wp_kses_uri_attributes() ) ? esc_url( $value ) : esc_attr( $value ); - $updated_attribute = "{$name}=\"{$escaped_new_value}\""; - } - - /* - * > There must never be two or more attributes on - * > the same start tag whose names are an ASCII - * > case-insensitive match for each other. - * - HTML 5 spec - * - * @see https://html.spec.whatwg.org/multipage/syntax.html#attributes-2:ascii-case-insensitive - */ - $comparable_name = strtolower( $name ); - - if ( isset( $this->attributes[ $comparable_name ] ) ) { - /* - * Update an existing attribute. - * - * Example – set attribute id to "new" in
: - * - *
- * ^-------------^ - * start end - * replacement: `id="new"` - * - * Result:
- */ - $existing_attribute = $this->attributes[ $comparable_name ]; - $this->lexical_updates[ $comparable_name ] = new WP_HTML_Text_Replacement( - $existing_attribute->start, - $existing_attribute->length, - $updated_attribute - ); - } else { - /* - * Create a new attribute at the tag's name end. - * - * Example – add attribute id="new" to
: - * - *
- * ^ - * start and end - * replacement: ` id="new"` - * - * Result:
- */ - $this->lexical_updates[ $comparable_name ] = new WP_HTML_Text_Replacement( - $this->tag_name_starts_at + $this->tag_name_length, - 0, - ' ' . $updated_attribute - ); - } - - /* - * Any calls to update the `class` attribute directly should wipe out any - * enqueued class changes from `add_class` and `remove_class`. - */ - if ( 'class' === $comparable_name && ! empty( $this->classname_updates ) ) { - $this->classname_updates = array(); - } - - return true; - } - - /** - * Remove an attribute from the currently-matched tag. - * - * @since 6.2.0 - * - * @param string $name The attribute name to remove. - * @return bool Whether an attribute was removed. - */ - public function remove_attribute( $name ) { - if ( - self::STATE_MATCHED_TAG !== $this->parser_state || - $this->is_closing_tag - ) { - return false; - } - - /* - * > There must never be two or more attributes on - * > the same start tag whose names are an ASCII - * > case-insensitive match for each other. - * - HTML 5 spec - * - * @see https://html.spec.whatwg.org/multipage/syntax.html#attributes-2:ascii-case-insensitive - */ - $name = strtolower( $name ); - - /* - * Any calls to update the `class` attribute directly should wipe out any - * enqueued class changes from `add_class` and `remove_class`. - */ - if ( 'class' === $name && count( $this->classname_updates ) !== 0 ) { - $this->classname_updates = array(); - } - - /* - * If updating an attribute that didn't exist in the input - * document, then remove the enqueued update and move on. - * - * For example, this might occur when calling `remove_attribute()` - * after calling `set_attribute()` for the same attribute - * and when that attribute wasn't originally present. - */ - if ( ! isset( $this->attributes[ $name ] ) ) { - if ( isset( $this->lexical_updates[ $name ] ) ) { - unset( $this->lexical_updates[ $name ] ); - } - return false; - } - - /* - * Removes an existing tag attribute. - * - * Example – remove the attribute id from
: - *
- * ^-------------^ - * start end - * replacement: `` - * - * Result:
- */ - $this->lexical_updates[ $name ] = new WP_HTML_Text_Replacement( - $this->attributes[ $name ]->start, - $this->attributes[ $name ]->length, - '' - ); - - // Removes any duplicated attributes if they were also present. - if ( null !== $this->duplicate_attributes && array_key_exists( $name, $this->duplicate_attributes ) ) { - foreach ( $this->duplicate_attributes[ $name ] as $attribute_token ) { - $this->lexical_updates[] = new WP_HTML_Text_Replacement( - $attribute_token->start, - $attribute_token->length, - '' - ); - } - } - - return true; - } - - /** - * Adds a new class name to the currently matched tag. - * - * @since 6.2.0 - * - * @param string $class_name The class name to add. - * @return bool Whether the class was set to be added. - */ - public function add_class( $class_name ) { - if ( - self::STATE_MATCHED_TAG !== $this->parser_state || - $this->is_closing_tag - ) { - return false; - } - - $this->classname_updates[ $class_name ] = self::ADD_CLASS; - - return true; - } - - /** - * Removes a class name from the currently matched tag. - * - * @since 6.2.0 - * - * @param string $class_name The class name to remove. - * @return bool Whether the class was set to be removed. - */ - public function remove_class( $class_name ) { - if ( - self::STATE_MATCHED_TAG !== $this->parser_state || - $this->is_closing_tag - ) { - return false; - } - - if ( null !== $this->tag_name_starts_at ) { - $this->classname_updates[ $class_name ] = self::REMOVE_CLASS; - } - - return true; - } - - /** - * Returns the string representation of the HTML Tag Processor. - * - * @since 6.2.0 - * - * @see WP_HTML_Tag_Processor::get_updated_html() - * - * @return string The processed HTML. - */ - public function __toString() { - return $this->get_updated_html(); - } - - /** - * Returns the string representation of the HTML Tag Processor. - * - * @since 6.2.0 - * @since 6.2.1 Shifts the internal cursor corresponding to the applied updates. - * @since 6.4.0 No longer calls subclass method `next_tag()` after updating HTML. - * - * @return string The processed HTML. - */ - public function get_updated_html() { - $requires_no_updating = 0 === count( $this->classname_updates ) && 0 === count( $this->lexical_updates ); - - /* - * When there is nothing more to update and nothing has already been - * updated, return the original document and avoid a string copy. - */ - if ( $requires_no_updating ) { - return $this->html; - } - - /* - * Keep track of the position right before the current tag. This will - * be necessary for reparsing the current tag after updating the HTML. - */ - $before_current_tag = $this->token_starts_at ?? 0; - - /* - * 1. Apply the enqueued edits and update all the pointers to reflect those changes. - */ - $this->class_name_updates_to_attributes_updates(); - $before_current_tag += $this->apply_attributes_updates( $before_current_tag ); - - /* - * 2. Rewind to before the current tag and reparse to get updated attributes. - * - * At this point the internal cursor points to the end of the tag name. - * Rewind before the tag name starts so that it's as if the cursor didn't - * move; a call to `next_tag()` will reparse the recently-updated attributes - * and additional calls to modify the attributes will apply at this same - * location, but in order to avoid issues with subclasses that might add - * behaviors to `next_tag()`, the internal methods should be called here - * instead. - * - * It's important to note that in this specific place there will be no change - * because the processor was already at a tag when this was called and it's - * rewinding only to the beginning of this very tag before reprocessing it - * and its attributes. - * - *

Previous HTMLMore HTML

- * ↑ │ back up by the length of the tag name plus the opening < - * └←─┘ back up by strlen("em") + 1 ==> 3 - */ - $this->bytes_already_parsed = $before_current_tag; - $this->base_class_next_token(); - - return $this->html; - } - - /** - * Parses tag query input into internal search criteria. - * - * @since 6.2.0 - * - * @param array|string|null $query { - * Optional. Which tag name to find, having which class, etc. Default is to find any tag. - * - * @type string|null $tag_name Which tag to find, or `null` for "any tag." - * @type int|null $match_offset Find the Nth tag matching all search criteria. - * 1 for "first" tag, 3 for "third," etc. - * Defaults to first tag. - * @type string|null $class_name Tag must contain this class name to match. - * @type string $tag_closers "visit" or "skip": whether to stop on tag closers, e.g.
. - * } - */ - private function parse_query( $query ) { - if ( null !== $query && $query === $this->last_query ) { - return; - } - - $this->last_query = $query; - $this->sought_tag_name = null; - $this->sought_class_name = null; - $this->sought_match_offset = 1; - $this->stop_on_tag_closers = false; - - // A single string value means "find the tag of this name". - if ( is_string( $query ) ) { - $this->sought_tag_name = $query; - return; - } - - // An empty query parameter applies no restrictions on the search. - if ( null === $query ) { - return; - } - - // If not using the string interface, an associative array is required. - if ( ! is_array( $query ) ) { - _doing_it_wrong( - __METHOD__, - __( 'The query argument must be an array or a tag name.' ), - '6.2.0' - ); - return; - } - - if ( isset( $query['tag_name'] ) && is_string( $query['tag_name'] ) ) { - $this->sought_tag_name = $query['tag_name']; - } - - if ( isset( $query['class_name'] ) && is_string( $query['class_name'] ) ) { - $this->sought_class_name = $query['class_name']; - } - - if ( isset( $query['match_offset'] ) && is_int( $query['match_offset'] ) && 0 < $query['match_offset'] ) { - $this->sought_match_offset = $query['match_offset']; - } - - if ( isset( $query['tag_closers'] ) ) { - $this->stop_on_tag_closers = 'visit' === $query['tag_closers']; - } - } - - - /** - * Checks whether a given tag and its attributes match the search criteria. - * - * @since 6.2.0 - * - * @return bool Whether the given tag and its attribute match the search criteria. - */ - private function matches() { - if ( $this->is_closing_tag && ! $this->stop_on_tag_closers ) { - return false; - } - - // Does the tag name match the requested tag name in a case-insensitive manner? - if ( null !== $this->sought_tag_name ) { - /* - * String (byte) length lookup is fast. If they aren't the - * same length then they can't be the same string values. - */ - if ( strlen( $this->sought_tag_name ) !== $this->tag_name_length ) { - return false; - } - - /* - * Check each character to determine if they are the same. - * Defer calls to `strtoupper()` to avoid them when possible. - * Calling `strcasecmp()` here tested slowed than comparing each - * character, so unless benchmarks show otherwise, it should - * not be used. - * - * It's expected that most of the time that this runs, a - * lower-case tag name will be supplied and the input will - * contain lower-case tag names, thus normally bypassing - * the case comparison code. - */ - for ( $i = 0; $i < $this->tag_name_length; $i++ ) { - $html_char = $this->html[ $this->tag_name_starts_at + $i ]; - $tag_char = $this->sought_tag_name[ $i ]; - - if ( $html_char !== $tag_char && strtoupper( $html_char ) !== $tag_char ) { - return false; - } - } - } - - if ( null !== $this->sought_class_name && ! $this->has_class( $this->sought_class_name ) ) { - return false; - } - - return true; - } - - /** - * Parser Ready State. - * - * Indicates that the parser is ready to run and waiting for a state transition. - * It may not have started yet, or it may have just finished parsing a token and - * is ready to find the next one. - * - * @since 6.5.0 - * - * @access private - */ - const STATE_READY = 'STATE_READY'; - - /** - * Parser Complete State. - * - * Indicates that the parser has reached the end of the document and there is - * nothing left to scan. It finished parsing the last token completely. - * - * @since 6.5.0 - * - * @access private - */ - const STATE_COMPLETE = 'STATE_COMPLETE'; - - /** - * Parser Incomplete Input State. - * - * Indicates that the parser has reached the end of the document before finishing - * a token. It started parsing a token but there is a possibility that the input - * HTML document was truncated in the middle of a token. - * - * The parser is reset at the start of the incomplete token and has paused. There - * is nothing more than can be scanned unless provided a more complete document. - * - * @since 6.5.0 - * - * @access private - */ - const STATE_INCOMPLETE_INPUT = 'STATE_INCOMPLETE_INPUT'; - - /** - * Parser Matched Tag State. - * - * Indicates that the parser has found an HTML tag and it's possible to get - * the tag name and read or modify its attributes (if it's not a closing tag). - * - * @since 6.5.0 - * - * @access private - */ - const STATE_MATCHED_TAG = 'STATE_MATCHED_TAG'; - - /** - * Parser Text Node State. - * - * Indicates that the parser has found a text node and it's possible - * to read and modify that text. - * - * @since 6.5.0 - * - * @access private - */ - const STATE_TEXT_NODE = 'STATE_TEXT_NODE'; - - /** - * Parser CDATA Node State. - * - * Indicates that the parser has found a CDATA node and it's possible - * to read and modify its modifiable text. Note that in HTML there are - * no CDATA nodes outside of foreign content (SVG and MathML). Outside - * of foreign content, they are treated as HTML comments. - * - * @since 6.5.0 - * - * @access private - */ - const STATE_CDATA_NODE = 'STATE_CDATA_NODE'; - - /** - * Indicates that the parser has found an HTML comment and it's - * possible to read and modify its modifiable text. - * - * @since 6.5.0 - * - * @access private - */ - const STATE_COMMENT = 'STATE_COMMENT'; - - /** - * Indicates that the parser has found a DOCTYPE node and it's - * possible to read and modify its modifiable text. - * - * @since 6.5.0 - * - * @access private - */ - const STATE_DOCTYPE = 'STATE_DOCTYPE'; - - /** - * Indicates that the parser has found an empty tag closer ``. - * - * Note that in HTML there are no empty tag closers, and they - * are ignored. Nonetheless, the Tag Processor still - * recognizes them as they appear in the HTML stream. - * - * These were historically discussed as a "presumptuous tag - * closer," which would close the nearest open tag, but were - * dismissed in favor of explicitly-closing tags. - * - * @since 6.5.0 - * - * @access private - */ - const STATE_PRESUMPTUOUS_TAG = 'STATE_PRESUMPTUOUS_TAG'; - - /** - * Indicates that the parser has found a "funky comment" - * and it's possible to read and modify its modifiable text. - * - * Example: - * - * - * - * - * - * Funky comments are tag closers with invalid tag names. Note - * that in HTML these are turn into bogus comments. Nonetheless, - * the Tag Processor recognizes them in a stream of HTML and - * exposes them for inspection and modification. - * - * @since 6.5.0 - * - * @access private - */ - const STATE_FUNKY_COMMENT = 'STATE_WP_FUNKY'; - - /** - * Indicates that a comment was created when encountering abruptly-closed HTML comment. - * - * Example: - * - * - * - * - * @since 6.5.0 - */ - const COMMENT_AS_ABRUPTLY_CLOSED_COMMENT = 'COMMENT_AS_ABRUPTLY_CLOSED_COMMENT'; - - /** - * Indicates that a comment would be parsed as a CDATA node, - * were HTML to allow CDATA nodes outside of foreign content. - * - * Example: - * - * - * - * This is an HTML comment, but it looks like a CDATA node. - * - * @since 6.5.0 - */ - const COMMENT_AS_CDATA_LOOKALIKE = 'COMMENT_AS_CDATA_LOOKALIKE'; - - /** - * Indicates that a comment was created when encountering - * normative HTML comment syntax. - * - * Example: - * - * - * - * @since 6.5.0 - */ - const COMMENT_AS_HTML_COMMENT = 'COMMENT_AS_HTML_COMMENT'; - - /** - * Indicates that a comment would be parsed as a Processing - * Instruction node, were they to exist within HTML. - * - * Example: - * - * - * - * This is an HTML comment, but it looks like a CDATA node. - * - * @since 6.5.0 - */ - const COMMENT_AS_PI_NODE_LOOKALIKE = 'COMMENT_AS_PI_NODE_LOOKALIKE'; - - /** - * Indicates that a comment was created when encountering invalid - * HTML input, a so-called "bogus comment." - * - * Example: - * - * - * - * - * @since 6.5.0 - */ - const COMMENT_AS_INVALID_HTML = 'COMMENT_AS_INVALID_HTML'; -} diff --git a/lib/compat/wordpress-6.6/html-api/gutenberg-html5-named-character-references-6-6.php b/lib/compat/wordpress-6.6/html-api/gutenberg-html5-named-character-references-6-6.php deleted file mode 100644 index c09deb3c9e6642..00000000000000 --- a/lib/compat/wordpress-6.6/html-api/gutenberg-html5-named-character-references-6-6.php +++ /dev/null @@ -1,1315 +0,0 @@ - "6.6.0-trunk", - "key_length" => 2, - "groups" => "AE\x00AM\x00Aa\x00Ab\x00Ac\x00Af\x00Ag\x00Al\x00Am\x00An\x00Ao\x00Ap\x00Ar\x00As\x00At\x00Au\x00Ba\x00Bc\x00Be\x00Bf\x00Bo\x00Br\x00Bs\x00Bu\x00CH\x00CO\x00Ca\x00Cc\x00Cd\x00Ce\x00Cf\x00Ch\x00Ci\x00Cl\x00Co\x00Cr\x00Cs\x00Cu\x00DD\x00DJ\x00DS\x00DZ\x00Da\x00Dc\x00De\x00Df\x00Di\x00Do\x00Ds\x00EN\x00ET\x00Ea\x00Ec\x00Ed\x00Ef\x00Eg\x00El\x00Em\x00Eo\x00Ep\x00Eq\x00Es\x00Et\x00Eu\x00Ex\x00Fc\x00Ff\x00Fi\x00Fo\x00Fs\x00GJ\x00GT\x00Ga\x00Gb\x00Gc\x00Gd\x00Gf\x00Gg\x00Go\x00Gr\x00Gs\x00Gt\x00HA\x00Ha\x00Hc\x00Hf\x00Hi\x00Ho\x00Hs\x00Hu\x00IE\x00IJ\x00IO\x00Ia\x00Ic\x00Id\x00If\x00Ig\x00Im\x00In\x00Io\x00Is\x00It\x00Iu\x00Jc\x00Jf\x00Jo\x00Js\x00Ju\x00KH\x00KJ\x00Ka\x00Kc\x00Kf\x00Ko\x00Ks\x00LJ\x00LT\x00La\x00Lc\x00Le\x00Lf\x00Ll\x00Lm\x00Lo\x00Ls\x00Lt\x00Ma\x00Mc\x00Me\x00Mf\x00Mi\x00Mo\x00Ms\x00Mu\x00NJ\x00Na\x00Nc\x00Ne\x00Nf\x00No\x00Ns\x00Nt\x00Nu\x00OE\x00Oa\x00Oc\x00Od\x00Of\x00Og\x00Om\x00Oo\x00Op\x00Or\x00Os\x00Ot\x00Ou\x00Ov\x00Pa\x00Pc\x00Pf\x00Ph\x00Pi\x00Pl\x00Po\x00Pr\x00Ps\x00QU\x00Qf\x00Qo\x00Qs\x00RB\x00RE\x00Ra\x00Rc\x00Re\x00Rf\x00Rh\x00Ri\x00Ro\x00Rr\x00Rs\x00Ru\x00SH\x00SO\x00Sa\x00Sc\x00Sf\x00Sh\x00Si\x00Sm\x00So\x00Sq\x00Ss\x00St\x00Su\x00TH\x00TR\x00TS\x00Ta\x00Tc\x00Tf\x00Th\x00Ti\x00To\x00Tr\x00Ts\x00Ua\x00Ub\x00Uc\x00Ud\x00Uf\x00Ug\x00Um\x00Un\x00Uo\x00Up\x00Ur\x00Us\x00Ut\x00Uu\x00VD\x00Vb\x00Vc\x00Vd\x00Ve\x00Vf\x00Vo\x00Vs\x00Vv\x00Wc\x00We\x00Wf\x00Wo\x00Ws\x00Xf\x00Xi\x00Xo\x00Xs\x00YA\x00YI\x00YU\x00Ya\x00Yc\x00Yf\x00Yo\x00Ys\x00Yu\x00ZH\x00Za\x00Zc\x00Zd\x00Ze\x00Zf\x00Zo\x00Zs\x00aa\x00ab\x00ac\x00ae\x00af\x00ag\x00al\x00am\x00an\x00ao\x00ap\x00ar\x00as\x00at\x00au\x00aw\x00bN\x00ba\x00bb\x00bc\x00bd\x00be\x00bf\x00bi\x00bk\x00bl\x00bn\x00bo\x00bp\x00br\x00bs\x00bu\x00ca\x00cc\x00cd\x00ce\x00cf\x00ch\x00ci\x00cl\x00co\x00cr\x00cs\x00ct\x00cu\x00cw\x00cy\x00dA\x00dH\x00da\x00db\x00dc\x00dd\x00de\x00df\x00dh\x00di\x00dj\x00dl\x00do\x00dr\x00ds\x00dt\x00du\x00dw\x00dz\x00eD\x00ea\x00ec\x00ed\x00ee\x00ef\x00eg\x00el\x00em\x00en\x00eo\x00ep\x00eq\x00er\x00es\x00et\x00eu\x00ex\x00fa\x00fc\x00fe\x00ff\x00fi\x00fj\x00fl\x00fn\x00fo\x00fp\x00fr\x00fs\x00gE\x00ga\x00gb\x00gc\x00gd\x00ge\x00gf\x00gg\x00gi\x00gj\x00gl\x00gn\x00go\x00gr\x00gs\x00gt\x00gv\x00hA\x00ha\x00hb\x00hc\x00he\x00hf\x00hk\x00ho\x00hs\x00hy\x00ia\x00ic\x00ie\x00if\x00ig\x00ii\x00ij\x00im\x00in\x00io\x00ip\x00iq\x00is\x00it\x00iu\x00jc\x00jf\x00jm\x00jo\x00js\x00ju\x00ka\x00kc\x00kf\x00kg\x00kh\x00kj\x00ko\x00ks\x00lA\x00lB\x00lE\x00lH\x00la\x00lb\x00lc\x00ld\x00le\x00lf\x00lg\x00lh\x00lj\x00ll\x00lm\x00ln\x00lo\x00lp\x00lr\x00ls\x00lt\x00lu\x00lv\x00mD\x00ma\x00mc\x00md\x00me\x00mf\x00mh\x00mi\x00ml\x00mn\x00mo\x00mp\x00ms\x00mu\x00nG\x00nL\x00nR\x00nV\x00na\x00nb\x00nc\x00nd\x00ne\x00nf\x00ng\x00nh\x00ni\x00nj\x00nl\x00nm\x00no\x00np\x00nr\x00ns\x00nt\x00nu\x00nv\x00nw\x00oS\x00oa\x00oc\x00od\x00oe\x00of\x00og\x00oh\x00oi\x00ol\x00om\x00oo\x00op\x00or\x00os\x00ot\x00ou\x00ov\x00pa\x00pc\x00pe\x00pf\x00ph\x00pi\x00pl\x00pm\x00po\x00pr\x00ps\x00pu\x00qf\x00qi\x00qo\x00qp\x00qs\x00qu\x00rA\x00rB\x00rH\x00ra\x00rb\x00rc\x00rd\x00re\x00rf\x00rh\x00ri\x00rl\x00rm\x00rn\x00ro\x00rp\x00rr\x00rs\x00rt\x00ru\x00rx\x00sa\x00sb\x00sc\x00sd\x00se\x00sf\x00sh\x00si\x00sl\x00sm\x00so\x00sp\x00sq\x00sr\x00ss\x00st\x00su\x00sw\x00sz\x00ta\x00tb\x00tc\x00td\x00te\x00tf\x00th\x00ti\x00to\x00tp\x00tr\x00ts\x00tw\x00uA\x00uH\x00ua\x00ub\x00uc\x00ud\x00uf\x00ug\x00uh\x00ul\x00um\x00uo\x00up\x00ur\x00us\x00ut\x00uu\x00uw\x00vA\x00vB\x00vD\x00va\x00vc\x00vd\x00ve\x00vf\x00vl\x00vn\x00vo\x00vp\x00vr\x00vs\x00vz\x00wc\x00we\x00wf\x00wo\x00wp\x00wr\x00ws\x00xc\x00xd\x00xf\x00xh\x00xi\x00xl\x00xm\x00xn\x00xo\x00xr\x00xs\x00xu\x00xv\x00xw\x00ya\x00yc\x00ye\x00yf\x00yi\x00yo\x00ys\x00yu\x00za\x00zc\x00zd\x00ze\x00zf\x00zh\x00zi\x00zo\x00zs\x00zw\x00", - "large_words" => array( - // AElig;[Æ] AElig[Æ]. - "\x04lig;\x02Æ\x03lig\x02Æ", - // AMP;[&] AMP[&]. - "\x02P;\x01&\x01P\x01&", - // Aacute;[Á] Aacute[Á]. - "\x05cute;\x02Á\x04cute\x02Á", - // Abreve;[Ă]. - "\x05reve;\x02Ă", - // Acirc;[Â] Acirc[Â] Acy;[А]. - "\x04irc;\x02Â\x03irc\x02Â\x02y;\x02А", - // Afr;[𝔄]. - "\x02r;\x04𝔄", - // Agrave;[À] Agrave[À]. - "\x05rave;\x02À\x04rave\x02À", - // Alpha;[Α]. - "\x04pha;\x02Α", - // Amacr;[Ā]. - "\x04acr;\x02Ā", - // And;[⩓]. - "\x02d;\x03⩓", - // Aogon;[Ą] Aopf;[𝔸]. - "\x04gon;\x02Ą\x03pf;\x04𝔸", - // ApplyFunction;[⁡]. - "\x0cplyFunction;\x03⁡", - // Aring;[Å] Aring[Å]. - "\x04ing;\x02Å\x03ing\x02Å", - // Assign;[≔] Ascr;[𝒜]. - "\x05sign;\x03≔\x03cr;\x04𝒜", - // Atilde;[Ã] Atilde[Ã]. - "\x05ilde;\x02Ã\x04ilde\x02Ã", - // Auml;[Ä] Auml[Ä]. - "\x03ml;\x02Ä\x02ml\x02Ä", - // Backslash;[∖] Barwed;[⌆] Barv;[⫧]. - "\x08ckslash;\x03∖\x05rwed;\x03⌆\x03rv;\x03⫧", - // Bcy;[Б]. - "\x02y;\x02Б", - // Bernoullis;[ℬ] Because;[∵] Beta;[Β]. - "\x09rnoullis;\x03ℬ\x06cause;\x03∵\x03ta;\x02Β", - // Bfr;[𝔅]. - "\x02r;\x04𝔅", - // Bopf;[𝔹]. - "\x03pf;\x04𝔹", - // Breve;[˘]. - "\x04eve;\x02˘", - // Bscr;[ℬ]. - "\x03cr;\x03ℬ", - // Bumpeq;[≎]. - "\x05mpeq;\x03≎", - // CHcy;[Ч]. - "\x03cy;\x02Ч", - // COPY;[©] COPY[©]. - "\x03PY;\x02©\x02PY\x02©", - // CapitalDifferentialD;[ⅅ] Cayleys;[ℭ] Cacute;[Ć] Cap;[⋒]. - "\x13pitalDifferentialD;\x03ⅅ\x06yleys;\x03ℭ\x05cute;\x02Ć\x02p;\x03⋒", - // Cconint;[∰] Ccaron;[Č] Ccedil;[Ç] Ccedil[Ç] Ccirc;[Ĉ]. - "\x06onint;\x03∰\x05aron;\x02Č\x05edil;\x02Ç\x04edil\x02Ç\x04irc;\x02Ĉ", - // Cdot;[Ċ]. - "\x03ot;\x02Ċ", - // CenterDot;[·] Cedilla;[¸]. - "\x08nterDot;\x02·\x06dilla;\x02¸", - // Cfr;[ℭ]. - "\x02r;\x03ℭ", - // Chi;[Χ]. - "\x02i;\x02Χ", - // CircleMinus;[⊖] CircleTimes;[⊗] CirclePlus;[⊕] CircleDot;[⊙]. - "\x0arcleMinus;\x03⊖\x0arcleTimes;\x03⊗\x09rclePlus;\x03⊕\x08rcleDot;\x03⊙", - // ClockwiseContourIntegral;[∲] CloseCurlyDoubleQuote;[”] CloseCurlyQuote;[’]. - "\x17ockwiseContourIntegral;\x03∲\x14oseCurlyDoubleQuote;\x03”\x0eoseCurlyQuote;\x03’", - // CounterClockwiseContourIntegral;[∳] ContourIntegral;[∮] Congruent;[≡] Coproduct;[∐] Colone;[⩴] Conint;[∯] Colon;[∷] Copf;[ℂ]. - "\x1eunterClockwiseContourIntegral;\x03∳\x0entourIntegral;\x03∮\x08ngruent;\x03≡\x08product;\x03∐\x05lone;\x03⩴\x05nint;\x03∯\x04lon;\x03∷\x03pf;\x03ℂ", - // Cross;[⨯]. - "\x04oss;\x03⨯", - // Cscr;[𝒞]. - "\x03cr;\x04𝒞", - // CupCap;[≍] Cup;[⋓]. - "\x05pCap;\x03≍\x02p;\x03⋓", - // DDotrahd;[⤑] DD;[ⅅ]. - "\x07otrahd;\x03⤑\x01;\x03ⅅ", - // DJcy;[Ђ]. - "\x03cy;\x02Ђ", - // DScy;[Ѕ]. - "\x03cy;\x02Ѕ", - // DZcy;[Џ]. - "\x03cy;\x02Џ", - // Dagger;[‡] Dashv;[⫤] Darr;[↡]. - "\x05gger;\x03‡\x04shv;\x03⫤\x03rr;\x03↡", - // Dcaron;[Ď] Dcy;[Д]. - "\x05aron;\x02Ď\x02y;\x02Д", - // Delta;[Δ] Del;[∇]. - "\x04lta;\x02Δ\x02l;\x03∇", - // Dfr;[𝔇]. - "\x02r;\x04𝔇", - // DiacriticalDoubleAcute;[˝] DiacriticalAcute;[´] DiacriticalGrave;[`] DiacriticalTilde;[˜] DiacriticalDot;[˙] DifferentialD;[ⅆ] Diamond;[⋄]. - "\x15acriticalDoubleAcute;\x02˝\x0facriticalAcute;\x02´\x0facriticalGrave;\x01`\x0facriticalTilde;\x02˜\x0dacriticalDot;\x02˙\x0cfferentialD;\x03ⅆ\x06amond;\x03⋄", - // DoubleLongLeftRightArrow;[⟺] DoubleContourIntegral;[∯] DoubleLeftRightArrow;[⇔] DoubleLongRightArrow;[⟹] DoubleLongLeftArrow;[⟸] DownLeftRightVector;[⥐] DownRightTeeVector;[⥟] DownRightVectorBar;[⥗] DoubleUpDownArrow;[⇕] DoubleVerticalBar;[∥] DownLeftTeeVector;[⥞] DownLeftVectorBar;[⥖] DoubleRightArrow;[⇒] DownArrowUpArrow;[⇵] DoubleDownArrow;[⇓] DoubleLeftArrow;[⇐] DownRightVector;[⇁] DoubleRightTee;[⊨] DownLeftVector;[↽] DoubleLeftTee;[⫤] DoubleUpArrow;[⇑] DownArrowBar;[⤓] DownTeeArrow;[↧] DoubleDot;[¨] DownArrow;[↓] DownBreve;[̑] Downarrow;[⇓] DotEqual;[≐] DownTee;[⊤] DotDot;[⃜] Dopf;[𝔻] Dot;[¨]. - "\x17ubleLongLeftRightArrow;\x03⟺\x14ubleContourIntegral;\x03∯\x13ubleLeftRightArrow;\x03⇔\x13ubleLongRightArrow;\x03⟹\x12ubleLongLeftArrow;\x03⟸\x12wnLeftRightVector;\x03⥐\x11wnRightTeeVector;\x03⥟\x11wnRightVectorBar;\x03⥗\x10ubleUpDownArrow;\x03⇕\x10ubleVerticalBar;\x03∥\x10wnLeftTeeVector;\x03⥞\x10wnLeftVectorBar;\x03⥖\x0fubleRightArrow;\x03⇒\x0fwnArrowUpArrow;\x03⇵\x0eubleDownArrow;\x03⇓\x0eubleLeftArrow;\x03⇐\x0ewnRightVector;\x03⇁\x0dubleRightTee;\x03⊨\x0dwnLeftVector;\x03↽\x0cubleLeftTee;\x03⫤\x0cubleUpArrow;\x03⇑\x0bwnArrowBar;\x03⤓\x0bwnTeeArrow;\x03↧\x08ubleDot;\x02¨\x08wnArrow;\x03↓\x08wnBreve;\x02̑\x08wnarrow;\x03⇓\x07tEqual;\x03≐\x06wnTee;\x03⊤\x05tDot;\x03⃜\x03pf;\x04𝔻\x02t;\x02¨", - // Dstrok;[Đ] Dscr;[𝒟]. - "\x05trok;\x02Đ\x03cr;\x04𝒟", - // ENG;[Ŋ]. - "\x02G;\x02Ŋ", - // ETH;[Ð] ETH[Ð]. - "\x02H;\x02Ð\x01H\x02Ð", - // Eacute;[É] Eacute[É]. - "\x05cute;\x02É\x04cute\x02É", - // Ecaron;[Ě] Ecirc;[Ê] Ecirc[Ê] Ecy;[Э]. - "\x05aron;\x02Ě\x04irc;\x02Ê\x03irc\x02Ê\x02y;\x02Э", - // Edot;[Ė]. - "\x03ot;\x02Ė", - // Efr;[𝔈]. - "\x02r;\x04𝔈", - // Egrave;[È] Egrave[È]. - "\x05rave;\x02È\x04rave\x02È", - // Element;[∈]. - "\x06ement;\x03∈", - // EmptyVerySmallSquare;[▫] EmptySmallSquare;[◻] Emacr;[Ē]. - "\x13ptyVerySmallSquare;\x03▫\x0fptySmallSquare;\x03◻\x04acr;\x02Ē", - // Eogon;[Ę] Eopf;[𝔼]. - "\x04gon;\x02Ę\x03pf;\x04𝔼", - // Epsilon;[Ε]. - "\x06silon;\x02Ε", - // Equilibrium;[⇌] EqualTilde;[≂] Equal;[⩵]. - "\x0auilibrium;\x03⇌\x09ualTilde;\x03≂\x04ual;\x03⩵", - // Escr;[ℰ] Esim;[⩳]. - "\x03cr;\x03ℰ\x03im;\x03⩳", - // Eta;[Η]. - "\x02a;\x02Η", - // Euml;[Ë] Euml[Ë]. - "\x03ml;\x02Ë\x02ml\x02Ë", - // ExponentialE;[ⅇ] Exists;[∃]. - "\x0bponentialE;\x03ⅇ\x05ists;\x03∃", - // Fcy;[Ф]. - "\x02y;\x02Ф", - // Ffr;[𝔉]. - "\x02r;\x04𝔉", - // FilledVerySmallSquare;[▪] FilledSmallSquare;[◼]. - "\x14lledVerySmallSquare;\x03▪\x10lledSmallSquare;\x03◼", - // Fouriertrf;[ℱ] ForAll;[∀] Fopf;[𝔽]. - "\x09uriertrf;\x03ℱ\x05rAll;\x03∀\x03pf;\x04𝔽", - // Fscr;[ℱ]. - "\x03cr;\x03ℱ", - // GJcy;[Ѓ]. - "\x03cy;\x02Ѓ", - // GT;[>]. - "\x01;\x01>", - // Gammad;[Ϝ] Gamma;[Γ]. - "\x05mmad;\x02Ϝ\x04mma;\x02Γ", - // Gbreve;[Ğ]. - "\x05reve;\x02Ğ", - // Gcedil;[Ģ] Gcirc;[Ĝ] Gcy;[Г]. - "\x05edil;\x02Ģ\x04irc;\x02Ĝ\x02y;\x02Г", - // Gdot;[Ġ]. - "\x03ot;\x02Ġ", - // Gfr;[𝔊]. - "\x02r;\x04𝔊", - // Gg;[⋙]. - "\x01;\x03⋙", - // Gopf;[𝔾]. - "\x03pf;\x04𝔾", - // GreaterSlantEqual;[⩾] GreaterEqualLess;[⋛] GreaterFullEqual;[≧] GreaterGreater;[⪢] GreaterEqual;[≥] GreaterTilde;[≳] GreaterLess;[≷]. - "\x10eaterSlantEqual;\x03⩾\x0featerEqualLess;\x03⋛\x0featerFullEqual;\x03≧\x0deaterGreater;\x03⪢\x0beaterEqual;\x03≥\x0beaterTilde;\x03≳\x0aeaterLess;\x03≷", - // Gscr;[𝒢]. - "\x03cr;\x04𝒢", - // Gt;[≫]. - "\x01;\x03≫", - // HARDcy;[Ъ]. - "\x05RDcy;\x02Ъ", - // Hacek;[ˇ] Hat;[^]. - "\x04cek;\x02ˇ\x02t;\x01^", - // Hcirc;[Ĥ]. - "\x04irc;\x02Ĥ", - // Hfr;[ℌ]. - "\x02r;\x03ℌ", - // HilbertSpace;[ℋ]. - "\x0blbertSpace;\x03ℋ", - // HorizontalLine;[─] Hopf;[ℍ]. - "\x0drizontalLine;\x03─\x03pf;\x03ℍ", - // Hstrok;[Ħ] Hscr;[ℋ]. - "\x05trok;\x02Ħ\x03cr;\x03ℋ", - // HumpDownHump;[≎] HumpEqual;[≏]. - "\x0bmpDownHump;\x03≎\x08mpEqual;\x03≏", - // IEcy;[Е]. - "\x03cy;\x02Е", - // IJlig;[IJ]. - "\x04lig;\x02IJ", - // IOcy;[Ё]. - "\x03cy;\x02Ё", - // Iacute;[Í] Iacute[Í]. - "\x05cute;\x02Í\x04cute\x02Í", - // Icirc;[Î] Icirc[Î] Icy;[И]. - "\x04irc;\x02Î\x03irc\x02Î\x02y;\x02И", - // Idot;[İ]. - "\x03ot;\x02İ", - // Ifr;[ℑ]. - "\x02r;\x03ℑ", - // Igrave;[Ì] Igrave[Ì]. - "\x05rave;\x02Ì\x04rave\x02Ì", - // ImaginaryI;[ⅈ] Implies;[⇒] Imacr;[Ī] Im;[ℑ]. - "\x09aginaryI;\x03ⅈ\x06plies;\x03⇒\x04acr;\x02Ī\x01;\x03ℑ", - // InvisibleComma;[⁣] InvisibleTimes;[⁢] Intersection;[⋂] Integral;[∫] Int;[∬]. - "\x0dvisibleComma;\x03⁣\x0dvisibleTimes;\x03⁢\x0btersection;\x03⋂\x07tegral;\x03∫\x02t;\x03∬", - // Iogon;[Į] Iopf;[𝕀] Iota;[Ι]. - "\x04gon;\x02Į\x03pf;\x04𝕀\x03ta;\x02Ι", - // Iscr;[ℐ]. - "\x03cr;\x03ℐ", - // Itilde;[Ĩ]. - "\x05ilde;\x02Ĩ", - // Iukcy;[І] Iuml;[Ï] Iuml[Ï]. - "\x04kcy;\x02І\x03ml;\x02Ï\x02ml\x02Ï", - // Jcirc;[Ĵ] Jcy;[Й]. - "\x04irc;\x02Ĵ\x02y;\x02Й", - // Jfr;[𝔍]. - "\x02r;\x04𝔍", - // Jopf;[𝕁]. - "\x03pf;\x04𝕁", - // Jsercy;[Ј] Jscr;[𝒥]. - "\x05ercy;\x02Ј\x03cr;\x04𝒥", - // Jukcy;[Є]. - "\x04kcy;\x02Є", - // KHcy;[Х]. - "\x03cy;\x02Х", - // KJcy;[Ќ]. - "\x03cy;\x02Ќ", - // Kappa;[Κ]. - "\x04ppa;\x02Κ", - // Kcedil;[Ķ] Kcy;[К]. - "\x05edil;\x02Ķ\x02y;\x02К", - // Kfr;[𝔎]. - "\x02r;\x04𝔎", - // Kopf;[𝕂]. - "\x03pf;\x04𝕂", - // Kscr;[𝒦]. - "\x03cr;\x04𝒦", - // LJcy;[Љ]. - "\x03cy;\x02Љ", - // LT;[<]. - "\x01;\x01<", - // Laplacetrf;[ℒ] Lacute;[Ĺ] Lambda;[Λ] Lang;[⟪] Larr;[↞]. - "\x09placetrf;\x03ℒ\x05cute;\x02Ĺ\x05mbda;\x02Λ\x03ng;\x03⟪\x03rr;\x03↞", - // Lcaron;[Ľ] Lcedil;[Ļ] Lcy;[Л]. - "\x05aron;\x02Ľ\x05edil;\x02Ļ\x02y;\x02Л", - // LeftArrowRightArrow;[⇆] LeftDoubleBracket;[⟦] LeftDownTeeVector;[⥡] LeftDownVectorBar;[⥙] LeftTriangleEqual;[⊴] LeftAngleBracket;[⟨] LeftUpDownVector;[⥑] LessEqualGreater;[⋚] LeftRightVector;[⥎] LeftTriangleBar;[⧏] LeftUpTeeVector;[⥠] LeftUpVectorBar;[⥘] LeftDownVector;[⇃] LeftRightArrow;[↔] Leftrightarrow;[⇔] LessSlantEqual;[⩽] LeftTeeVector;[⥚] LeftVectorBar;[⥒] LessFullEqual;[≦] LeftArrowBar;[⇤] LeftTeeArrow;[↤] LeftTriangle;[⊲] LeftUpVector;[↿] LeftCeiling;[⌈] LessGreater;[≶] LeftVector;[↼] LeftArrow;[←] LeftFloor;[⌊] Leftarrow;[⇐] LessTilde;[≲] LessLess;[⪡] LeftTee;[⊣]. - "\x12ftArrowRightArrow;\x03⇆\x10ftDoubleBracket;\x03⟦\x10ftDownTeeVector;\x03⥡\x10ftDownVectorBar;\x03⥙\x10ftTriangleEqual;\x03⊴\x0fftAngleBracket;\x03⟨\x0fftUpDownVector;\x03⥑\x0fssEqualGreater;\x03⋚\x0eftRightVector;\x03⥎\x0eftTriangleBar;\x03⧏\x0eftUpTeeVector;\x03⥠\x0eftUpVectorBar;\x03⥘\x0dftDownVector;\x03⇃\x0dftRightArrow;\x03↔\x0dftrightarrow;\x03⇔\x0dssSlantEqual;\x03⩽\x0cftTeeVector;\x03⥚\x0cftVectorBar;\x03⥒\x0cssFullEqual;\x03≦\x0bftArrowBar;\x03⇤\x0bftTeeArrow;\x03↤\x0bftTriangle;\x03⊲\x0bftUpVector;\x03↿\x0aftCeiling;\x03⌈\x0assGreater;\x03≶\x09ftVector;\x03↼\x08ftArrow;\x03←\x08ftFloor;\x03⌊\x08ftarrow;\x03⇐\x08ssTilde;\x03≲\x07ssLess;\x03⪡\x06ftTee;\x03⊣", - // Lfr;[𝔏]. - "\x02r;\x04𝔏", - // Lleftarrow;[⇚] Ll;[⋘]. - "\x09eftarrow;\x03⇚\x01;\x03⋘", - // Lmidot;[Ŀ]. - "\x05idot;\x02Ŀ", - // LongLeftRightArrow;[⟷] Longleftrightarrow;[⟺] LowerRightArrow;[↘] LongRightArrow;[⟶] Longrightarrow;[⟹] LowerLeftArrow;[↙] LongLeftArrow;[⟵] Longleftarrow;[⟸] Lopf;[𝕃]. - "\x11ngLeftRightArrow;\x03⟷\x11ngleftrightarrow;\x03⟺\x0ewerRightArrow;\x03↘\x0dngRightArrow;\x03⟶\x0dngrightarrow;\x03⟹\x0dwerLeftArrow;\x03↙\x0cngLeftArrow;\x03⟵\x0cngleftarrow;\x03⟸\x03pf;\x04𝕃", - // Lstrok;[Ł] Lscr;[ℒ] Lsh;[↰]. - "\x05trok;\x02Ł\x03cr;\x03ℒ\x02h;\x03↰", - // Lt;[≪]. - "\x01;\x03≪", - // Map;[⤅]. - "\x02p;\x03⤅", - // Mcy;[М]. - "\x02y;\x02М", - // MediumSpace;[ ] Mellintrf;[ℳ]. - "\x0adiumSpace;\x03 \x08llintrf;\x03ℳ", - // Mfr;[𝔐]. - "\x02r;\x04𝔐", - // MinusPlus;[∓]. - "\x08nusPlus;\x03∓", - // Mopf;[𝕄]. - "\x03pf;\x04𝕄", - // Mscr;[ℳ]. - "\x03cr;\x03ℳ", - // Mu;[Μ]. - "\x01;\x02Μ", - // NJcy;[Њ]. - "\x03cy;\x02Њ", - // Nacute;[Ń]. - "\x05cute;\x02Ń", - // Ncaron;[Ň] Ncedil;[Ņ] Ncy;[Н]. - "\x05aron;\x02Ň\x05edil;\x02Ņ\x02y;\x02Н", - // NegativeVeryThinSpace;[​] NestedGreaterGreater;[≫] NegativeMediumSpace;[​] NegativeThickSpace;[​] NegativeThinSpace;[​] NestedLessLess;[≪] NewLine;[\xa]. - "\x14gativeVeryThinSpace;\x03​\x13stedGreaterGreater;\x03≫\x12gativeMediumSpace;\x03​\x11gativeThickSpace;\x03​\x10gativeThinSpace;\x03​\x0dstedLessLess;\x03≪\x06wLine;\x01\xa", - // Nfr;[𝔑]. - "\x02r;\x04𝔑", - // NotNestedGreaterGreater;[⪢̸] NotSquareSupersetEqual;[⋣] NotPrecedesSlantEqual;[⋠] NotRightTriangleEqual;[⋭] NotSucceedsSlantEqual;[⋡] NotDoubleVerticalBar;[∦] NotGreaterSlantEqual;[⩾̸] NotLeftTriangleEqual;[⋬] NotSquareSubsetEqual;[⋢] NotGreaterFullEqual;[≧̸] NotRightTriangleBar;[⧐̸] NotLeftTriangleBar;[⧏̸] NotGreaterGreater;[≫̸] NotLessSlantEqual;[⩽̸] NotNestedLessLess;[⪡̸] NotReverseElement;[∌] NotSquareSuperset;[⊐̸] NotTildeFullEqual;[≇] NonBreakingSpace;[ ] NotPrecedesEqual;[⪯̸] NotRightTriangle;[⋫] NotSucceedsEqual;[⪰̸] NotSucceedsTilde;[≿̸] NotSupersetEqual;[⊉] NotGreaterEqual;[≱] NotGreaterTilde;[≵] NotHumpDownHump;[≎̸] NotLeftTriangle;[⋪] NotSquareSubset;[⊏̸] NotGreaterLess;[≹] NotLessGreater;[≸] NotSubsetEqual;[⊈] NotVerticalBar;[∤] NotEqualTilde;[≂̸] NotTildeEqual;[≄] NotTildeTilde;[≉] NotCongruent;[≢] NotHumpEqual;[≏̸] NotLessEqual;[≰] NotLessTilde;[≴] NotLessLess;[≪̸] NotPrecedes;[⊀] NotSucceeds;[⊁] NotSuperset;[⊃⃒] NotElement;[∉] NotGreater;[≯] NotCupCap;[≭] NotExists;[∄] NotSubset;[⊂⃒] NotEqual;[≠] NotTilde;[≁] NoBreak;[⁠] NotLess;[≮] Nopf;[ℕ] Not;[⫬]. - "\x16tNestedGreaterGreater;\x05⪢̸\x15tSquareSupersetEqual;\x03⋣\x14tPrecedesSlantEqual;\x03⋠\x14tRightTriangleEqual;\x03⋭\x14tSucceedsSlantEqual;\x03⋡\x13tDoubleVerticalBar;\x03∦\x13tGreaterSlantEqual;\x05⩾̸\x13tLeftTriangleEqual;\x03⋬\x13tSquareSubsetEqual;\x03⋢\x12tGreaterFullEqual;\x05≧̸\x12tRightTriangleBar;\x05⧐̸\x11tLeftTriangleBar;\x05⧏̸\x10tGreaterGreater;\x05≫̸\x10tLessSlantEqual;\x05⩽̸\x10tNestedLessLess;\x05⪡̸\x10tReverseElement;\x03∌\x10tSquareSuperset;\x05⊐̸\x10tTildeFullEqual;\x03≇\x0fnBreakingSpace;\x02 \x0ftPrecedesEqual;\x05⪯̸\x0ftRightTriangle;\x03⋫\x0ftSucceedsEqual;\x05⪰̸\x0ftSucceedsTilde;\x05≿̸\x0ftSupersetEqual;\x03⊉\x0etGreaterEqual;\x03≱\x0etGreaterTilde;\x03≵\x0etHumpDownHump;\x05≎̸\x0etLeftTriangle;\x03⋪\x0etSquareSubset;\x05⊏̸\x0dtGreaterLess;\x03≹\x0dtLessGreater;\x03≸\x0dtSubsetEqual;\x03⊈\x0dtVerticalBar;\x03∤\x0ctEqualTilde;\x05≂̸\x0ctTildeEqual;\x03≄\x0ctTildeTilde;\x03≉\x0btCongruent;\x03≢\x0btHumpEqual;\x05≏̸\x0btLessEqual;\x03≰\x0btLessTilde;\x03≴\x0atLessLess;\x05≪̸\x0atPrecedes;\x03⊀\x0atSucceeds;\x03⊁\x0atSuperset;\x06⊃⃒\x09tElement;\x03∉\x09tGreater;\x03≯\x08tCupCap;\x03≭\x08tExists;\x03∄\x08tSubset;\x06⊂⃒\x07tEqual;\x03≠\x07tTilde;\x03≁\x06Break;\x03⁠\x06tLess;\x03≮\x03pf;\x03ℕ\x02t;\x03⫬", - // Nscr;[𝒩]. - "\x03cr;\x04𝒩", - // Ntilde;[Ñ] Ntilde[Ñ]. - "\x05ilde;\x02Ñ\x04ilde\x02Ñ", - // Nu;[Ν]. - "\x01;\x02Ν", - // OElig;[Œ]. - "\x04lig;\x02Œ", - // Oacute;[Ó] Oacute[Ó]. - "\x05cute;\x02Ó\x04cute\x02Ó", - // Ocirc;[Ô] Ocirc[Ô] Ocy;[О]. - "\x04irc;\x02Ô\x03irc\x02Ô\x02y;\x02О", - // Odblac;[Ő]. - "\x05blac;\x02Ő", - // Ofr;[𝔒]. - "\x02r;\x04𝔒", - // Ograve;[Ò] Ograve[Ò]. - "\x05rave;\x02Ò\x04rave\x02Ò", - // Omicron;[Ο] Omacr;[Ō] Omega;[Ω]. - "\x06icron;\x02Ο\x04acr;\x02Ō\x04ega;\x02Ω", - // Oopf;[𝕆]. - "\x03pf;\x04𝕆", - // OpenCurlyDoubleQuote;[“] OpenCurlyQuote;[‘]. - "\x13enCurlyDoubleQuote;\x03“\x0denCurlyQuote;\x03‘", - // Or;[⩔]. - "\x01;\x03⩔", - // Oslash;[Ø] Oslash[Ø] Oscr;[𝒪]. - "\x05lash;\x02Ø\x04lash\x02Ø\x03cr;\x04𝒪", - // Otilde;[Õ] Otimes;[⨷] Otilde[Õ]. - "\x05ilde;\x02Õ\x05imes;\x03⨷\x04ilde\x02Õ", - // Ouml;[Ö] Ouml[Ö]. - "\x03ml;\x02Ö\x02ml\x02Ö", - // OverParenthesis;[⏜] OverBracket;[⎴] OverBrace;[⏞] OverBar;[‾]. - "\x0eerParenthesis;\x03⏜\x0aerBracket;\x03⎴\x08erBrace;\x03⏞\x06erBar;\x03‾", - // PartialD;[∂]. - "\x07rtialD;\x03∂", - // Pcy;[П]. - "\x02y;\x02П", - // Pfr;[𝔓]. - "\x02r;\x04𝔓", - // Phi;[Φ]. - "\x02i;\x02Φ", - // Pi;[Π]. - "\x01;\x02Π", - // PlusMinus;[±]. - "\x08usMinus;\x02±", - // Poincareplane;[ℌ] Popf;[ℙ]. - "\x0cincareplane;\x03ℌ\x03pf;\x03ℙ", - // PrecedesSlantEqual;[≼] PrecedesEqual;[⪯] PrecedesTilde;[≾] Proportional;[∝] Proportion;[∷] Precedes;[≺] Product;[∏] Prime;[″] Pr;[⪻]. - "\x11ecedesSlantEqual;\x03≼\x0cecedesEqual;\x03⪯\x0cecedesTilde;\x03≾\x0boportional;\x03∝\x09oportion;\x03∷\x07ecedes;\x03≺\x06oduct;\x03∏\x04ime;\x03″\x01;\x03⪻", - // Pscr;[𝒫] Psi;[Ψ]. - "\x03cr;\x04𝒫\x02i;\x02Ψ", - // QUOT;[\"] QUOT[\"]. - "\x03OT;\x01\"\x02OT\x01\"", - // Qfr;[𝔔]. - "\x02r;\x04𝔔", - // Qopf;[ℚ]. - "\x03pf;\x03ℚ", - // Qscr;[𝒬]. - "\x03cr;\x04𝒬", - // RBarr;[⤐]. - "\x04arr;\x03⤐", - // REG;[®] REG[®]. - "\x02G;\x02®\x01G\x02®", - // Racute;[Ŕ] Rarrtl;[⤖] Rang;[⟫] Rarr;[↠]. - "\x05cute;\x02Ŕ\x05rrtl;\x03⤖\x03ng;\x03⟫\x03rr;\x03↠", - // Rcaron;[Ř] Rcedil;[Ŗ] Rcy;[Р]. - "\x05aron;\x02Ř\x05edil;\x02Ŗ\x02y;\x02Р", - // ReverseUpEquilibrium;[⥯] ReverseEquilibrium;[⇋] ReverseElement;[∋] Re;[ℜ]. - "\x13verseUpEquilibrium;\x03⥯\x11verseEquilibrium;\x03⇋\x0dverseElement;\x03∋\x01;\x03ℜ", - // Rfr;[ℜ]. - "\x02r;\x03ℜ", - // Rho;[Ρ]. - "\x02o;\x02Ρ", - // RightArrowLeftArrow;[⇄] RightDoubleBracket;[⟧] RightDownTeeVector;[⥝] RightDownVectorBar;[⥕] RightTriangleEqual;[⊵] RightAngleBracket;[⟩] RightUpDownVector;[⥏] RightTriangleBar;[⧐] RightUpTeeVector;[⥜] RightUpVectorBar;[⥔] RightDownVector;[⇂] RightTeeVector;[⥛] RightVectorBar;[⥓] RightArrowBar;[⇥] RightTeeArrow;[↦] RightTriangle;[⊳] RightUpVector;[↾] RightCeiling;[⌉] RightVector;[⇀] RightArrow;[→] RightFloor;[⌋] Rightarrow;[⇒] RightTee;[⊢]. - "\x12ghtArrowLeftArrow;\x03⇄\x11ghtDoubleBracket;\x03⟧\x11ghtDownTeeVector;\x03⥝\x11ghtDownVectorBar;\x03⥕\x11ghtTriangleEqual;\x03⊵\x10ghtAngleBracket;\x03⟩\x10ghtUpDownVector;\x03⥏\x0fghtTriangleBar;\x03⧐\x0fghtUpTeeVector;\x03⥜\x0fghtUpVectorBar;\x03⥔\x0eghtDownVector;\x03⇂\x0dghtTeeVector;\x03⥛\x0dghtVectorBar;\x03⥓\x0cghtArrowBar;\x03⇥\x0cghtTeeArrow;\x03↦\x0cghtTriangle;\x03⊳\x0cghtUpVector;\x03↾\x0bghtCeiling;\x03⌉\x0aghtVector;\x03⇀\x09ghtArrow;\x03→\x09ghtFloor;\x03⌋\x09ghtarrow;\x03⇒\x07ghtTee;\x03⊢", - // RoundImplies;[⥰] Ropf;[ℝ]. - "\x0bundImplies;\x03⥰\x03pf;\x03ℝ", - // Rrightarrow;[⇛]. - "\x0aightarrow;\x03⇛", - // Rscr;[ℛ] Rsh;[↱]. - "\x03cr;\x03ℛ\x02h;\x03↱", - // RuleDelayed;[⧴]. - "\x0aleDelayed;\x03⧴", - // SHCHcy;[Щ] SHcy;[Ш]. - "\x05CHcy;\x02Щ\x03cy;\x02Ш", - // SOFTcy;[Ь]. - "\x05FTcy;\x02Ь", - // Sacute;[Ś]. - "\x05cute;\x02Ś", - // Scaron;[Š] Scedil;[Ş] Scirc;[Ŝ] Scy;[С] Sc;[⪼]. - "\x05aron;\x02Š\x05edil;\x02Ş\x04irc;\x02Ŝ\x02y;\x02С\x01;\x03⪼", - // Sfr;[𝔖]. - "\x02r;\x04𝔖", - // ShortRightArrow;[→] ShortDownArrow;[↓] ShortLeftArrow;[←] ShortUpArrow;[↑]. - "\x0eortRightArrow;\x03→\x0dortDownArrow;\x03↓\x0dortLeftArrow;\x03←\x0bortUpArrow;\x03↑", - // Sigma;[Σ]. - "\x04gma;\x02Σ", - // SmallCircle;[∘]. - "\x0aallCircle;\x03∘", - // Sopf;[𝕊]. - "\x03pf;\x04𝕊", - // SquareSupersetEqual;[⊒] SquareIntersection;[⊓] SquareSubsetEqual;[⊑] SquareSuperset;[⊐] SquareSubset;[⊏] SquareUnion;[⊔] Square;[□] Sqrt;[√]. - "\x12uareSupersetEqual;\x03⊒\x11uareIntersection;\x03⊓\x10uareSubsetEqual;\x03⊑\x0duareSuperset;\x03⊐\x0buareSubset;\x03⊏\x0auareUnion;\x03⊔\x05uare;\x03□\x03rt;\x03√", - // Sscr;[𝒮]. - "\x03cr;\x04𝒮", - // Star;[⋆]. - "\x03ar;\x03⋆", - // SucceedsSlantEqual;[≽] SucceedsEqual;[⪰] SucceedsTilde;[≿] SupersetEqual;[⊇] SubsetEqual;[⊆] Succeeds;[≻] SuchThat;[∋] Superset;[⊃] Subset;[⋐] Supset;[⋑] Sub;[⋐] Sum;[∑] Sup;[⋑]. - "\x11cceedsSlantEqual;\x03≽\x0ccceedsEqual;\x03⪰\x0ccceedsTilde;\x03≿\x0cpersetEqual;\x03⊇\x0absetEqual;\x03⊆\x07cceeds;\x03≻\x07chThat;\x03∋\x07perset;\x03⊃\x05bset;\x03⋐\x05pset;\x03⋑\x02b;\x03⋐\x02m;\x03∑\x02p;\x03⋑", - // THORN;[Þ] THORN[Þ]. - "\x04ORN;\x02Þ\x03ORN\x02Þ", - // TRADE;[™]. - "\x04ADE;\x03™", - // TSHcy;[Ћ] TScy;[Ц]. - "\x04Hcy;\x02Ћ\x03cy;\x02Ц", - // Tab;[\x9] Tau;[Τ]. - "\x02b;\x01\x9\x02u;\x02Τ", - // Tcaron;[Ť] Tcedil;[Ţ] Tcy;[Т]. - "\x05aron;\x02Ť\x05edil;\x02Ţ\x02y;\x02Т", - // Tfr;[𝔗]. - "\x02r;\x04𝔗", - // ThickSpace;[  ] Therefore;[∴] ThinSpace;[ ] Theta;[Θ]. - "\x09ickSpace;\x06  \x08erefore;\x03∴\x08inSpace;\x03 \x04eta;\x02Θ", - // TildeFullEqual;[≅] TildeEqual;[≃] TildeTilde;[≈] Tilde;[∼]. - "\x0dldeFullEqual;\x03≅\x09ldeEqual;\x03≃\x09ldeTilde;\x03≈\x04lde;\x03∼", - // Topf;[𝕋]. - "\x03pf;\x04𝕋", - // TripleDot;[⃛]. - "\x08ipleDot;\x03⃛", - // Tstrok;[Ŧ] Tscr;[𝒯]. - "\x05trok;\x02Ŧ\x03cr;\x04𝒯", - // Uarrocir;[⥉] Uacute;[Ú] Uacute[Ú] Uarr;[↟]. - "\x07rrocir;\x03⥉\x05cute;\x02Ú\x04cute\x02Ú\x03rr;\x03↟", - // Ubreve;[Ŭ] Ubrcy;[Ў]. - "\x05reve;\x02Ŭ\x04rcy;\x02Ў", - // Ucirc;[Û] Ucirc[Û] Ucy;[У]. - "\x04irc;\x02Û\x03irc\x02Û\x02y;\x02У", - // Udblac;[Ű]. - "\x05blac;\x02Ű", - // Ufr;[𝔘]. - "\x02r;\x04𝔘", - // Ugrave;[Ù] Ugrave[Ù]. - "\x05rave;\x02Ù\x04rave\x02Ù", - // Umacr;[Ū]. - "\x04acr;\x02Ū", - // UnderParenthesis;[⏝] UnderBracket;[⎵] UnderBrace;[⏟] UnionPlus;[⊎] UnderBar;[_] Union;[⋃]. - "\x0fderParenthesis;\x03⏝\x0bderBracket;\x03⎵\x09derBrace;\x03⏟\x08ionPlus;\x03⊎\x07derBar;\x01_\x04ion;\x03⋃", - // Uogon;[Ų] Uopf;[𝕌]. - "\x04gon;\x02Ų\x03pf;\x04𝕌", - // UpArrowDownArrow;[⇅] UpperRightArrow;[↗] UpperLeftArrow;[↖] UpEquilibrium;[⥮] UpDownArrow;[↕] Updownarrow;[⇕] UpArrowBar;[⤒] UpTeeArrow;[↥] UpArrow;[↑] Uparrow;[⇑] Upsilon;[Υ] UpTee;[⊥] Upsi;[ϒ]. - "\x0fArrowDownArrow;\x03⇅\x0eperRightArrow;\x03↗\x0dperLeftArrow;\x03↖\x0cEquilibrium;\x03⥮\x0aDownArrow;\x03↕\x0adownarrow;\x03⇕\x09ArrowBar;\x03⤒\x09TeeArrow;\x03↥\x06Arrow;\x03↑\x06arrow;\x03⇑\x06silon;\x02Υ\x04Tee;\x03⊥\x03si;\x02ϒ", - // Uring;[Ů]. - "\x04ing;\x02Ů", - // Uscr;[𝒰]. - "\x03cr;\x04𝒰", - // Utilde;[Ũ]. - "\x05ilde;\x02Ũ", - // Uuml;[Ü] Uuml[Ü]. - "\x03ml;\x02Ü\x02ml\x02Ü", - // VDash;[⊫]. - "\x04ash;\x03⊫", - // Vbar;[⫫]. - "\x03ar;\x03⫫", - // Vcy;[В]. - "\x02y;\x02В", - // Vdashl;[⫦] Vdash;[⊩]. - "\x05ashl;\x03⫦\x04ash;\x03⊩", - // VerticalSeparator;[❘] VerticalTilde;[≀] VeryThinSpace;[ ] VerticalLine;[|] VerticalBar;[∣] Verbar;[‖] Vert;[‖] Vee;[⋁]. - "\x10rticalSeparator;\x03❘\x0crticalTilde;\x03≀\x0cryThinSpace;\x03 \x0brticalLine;\x01|\x0articalBar;\x03∣\x05rbar;\x03‖\x03rt;\x03‖\x02e;\x03⋁", - // Vfr;[𝔙]. - "\x02r;\x04𝔙", - // Vopf;[𝕍]. - "\x03pf;\x04𝕍", - // Vscr;[𝒱]. - "\x03cr;\x04𝒱", - // Vvdash;[⊪]. - "\x05dash;\x03⊪", - // Wcirc;[Ŵ]. - "\x04irc;\x02Ŵ", - // Wedge;[⋀]. - "\x04dge;\x03⋀", - // Wfr;[𝔚]. - "\x02r;\x04𝔚", - // Wopf;[𝕎]. - "\x03pf;\x04𝕎", - // Wscr;[𝒲]. - "\x03cr;\x04𝒲", - // Xfr;[𝔛]. - "\x02r;\x04𝔛", - // Xi;[Ξ]. - "\x01;\x02Ξ", - // Xopf;[𝕏]. - "\x03pf;\x04𝕏", - // Xscr;[𝒳]. - "\x03cr;\x04𝒳", - // YAcy;[Я]. - "\x03cy;\x02Я", - // YIcy;[Ї]. - "\x03cy;\x02Ї", - // YUcy;[Ю]. - "\x03cy;\x02Ю", - // Yacute;[Ý] Yacute[Ý]. - "\x05cute;\x02Ý\x04cute\x02Ý", - // Ycirc;[Ŷ] Ycy;[Ы]. - "\x04irc;\x02Ŷ\x02y;\x02Ы", - // Yfr;[𝔜]. - "\x02r;\x04𝔜", - // Yopf;[𝕐]. - "\x03pf;\x04𝕐", - // Yscr;[𝒴]. - "\x03cr;\x04𝒴", - // Yuml;[Ÿ]. - "\x03ml;\x02Ÿ", - // ZHcy;[Ж]. - "\x03cy;\x02Ж", - // Zacute;[Ź]. - "\x05cute;\x02Ź", - // Zcaron;[Ž] Zcy;[З]. - "\x05aron;\x02Ž\x02y;\x02З", - // Zdot;[Ż]. - "\x03ot;\x02Ż", - // ZeroWidthSpace;[​] Zeta;[Ζ]. - "\x0droWidthSpace;\x03​\x03ta;\x02Ζ", - // Zfr;[ℨ]. - "\x02r;\x03ℨ", - // Zopf;[ℤ]. - "\x03pf;\x03ℤ", - // Zscr;[𝒵]. - "\x03cr;\x04𝒵", - // aacute;[á] aacute[á]. - "\x05cute;\x02á\x04cute\x02á", - // abreve;[ă]. - "\x05reve;\x02ă", - // acirc;[â] acute;[´] acirc[â] acute[´] acE;[∾̳] acd;[∿] acy;[а] ac;[∾]. - "\x04irc;\x02â\x04ute;\x02´\x03irc\x02â\x03ute\x02´\x02E;\x05∾̳\x02d;\x03∿\x02y;\x02а\x01;\x03∾", - // aelig;[æ] aelig[æ]. - "\x04lig;\x02æ\x03lig\x02æ", - // afr;[𝔞] af;[⁡]. - "\x02r;\x04𝔞\x01;\x03⁡", - // agrave;[à] agrave[à]. - "\x05rave;\x02à\x04rave\x02à", - // alefsym;[ℵ] aleph;[ℵ] alpha;[α]. - "\x06efsym;\x03ℵ\x04eph;\x03ℵ\x04pha;\x02α", - // amacr;[ā] amalg;[⨿] amp;[&] amp[&]. - "\x04acr;\x02ā\x04alg;\x03⨿\x02p;\x01&\x01p\x01&", - // andslope;[⩘] angmsdaa;[⦨] angmsdab;[⦩] angmsdac;[⦪] angmsdad;[⦫] angmsdae;[⦬] angmsdaf;[⦭] angmsdag;[⦮] angmsdah;[⦯] angrtvbd;[⦝] angrtvb;[⊾] angzarr;[⍼] andand;[⩕] angmsd;[∡] angsph;[∢] angle;[∠] angrt;[∟] angst;[Å] andd;[⩜] andv;[⩚] ange;[⦤] and;[∧] ang;[∠]. - "\x07dslope;\x03⩘\x07gmsdaa;\x03⦨\x07gmsdab;\x03⦩\x07gmsdac;\x03⦪\x07gmsdad;\x03⦫\x07gmsdae;\x03⦬\x07gmsdaf;\x03⦭\x07gmsdag;\x03⦮\x07gmsdah;\x03⦯\x07grtvbd;\x03⦝\x06grtvb;\x03⊾\x06gzarr;\x03⍼\x05dand;\x03⩕\x05gmsd;\x03∡\x05gsph;\x03∢\x04gle;\x03∠\x04grt;\x03∟\x04gst;\x02Å\x03dd;\x03⩜\x03dv;\x03⩚\x03ge;\x03⦤\x02d;\x03∧\x02g;\x03∠", - // aogon;[ą] aopf;[𝕒]. - "\x04gon;\x02ą\x03pf;\x04𝕒", - // approxeq;[≊] apacir;[⩯] approx;[≈] apid;[≋] apos;['] apE;[⩰] ape;[≊] ap;[≈]. - "\x07proxeq;\x03≊\x05acir;\x03⩯\x05prox;\x03≈\x03id;\x03≋\x03os;\x01'\x02E;\x03⩰\x02e;\x03≊\x01;\x03≈", - // aring;[å] aring[å]. - "\x04ing;\x02å\x03ing\x02å", - // asympeq;[≍] asymp;[≈] ascr;[𝒶] ast;[*]. - "\x06ympeq;\x03≍\x04ymp;\x03≈\x03cr;\x04𝒶\x02t;\x01*", - // atilde;[ã] atilde[ã]. - "\x05ilde;\x02ã\x04ilde\x02ã", - // auml;[ä] auml[ä]. - "\x03ml;\x02ä\x02ml\x02ä", - // awconint;[∳] awint;[⨑]. - "\x07conint;\x03∳\x04int;\x03⨑", - // bNot;[⫭]. - "\x03ot;\x03⫭", - // backepsilon;[϶] backprime;[‵] backsimeq;[⋍] backcong;[≌] barwedge;[⌅] backsim;[∽] barvee;[⊽] barwed;[⌅]. - "\x0ackepsilon;\x02϶\x08ckprime;\x03‵\x08cksimeq;\x03⋍\x07ckcong;\x03≌\x07rwedge;\x03⌅\x06cksim;\x03∽\x05rvee;\x03⊽\x05rwed;\x03⌅", - // bbrktbrk;[⎶] bbrk;[⎵]. - "\x07rktbrk;\x03⎶\x03rk;\x03⎵", - // bcong;[≌] bcy;[б]. - "\x04ong;\x03≌\x02y;\x02б", - // bdquo;[„]. - "\x04quo;\x03„", - // because;[∵] bemptyv;[⦰] between;[≬] becaus;[∵] bernou;[ℬ] bepsi;[϶] beta;[β] beth;[ℶ]. - "\x06cause;\x03∵\x06mptyv;\x03⦰\x06tween;\x03≬\x05caus;\x03∵\x05rnou;\x03ℬ\x04psi;\x02϶\x03ta;\x02β\x03th;\x03ℶ", - // bfr;[𝔟]. - "\x02r;\x04𝔟", - // bigtriangledown;[▽] bigtriangleup;[△] bigotimes;[⨂] bigoplus;[⨁] bigsqcup;[⨆] biguplus;[⨄] bigwedge;[⋀] bigcirc;[◯] bigodot;[⨀] bigstar;[★] bigcap;[⋂] bigcup;[⋃] bigvee;[⋁]. - "\x0egtriangledown;\x03▽\x0cgtriangleup;\x03△\x08gotimes;\x03⨂\x07goplus;\x03⨁\x07gsqcup;\x03⨆\x07guplus;\x03⨄\x07gwedge;\x03⋀\x06gcirc;\x03◯\x06godot;\x03⨀\x06gstar;\x03★\x05gcap;\x03⋂\x05gcup;\x03⋃\x05gvee;\x03⋁", - // bkarow;[⤍]. - "\x05arow;\x03⤍", - // blacktriangleright;[▸] blacktriangledown;[▾] blacktriangleleft;[◂] blacktriangle;[▴] blacklozenge;[⧫] blacksquare;[▪] blank;[␣] blk12;[▒] blk14;[░] blk34;[▓] block;[█]. - "\x11acktriangleright;\x03▸\x10acktriangledown;\x03▾\x10acktriangleleft;\x03◂\x0cacktriangle;\x03▴\x0backlozenge;\x03⧫\x0aacksquare;\x03▪\x04ank;\x03␣\x04k12;\x03▒\x04k14;\x03░\x04k34;\x03▓\x04ock;\x03█", - // bnequiv;[≡⃥] bnot;[⌐] bne;[=⃥]. - "\x06equiv;\x06≡⃥\x03ot;\x03⌐\x02e;\x04=⃥", - // boxminus;[⊟] boxtimes;[⊠] boxplus;[⊞] bottom;[⊥] bowtie;[⋈] boxbox;[⧉] boxDL;[╗] boxDR;[╔] boxDl;[╖] boxDr;[╓] boxHD;[╦] boxHU;[╩] boxHd;[╤] boxHu;[╧] boxUL;[╝] boxUR;[╚] boxUl;[╜] boxUr;[╙] boxVH;[╬] boxVL;[╣] boxVR;[╠] boxVh;[╫] boxVl;[╢] boxVr;[╟] boxdL;[╕] boxdR;[╒] boxdl;[┐] boxdr;[┌] boxhD;[╥] boxhU;[╨] boxhd;[┬] boxhu;[┴] boxuL;[╛] boxuR;[╘] boxul;[┘] boxur;[└] boxvH;[╪] boxvL;[╡] boxvR;[╞] boxvh;[┼] boxvl;[┤] boxvr;[├] bopf;[𝕓] boxH;[═] boxV;[║] boxh;[─] boxv;[│] bot;[⊥]. - "\x07xminus;\x03⊟\x07xtimes;\x03⊠\x06xplus;\x03⊞\x05ttom;\x03⊥\x05wtie;\x03⋈\x05xbox;\x03⧉\x04xDL;\x03╗\x04xDR;\x03╔\x04xDl;\x03╖\x04xDr;\x03╓\x04xHD;\x03╦\x04xHU;\x03╩\x04xHd;\x03╤\x04xHu;\x03╧\x04xUL;\x03╝\x04xUR;\x03╚\x04xUl;\x03╜\x04xUr;\x03╙\x04xVH;\x03╬\x04xVL;\x03╣\x04xVR;\x03╠\x04xVh;\x03╫\x04xVl;\x03╢\x04xVr;\x03╟\x04xdL;\x03╕\x04xdR;\x03╒\x04xdl;\x03┐\x04xdr;\x03┌\x04xhD;\x03╥\x04xhU;\x03╨\x04xhd;\x03┬\x04xhu;\x03┴\x04xuL;\x03╛\x04xuR;\x03╘\x04xul;\x03┘\x04xur;\x03└\x04xvH;\x03╪\x04xvL;\x03╡\x04xvR;\x03╞\x04xvh;\x03┼\x04xvl;\x03┤\x04xvr;\x03├\x03pf;\x04𝕓\x03xH;\x03═\x03xV;\x03║\x03xh;\x03─\x03xv;\x03│\x02t;\x03⊥", - // bprime;[‵]. - "\x05rime;\x03‵", - // brvbar;[¦] breve;[˘] brvbar[¦]. - "\x05vbar;\x02¦\x04eve;\x02˘\x04vbar\x02¦", - // bsolhsub;[⟈] bsemi;[⁏] bsime;[⋍] bsolb;[⧅] bscr;[𝒷] bsim;[∽] bsol;[\\]. - "\x07olhsub;\x03⟈\x04emi;\x03⁏\x04ime;\x03⋍\x04olb;\x03⧅\x03cr;\x04𝒷\x03im;\x03∽\x03ol;\x01\\", - // bullet;[•] bumpeq;[≏] bumpE;[⪮] bumpe;[≏] bull;[•] bump;[≎]. - "\x05llet;\x03•\x05mpeq;\x03≏\x04mpE;\x03⪮\x04mpe;\x03≏\x03ll;\x03•\x03mp;\x03≎", - // capbrcup;[⩉] cacute;[ć] capand;[⩄] capcap;[⩋] capcup;[⩇] capdot;[⩀] caret;[⁁] caron;[ˇ] caps;[∩︀] cap;[∩]. - "\x07pbrcup;\x03⩉\x05cute;\x02ć\x05pand;\x03⩄\x05pcap;\x03⩋\x05pcup;\x03⩇\x05pdot;\x03⩀\x04ret;\x03⁁\x04ron;\x02ˇ\x03ps;\x06∩︀\x02p;\x03∩", - // ccupssm;[⩐] ccaron;[č] ccedil;[ç] ccaps;[⩍] ccedil[ç] ccirc;[ĉ] ccups;[⩌]. - "\x06upssm;\x03⩐\x05aron;\x02č\x05edil;\x02ç\x04aps;\x03⩍\x04edil\x02ç\x04irc;\x02ĉ\x04ups;\x03⩌", - // cdot;[ċ]. - "\x03ot;\x02ċ", - // centerdot;[·] cemptyv;[⦲] cedil;[¸] cedil[¸] cent;[¢] cent[¢]. - "\x08nterdot;\x02·\x06mptyv;\x03⦲\x04dil;\x02¸\x03dil\x02¸\x03nt;\x02¢\x02nt\x02¢", - // cfr;[𝔠]. - "\x02r;\x04𝔠", - // checkmark;[✓] check;[✓] chcy;[ч] chi;[χ]. - "\x08eckmark;\x03✓\x04eck;\x03✓\x03cy;\x02ч\x02i;\x02χ", - // circlearrowright;[↻] circlearrowleft;[↺] circledcirc;[⊚] circleddash;[⊝] circledast;[⊛] circledR;[®] circledS;[Ⓢ] cirfnint;[⨐] cirscir;[⧂] circeq;[≗] cirmid;[⫯] cirE;[⧃] circ;[ˆ] cire;[≗] cir;[○]. - "\x0frclearrowright;\x03↻\x0erclearrowleft;\x03↺\x0arcledcirc;\x03⊚\x0arcleddash;\x03⊝\x09rcledast;\x03⊛\x07rcledR;\x02®\x07rcledS;\x03Ⓢ\x07rfnint;\x03⨐\x06rscir;\x03⧂\x05rceq;\x03≗\x05rmid;\x03⫯\x03rE;\x03⧃\x03rc;\x02ˆ\x03re;\x03≗\x02r;\x03○", - // clubsuit;[♣] clubs;[♣]. - "\x07ubsuit;\x03♣\x04ubs;\x03♣", - // complement;[∁] complexes;[ℂ] coloneq;[≔] congdot;[⩭] colone;[≔] commat;[@] compfn;[∘] conint;[∮] coprod;[∐] copysr;[℗] colon;[:] comma;[,] comp;[∁] cong;[≅] copf;[𝕔] copy;[©] copy[©]. - "\x09mplement;\x03∁\x08mplexes;\x03ℂ\x06loneq;\x03≔\x06ngdot;\x03⩭\x05lone;\x03≔\x05mmat;\x01@\x05mpfn;\x03∘\x05nint;\x03∮\x05prod;\x03∐\x05pysr;\x03℗\x04lon;\x01:\x04mma;\x01,\x03mp;\x03∁\x03ng;\x03≅\x03pf;\x04𝕔\x03py;\x02©\x02py\x02©", - // crarr;[↵] cross;[✗]. - "\x04arr;\x03↵\x04oss;\x03✗", - // csube;[⫑] csupe;[⫒] cscr;[𝒸] csub;[⫏] csup;[⫐]. - "\x04ube;\x03⫑\x04upe;\x03⫒\x03cr;\x04𝒸\x03ub;\x03⫏\x03up;\x03⫐", - // ctdot;[⋯]. - "\x04dot;\x03⋯", - // curvearrowright;[↷] curvearrowleft;[↶] curlyeqprec;[⋞] curlyeqsucc;[⋟] curlywedge;[⋏] cupbrcap;[⩈] curlyvee;[⋎] cudarrl;[⤸] cudarrr;[⤵] cularrp;[⤽] curarrm;[⤼] cularr;[↶] cupcap;[⩆] cupcup;[⩊] cupdot;[⊍] curarr;[↷] curren;[¤] cuepr;[⋞] cuesc;[⋟] cupor;[⩅] curren[¤] cuvee;[⋎] cuwed;[⋏] cups;[∪︀] cup;[∪]. - "\x0ervearrowright;\x03↷\x0drvearrowleft;\x03↶\x0arlyeqprec;\x03⋞\x0arlyeqsucc;\x03⋟\x09rlywedge;\x03⋏\x07pbrcap;\x03⩈\x07rlyvee;\x03⋎\x06darrl;\x03⤸\x06darrr;\x03⤵\x06larrp;\x03⤽\x06rarrm;\x03⤼\x05larr;\x03↶\x05pcap;\x03⩆\x05pcup;\x03⩊\x05pdot;\x03⊍\x05rarr;\x03↷\x05rren;\x02¤\x04epr;\x03⋞\x04esc;\x03⋟\x04por;\x03⩅\x04rren\x02¤\x04vee;\x03⋎\x04wed;\x03⋏\x03ps;\x06∪︀\x02p;\x03∪", - // cwconint;[∲] cwint;[∱]. - "\x07conint;\x03∲\x04int;\x03∱", - // cylcty;[⌭]. - "\x05lcty;\x03⌭", - // dArr;[⇓]. - "\x03rr;\x03⇓", - // dHar;[⥥]. - "\x03ar;\x03⥥", - // dagger;[†] daleth;[ℸ] dashv;[⊣] darr;[↓] dash;[‐]. - "\x05gger;\x03†\x05leth;\x03ℸ\x04shv;\x03⊣\x03rr;\x03↓\x03sh;\x03‐", - // dbkarow;[⤏] dblac;[˝]. - "\x06karow;\x03⤏\x04lac;\x02˝", - // dcaron;[ď] dcy;[д]. - "\x05aron;\x02ď\x02y;\x02д", - // ddagger;[‡] ddotseq;[⩷] ddarr;[⇊] dd;[ⅆ]. - "\x06agger;\x03‡\x06otseq;\x03⩷\x04arr;\x03⇊\x01;\x03ⅆ", - // demptyv;[⦱] delta;[δ] deg;[°] deg[°]. - "\x06mptyv;\x03⦱\x04lta;\x02δ\x02g;\x02°\x01g\x02°", - // dfisht;[⥿] dfr;[𝔡]. - "\x05isht;\x03⥿\x02r;\x04𝔡", - // dharl;[⇃] dharr;[⇂]. - "\x04arl;\x03⇃\x04arr;\x03⇂", - // divideontimes;[⋇] diamondsuit;[♦] diamond;[⋄] digamma;[ϝ] divide;[÷] divonx;[⋇] diams;[♦] disin;[⋲] divide[÷] diam;[⋄] die;[¨] div;[÷]. - "\x0cvideontimes;\x03⋇\x0aamondsuit;\x03♦\x06amond;\x03⋄\x06gamma;\x02ϝ\x05vide;\x02÷\x05vonx;\x03⋇\x04ams;\x03♦\x04sin;\x03⋲\x04vide\x02÷\x03am;\x03⋄\x02e;\x02¨\x02v;\x02÷", - // djcy;[ђ]. - "\x03cy;\x02ђ", - // dlcorn;[⌞] dlcrop;[⌍]. - "\x05corn;\x03⌞\x05crop;\x03⌍", - // downharpoonright;[⇂] downharpoonleft;[⇃] doublebarwedge;[⌆] downdownarrows;[⇊] dotsquare;[⊡] downarrow;[↓] doteqdot;[≑] dotminus;[∸] dotplus;[∔] dollar;[$] doteq;[≐] dopf;[𝕕] dot;[˙]. - "\x0fwnharpoonright;\x03⇂\x0ewnharpoonleft;\x03⇃\x0dublebarwedge;\x03⌆\x0dwndownarrows;\x03⇊\x08tsquare;\x03⊡\x08wnarrow;\x03↓\x07teqdot;\x03≑\x07tminus;\x03∸\x06tplus;\x03∔\x05llar;\x01$\x04teq;\x03≐\x03pf;\x04𝕕\x02t;\x02˙", - // drbkarow;[⤐] drcorn;[⌟] drcrop;[⌌]. - "\x07bkarow;\x03⤐\x05corn;\x03⌟\x05crop;\x03⌌", - // dstrok;[đ] dscr;[𝒹] dscy;[ѕ] dsol;[⧶]. - "\x05trok;\x02đ\x03cr;\x04𝒹\x03cy;\x02ѕ\x03ol;\x03⧶", - // dtdot;[⋱] dtrif;[▾] dtri;[▿]. - "\x04dot;\x03⋱\x04rif;\x03▾\x03ri;\x03▿", - // duarr;[⇵] duhar;[⥯]. - "\x04arr;\x03⇵\x04har;\x03⥯", - // dwangle;[⦦]. - "\x06angle;\x03⦦", - // dzigrarr;[⟿] dzcy;[џ]. - "\x07igrarr;\x03⟿\x03cy;\x02џ", - // eDDot;[⩷] eDot;[≑]. - "\x04Dot;\x03⩷\x03ot;\x03≑", - // eacute;[é] easter;[⩮] eacute[é]. - "\x05cute;\x02é\x05ster;\x03⩮\x04cute\x02é", - // ecaron;[ě] ecolon;[≕] ecirc;[ê] ecir;[≖] ecirc[ê] ecy;[э]. - "\x05aron;\x02ě\x05olon;\x03≕\x04irc;\x02ê\x03ir;\x03≖\x03irc\x02ê\x02y;\x02э", - // edot;[ė]. - "\x03ot;\x02ė", - // ee;[ⅇ]. - "\x01;\x03ⅇ", - // efDot;[≒] efr;[𝔢]. - "\x04Dot;\x03≒\x02r;\x04𝔢", - // egrave;[è] egsdot;[⪘] egrave[è] egs;[⪖] eg;[⪚]. - "\x05rave;\x02è\x05sdot;\x03⪘\x04rave\x02è\x02s;\x03⪖\x01;\x03⪚", - // elinters;[⏧] elsdot;[⪗] ell;[ℓ] els;[⪕] el;[⪙]. - "\x07inters;\x03⏧\x05sdot;\x03⪗\x02l;\x03ℓ\x02s;\x03⪕\x01;\x03⪙", - // emptyset;[∅] emptyv;[∅] emsp13;[ ] emsp14;[ ] emacr;[ē] empty;[∅] emsp;[ ]. - "\x07ptyset;\x03∅\x05ptyv;\x03∅\x05sp13;\x03 \x05sp14;\x03 \x04acr;\x02ē\x04pty;\x03∅\x03sp;\x03 ", - // ensp;[ ] eng;[ŋ]. - "\x03sp;\x03 \x02g;\x02ŋ", - // eogon;[ę] eopf;[𝕖]. - "\x04gon;\x02ę\x03pf;\x04𝕖", - // epsilon;[ε] eparsl;[⧣] eplus;[⩱] epsiv;[ϵ] epar;[⋕] epsi;[ε]. - "\x06silon;\x02ε\x05arsl;\x03⧣\x04lus;\x03⩱\x04siv;\x02ϵ\x03ar;\x03⋕\x03si;\x02ε", - // eqslantless;[⪕] eqslantgtr;[⪖] eqvparsl;[⧥] eqcolon;[≕] equivDD;[⩸] eqcirc;[≖] equals;[=] equest;[≟] eqsim;[≂] equiv;[≡]. - "\x0aslantless;\x03⪕\x09slantgtr;\x03⪖\x07vparsl;\x03⧥\x06colon;\x03≕\x06uivDD;\x03⩸\x05circ;\x03≖\x05uals;\x01=\x05uest;\x03≟\x04sim;\x03≂\x04uiv;\x03≡", - // erDot;[≓] erarr;[⥱]. - "\x04Dot;\x03≓\x04arr;\x03⥱", - // esdot;[≐] escr;[ℯ] esim;[≂]. - "\x04dot;\x03≐\x03cr;\x03ℯ\x03im;\x03≂", - // eta;[η] eth;[ð] eth[ð]. - "\x02a;\x02η\x02h;\x02ð\x01h\x02ð", - // euml;[ë] euro;[€] euml[ë]. - "\x03ml;\x02ë\x03ro;\x03€\x02ml\x02ë", - // exponentiale;[ⅇ] expectation;[ℰ] exist;[∃] excl;[!]. - "\x0bponentiale;\x03ⅇ\x0apectation;\x03ℰ\x04ist;\x03∃\x03cl;\x01!", - // fallingdotseq;[≒]. - "\x0cllingdotseq;\x03≒", - // fcy;[ф]. - "\x02y;\x02ф", - // female;[♀]. - "\x05male;\x03♀", - // ffilig;[ffi] ffllig;[ffl] fflig;[ff] ffr;[𝔣]. - "\x05ilig;\x03ffi\x05llig;\x03ffl\x04lig;\x03ff\x02r;\x04𝔣", - // filig;[fi]. - "\x04lig;\x03fi", - // fjlig;[fj]. - "\x04lig;\x02fj", - // fllig;[fl] fltns;[▱] flat;[♭]. - "\x04lig;\x03fl\x04tns;\x03▱\x03at;\x03♭", - // fnof;[ƒ]. - "\x03of;\x02ƒ", - // forall;[∀] forkv;[⫙] fopf;[𝕗] fork;[⋔]. - "\x05rall;\x03∀\x04rkv;\x03⫙\x03pf;\x04𝕗\x03rk;\x03⋔", - // fpartint;[⨍]. - "\x07artint;\x03⨍", - // frac12;[½] frac13;[⅓] frac14;[¼] frac15;[⅕] frac16;[⅙] frac18;[⅛] frac23;[⅔] frac25;[⅖] frac34;[¾] frac35;[⅗] frac38;[⅜] frac45;[⅘] frac56;[⅚] frac58;[⅝] frac78;[⅞] frac12[½] frac14[¼] frac34[¾] frasl;[⁄] frown;[⌢]. - "\x05ac12;\x02½\x05ac13;\x03⅓\x05ac14;\x02¼\x05ac15;\x03⅕\x05ac16;\x03⅙\x05ac18;\x03⅛\x05ac23;\x03⅔\x05ac25;\x03⅖\x05ac34;\x02¾\x05ac35;\x03⅗\x05ac38;\x03⅜\x05ac45;\x03⅘\x05ac56;\x03⅚\x05ac58;\x03⅝\x05ac78;\x03⅞\x04ac12\x02½\x04ac14\x02¼\x04ac34\x02¾\x04asl;\x03⁄\x04own;\x03⌢", - // fscr;[𝒻]. - "\x03cr;\x04𝒻", - // gEl;[⪌] gE;[≧]. - "\x02l;\x03⪌\x01;\x03≧", - // gacute;[ǵ] gammad;[ϝ] gamma;[γ] gap;[⪆]. - "\x05cute;\x02ǵ\x05mmad;\x02ϝ\x04mma;\x02γ\x02p;\x03⪆", - // gbreve;[ğ]. - "\x05reve;\x02ğ", - // gcirc;[ĝ] gcy;[г]. - "\x04irc;\x02ĝ\x02y;\x02г", - // gdot;[ġ]. - "\x03ot;\x02ġ", - // geqslant;[⩾] gesdotol;[⪄] gesdoto;[⪂] gesdot;[⪀] gesles;[⪔] gescc;[⪩] geqq;[≧] gesl;[⋛︀] gel;[⋛] geq;[≥] ges;[⩾] ge;[≥]. - "\x07qslant;\x03⩾\x07sdotol;\x03⪄\x06sdoto;\x03⪂\x05sdot;\x03⪀\x05sles;\x03⪔\x04scc;\x03⪩\x03qq;\x03≧\x03sl;\x06⋛︀\x02l;\x03⋛\x02q;\x03≥\x02s;\x03⩾\x01;\x03≥", - // gfr;[𝔤]. - "\x02r;\x04𝔤", - // ggg;[⋙] gg;[≫]. - "\x02g;\x03⋙\x01;\x03≫", - // gimel;[ℷ]. - "\x04mel;\x03ℷ", - // gjcy;[ѓ]. - "\x03cy;\x02ѓ", - // glE;[⪒] gla;[⪥] glj;[⪤] gl;[≷]. - "\x02E;\x03⪒\x02a;\x03⪥\x02j;\x03⪤\x01;\x03≷", - // gnapprox;[⪊] gneqq;[≩] gnsim;[⋧] gnap;[⪊] gneq;[⪈] gnE;[≩] gne;[⪈]. - "\x07approx;\x03⪊\x04eqq;\x03≩\x04sim;\x03⋧\x03ap;\x03⪊\x03eq;\x03⪈\x02E;\x03≩\x02e;\x03⪈", - // gopf;[𝕘]. - "\x03pf;\x04𝕘", - // grave;[`]. - "\x04ave;\x01`", - // gsime;[⪎] gsiml;[⪐] gscr;[ℊ] gsim;[≳]. - "\x04ime;\x03⪎\x04iml;\x03⪐\x03cr;\x03ℊ\x03im;\x03≳", - // gtreqqless;[⪌] gtrapprox;[⪆] gtreqless;[⋛] gtquest;[⩼] gtrless;[≷] gtlPar;[⦕] gtrarr;[⥸] gtrdot;[⋗] gtrsim;[≳] gtcir;[⩺] gtdot;[⋗] gtcc;[⪧] gt;[>]. - "\x09reqqless;\x03⪌\x08rapprox;\x03⪆\x08reqless;\x03⋛\x06quest;\x03⩼\x06rless;\x03≷\x05lPar;\x03⦕\x05rarr;\x03⥸\x05rdot;\x03⋗\x05rsim;\x03≳\x04cir;\x03⩺\x04dot;\x03⋗\x03cc;\x03⪧\x01;\x01>", - // gvertneqq;[≩︀] gvnE;[≩︀]. - "\x08ertneqq;\x06≩︀\x03nE;\x06≩︀", - // hArr;[⇔]. - "\x03rr;\x03⇔", - // harrcir;[⥈] hairsp;[ ] hamilt;[ℋ] hardcy;[ъ] harrw;[↭] half;[½] harr;[↔]. - "\x06rrcir;\x03⥈\x05irsp;\x03 \x05milt;\x03ℋ\x05rdcy;\x02ъ\x04rrw;\x03↭\x03lf;\x02½\x03rr;\x03↔", - // hbar;[ℏ]. - "\x03ar;\x03ℏ", - // hcirc;[ĥ]. - "\x04irc;\x02ĥ", - // heartsuit;[♥] hearts;[♥] hellip;[…] hercon;[⊹]. - "\x08artsuit;\x03♥\x05arts;\x03♥\x05llip;\x03…\x05rcon;\x03⊹", - // hfr;[𝔥]. - "\x02r;\x04𝔥", - // hksearow;[⤥] hkswarow;[⤦]. - "\x07searow;\x03⤥\x07swarow;\x03⤦", - // hookrightarrow;[↪] hookleftarrow;[↩] homtht;[∻] horbar;[―] hoarr;[⇿] hopf;[𝕙]. - "\x0dokrightarrow;\x03↪\x0cokleftarrow;\x03↩\x05mtht;\x03∻\x05rbar;\x03―\x04arr;\x03⇿\x03pf;\x04𝕙", - // hslash;[ℏ] hstrok;[ħ] hscr;[𝒽]. - "\x05lash;\x03ℏ\x05trok;\x02ħ\x03cr;\x04𝒽", - // hybull;[⁃] hyphen;[‐]. - "\x05bull;\x03⁃\x05phen;\x03‐", - // iacute;[í] iacute[í]. - "\x05cute;\x02í\x04cute\x02í", - // icirc;[î] icirc[î] icy;[и] ic;[⁣]. - "\x04irc;\x02î\x03irc\x02î\x02y;\x02и\x01;\x03⁣", - // iexcl;[¡] iecy;[е] iexcl[¡]. - "\x04xcl;\x02¡\x03cy;\x02е\x03xcl\x02¡", - // iff;[⇔] ifr;[𝔦]. - "\x02f;\x03⇔\x02r;\x04𝔦", - // igrave;[ì] igrave[ì]. - "\x05rave;\x02ì\x04rave\x02ì", - // iiiint;[⨌] iinfin;[⧜] iiint;[∭] iiota;[℩] ii;[ⅈ]. - "\x05iint;\x03⨌\x05nfin;\x03⧜\x04int;\x03∭\x04ota;\x03℩\x01;\x03ⅈ", - // ijlig;[ij]. - "\x04lig;\x02ij", - // imagline;[ℐ] imagpart;[ℑ] imacr;[ī] image;[ℑ] imath;[ı] imped;[Ƶ] imof;[⊷]. - "\x07agline;\x03ℐ\x07agpart;\x03ℑ\x04acr;\x02ī\x04age;\x03ℑ\x04ath;\x02ı\x04ped;\x02Ƶ\x03of;\x03⊷", - // infintie;[⧝] integers;[ℤ] intercal;[⊺] intlarhk;[⨗] intprod;[⨼] incare;[℅] inodot;[ı] intcal;[⊺] infin;[∞] int;[∫] in;[∈]. - "\x07fintie;\x03⧝\x07tegers;\x03ℤ\x07tercal;\x03⊺\x07tlarhk;\x03⨗\x06tprod;\x03⨼\x05care;\x03℅\x05odot;\x02ı\x05tcal;\x03⊺\x04fin;\x03∞\x02t;\x03∫\x01;\x03∈", - // iogon;[į] iocy;[ё] iopf;[𝕚] iota;[ι]. - "\x04gon;\x02į\x03cy;\x02ё\x03pf;\x04𝕚\x03ta;\x02ι", - // iprod;[⨼]. - "\x04rod;\x03⨼", - // iquest;[¿] iquest[¿]. - "\x05uest;\x02¿\x04uest\x02¿", - // isindot;[⋵] isinsv;[⋳] isinE;[⋹] isins;[⋴] isinv;[∈] iscr;[𝒾] isin;[∈]. - "\x06indot;\x03⋵\x05insv;\x03⋳\x04inE;\x03⋹\x04ins;\x03⋴\x04inv;\x03∈\x03cr;\x04𝒾\x03in;\x03∈", - // itilde;[ĩ] it;[⁢]. - "\x05ilde;\x02ĩ\x01;\x03⁢", - // iukcy;[і] iuml;[ï] iuml[ï]. - "\x04kcy;\x02і\x03ml;\x02ï\x02ml\x02ï", - // jcirc;[ĵ] jcy;[й]. - "\x04irc;\x02ĵ\x02y;\x02й", - // jfr;[𝔧]. - "\x02r;\x04𝔧", - // jmath;[ȷ]. - "\x04ath;\x02ȷ", - // jopf;[𝕛]. - "\x03pf;\x04𝕛", - // jsercy;[ј] jscr;[𝒿]. - "\x05ercy;\x02ј\x03cr;\x04𝒿", - // jukcy;[є]. - "\x04kcy;\x02є", - // kappav;[ϰ] kappa;[κ]. - "\x05ppav;\x02ϰ\x04ppa;\x02κ", - // kcedil;[ķ] kcy;[к]. - "\x05edil;\x02ķ\x02y;\x02к", - // kfr;[𝔨]. - "\x02r;\x04𝔨", - // kgreen;[ĸ]. - "\x05reen;\x02ĸ", - // khcy;[х]. - "\x03cy;\x02х", - // kjcy;[ќ]. - "\x03cy;\x02ќ", - // kopf;[𝕜]. - "\x03pf;\x04𝕜", - // kscr;[𝓀]. - "\x03cr;\x04𝓀", - // lAtail;[⤛] lAarr;[⇚] lArr;[⇐]. - "\x05tail;\x03⤛\x04arr;\x03⇚\x03rr;\x03⇐", - // lBarr;[⤎]. - "\x04arr;\x03⤎", - // lEg;[⪋] lE;[≦]. - "\x02g;\x03⪋\x01;\x03≦", - // lHar;[⥢]. - "\x03ar;\x03⥢", - // laemptyv;[⦴] larrbfs;[⤟] larrsim;[⥳] lacute;[ĺ] lagran;[ℒ] lambda;[λ] langle;[⟨] larrfs;[⤝] larrhk;[↩] larrlp;[↫] larrpl;[⤹] larrtl;[↢] latail;[⤙] langd;[⦑] laquo;[«] larrb;[⇤] lates;[⪭︀] lang;[⟨] laquo[«] larr;[←] late;[⪭] lap;[⪅] lat;[⪫]. - "\x07emptyv;\x03⦴\x06rrbfs;\x03⤟\x06rrsim;\x03⥳\x05cute;\x02ĺ\x05gran;\x03ℒ\x05mbda;\x02λ\x05ngle;\x03⟨\x05rrfs;\x03⤝\x05rrhk;\x03↩\x05rrlp;\x03↫\x05rrpl;\x03⤹\x05rrtl;\x03↢\x05tail;\x03⤙\x04ngd;\x03⦑\x04quo;\x02«\x04rrb;\x03⇤\x04tes;\x06⪭︀\x03ng;\x03⟨\x03quo\x02«\x03rr;\x03←\x03te;\x03⪭\x02p;\x03⪅\x02t;\x03⪫", - // lbrksld;[⦏] lbrkslu;[⦍] lbrace;[{] lbrack;[[] lbarr;[⤌] lbbrk;[❲] lbrke;[⦋]. - "\x06rksld;\x03⦏\x06rkslu;\x03⦍\x05race;\x01{\x05rack;\x01[\x04arr;\x03⤌\x04brk;\x03❲\x04rke;\x03⦋", - // lcaron;[ľ] lcedil;[ļ] lceil;[⌈] lcub;[{] lcy;[л]. - "\x05aron;\x02ľ\x05edil;\x02ļ\x04eil;\x03⌈\x03ub;\x01{\x02y;\x02л", - // ldrushar;[⥋] ldrdhar;[⥧] ldquor;[„] ldquo;[“] ldca;[⤶] ldsh;[↲]. - "\x07rushar;\x03⥋\x06rdhar;\x03⥧\x05quor;\x03„\x04quo;\x03“\x03ca;\x03⤶\x03sh;\x03↲", - // leftrightsquigarrow;[↭] leftrightharpoons;[⇋] leftharpoondown;[↽] leftrightarrows;[⇆] leftleftarrows;[⇇] leftrightarrow;[↔] leftthreetimes;[⋋] leftarrowtail;[↢] leftharpoonup;[↼] lessapprox;[⪅] lesseqqgtr;[⪋] leftarrow;[←] lesseqgtr;[⋚] leqslant;[⩽] lesdotor;[⪃] lesdoto;[⪁] lessdot;[⋖] lessgtr;[≶] lesssim;[≲] lesdot;[⩿] lesges;[⪓] lescc;[⪨] leqq;[≦] lesg;[⋚︀] leg;[⋚] leq;[≤] les;[⩽] le;[≤]. - "\x12ftrightsquigarrow;\x03↭\x10ftrightharpoons;\x03⇋\x0eftharpoondown;\x03↽\x0eftrightarrows;\x03⇆\x0dftleftarrows;\x03⇇\x0dftrightarrow;\x03↔\x0dftthreetimes;\x03⋋\x0cftarrowtail;\x03↢\x0cftharpoonup;\x03↼\x09ssapprox;\x03⪅\x09sseqqgtr;\x03⪋\x08ftarrow;\x03←\x08sseqgtr;\x03⋚\x07qslant;\x03⩽\x07sdotor;\x03⪃\x06sdoto;\x03⪁\x06ssdot;\x03⋖\x06ssgtr;\x03≶\x06sssim;\x03≲\x05sdot;\x03⩿\x05sges;\x03⪓\x04scc;\x03⪨\x03qq;\x03≦\x03sg;\x06⋚︀\x02g;\x03⋚\x02q;\x03≤\x02s;\x03⩽\x01;\x03≤", - // lfisht;[⥼] lfloor;[⌊] lfr;[𝔩]. - "\x05isht;\x03⥼\x05loor;\x03⌊\x02r;\x04𝔩", - // lgE;[⪑] lg;[≶]. - "\x02E;\x03⪑\x01;\x03≶", - // lharul;[⥪] lhard;[↽] lharu;[↼] lhblk;[▄]. - "\x05arul;\x03⥪\x04ard;\x03↽\x04aru;\x03↼\x04blk;\x03▄", - // ljcy;[љ]. - "\x03cy;\x02љ", - // llcorner;[⌞] llhard;[⥫] llarr;[⇇] lltri;[◺] ll;[≪]. - "\x07corner;\x03⌞\x05hard;\x03⥫\x04arr;\x03⇇\x04tri;\x03◺\x01;\x03≪", - // lmoustache;[⎰] lmidot;[ŀ] lmoust;[⎰]. - "\x09oustache;\x03⎰\x05idot;\x02ŀ\x05oust;\x03⎰", - // lnapprox;[⪉] lneqq;[≨] lnsim;[⋦] lnap;[⪉] lneq;[⪇] lnE;[≨] lne;[⪇]. - "\x07approx;\x03⪉\x04eqq;\x03≨\x04sim;\x03⋦\x03ap;\x03⪉\x03eq;\x03⪇\x02E;\x03≨\x02e;\x03⪇", - // longleftrightarrow;[⟷] longrightarrow;[⟶] looparrowright;[↬] longleftarrow;[⟵] looparrowleft;[↫] longmapsto;[⟼] lotimes;[⨴] lozenge;[◊] loplus;[⨭] lowast;[∗] lowbar;[_] loang;[⟬] loarr;[⇽] lobrk;[⟦] lopar;[⦅] lopf;[𝕝] lozf;[⧫] loz;[◊]. - "\x11ngleftrightarrow;\x03⟷\x0dngrightarrow;\x03⟶\x0doparrowright;\x03↬\x0cngleftarrow;\x03⟵\x0coparrowleft;\x03↫\x09ngmapsto;\x03⟼\x06times;\x03⨴\x06zenge;\x03◊\x05plus;\x03⨭\x05wast;\x03∗\x05wbar;\x01_\x04ang;\x03⟬\x04arr;\x03⇽\x04brk;\x03⟦\x04par;\x03⦅\x03pf;\x04𝕝\x03zf;\x03⧫\x02z;\x03◊", - // lparlt;[⦓] lpar;[(]. - "\x05arlt;\x03⦓\x03ar;\x01(", - // lrcorner;[⌟] lrhard;[⥭] lrarr;[⇆] lrhar;[⇋] lrtri;[⊿] lrm;[‎]. - "\x07corner;\x03⌟\x05hard;\x03⥭\x04arr;\x03⇆\x04har;\x03⇋\x04tri;\x03⊿\x02m;\x03‎", - // lsaquo;[‹] lsquor;[‚] lstrok;[ł] lsime;[⪍] lsimg;[⪏] lsquo;[‘] lscr;[𝓁] lsim;[≲] lsqb;[[] lsh;[↰]. - "\x05aquo;\x03‹\x05quor;\x03‚\x05trok;\x02ł\x04ime;\x03⪍\x04img;\x03⪏\x04quo;\x03‘\x03cr;\x04𝓁\x03im;\x03≲\x03qb;\x01[\x02h;\x03↰", - // ltquest;[⩻] lthree;[⋋] ltimes;[⋉] ltlarr;[⥶] ltrPar;[⦖] ltcir;[⩹] ltdot;[⋖] ltrie;[⊴] ltrif;[◂] ltcc;[⪦] ltri;[◃] lt;[<]. - "\x06quest;\x03⩻\x05hree;\x03⋋\x05imes;\x03⋉\x05larr;\x03⥶\x05rPar;\x03⦖\x04cir;\x03⩹\x04dot;\x03⋖\x04rie;\x03⊴\x04rif;\x03◂\x03cc;\x03⪦\x03ri;\x03◃\x01;\x01<", - // lurdshar;[⥊] luruhar;[⥦]. - "\x07rdshar;\x03⥊\x06ruhar;\x03⥦", - // lvertneqq;[≨︀] lvnE;[≨︀]. - "\x08ertneqq;\x06≨︀\x03nE;\x06≨︀", - // mDDot;[∺]. - "\x04Dot;\x03∺", - // mapstodown;[↧] mapstoleft;[↤] mapstoup;[↥] maltese;[✠] mapsto;[↦] marker;[▮] macr;[¯] male;[♂] malt;[✠] macr[¯] map;[↦]. - "\x09pstodown;\x03↧\x09pstoleft;\x03↤\x07pstoup;\x03↥\x06ltese;\x03✠\x05psto;\x03↦\x05rker;\x03▮\x03cr;\x02¯\x03le;\x03♂\x03lt;\x03✠\x02cr\x02¯\x02p;\x03↦", - // mcomma;[⨩] mcy;[м]. - "\x05omma;\x03⨩\x02y;\x02м", - // mdash;[—]. - "\x04ash;\x03—", - // measuredangle;[∡]. - "\x0casuredangle;\x03∡", - // mfr;[𝔪]. - "\x02r;\x04𝔪", - // mho;[℧]. - "\x02o;\x03℧", - // minusdu;[⨪] midast;[*] midcir;[⫰] middot;[·] minusb;[⊟] minusd;[∸] micro;[µ] middot[·] minus;[−] micro[µ] mid;[∣]. - "\x06nusdu;\x03⨪\x05dast;\x01*\x05dcir;\x03⫰\x05ddot;\x02·\x05nusb;\x03⊟\x05nusd;\x03∸\x04cro;\x02µ\x04ddot\x02·\x04nus;\x03−\x03cro\x02µ\x02d;\x03∣", - // mlcp;[⫛] mldr;[…]. - "\x03cp;\x03⫛\x03dr;\x03…", - // mnplus;[∓]. - "\x05plus;\x03∓", - // models;[⊧] mopf;[𝕞]. - "\x05dels;\x03⊧\x03pf;\x04𝕞", - // mp;[∓]. - "\x01;\x03∓", - // mstpos;[∾] mscr;[𝓂]. - "\x05tpos;\x03∾\x03cr;\x04𝓂", - // multimap;[⊸] mumap;[⊸] mu;[μ]. - "\x07ltimap;\x03⊸\x04map;\x03⊸\x01;\x02μ", - // nGtv;[≫̸] nGg;[⋙̸] nGt;[≫⃒]. - "\x03tv;\x05≫̸\x02g;\x05⋙̸\x02t;\x06≫⃒", - // nLeftrightarrow;[⇎] nLeftarrow;[⇍] nLtv;[≪̸] nLl;[⋘̸] nLt;[≪⃒]. - "\x0eeftrightarrow;\x03⇎\x09eftarrow;\x03⇍\x03tv;\x05≪̸\x02l;\x05⋘̸\x02t;\x06≪⃒", - // nRightarrow;[⇏]. - "\x0aightarrow;\x03⇏", - // nVDash;[⊯] nVdash;[⊮]. - "\x05Dash;\x03⊯\x05dash;\x03⊮", - // naturals;[ℕ] napprox;[≉] natural;[♮] nacute;[ń] nabla;[∇] napid;[≋̸] napos;[ʼn] natur;[♮] nang;[∠⃒] napE;[⩰̸] nap;[≉]. - "\x07turals;\x03ℕ\x06pprox;\x03≉\x06tural;\x03♮\x05cute;\x02ń\x04bla;\x03∇\x04pid;\x05≋̸\x04pos;\x02ʼn\x04tur;\x03♮\x03ng;\x06∠⃒\x03pE;\x05⩰̸\x02p;\x03≉", - // nbumpe;[≏̸] nbump;[≎̸] nbsp;[ ] nbsp[ ]. - "\x05umpe;\x05≏̸\x04ump;\x05≎̸\x03sp;\x02 \x02sp\x02 ", - // ncongdot;[⩭̸] ncaron;[ň] ncedil;[ņ] ncong;[≇] ncap;[⩃] ncup;[⩂] ncy;[н]. - "\x07ongdot;\x05⩭̸\x05aron;\x02ň\x05edil;\x02ņ\x04ong;\x03≇\x03ap;\x03⩃\x03up;\x03⩂\x02y;\x02н", - // ndash;[–]. - "\x04ash;\x03–", - // nearrow;[↗] nexists;[∄] nearhk;[⤤] nequiv;[≢] nesear;[⤨] nexist;[∄] neArr;[⇗] nearr;[↗] nedot;[≐̸] nesim;[≂̸] ne;[≠]. - "\x06arrow;\x03↗\x06xists;\x03∄\x05arhk;\x03⤤\x05quiv;\x03≢\x05sear;\x03⤨\x05xist;\x03∄\x04Arr;\x03⇗\x04arr;\x03↗\x04dot;\x05≐̸\x04sim;\x05≂̸\x01;\x03≠", - // nfr;[𝔫]. - "\x02r;\x04𝔫", - // ngeqslant;[⩾̸] ngeqq;[≧̸] ngsim;[≵] ngeq;[≱] nges;[⩾̸] ngtr;[≯] ngE;[≧̸] nge;[≱] ngt;[≯]. - "\x08eqslant;\x05⩾̸\x04eqq;\x05≧̸\x04sim;\x03≵\x03eq;\x03≱\x03es;\x05⩾̸\x03tr;\x03≯\x02E;\x05≧̸\x02e;\x03≱\x02t;\x03≯", - // nhArr;[⇎] nharr;[↮] nhpar;[⫲]. - "\x04Arr;\x03⇎\x04arr;\x03↮\x04par;\x03⫲", - // nisd;[⋺] nis;[⋼] niv;[∋] ni;[∋]. - "\x03sd;\x03⋺\x02s;\x03⋼\x02v;\x03∋\x01;\x03∋", - // njcy;[њ]. - "\x03cy;\x02њ", - // nleftrightarrow;[↮] nleftarrow;[↚] nleqslant;[⩽̸] nltrie;[⋬] nlArr;[⇍] nlarr;[↚] nleqq;[≦̸] nless;[≮] nlsim;[≴] nltri;[⋪] nldr;[‥] nleq;[≰] nles;[⩽̸] nlE;[≦̸] nle;[≰] nlt;[≮]. - "\x0eeftrightarrow;\x03↮\x09eftarrow;\x03↚\x08eqslant;\x05⩽̸\x05trie;\x03⋬\x04Arr;\x03⇍\x04arr;\x03↚\x04eqq;\x05≦̸\x04ess;\x03≮\x04sim;\x03≴\x04tri;\x03⋪\x03dr;\x03‥\x03eq;\x03≰\x03es;\x05⩽̸\x02E;\x05≦̸\x02e;\x03≰\x02t;\x03≮", - // nmid;[∤]. - "\x03id;\x03∤", - // notindot;[⋵̸] notinva;[∉] notinvb;[⋷] notinvc;[⋶] notniva;[∌] notnivb;[⋾] notnivc;[⋽] notinE;[⋹̸] notin;[∉] notni;[∌] nopf;[𝕟] not;[¬] not[¬]. - "\x07tindot;\x05⋵̸\x06tinva;\x03∉\x06tinvb;\x03⋷\x06tinvc;\x03⋶\x06tniva;\x03∌\x06tnivb;\x03⋾\x06tnivc;\x03⋽\x05tinE;\x05⋹̸\x04tin;\x03∉\x04tni;\x03∌\x03pf;\x04𝕟\x02t;\x02¬\x01t\x02¬", - // nparallel;[∦] npolint;[⨔] npreceq;[⪯̸] nparsl;[⫽⃥] nprcue;[⋠] npart;[∂̸] nprec;[⊀] npar;[∦] npre;[⪯̸] npr;[⊀]. - "\x08arallel;\x03∦\x06olint;\x03⨔\x06receq;\x05⪯̸\x05arsl;\x06⫽⃥\x05rcue;\x03⋠\x04art;\x05∂̸\x04rec;\x03⊀\x03ar;\x03∦\x03re;\x05⪯̸\x02r;\x03⊀", - // nrightarrow;[↛] nrarrc;[⤳̸] nrarrw;[↝̸] nrtrie;[⋭] nrArr;[⇏] nrarr;[↛] nrtri;[⋫]. - "\x0aightarrow;\x03↛\x05arrc;\x05⤳̸\x05arrw;\x05↝̸\x05trie;\x03⋭\x04Arr;\x03⇏\x04arr;\x03↛\x04tri;\x03⋫", - // nshortparallel;[∦] nsubseteqq;[⫅̸] nsupseteqq;[⫆̸] nshortmid;[∤] nsubseteq;[⊈] nsupseteq;[⊉] nsqsube;[⋢] nsqsupe;[⋣] nsubset;[⊂⃒] nsucceq;[⪰̸] nsupset;[⊃⃒] nsccue;[⋡] nsimeq;[≄] nsime;[≄] nsmid;[∤] nspar;[∦] nsubE;[⫅̸] nsube;[⊈] nsucc;[⊁] nsupE;[⫆̸] nsupe;[⊉] nsce;[⪰̸] nscr;[𝓃] nsim;[≁] nsub;[⊄] nsup;[⊅] nsc;[⊁]. - "\x0dhortparallel;\x03∦\x09ubseteqq;\x05⫅̸\x09upseteqq;\x05⫆̸\x08hortmid;\x03∤\x08ubseteq;\x03⊈\x08upseteq;\x03⊉\x06qsube;\x03⋢\x06qsupe;\x03⋣\x06ubset;\x06⊂⃒\x06ucceq;\x05⪰̸\x06upset;\x06⊃⃒\x05ccue;\x03⋡\x05imeq;\x03≄\x04ime;\x03≄\x04mid;\x03∤\x04par;\x03∦\x04ubE;\x05⫅̸\x04ube;\x03⊈\x04ucc;\x03⊁\x04upE;\x05⫆̸\x04upe;\x03⊉\x03ce;\x05⪰̸\x03cr;\x04𝓃\x03im;\x03≁\x03ub;\x03⊄\x03up;\x03⊅\x02c;\x03⊁", - // ntrianglerighteq;[⋭] ntrianglelefteq;[⋬] ntriangleright;[⋫] ntriangleleft;[⋪] ntilde;[ñ] ntilde[ñ] ntgl;[≹] ntlg;[≸]. - "\x0frianglerighteq;\x03⋭\x0erianglelefteq;\x03⋬\x0driangleright;\x03⋫\x0criangleleft;\x03⋪\x05ilde;\x02ñ\x04ilde\x02ñ\x03gl;\x03≹\x03lg;\x03≸", - // numero;[№] numsp;[ ] num;[#] nu;[ν]. - "\x05mero;\x03№\x04msp;\x03 \x02m;\x01#\x01;\x02ν", - // nvinfin;[⧞] nvltrie;[⊴⃒] nvrtrie;[⊵⃒] nvDash;[⊭] nvHarr;[⤄] nvdash;[⊬] nvlArr;[⤂] nvrArr;[⤃] nvsim;[∼⃒] nvap;[≍⃒] nvge;[≥⃒] nvgt;[>⃒] nvle;[≤⃒] nvlt;[<⃒]. - "\x06infin;\x03⧞\x06ltrie;\x06⊴⃒\x06rtrie;\x06⊵⃒\x05Dash;\x03⊭\x05Harr;\x03⤄\x05dash;\x03⊬\x05lArr;\x03⤂\x05rArr;\x03⤃\x04sim;\x06∼⃒\x03ap;\x06≍⃒\x03ge;\x06≥⃒\x03gt;\x04>⃒\x03le;\x06≤⃒\x03lt;\x04<⃒", - // nwarrow;[↖] nwarhk;[⤣] nwnear;[⤧] nwArr;[⇖] nwarr;[↖]. - "\x06arrow;\x03↖\x05arhk;\x03⤣\x05near;\x03⤧\x04Arr;\x03⇖\x04arr;\x03↖", - // oS;[Ⓢ]. - "\x01;\x03Ⓢ", - // oacute;[ó] oacute[ó] oast;[⊛]. - "\x05cute;\x02ó\x04cute\x02ó\x03st;\x03⊛", - // ocirc;[ô] ocir;[⊚] ocirc[ô] ocy;[о]. - "\x04irc;\x02ô\x03ir;\x03⊚\x03irc\x02ô\x02y;\x02о", - // odblac;[ő] odsold;[⦼] odash;[⊝] odiv;[⨸] odot;[⊙]. - "\x05blac;\x02ő\x05sold;\x03⦼\x04ash;\x03⊝\x03iv;\x03⨸\x03ot;\x03⊙", - // oelig;[œ]. - "\x04lig;\x02œ", - // ofcir;[⦿] ofr;[𝔬]. - "\x04cir;\x03⦿\x02r;\x04𝔬", - // ograve;[ò] ograve[ò] ogon;[˛] ogt;[⧁]. - "\x05rave;\x02ò\x04rave\x02ò\x03on;\x02˛\x02t;\x03⧁", - // ohbar;[⦵] ohm;[Ω]. - "\x04bar;\x03⦵\x02m;\x02Ω", - // oint;[∮]. - "\x03nt;\x03∮", - // olcross;[⦻] olarr;[↺] olcir;[⦾] oline;[‾] olt;[⧀]. - "\x06cross;\x03⦻\x04arr;\x03↺\x04cir;\x03⦾\x04ine;\x03‾\x02t;\x03⧀", - // omicron;[ο] ominus;[⊖] omacr;[ō] omega;[ω] omid;[⦶]. - "\x06icron;\x02ο\x05inus;\x03⊖\x04acr;\x02ō\x04ega;\x02ω\x03id;\x03⦶", - // oopf;[𝕠]. - "\x03pf;\x04𝕠", - // operp;[⦹] oplus;[⊕] opar;[⦷]. - "\x04erp;\x03⦹\x04lus;\x03⊕\x03ar;\x03⦷", - // orderof;[ℴ] orslope;[⩗] origof;[⊶] orarr;[↻] order;[ℴ] ordf;[ª] ordm;[º] oror;[⩖] ord;[⩝] ordf[ª] ordm[º] orv;[⩛] or;[∨]. - "\x06derof;\x03ℴ\x06slope;\x03⩗\x05igof;\x03⊶\x04arr;\x03↻\x04der;\x03ℴ\x03df;\x02ª\x03dm;\x02º\x03or;\x03⩖\x02d;\x03⩝\x02df\x02ª\x02dm\x02º\x02v;\x03⩛\x01;\x03∨", - // oslash;[ø] oslash[ø] oscr;[ℴ] osol;[⊘]. - "\x05lash;\x02ø\x04lash\x02ø\x03cr;\x03ℴ\x03ol;\x03⊘", - // otimesas;[⨶] otilde;[õ] otimes;[⊗] otilde[õ]. - "\x07imesas;\x03⨶\x05ilde;\x02õ\x05imes;\x03⊗\x04ilde\x02õ", - // ouml;[ö] ouml[ö]. - "\x03ml;\x02ö\x02ml\x02ö", - // ovbar;[⌽]. - "\x04bar;\x03⌽", - // parallel;[∥] parsim;[⫳] parsl;[⫽] para;[¶] part;[∂] par;[∥] para[¶]. - "\x07rallel;\x03∥\x05rsim;\x03⫳\x04rsl;\x03⫽\x03ra;\x02¶\x03rt;\x03∂\x02r;\x03∥\x02ra\x02¶", - // pcy;[п]. - "\x02y;\x02п", - // pertenk;[‱] percnt;[%] period;[.] permil;[‰] perp;[⊥]. - "\x06rtenk;\x03‱\x05rcnt;\x01%\x05riod;\x01.\x05rmil;\x03‰\x03rp;\x03⊥", - // pfr;[𝔭]. - "\x02r;\x04𝔭", - // phmmat;[ℳ] phone;[☎] phiv;[ϕ] phi;[φ]. - "\x05mmat;\x03ℳ\x04one;\x03☎\x03iv;\x02ϕ\x02i;\x02φ", - // pitchfork;[⋔] piv;[ϖ] pi;[π]. - "\x08tchfork;\x03⋔\x02v;\x02ϖ\x01;\x02π", - // plusacir;[⨣] planckh;[ℎ] pluscir;[⨢] plussim;[⨦] plustwo;[⨧] planck;[ℏ] plankv;[ℏ] plusdo;[∔] plusdu;[⨥] plusmn;[±] plusb;[⊞] pluse;[⩲] plusmn[±] plus;[+]. - "\x07usacir;\x03⨣\x06anckh;\x03ℎ\x06uscir;\x03⨢\x06ussim;\x03⨦\x06ustwo;\x03⨧\x05anck;\x03ℏ\x05ankv;\x03ℏ\x05usdo;\x03∔\x05usdu;\x03⨥\x05usmn;\x02±\x04usb;\x03⊞\x04use;\x03⩲\x04usmn\x02±\x03us;\x01+", - // pm;[±]. - "\x01;\x02±", - // pointint;[⨕] pound;[£] popf;[𝕡] pound[£]. - "\x07intint;\x03⨕\x04und;\x02£\x03pf;\x04𝕡\x03und\x02£", - // preccurlyeq;[≼] precnapprox;[⪹] precapprox;[⪷] precneqq;[⪵] precnsim;[⋨] profalar;[⌮] profline;[⌒] profsurf;[⌓] precsim;[≾] preceq;[⪯] primes;[ℙ] prnsim;[⋨] propto;[∝] prurel;[⊰] prcue;[≼] prime;[′] prnap;[⪹] prsim;[≾] prap;[⪷] prec;[≺] prnE;[⪵] prod;[∏] prop;[∝] prE;[⪳] pre;[⪯] pr;[≺]. - "\x0aeccurlyeq;\x03≼\x0aecnapprox;\x03⪹\x09ecapprox;\x03⪷\x07ecneqq;\x03⪵\x07ecnsim;\x03⋨\x07ofalar;\x03⌮\x07ofline;\x03⌒\x07ofsurf;\x03⌓\x06ecsim;\x03≾\x05eceq;\x03⪯\x05imes;\x03ℙ\x05nsim;\x03⋨\x05opto;\x03∝\x05urel;\x03⊰\x04cue;\x03≼\x04ime;\x03′\x04nap;\x03⪹\x04sim;\x03≾\x03ap;\x03⪷\x03ec;\x03≺\x03nE;\x03⪵\x03od;\x03∏\x03op;\x03∝\x02E;\x03⪳\x02e;\x03⪯\x01;\x03≺", - // pscr;[𝓅] psi;[ψ]. - "\x03cr;\x04𝓅\x02i;\x02ψ", - // puncsp;[ ]. - "\x05ncsp;\x03 ", - // qfr;[𝔮]. - "\x02r;\x04𝔮", - // qint;[⨌]. - "\x03nt;\x03⨌", - // qopf;[𝕢]. - "\x03pf;\x04𝕢", - // qprime;[⁗]. - "\x05rime;\x03⁗", - // qscr;[𝓆]. - "\x03cr;\x04𝓆", - // quaternions;[ℍ] quatint;[⨖] questeq;[≟] quest;[?] quot;[\"] quot[\"]. - "\x0aaternions;\x03ℍ\x06atint;\x03⨖\x06esteq;\x03≟\x04est;\x01?\x03ot;\x01\"\x02ot\x01\"", - // rAtail;[⤜] rAarr;[⇛] rArr;[⇒]. - "\x05tail;\x03⤜\x04arr;\x03⇛\x03rr;\x03⇒", - // rBarr;[⤏]. - "\x04arr;\x03⤏", - // rHar;[⥤]. - "\x03ar;\x03⥤", - // rationals;[ℚ] raemptyv;[⦳] rarrbfs;[⤠] rarrsim;[⥴] racute;[ŕ] rangle;[⟩] rarrap;[⥵] rarrfs;[⤞] rarrhk;[↪] rarrlp;[↬] rarrpl;[⥅] rarrtl;[↣] ratail;[⤚] radic;[√] rangd;[⦒] range;[⦥] raquo;[»] rarrb;[⇥] rarrc;[⤳] rarrw;[↝] ratio;[∶] race;[∽̱] rang;[⟩] raquo[»] rarr;[→]. - "\x08tionals;\x03ℚ\x07emptyv;\x03⦳\x06rrbfs;\x03⤠\x06rrsim;\x03⥴\x05cute;\x02ŕ\x05ngle;\x03⟩\x05rrap;\x03⥵\x05rrfs;\x03⤞\x05rrhk;\x03↪\x05rrlp;\x03↬\x05rrpl;\x03⥅\x05rrtl;\x03↣\x05tail;\x03⤚\x04dic;\x03√\x04ngd;\x03⦒\x04nge;\x03⦥\x04quo;\x02»\x04rrb;\x03⇥\x04rrc;\x03⤳\x04rrw;\x03↝\x04tio;\x03∶\x03ce;\x05∽̱\x03ng;\x03⟩\x03quo\x02»\x03rr;\x03→", - // rbrksld;[⦎] rbrkslu;[⦐] rbrace;[}] rbrack;[]] rbarr;[⤍] rbbrk;[❳] rbrke;[⦌]. - "\x06rksld;\x03⦎\x06rkslu;\x03⦐\x05race;\x01}\x05rack;\x01]\x04arr;\x03⤍\x04brk;\x03❳\x04rke;\x03⦌", - // rcaron;[ř] rcedil;[ŗ] rceil;[⌉] rcub;[}] rcy;[р]. - "\x05aron;\x02ř\x05edil;\x02ŗ\x04eil;\x03⌉\x03ub;\x01}\x02y;\x02р", - // rdldhar;[⥩] rdquor;[”] rdquo;[”] rdca;[⤷] rdsh;[↳]. - "\x06ldhar;\x03⥩\x05quor;\x03”\x04quo;\x03”\x03ca;\x03⤷\x03sh;\x03↳", - // realpart;[ℜ] realine;[ℛ] reals;[ℝ] real;[ℜ] rect;[▭] reg;[®] reg[®]. - "\x07alpart;\x03ℜ\x06aline;\x03ℛ\x04als;\x03ℝ\x03al;\x03ℜ\x03ct;\x03▭\x02g;\x02®\x01g\x02®", - // rfisht;[⥽] rfloor;[⌋] rfr;[𝔯]. - "\x05isht;\x03⥽\x05loor;\x03⌋\x02r;\x04𝔯", - // rharul;[⥬] rhard;[⇁] rharu;[⇀] rhov;[ϱ] rho;[ρ]. - "\x05arul;\x03⥬\x04ard;\x03⇁\x04aru;\x03⇀\x03ov;\x02ϱ\x02o;\x02ρ", - // rightleftharpoons;[⇌] rightharpoondown;[⇁] rightrightarrows;[⇉] rightleftarrows;[⇄] rightsquigarrow;[↝] rightthreetimes;[⋌] rightarrowtail;[↣] rightharpoonup;[⇀] risingdotseq;[≓] rightarrow;[→] ring;[˚]. - "\x10ghtleftharpoons;\x03⇌\x0fghtharpoondown;\x03⇁\x0fghtrightarrows;\x03⇉\x0eghtleftarrows;\x03⇄\x0eghtsquigarrow;\x03↝\x0eghtthreetimes;\x03⋌\x0dghtarrowtail;\x03↣\x0dghtharpoonup;\x03⇀\x0bsingdotseq;\x03≓\x09ghtarrow;\x03→\x03ng;\x02˚", - // rlarr;[⇄] rlhar;[⇌] rlm;[‏]. - "\x04arr;\x03⇄\x04har;\x03⇌\x02m;\x03‏", - // rmoustache;[⎱] rmoust;[⎱]. - "\x09oustache;\x03⎱\x05oust;\x03⎱", - // rnmid;[⫮]. - "\x04mid;\x03⫮", - // rotimes;[⨵] roplus;[⨮] roang;[⟭] roarr;[⇾] robrk;[⟧] ropar;[⦆] ropf;[𝕣]. - "\x06times;\x03⨵\x05plus;\x03⨮\x04ang;\x03⟭\x04arr;\x03⇾\x04brk;\x03⟧\x04par;\x03⦆\x03pf;\x04𝕣", - // rppolint;[⨒] rpargt;[⦔] rpar;[)]. - "\x07polint;\x03⨒\x05argt;\x03⦔\x03ar;\x01)", - // rrarr;[⇉]. - "\x04arr;\x03⇉", - // rsaquo;[›] rsquor;[’] rsquo;[’] rscr;[𝓇] rsqb;[]] rsh;[↱]. - "\x05aquo;\x03›\x05quor;\x03’\x04quo;\x03’\x03cr;\x04𝓇\x03qb;\x01]\x02h;\x03↱", - // rtriltri;[⧎] rthree;[⋌] rtimes;[⋊] rtrie;[⊵] rtrif;[▸] rtri;[▹]. - "\x07riltri;\x03⧎\x05hree;\x03⋌\x05imes;\x03⋊\x04rie;\x03⊵\x04rif;\x03▸\x03ri;\x03▹", - // ruluhar;[⥨]. - "\x06luhar;\x03⥨", - // rx;[℞]. - "\x01;\x03℞", - // sacute;[ś]. - "\x05cute;\x02ś", - // sbquo;[‚]. - "\x04quo;\x03‚", - // scpolint;[⨓] scaron;[š] scedil;[ş] scnsim;[⋩] sccue;[≽] scirc;[ŝ] scnap;[⪺] scsim;[≿] scap;[⪸] scnE;[⪶] scE;[⪴] sce;[⪰] scy;[с] sc;[≻]. - "\x07polint;\x03⨓\x05aron;\x02š\x05edil;\x02ş\x05nsim;\x03⋩\x04cue;\x03≽\x04irc;\x02ŝ\x04nap;\x03⪺\x04sim;\x03≿\x03ap;\x03⪸\x03nE;\x03⪶\x02E;\x03⪴\x02e;\x03⪰\x02y;\x02с\x01;\x03≻", - // sdotb;[⊡] sdote;[⩦] sdot;[⋅]. - "\x04otb;\x03⊡\x04ote;\x03⩦\x03ot;\x03⋅", - // setminus;[∖] searrow;[↘] searhk;[⤥] seswar;[⤩] seArr;[⇘] searr;[↘] setmn;[∖] sect;[§] semi;[;] sext;[✶] sect[§]. - "\x07tminus;\x03∖\x06arrow;\x03↘\x05arhk;\x03⤥\x05swar;\x03⤩\x04Arr;\x03⇘\x04arr;\x03↘\x04tmn;\x03∖\x03ct;\x02§\x03mi;\x01;\x03xt;\x03✶\x02ct\x02§", - // sfrown;[⌢] sfr;[𝔰]. - "\x05rown;\x03⌢\x02r;\x04𝔰", - // shortparallel;[∥] shortmid;[∣] shchcy;[щ] sharp;[♯] shcy;[ш] shy;[­] shy[­]. - "\x0cortparallel;\x03∥\x07ortmid;\x03∣\x05chcy;\x02щ\x04arp;\x03♯\x03cy;\x02ш\x02y;\x02­\x01y\x02­", - // simplus;[⨤] simrarr;[⥲] sigmaf;[ς] sigmav;[ς] simdot;[⩪] sigma;[σ] simeq;[≃] simgE;[⪠] simlE;[⪟] simne;[≆] sime;[≃] simg;[⪞] siml;[⪝] sim;[∼]. - "\x06mplus;\x03⨤\x06mrarr;\x03⥲\x05gmaf;\x02ς\x05gmav;\x02ς\x05mdot;\x03⩪\x04gma;\x02σ\x04meq;\x03≃\x04mgE;\x03⪠\x04mlE;\x03⪟\x04mne;\x03≆\x03me;\x03≃\x03mg;\x03⪞\x03ml;\x03⪝\x02m;\x03∼", - // slarr;[←]. - "\x04arr;\x03←", - // smallsetminus;[∖] smeparsl;[⧤] smashp;[⨳] smile;[⌣] smtes;[⪬︀] smid;[∣] smte;[⪬] smt;[⪪]. - "\x0callsetminus;\x03∖\x07eparsl;\x03⧤\x05ashp;\x03⨳\x04ile;\x03⌣\x04tes;\x06⪬︀\x03id;\x03∣\x03te;\x03⪬\x02t;\x03⪪", - // softcy;[ь] solbar;[⌿] solb;[⧄] sopf;[𝕤] sol;[/]. - "\x05ftcy;\x02ь\x05lbar;\x03⌿\x03lb;\x03⧄\x03pf;\x04𝕤\x02l;\x01/", - // spadesuit;[♠] spades;[♠] spar;[∥]. - "\x08adesuit;\x03♠\x05ades;\x03♠\x03ar;\x03∥", - // sqsubseteq;[⊑] sqsupseteq;[⊒] sqsubset;[⊏] sqsupset;[⊐] sqcaps;[⊓︀] sqcups;[⊔︀] sqsube;[⊑] sqsupe;[⊒] square;[□] squarf;[▪] sqcap;[⊓] sqcup;[⊔] sqsub;[⊏] sqsup;[⊐] squf;[▪] squ;[□]. - "\x09subseteq;\x03⊑\x09supseteq;\x03⊒\x07subset;\x03⊏\x07supset;\x03⊐\x05caps;\x06⊓︀\x05cups;\x06⊔︀\x05sube;\x03⊑\x05supe;\x03⊒\x05uare;\x03□\x05uarf;\x03▪\x04cap;\x03⊓\x04cup;\x03⊔\x04sub;\x03⊏\x04sup;\x03⊐\x03uf;\x03▪\x02u;\x03□", - // srarr;[→]. - "\x04arr;\x03→", - // ssetmn;[∖] ssmile;[⌣] sstarf;[⋆] sscr;[𝓈]. - "\x05etmn;\x03∖\x05mile;\x03⌣\x05tarf;\x03⋆\x03cr;\x04𝓈", - // straightepsilon;[ϵ] straightphi;[ϕ] starf;[★] strns;[¯] star;[☆]. - "\x0eraightepsilon;\x02ϵ\x0araightphi;\x02ϕ\x04arf;\x03★\x04rns;\x02¯\x03ar;\x03☆", - // succcurlyeq;[≽] succnapprox;[⪺] subsetneqq;[⫋] succapprox;[⪸] supsetneqq;[⫌] subseteqq;[⫅] subsetneq;[⊊] supseteqq;[⫆] supsetneq;[⊋] subseteq;[⊆] succneqq;[⪶] succnsim;[⋩] supseteq;[⊇] subedot;[⫃] submult;[⫁] subplus;[⪿] subrarr;[⥹] succsim;[≿] supdsub;[⫘] supedot;[⫄] suphsol;[⟉] suphsub;[⫗] suplarr;[⥻] supmult;[⫂] supplus;[⫀] subdot;[⪽] subset;[⊂] subsim;[⫇] subsub;[⫕] subsup;[⫓] succeq;[⪰] supdot;[⪾] supset;[⊃] supsim;[⫈] supsub;[⫔] supsup;[⫖] subnE;[⫋] subne;[⊊] supnE;[⫌] supne;[⊋] subE;[⫅] sube;[⊆] succ;[≻] sung;[♪] sup1;[¹] sup2;[²] sup3;[³] supE;[⫆] supe;[⊇] sub;[⊂] sum;[∑] sup1[¹] sup2[²] sup3[³] sup;[⊃]. - "\x0acccurlyeq;\x03≽\x0accnapprox;\x03⪺\x09bsetneqq;\x03⫋\x09ccapprox;\x03⪸\x09psetneqq;\x03⫌\x08bseteqq;\x03⫅\x08bsetneq;\x03⊊\x08pseteqq;\x03⫆\x08psetneq;\x03⊋\x07bseteq;\x03⊆\x07ccneqq;\x03⪶\x07ccnsim;\x03⋩\x07pseteq;\x03⊇\x06bedot;\x03⫃\x06bmult;\x03⫁\x06bplus;\x03⪿\x06brarr;\x03⥹\x06ccsim;\x03≿\x06pdsub;\x03⫘\x06pedot;\x03⫄\x06phsol;\x03⟉\x06phsub;\x03⫗\x06plarr;\x03⥻\x06pmult;\x03⫂\x06pplus;\x03⫀\x05bdot;\x03⪽\x05bset;\x03⊂\x05bsim;\x03⫇\x05bsub;\x03⫕\x05bsup;\x03⫓\x05cceq;\x03⪰\x05pdot;\x03⪾\x05pset;\x03⊃\x05psim;\x03⫈\x05psub;\x03⫔\x05psup;\x03⫖\x04bnE;\x03⫋\x04bne;\x03⊊\x04pnE;\x03⫌\x04pne;\x03⊋\x03bE;\x03⫅\x03be;\x03⊆\x03cc;\x03≻\x03ng;\x03♪\x03p1;\x02¹\x03p2;\x02²\x03p3;\x02³\x03pE;\x03⫆\x03pe;\x03⊇\x02b;\x03⊂\x02m;\x03∑\x02p1\x02¹\x02p2\x02²\x02p3\x02³\x02p;\x03⊃", - // swarrow;[↙] swarhk;[⤦] swnwar;[⤪] swArr;[⇙] swarr;[↙]. - "\x06arrow;\x03↙\x05arhk;\x03⤦\x05nwar;\x03⤪\x04Arr;\x03⇙\x04arr;\x03↙", - // szlig;[ß] szlig[ß]. - "\x04lig;\x02ß\x03lig\x02ß", - // target;[⌖] tau;[τ]. - "\x05rget;\x03⌖\x02u;\x02τ", - // tbrk;[⎴]. - "\x03rk;\x03⎴", - // tcaron;[ť] tcedil;[ţ] tcy;[т]. - "\x05aron;\x02ť\x05edil;\x02ţ\x02y;\x02т", - // tdot;[⃛]. - "\x03ot;\x03⃛", - // telrec;[⌕]. - "\x05lrec;\x03⌕", - // tfr;[𝔱]. - "\x02r;\x04𝔱", - // thickapprox;[≈] therefore;[∴] thetasym;[ϑ] thicksim;[∼] there4;[∴] thetav;[ϑ] thinsp;[ ] thksim;[∼] theta;[θ] thkap;[≈] thorn;[þ] thorn[þ]. - "\x0aickapprox;\x03≈\x08erefore;\x03∴\x07etasym;\x02ϑ\x07icksim;\x03∼\x05ere4;\x03∴\x05etav;\x02ϑ\x05insp;\x03 \x05ksim;\x03∼\x04eta;\x02θ\x04kap;\x03≈\x04orn;\x02þ\x03orn\x02þ", - // timesbar;[⨱] timesb;[⊠] timesd;[⨰] tilde;[˜] times;[×] times[×] tint;[∭]. - "\x07mesbar;\x03⨱\x05mesb;\x03⊠\x05mesd;\x03⨰\x04lde;\x02˜\x04mes;\x02×\x03mes\x02×\x03nt;\x03∭", - // topfork;[⫚] topbot;[⌶] topcir;[⫱] toea;[⤨] topf;[𝕥] tosa;[⤩] top;[⊤]. - "\x06pfork;\x03⫚\x05pbot;\x03⌶\x05pcir;\x03⫱\x03ea;\x03⤨\x03pf;\x04𝕥\x03sa;\x03⤩\x02p;\x03⊤", - // tprime;[‴]. - "\x05rime;\x03‴", - // trianglerighteq;[⊵] trianglelefteq;[⊴] triangleright;[▹] triangledown;[▿] triangleleft;[◃] triangleq;[≜] triangle;[▵] triminus;[⨺] trpezium;[⏢] triplus;[⨹] tritime;[⨻] tridot;[◬] trade;[™] trisb;[⧍] trie;[≜]. - "\x0eianglerighteq;\x03⊵\x0dianglelefteq;\x03⊴\x0ciangleright;\x03▹\x0biangledown;\x03▿\x0biangleleft;\x03◃\x08iangleq;\x03≜\x07iangle;\x03▵\x07iminus;\x03⨺\x07pezium;\x03⏢\x06iplus;\x03⨹\x06itime;\x03⨻\x05idot;\x03◬\x04ade;\x03™\x04isb;\x03⧍\x03ie;\x03≜", - // tstrok;[ŧ] tshcy;[ћ] tscr;[𝓉] tscy;[ц]. - "\x05trok;\x02ŧ\x04hcy;\x02ћ\x03cr;\x04𝓉\x03cy;\x02ц", - // twoheadrightarrow;[↠] twoheadleftarrow;[↞] twixt;[≬]. - "\x10oheadrightarrow;\x03↠\x0foheadleftarrow;\x03↞\x04ixt;\x03≬", - // uArr;[⇑]. - "\x03rr;\x03⇑", - // uHar;[⥣]. - "\x03ar;\x03⥣", - // uacute;[ú] uacute[ú] uarr;[↑]. - "\x05cute;\x02ú\x04cute\x02ú\x03rr;\x03↑", - // ubreve;[ŭ] ubrcy;[ў]. - "\x05reve;\x02ŭ\x04rcy;\x02ў", - // ucirc;[û] ucirc[û] ucy;[у]. - "\x04irc;\x02û\x03irc\x02û\x02y;\x02у", - // udblac;[ű] udarr;[⇅] udhar;[⥮]. - "\x05blac;\x02ű\x04arr;\x03⇅\x04har;\x03⥮", - // ufisht;[⥾] ufr;[𝔲]. - "\x05isht;\x03⥾\x02r;\x04𝔲", - // ugrave;[ù] ugrave[ù]. - "\x05rave;\x02ù\x04rave\x02ù", - // uharl;[↿] uharr;[↾] uhblk;[▀]. - "\x04arl;\x03↿\x04arr;\x03↾\x04blk;\x03▀", - // ulcorner;[⌜] ulcorn;[⌜] ulcrop;[⌏] ultri;[◸]. - "\x07corner;\x03⌜\x05corn;\x03⌜\x05crop;\x03⌏\x04tri;\x03◸", - // umacr;[ū] uml;[¨] uml[¨]. - "\x04acr;\x02ū\x02l;\x02¨\x01l\x02¨", - // uogon;[ų] uopf;[𝕦]. - "\x04gon;\x02ų\x03pf;\x04𝕦", - // upharpoonright;[↾] upharpoonleft;[↿] updownarrow;[↕] upuparrows;[⇈] uparrow;[↑] upsilon;[υ] uplus;[⊎] upsih;[ϒ] upsi;[υ]. - "\x0dharpoonright;\x03↾\x0charpoonleft;\x03↿\x0adownarrow;\x03↕\x09uparrows;\x03⇈\x06arrow;\x03↑\x06silon;\x02υ\x04lus;\x03⊎\x04sih;\x02ϒ\x03si;\x02υ", - // urcorner;[⌝] urcorn;[⌝] urcrop;[⌎] uring;[ů] urtri;[◹]. - "\x07corner;\x03⌝\x05corn;\x03⌝\x05crop;\x03⌎\x04ing;\x02ů\x04tri;\x03◹", - // uscr;[𝓊]. - "\x03cr;\x04𝓊", - // utilde;[ũ] utdot;[⋰] utrif;[▴] utri;[▵]. - "\x05ilde;\x02ũ\x04dot;\x03⋰\x04rif;\x03▴\x03ri;\x03▵", - // uuarr;[⇈] uuml;[ü] uuml[ü]. - "\x04arr;\x03⇈\x03ml;\x02ü\x02ml\x02ü", - // uwangle;[⦧]. - "\x06angle;\x03⦧", - // vArr;[⇕]. - "\x03rr;\x03⇕", - // vBarv;[⫩] vBar;[⫨]. - "\x04arv;\x03⫩\x03ar;\x03⫨", - // vDash;[⊨]. - "\x04ash;\x03⊨", - // vartriangleright;[⊳] vartriangleleft;[⊲] varsubsetneqq;[⫋︀] varsupsetneqq;[⫌︀] varsubsetneq;[⊊︀] varsupsetneq;[⊋︀] varepsilon;[ϵ] varnothing;[∅] varpropto;[∝] varkappa;[ϰ] varsigma;[ς] vartheta;[ϑ] vangrt;[⦜] varphi;[ϕ] varrho;[ϱ] varpi;[ϖ] varr;[↕]. - "\x0frtriangleright;\x03⊳\x0ertriangleleft;\x03⊲\x0crsubsetneqq;\x06⫋︀\x0crsupsetneqq;\x06⫌︀\x0brsubsetneq;\x06⊊︀\x0brsupsetneq;\x06⊋︀\x09repsilon;\x02ϵ\x09rnothing;\x03∅\x08rpropto;\x03∝\x07rkappa;\x02ϰ\x07rsigma;\x02ς\x07rtheta;\x02ϑ\x05ngrt;\x03⦜\x05rphi;\x02ϕ\x05rrho;\x02ϱ\x04rpi;\x02ϖ\x03rr;\x03↕", - // vcy;[в]. - "\x02y;\x02в", - // vdash;[⊢]. - "\x04ash;\x03⊢", - // veebar;[⊻] vellip;[⋮] verbar;[|] veeeq;[≚] vert;[|] vee;[∨]. - "\x05ebar;\x03⊻\x05llip;\x03⋮\x05rbar;\x01|\x04eeq;\x03≚\x03rt;\x01|\x02e;\x03∨", - // vfr;[𝔳]. - "\x02r;\x04𝔳", - // vltri;[⊲]. - "\x04tri;\x03⊲", - // vnsub;[⊂⃒] vnsup;[⊃⃒]. - "\x04sub;\x06⊂⃒\x04sup;\x06⊃⃒", - // vopf;[𝕧]. - "\x03pf;\x04𝕧", - // vprop;[∝]. - "\x04rop;\x03∝", - // vrtri;[⊳]. - "\x04tri;\x03⊳", - // vsubnE;[⫋︀] vsubne;[⊊︀] vsupnE;[⫌︀] vsupne;[⊋︀] vscr;[𝓋]. - "\x05ubnE;\x06⫋︀\x05ubne;\x06⊊︀\x05upnE;\x06⫌︀\x05upne;\x06⊋︀\x03cr;\x04𝓋", - // vzigzag;[⦚]. - "\x06igzag;\x03⦚", - // wcirc;[ŵ]. - "\x04irc;\x02ŵ", - // wedbar;[⩟] wedgeq;[≙] weierp;[℘] wedge;[∧]. - "\x05dbar;\x03⩟\x05dgeq;\x03≙\x05ierp;\x03℘\x04dge;\x03∧", - // wfr;[𝔴]. - "\x02r;\x04𝔴", - // wopf;[𝕨]. - "\x03pf;\x04𝕨", - // wp;[℘]. - "\x01;\x03℘", - // wreath;[≀] wr;[≀]. - "\x05eath;\x03≀\x01;\x03≀", - // wscr;[𝓌]. - "\x03cr;\x04𝓌", - // xcirc;[◯] xcap;[⋂] xcup;[⋃]. - "\x04irc;\x03◯\x03ap;\x03⋂\x03up;\x03⋃", - // xdtri;[▽]. - "\x04tri;\x03▽", - // xfr;[𝔵]. - "\x02r;\x04𝔵", - // xhArr;[⟺] xharr;[⟷]. - "\x04Arr;\x03⟺\x04arr;\x03⟷", - // xi;[ξ]. - "\x01;\x02ξ", - // xlArr;[⟸] xlarr;[⟵]. - "\x04Arr;\x03⟸\x04arr;\x03⟵", - // xmap;[⟼]. - "\x03ap;\x03⟼", - // xnis;[⋻]. - "\x03is;\x03⋻", - // xoplus;[⨁] xotime;[⨂] xodot;[⨀] xopf;[𝕩]. - "\x05plus;\x03⨁\x05time;\x03⨂\x04dot;\x03⨀\x03pf;\x04𝕩", - // xrArr;[⟹] xrarr;[⟶]. - "\x04Arr;\x03⟹\x04arr;\x03⟶", - // xsqcup;[⨆] xscr;[𝓍]. - "\x05qcup;\x03⨆\x03cr;\x04𝓍", - // xuplus;[⨄] xutri;[△]. - "\x05plus;\x03⨄\x04tri;\x03△", - // xvee;[⋁]. - "\x03ee;\x03⋁", - // xwedge;[⋀]. - "\x05edge;\x03⋀", - // yacute;[ý] yacute[ý] yacy;[я]. - "\x05cute;\x02ý\x04cute\x02ý\x03cy;\x02я", - // ycirc;[ŷ] ycy;[ы]. - "\x04irc;\x02ŷ\x02y;\x02ы", - // yen;[¥] yen[¥]. - "\x02n;\x02¥\x01n\x02¥", - // yfr;[𝔶]. - "\x02r;\x04𝔶", - // yicy;[ї]. - "\x03cy;\x02ї", - // yopf;[𝕪]. - "\x03pf;\x04𝕪", - // yscr;[𝓎]. - "\x03cr;\x04𝓎", - // yucy;[ю] yuml;[ÿ] yuml[ÿ]. - "\x03cy;\x02ю\x03ml;\x02ÿ\x02ml\x02ÿ", - // zacute;[ź]. - "\x05cute;\x02ź", - // zcaron;[ž] zcy;[з]. - "\x05aron;\x02ž\x02y;\x02з", - // zdot;[ż]. - "\x03ot;\x02ż", - // zeetrf;[ℨ] zeta;[ζ]. - "\x05etrf;\x03ℨ\x03ta;\x02ζ", - // zfr;[𝔷]. - "\x02r;\x04𝔷", - // zhcy;[ж]. - "\x03cy;\x02ж", - // zigrarr;[⇝]. - "\x06grarr;\x03⇝", - // zopf;[𝕫]. - "\x03pf;\x04𝕫", - // zscr;[𝓏]. - "\x03cr;\x04𝓏", - // zwnj;[‌] zwj;[‍]. - "\x03nj;\x03‌\x02j;\x03‍", - ), - "small_words" => "GT\x00LT\x00gt\x00lt\x00", - "small_mappings" => array( - ">", - "<", - ">", - "<", - ) - ) - ); -} diff --git a/lib/compat/wordpress-6.6/option.php b/lib/compat/wordpress-6.6/option.php deleted file mode 100644 index 9ec81467c261da..00000000000000 --- a/lib/compat/wordpress-6.6/option.php +++ /dev/null @@ -1,53 +0,0 @@ - __( 'Title' ), - 'blogdescription' => __( 'Tagline' ), - 'show_on_front' => __( 'Show on front' ), - 'page_on_front' => __( 'Page on front' ), - 'posts_per_page' => __( 'Maximum posts per page' ), - 'default_comment_status' => __( 'Allow comments on new posts' ), - ); - - if ( isset( $settings_label_map[ $option_name ] ) ) { - $args['label'] = $settings_label_map[ $option_name ]; - } - - // Don't update schema when a setting isn't exposed via REST API. - if ( ! isset( $args['show_in_rest'] ) ) { - return $args; - } - - // Don't update schema when label isn't provided. - if ( ! isset( $args['label'] ) ) { - return $args; - } - - $schema = array( 'title' => $args['label'] ); - if ( ! is_array( $args['show_in_rest'] ) ) { - $args['show_in_rest'] = array( - 'schema' => $schema, - ); - return $args; - } - - if ( ! empty( $args['show_in_rest']['schema'] ) ) { - $args['show_in_rest']['schema'] = array_merge( $args['show_in_rest']['schema'], $schema ); - } else { - $args['show_in_rest']['schema'] = $schema; - } - - return $args; -} -add_filter( 'register_setting_args', 'gutenberg_update_initial_settings', 10, 4 ); diff --git a/lib/compat/wordpress-6.6/post.php b/lib/compat/wordpress-6.6/post.php deleted file mode 100644 index 8415f3bf42f18f..00000000000000 --- a/lib/compat/wordpress-6.6/post.php +++ /dev/null @@ -1,39 +0,0 @@ -item_updated = __( 'Template updated.', 'gutenberg' ); - return $labels; -} -add_filter( 'post_type_labels_wp_template', 'gutenberg_update_wp_template_labels', 10, 1 ); - -/** - * Updates the labels for the template parts post type. - * - * @param object $labels Object with labels for the post type as member variables. - * @return object Object with all the labels as member variables. - */ -function gutenberg_update_wp_template__part_labels( $labels ) { - $labels->item_updated = __( 'Template part updated.', 'gutenberg' ); - return $labels; -} -add_filter( 'post_type_labels_wp_template_part', 'gutenberg_update_wp_template__part_labels', 10, 1 ); diff --git a/lib/compat/wordpress-6.6/resolve-patterns.php b/lib/compat/wordpress-6.6/resolve-patterns.php deleted file mode 100644 index 5105619c42613c..00000000000000 --- a/lib/compat/wordpress-6.6/resolve-patterns.php +++ /dev/null @@ -1,84 +0,0 @@ -get_registered( $slug ); - - // Skip unknown patterns. - if ( ! $pattern ) { - ++$i; - continue; - } - - $blocks_to_insert = parse_blocks( $pattern['content'] ); - $seen_refs[ $slug ] = true; - $blocks_to_insert = gutenberg_replace_pattern_blocks( $blocks_to_insert ); - unset( $seen_refs[ $slug ] ); - array_splice( $blocks, $i, 1, $blocks_to_insert ); - - // If we have inner content, we need to insert nulls in the - // inner content array, otherwise serialize_blocks will skip - // blocks. - if ( $inner_content ) { - $null_indices = array_keys( $inner_content, null, true ); - $content_index = $null_indices[ $i ]; - $nulls = array_fill( 0, count( $blocks_to_insert ), null ); - array_splice( $inner_content, $content_index, 1, $nulls ); - } - - // Skip inserted blocks. - $i += count( $blocks_to_insert ); - } else { - if ( ! empty( $blocks[ $i ]['innerBlocks'] ) ) { - $blocks[ $i ]['innerBlocks'] = gutenberg_replace_pattern_blocks( - $blocks[ $i ]['innerBlocks'], - $blocks[ $i ]['innerContent'] - ); - } - ++$i; - } - } - return $blocks; -} - -function gutenberg_replace_pattern_blocks_patterns_endpoint( $result, $server, $request ) { - if ( $request->get_route() !== '/wp/v2/block-patterns/patterns' ) { - return $result; - } - - $data = $result->get_data(); - - foreach ( $data as $index => $pattern ) { - $blocks = parse_blocks( $pattern['content'] ); - $blocks = gutenberg_replace_pattern_blocks( $blocks ); - $data[ $index ]['content'] = serialize_blocks( $blocks ); - } - - $result->set_data( $data ); - - return $result; -} - -// Similarly, for patterns, we can avoid the double parse here: -// https://github.com/WordPress/wordpress-develop/blob/02fb53498f1ce7e63d807b9bafc47a7dba19d169/src/wp-includes/class-wp-block-patterns-registry.php#L175 -add_filter( 'rest_post_dispatch', 'gutenberg_replace_pattern_blocks_patterns_endpoint', 10, 3 ); diff --git a/lib/compat/wordpress-6.6/rest-api.php b/lib/compat/wordpress-6.6/rest-api.php deleted file mode 100644 index eadd3b1d376a72..00000000000000 --- a/lib/compat/wordpress-6.6/rest-api.php +++ /dev/null @@ -1,216 +0,0 @@ - true, - 'show_in_rest' => true, - ), - 'names' - ); - - if ( ! empty( $post_types ) ) { - register_rest_field( - $post_types, - 'class_list', - array( - 'get_callback' => 'gutenberg_add_class_list_to_api_response', - 'schema' => array( - 'description' => __( 'An array of the class names for the post container element.', 'gutenberg' ), - 'type' => 'array', - 'items' => array( - 'type' => 'string', - ), - ), - ) - ); - } -} -add_action( 'rest_api_init', 'gutenberg_add_class_list_to_public_post_types' ); - - -/** - * Registers the Global Styles Revisions REST API routes. - */ -function gutenberg_register_global_styles_revisions_endpoints() { - $global_styles_revisions_controller = new Gutenberg_REST_Global_Styles_Revisions_Controller_6_6(); - $global_styles_revisions_controller->register_routes(); -} - -add_action( 'rest_api_init', 'gutenberg_register_global_styles_revisions_endpoints' ); - -/** - * Adds `stylesheet_uri` fields to WP_REST_Themes_Controller class. - */ -function gutenberg_register_wp_rest_themes_stylesheet_directory_uri_field() { - register_rest_field( - 'theme', - 'stylesheet_uri', - array( - 'get_callback' => function ( $item ) { - if ( ! empty( $item['stylesheet'] ) ) { - $theme = wp_get_theme( $item['stylesheet'] ); - $current_theme = wp_get_theme(); - if ( $theme->get_stylesheet() === $current_theme->get_stylesheet() ) { - return get_stylesheet_directory_uri(); - } else { - return $theme->get_stylesheet_directory_uri(); - } - } - - return null; - }, - 'schema' => array( - 'type' => 'string', - 'description' => __( 'The uri for the theme\'s stylesheet directory.', 'gutenberg' ), - 'format' => 'uri', - 'readonly' => true, - 'context' => array( 'view', 'edit', 'embed' ), - ), - ) - ); -} -add_action( 'rest_api_init', 'gutenberg_register_wp_rest_themes_stylesheet_directory_uri_field' ); - -/** - * Adds `template_uri` fields to WP_REST_Themes_Controller class. - */ -function gutenberg_register_wp_rest_themes_template_directory_uri_field() { - register_rest_field( - 'theme', - 'template_uri', - array( - 'get_callback' => function ( $item ) { - if ( ! empty( $item['stylesheet'] ) ) { - $theme = wp_get_theme( $item['stylesheet'] ); - $current_theme = wp_get_theme(); - if ( $theme->get_stylesheet() === $current_theme->get_stylesheet() ) { - return get_template_directory_uri(); - } else { - return $theme->get_template_directory_uri(); - } - } - - return null; - }, - 'schema' => array( - 'type' => 'string', - 'description' => __( 'The uri for the theme\'s template directory. If this is a child theme, this refers to the parent theme, otherwise this is the same as the theme\'s stylesheet directory.', 'gutenberg' ), - 'format' => 'uri', - 'readonly' => true, - 'context' => array( 'view', 'edit', 'embed' ), - ), - ) - ); -} -add_action( 'rest_api_init', 'gutenberg_register_wp_rest_themes_template_directory_uri_field' ); - -/** - * Adds `template` and `template_lock` fields to WP_REST_Post_Types_Controller class. - */ -function gutenberg_register_wp_rest_post_types_controller_fields() { - register_rest_field( - 'type', - 'template', - array( - 'get_callback' => function ( $item ) { - $post_type = get_post_type_object( $item['slug'] ); - if ( ! empty( $post_type ) ) { - return $post_type->template ?? array(); - } - }, - 'schema' => array( - 'type' => 'array', - 'description' => __( 'The block template associated with the post type.', 'gutenberg' ), - 'readonly' => true, - 'context' => array( 'view', 'edit', 'embed' ), - ), - ) - ); - register_rest_field( - 'type', - 'template_lock', - array( - 'get_callback' => function ( $item ) { - $post_type = get_post_type_object( $item['slug'] ); - if ( ! empty( $post_type ) ) { - return ! empty( $post_type->template_lock ) ? $post_type->template_lock : false; - } - }, - 'schema' => array( - 'type' => array( 'string', 'boolean' ), - 'enum' => array( 'all', 'insert', 'contentOnly', false ), - 'description' => __( 'The template_lock associated with the post type, or false if none.', 'gutenberg' ), - 'readonly' => true, - 'context' => array( 'view', 'edit', 'embed' ), - ), - ) - ); -} -add_action( 'rest_api_init', 'gutenberg_register_wp_rest_post_types_controller_fields' ); - -/** - * Preload theme and global styles paths to avoid flash of variation styles in post editor. - * - * @param array $paths REST API paths to preload. - * @param WP_Block_Editor_Context $context Current block editor context. - * @return array Filtered preload paths. - */ -function gutenberg_block_editor_preload_paths_6_6( $paths, $context ) { - if ( 'core/edit-post' === $context->name ) { - $paths[] = '/wp/v2/global-styles/themes/' . get_stylesheet(); - $paths[] = '/wp/v2/themes?context=edit&status=active'; - $paths[] = '/wp/v2/global-styles/' . WP_Theme_JSON_Resolver_Gutenberg::get_user_global_styles_post_id() . '?context=edit'; - } - return $paths; -} -add_filter( 'block_editor_rest_api_preload_paths', 'gutenberg_block_editor_preload_paths_6_6', 10, 2 ); diff --git a/lib/compat/wordpress-6.7/block-bindings.php b/lib/compat/wordpress-6.7/block-bindings.php index 3cecb7fbc0985c..08608a8d394e72 100644 --- a/lib/compat/wordpress-6.7/block-bindings.php +++ b/lib/compat/wordpress-6.7/block-bindings.php @@ -32,7 +32,7 @@ function gutenberg_bootstrap_server_block_bindings_sources() { /** * Initialize `canUpdateBlockBindings` editor setting if it doesn't exist. By default, it is `true` only for admin users. * - * @param array $settings The block editor settings from the `block_editor_settings_all` filter. + * @param array $editor_settings The block editor settings from the `block_editor_settings_all` filter. * @return array The editor settings including `canUpdateBlockBindings`. */ function gutenberg_add_can_update_block_bindings_editor_setting( $editor_settings ) { diff --git a/lib/compat/wordpress-6.7/class-gutenberg-rest-templates-controller-6-7.php b/lib/compat/wordpress-6.7/class-gutenberg-rest-templates-controller-6-7.php index ed67dded75ecb1..e5f6eb126f2a6a 100644 --- a/lib/compat/wordpress-6.7/class-gutenberg-rest-templates-controller-6-7.php +++ b/lib/compat/wordpress-6.7/class-gutenberg-rest-templates-controller-6-7.php @@ -9,7 +9,7 @@ * Gutenberg_REST_Templates_Controller_6_7 class * */ -class Gutenberg_REST_Templates_Controller_6_7 extends Gutenberg_REST_Templates_Controller_6_6 { +class Gutenberg_REST_Templates_Controller_6_7 extends WP_REST_Templates_Controller { /** * Returns the given template * diff --git a/lib/compat/wordpress-6.7/compat.php b/lib/compat/wordpress-6.7/compat.php index e58eca56ef71f9..98ea34c813ae43 100644 --- a/lib/compat/wordpress-6.7/compat.php +++ b/lib/compat/wordpress-6.7/compat.php @@ -46,7 +46,7 @@ function _gutenberg_add_block_templates_from_registry( $query_result, $query, $t // See: https://github.com/WordPress/gutenberg/issues/65584 $template_files_query = $query; unset( $template_files_query['post_type'] ); - $template_files = _gutenberg_get_block_templates_files( $template_type, $template_files_query ); + $template_files = _get_block_templates_files( $template_type, $template_files_query ); /* * Add templates registered in the template registry. Filtering out the ones which have a theme file. @@ -73,8 +73,8 @@ function ( $registered_template ) use ( $template_files ) { /** * Hooks into `get_block_template` to add the `plugin` property when necessary. * - * @param [WP_Block_Template|null] $block_template The found block template, or null if there isn’t one. - * @return [WP_Block_Template|null] The block template that was already found with the plugin property defined if it was registered by a plugin. + * @param WP_Block_Template|null $block_template The found block template, or null if there isn’t one. + * @return WP_Block_Template|null The block template that was already found with the plugin property defined if it was registered by a plugin. */ function _gutenberg_add_block_template_plugin_attribute( $block_template ) { if ( $block_template ) { diff --git a/lib/compat/wordpress-6.7/rest-api.php b/lib/compat/wordpress-6.7/rest-api.php index 741d0ad1805e07..2b53e6865dfa9b 100644 --- a/lib/compat/wordpress-6.7/rest-api.php +++ b/lib/compat/wordpress-6.7/rest-api.php @@ -124,15 +124,18 @@ function gutenberg_register_wp_rest_templates_controller_plugin_field() { } add_action( 'rest_api_init', 'gutenberg_register_wp_rest_templates_controller_plugin_field' ); -/** - * Overrides the default 'WP_REST_Server' class. - * - * @return string The name of the custom server class. - */ -function gutenberg_override_default_rest_server() { - return 'Gutenberg_REST_Server'; +// The `get_user` function was introduced in WP 6.7. +if ( ! function_exists( 'get_user' ) ) { + /** + * Overrides the default 'WP_REST_Server' class. + * + * @return string The name of the custom server class. + */ + function gutenberg_override_default_rest_server() { + return 'Gutenberg_REST_Server'; + } + add_filter( 'wp_rest_server_class', 'gutenberg_override_default_rest_server', 1 ); } -add_filter( 'wp_rest_server_class', 'gutenberg_override_default_rest_server', 1 ); /** diff --git a/lib/compat/wordpress-6.8/blocks.php b/lib/compat/wordpress-6.8/blocks.php index 3124dd2a12a615..6cfa98691020ef 100644 --- a/lib/compat/wordpress-6.8/blocks.php +++ b/lib/compat/wordpress-6.8/blocks.php @@ -5,43 +5,175 @@ * @package gutenberg */ +function gutenberg_apply_block_hooks_to_post_content( $content ) { + // The `the_content` filter does not provide the post that the content is coming from. + // However, we can infer it by calling `get_post()`, which will return the current post + // if no post ID is provided. + return apply_block_hooks_to_content( $content, get_post(), 'insert_hooked_blocks' ); +} +// We need to apply this filter before `do_blocks` (which is hooked to `the_content` at priority 9). +add_filter( 'the_content', 'gutenberg_apply_block_hooks_to_post_content', 8 ); + /** - * Filters the block type arguments during registration to stabilize experimental block supports. + * Hooks into the REST API response for the Posts endpoint and adds the first and last inner blocks. * - * This is a temporary compatibility shim as the approach in core is for this to be handled - * within the WP_Block_Type class rather than requiring a filter. + * @since 6.6.0 + * @since 6.8.0 Support non-`wp_navigation` post types. * - * @param array $args Array of arguments for registering a block type. - * @return array Array of arguments for registering a block type. + * @param WP_REST_Response $response The response object. + * @param WP_Post $post Post object. + * @return WP_REST_Response The response object. */ -function gutenberg_stabilize_experimental_block_supports( $args ) { - if ( empty( $args['supports']['typography'] ) ) { - return $args; - } - - $experimental_typography_supports_to_stable = array( - '__experimentalFontFamily' => 'fontFamily', - '__experimentalFontStyle' => 'fontStyle', - '__experimentalFontWeight' => 'fontWeight', - '__experimentalLetterSpacing' => 'letterSpacing', - '__experimentalTextDecoration' => 'textDecoration', - '__experimentalTextTransform' => 'textTransform', +function gutenberg_insert_hooked_blocks_into_rest_response( $response, $post ) { + if ( empty( $response->data['content']['raw'] ) ) { + return $response; + } + + $attributes = array(); + $ignored_hooked_blocks = get_post_meta( $post->ID, '_wp_ignored_hooked_blocks', true ); + if ( ! empty( $ignored_hooked_blocks ) ) { + $ignored_hooked_blocks = json_decode( $ignored_hooked_blocks, true ); + $attributes['metadata'] = array( + 'ignoredHookedBlocks' => $ignored_hooked_blocks, + ); + } + + if ( 'wp_navigation' === $post->post_type ) { + $wrapper_block_type = 'core/navigation'; + } elseif ( 'wp_block' === $post->post_type ) { + $wrapper_block_type = 'core/block'; + } else { + $wrapper_block_type = 'core/post-content'; + } + + $content = get_comment_delimited_block_content( + $wrapper_block_type, + $attributes, + $response->data['content']['raw'] ); - $current_typography_supports = $args['supports']['typography']; - $stable_typography_supports = array(); + $content = apply_block_hooks_to_content( + $content, + $post, + 'insert_hooked_blocks_and_set_ignored_hooked_blocks_metadata' + ); - foreach ( $current_typography_supports as $key => $value ) { - if ( array_key_exists( $key, $experimental_typography_supports_to_stable ) ) { - $stable_typography_supports[ $experimental_typography_supports_to_stable[ $key ] ] = $value; - } else { - $stable_typography_supports[ $key ] = $value; - } + // Remove mock block wrapper. + $content = remove_serialized_parent_block( $content ); + + $response->data['content']['raw'] = $content; + + // If the rendered content was previously empty, we leave it like that. + if ( empty( $response->data['content']['rendered'] ) ) { + return $response; + } + + // No need to inject hooked blocks twice. + $priority = has_filter( 'the_content', 'apply_block_hooks_to_content' ); + if ( false !== $priority ) { + remove_filter( 'the_content', 'apply_block_hooks_to_content', $priority ); } - $args['supports']['typography'] = $stable_typography_supports; + /** This filter is documented in wp-includes/post-template.php */ + $response->data['content']['rendered'] = apply_filters( 'the_content', $content ); + + // Add back the filter. + if ( false !== $priority ) { + add_filter( 'the_content', 'apply_block_hooks_to_content', $priority ); + } - return $args; + return $response; } +add_filter( 'rest_prepare_page', 'gutenberg_insert_hooked_blocks_into_rest_response', 10, 2 ); +add_filter( 'rest_prepare_post', 'gutenberg_insert_hooked_blocks_into_rest_response', 10, 2 ); +add_filter( 'rest_prepare_wp_block', 'gutenberg_insert_hooked_blocks_into_rest_response', 10, 2 ); -add_filter( 'register_block_type_args', 'gutenberg_stabilize_experimental_block_supports', PHP_INT_MAX, 1 ); +/** + * Updates the wp_postmeta with the list of ignored hooked blocks + * where the inner blocks are stored as post content. + * + * @since 6.6.0 + * @since 6.8.0 Support other post types. (Previously, it was limited to `wp_navigation` only.) + * @access private + * + * @param stdClass $post Post object. + * @return stdClass The updated post object. + */ +function gutenberg_update_ignored_hooked_blocks_postmeta( $post ) { + /* + * In this scenario the user has likely tried to create a new post object via the REST API. + * In which case we won't have a post ID to work with and store meta against. + */ + if ( empty( $post->ID ) ) { + return $post; + } + + /* + * Skip meta generation when consumers intentionally update specific fields + * and omit the content update. + */ + if ( ! isset( $post->post_content ) ) { + return $post; + } + + /* + * Skip meta generation if post type is not set. + */ + if ( ! isset( $post->post_type ) ) { + return $post; + } + + $attributes = array(); + + $ignored_hooked_blocks = get_post_meta( $post->ID, '_wp_ignored_hooked_blocks', true ); + if ( ! empty( $ignored_hooked_blocks ) ) { + $ignored_hooked_blocks = json_decode( $ignored_hooked_blocks, true ); + $attributes['metadata'] = array( + 'ignoredHookedBlocks' => $ignored_hooked_blocks, + ); + } + + if ( 'wp_navigation' === $post->post_type ) { + $wrapper_block_type = 'core/navigation'; + } elseif ( 'wp_block' === $post->post_type ) { + $wrapper_block_type = 'core/block'; + } else { + $wrapper_block_type = 'core/post-content'; + } + + $markup = get_comment_delimited_block_content( + $wrapper_block_type, + $attributes, + $post->post_content + ); + + $existing_post = get_post( $post->ID ); + // Merge the existing post object with the updated post object to pass to the block hooks algorithm for context. + $context = (object) array_merge( (array) $existing_post, (array) $post ); + $context = new WP_Post( $context ); // Convert to WP_Post object. + $serialized_block = apply_block_hooks_to_content( $markup, $context, 'set_ignored_hooked_blocks_metadata' ); + $root_block = parse_blocks( $serialized_block )[0]; + + $ignored_hooked_blocks = isset( $root_block['attrs']['metadata']['ignoredHookedBlocks'] ) + ? $root_block['attrs']['metadata']['ignoredHookedBlocks'] + : array(); + + if ( ! empty( $ignored_hooked_blocks ) ) { + $existing_ignored_hooked_blocks = get_post_meta( $post->ID, '_wp_ignored_hooked_blocks', true ); + if ( ! empty( $existing_ignored_hooked_blocks ) ) { + $existing_ignored_hooked_blocks = json_decode( $existing_ignored_hooked_blocks, true ); + $ignored_hooked_blocks = array_unique( array_merge( $ignored_hooked_blocks, $existing_ignored_hooked_blocks ) ); + } + + if ( ! isset( $post->meta_input ) ) { + $post->meta_input = array(); + } + $post->meta_input['_wp_ignored_hooked_blocks'] = json_encode( $ignored_hooked_blocks ); + } + + $post->post_content = remove_serialized_parent_block( $serialized_block ); + return $post; +} +add_filter( 'rest_pre_insert_page', 'gutenberg_update_ignored_hooked_blocks_postmeta' ); +add_filter( 'rest_pre_insert_post', 'gutenberg_update_ignored_hooked_blocks_postmeta' ); +add_filter( 'rest_pre_insert_wp_block', 'gutenberg_update_ignored_hooked_blocks_postmeta' ); diff --git a/lib/compat/wordpress-6.8/class-gutenberg-hierarchical-sort.php b/lib/compat/wordpress-6.8/class-gutenberg-hierarchical-sort.php new file mode 100644 index 00000000000000..f61002f435a760 --- /dev/null +++ b/lib/compat/wordpress-6.8/class-gutenberg-hierarchical-sort.php @@ -0,0 +1,205 @@ + 'id=>parent', + 'posts_per_page' => -1, + ) + ); + $query = new WP_Query( $new_args ); + $posts = $query->posts; + $result = self::sort( $posts ); + + self::$post_ids = $result['post_ids']; + self::$levels = $result['levels']; + } + + /** + * Check if the request is eligible for hierarchical sorting. + * + * @param array $request The request data. + * + * @return bool Return true if the request is eligible for hierarchical sorting. + */ + public static function is_eligible( $request ) { + if ( ! isset( $request['orderby_hierarchy'] ) || true !== $request['orderby_hierarchy'] ) { + return false; + } + + return true; + } + + public static function get_ancestor( $post_id ) { + return get_post( $post_id )->post_parent ?? 0; + } + + /** + * Sort posts by hierarchy. + * + * Takes an array of posts and sorts them based on their parent-child relationships. + * It also tracks the level depth of each post in the hierarchy. + * + * Example input: + * ``` + * [ + * ['ID' => 4, 'post_parent' => 2], + * ['ID' => 2, 'post_parent' => 0], + * ['ID' => 3, 'post_parent' => 2], + * ] + * ``` + * + * Example output: + * ``` + * [ + * 'post_ids' => [2, 4, 3], + * 'levels' => [0, 1, 1] + * ] + * ``` + * + * @param array $posts Array of post objects containing ID and post_parent properties. + * + * @return array { + * Sorted post IDs and their hierarchical levels + * + * @type array $post_ids Array of post IDs + * @type array $levels Array of levels for the corresponding post ID in the same index + * } + */ + public static function sort( $posts ) { + /* + * Arrange pages in two arrays: + * + * - $top_level: posts whose parent is 0 + * - $children: post ID as the key and an array of children post IDs as the value. + * Example: $children[10][] contains all sub-pages whose parent is 10. + * + * Additionally, keep track of the levels of each post in $levels. + * Example: $levels[10] = 0 means the post ID is a top-level page. + * + */ + $top_level = array(); + $children = array(); + foreach ( $posts as $post ) { + if ( empty( $post->post_parent ) ) { + $top_level[] = $post->ID; + } else { + $children[ $post->post_parent ][] = $post->ID; + } + } + + $ids = array(); + $levels = array(); + self::add_hierarchical_ids( $ids, $levels, 0, $top_level, $children ); + + // Process remaining children. + if ( ! empty( $children ) ) { + foreach ( $children as $parent_id => $child_ids ) { + $level = 0; + $ancestor = $parent_id; + while ( 0 !== $ancestor ) { + ++$level; + $ancestor = self::get_ancestor( $ancestor ); + } + self::add_hierarchical_ids( $ids, $levels, $level, $child_ids, $children ); + } + } + + return array( + 'post_ids' => $ids, + 'levels' => $levels, + ); + } + + private static function add_hierarchical_ids( &$ids, &$levels, $level, $to_process, $children ) { + foreach ( $to_process as $id ) { + if ( in_array( $id, $ids, true ) ) { + continue; + } + $ids[] = $id; + $levels[ $id ] = $level; + + if ( isset( $children[ $id ] ) ) { + self::add_hierarchical_ids( $ids, $levels, $level + 1, $children[ $id ], $children ); + unset( $children[ $id ] ); + } + } + } + + public static function get_post_ids() { + return self::$post_ids; + } + + public static function get_levels() { + return self::$levels; + } +} + +add_filter( + 'rest_page_collection_params', + function ( $params ) { + $params['orderby_hierarchy'] = array( + 'description' => 'Sort pages by hierarchy.', + 'type' => 'boolean', + 'default' => false, + ); + return $params; + } +); + +add_filter( + 'rest_page_query', + function ( $args, $request ) { + if ( ! Gutenberg_Hierarchical_Sort::is_eligible( $request ) ) { + return $args; + } + + $hs = Gutenberg_Hierarchical_Sort::get_instance(); + $hs->run( $args ); + + // Reconfigure the args to display only the ids in the list. + $args['post__in'] = $hs->get_post_ids(); + $args['orderby'] = 'post__in'; + + return $args; + }, + 10, + 2 +); + +add_filter( + 'rest_prepare_page', + function ( $response, $post, $request ) { + if ( ! Gutenberg_Hierarchical_Sort::is_eligible( $request ) ) { + return $response; + } + + $hs = Gutenberg_Hierarchical_Sort::get_instance(); + $response->data['level'] = $hs->get_levels()[ $post->ID ]; + + return $response; + }, + 10, + 3 +); diff --git a/lib/compat/wordpress-6.8/class-gutenberg-rest-user-controller.php b/lib/compat/wordpress-6.8/class-gutenberg-rest-user-controller.php new file mode 100644 index 00000000000000..c1ecb8c86660cd --- /dev/null +++ b/lib/compat/wordpress-6.8/class-gutenberg-rest-user-controller.php @@ -0,0 +1,62 @@ + array(), + 'description' => __( 'Array of column names to be searched.' ), + 'type' => 'array', + 'items' => array( + 'enum' => array( 'email', 'name', 'id', 'username', 'slug' ), + 'type' => 'string', + ), + ); + + return $query_params; +} + +add_filter( 'rest_user_collection_params', 'gutenberg_add_search_columns_param', 10, 1 ); + +/** + * Modify user query based on search_columns parameter + * + * @param array $prepared_args Array of arguments for WP_User_Query. + * @param WP_REST_Request $request The REST API request. + * @return array Modified arguments + */ +function gutenberg_modify_user_query_args( $prepared_args, $request ) { + if ( $request->get_param( 'search' ) && $request->get_param( 'search_columns' ) ) { + $search_columns = $request->get_param( 'search_columns' ); + + // Validate search columns + $valid_columns = isset( $prepared_args['search_columns'] ) + ? $prepared_args['search_columns'] + : array( 'ID', 'user_login', 'user_nicename', 'user_email', 'user_url', 'display_name' ); + $search_columns_mapping = array( + 'id' => 'ID', + 'username' => 'user_login', + 'slug' => 'user_nicename', + 'email' => 'user_email', + 'name' => 'display_name', + ); + $search_columns = array_map( + static function ( $column ) use ( $search_columns_mapping ) { + return $search_columns_mapping[ $column ]; + }, + $search_columns + ); + $search_columns = array_intersect( $search_columns, $valid_columns ); + + if ( ! empty( $search_columns ) ) { + $prepared_args['search_columns'] = $search_columns; + } + } + + return $prepared_args; +} +add_filter( 'rest_user_query', 'gutenberg_modify_user_query_args', 10, 2 ); diff --git a/lib/compat/wordpress-6.8/post.php b/lib/compat/wordpress-6.8/post.php new file mode 100644 index 00000000000000..2477e94f7393c6 --- /dev/null +++ b/lib/compat/wordpress-6.8/post.php @@ -0,0 +1,26 @@ + 'template-locked', + ); + } + + return $args; +} +add_action( 'register_page_post_type_args', 'gutenberg_update_page_editor_support' ); diff --git a/lib/compat/wordpress-6.8/preload.php b/lib/compat/wordpress-6.8/preload.php index aabe0d4fb574cc..0e887fc081bcb5 100644 --- a/lib/compat/wordpress-6.8/preload.php +++ b/lib/compat/wordpress-6.8/preload.php @@ -10,10 +10,27 @@ */ function gutenberg_block_editor_preload_paths_6_8( $paths, $context ) { if ( 'core/edit-site' === $context->name ) { - if ( ! empty( $_GET['postId'] ) ) { - $route_for_post = rest_get_route_for_post( $_GET['postId'] ); + $post = null; + if ( isset( $_GET['postId'] ) && is_numeric( $_GET['postId'] ) ) { + $post = get_post( (int) $_GET['postId'] ); + } + if ( isset( $_GET['p'] ) && preg_match( '/^\/page\/(\d+)$/', $_GET['p'], $matches ) ) { + $post = get_post( (int) $matches[1] ); + } + + if ( $post ) { + $route_for_post = rest_get_route_for_post( $post ); if ( $route_for_post ) { $paths[] = add_query_arg( 'context', 'edit', $route_for_post ); + $paths[] = add_query_arg( 'context', 'edit', '/wp/v2/types/' . $post->post_type ); + if ( 'page' === $post->post_type ) { + $paths[] = add_query_arg( + 'slug', + // @see https://github.com/WordPress/gutenberg/blob/489f6067c623926bce7151a76755bb68d8e22ea7/packages/edit-site/src/components/sync-state-with-url/use-init-edited-entity-from-url.js#L139-L140 + 'page-' . $post->post_name, + '/wp/v2/templates/lookup' + ); + } } } @@ -31,6 +48,8 @@ function gutenberg_block_editor_preload_paths_6_8( $paths, $context ) { 'site_icon_url', 'site_logo', 'timezone_string', + 'default_template_part_areas', + 'default_template_types', 'url', ) ); @@ -68,6 +87,9 @@ function gutenberg_block_editor_preload_paths_6_8( $paths, $context ) { */ $context = current_user_can( 'edit_theme_options' ) ? 'edit' : 'view'; $paths[] = "/wp/v2/global-styles/$global_styles_id?context=$context"; + + // Used by getBlockPatternCategories in useBlockEditorSettings. + $paths[] = '/wp/v2/block-patterns/categories'; } return $paths; } diff --git a/lib/compat/wordpress-6.8/rest-api.php b/lib/compat/wordpress-6.8/rest-api.php new file mode 100644 index 00000000000000..cc3d3e89014e93 --- /dev/null +++ b/lib/compat/wordpress-6.8/rest-api.php @@ -0,0 +1,74 @@ +args ) && is_array( $t->args ) ) { + $args = array_merge( $args, $t->args ); + } + return $args; +} +add_action( + 'registered_taxonomy', + function ( $taxonomy ) { + add_filter( "rest_{$taxonomy}_query", 'gutenberg_respect_taxonomy_default_args_in_rest_api' ); + } +); +add_action( + 'unregistered_taxonomy', + function ( $taxonomy ) { + remove_filter( "rest_{$taxonomy}_query", 'gutenberg_respect_taxonomy_default_args_in_rest_api' ); + } +); + +/** + * Adds the default template part areas to the REST API index. + * + * This function exposes the default template part areas through the WordPress REST API. + * Note: This function backports into the wp-includes/rest-api/class-wp-rest-server.php file. + * + * @param WP_REST_Response $response REST API response. + * @return WP_REST_Response Modified REST API response with default template part areas. + */ +function gutenberg_add_default_template_part_areas_to_index( WP_REST_Response $response ) { + $response->data['default_template_part_areas'] = get_allowed_block_template_part_areas(); + return $response; +} + +add_filter( 'rest_index', 'gutenberg_add_default_template_part_areas_to_index' ); + +/** + * Adds the default template types to the REST API index. + * + * This function exposes the default template types through the WordPress REST API. + * Note: This function backports into the wp-includes/rest-api/class-wp-rest-server.php file. + * + * @param WP_REST_Response $response REST API response. + * @return WP_REST_Response Modified REST API response with default template part areas. + */ +function gutenberg_add_default_template_types_to_index( WP_REST_Response $response ) { + $indexed_template_types = array(); + foreach ( get_default_block_template_types() as $slug => $template_type ) { + $template_type['slug'] = (string) $slug; + $indexed_template_types[] = $template_type; + } + + $response->data['default_template_types'] = $indexed_template_types; + return $response; +} + +add_filter( 'rest_index', 'gutenberg_add_default_template_types_to_index' ); diff --git a/lib/compat/wordpress-6.8/site-editor.php b/lib/compat/wordpress-6.8/site-editor.php new file mode 100644 index 00000000000000..9b2575676047d1 --- /dev/null +++ b/lib/compat/wordpress-6.8/site-editor.php @@ -0,0 +1,148 @@ + '/wp_navigation/' . $_REQUEST['postId'] ), remove_query_arg( array( 'postType', 'postId' ) ) ); + } + + if ( isset( $_REQUEST['postType'] ) && 'wp_navigation' === $_REQUEST['postType'] && empty( $_REQUEST['postId'] ) ) { + return add_query_arg( array( 'p' => '/navigation' ), remove_query_arg( 'postType' ) ); + } + + if ( isset( $_REQUEST['path'] ) && '/wp_global_styles' === $_REQUEST['path'] ) { + return add_query_arg( array( 'p' => '/styles' ), remove_query_arg( 'path' ) ); + } + + if ( isset( $_REQUEST['postType'] ) && 'page' === $_REQUEST['postType'] && ( empty( $_REQUEST['canvas'] ) || empty( $_REQUEST['postId'] ) ) ) { + return add_query_arg( array( 'p' => '/page' ), remove_query_arg( 'postType' ) ); + } + + if ( isset( $_REQUEST['postType'] ) && 'page' === $_REQUEST['postType'] && ! empty( $_REQUEST['postId'] ) ) { + return add_query_arg( array( 'p' => '/page/' . $_REQUEST['postId'] ), remove_query_arg( array( 'postType', 'postId' ) ) ); + } + + if ( isset( $_REQUEST['postType'] ) && 'wp_template' === $_REQUEST['postType'] && ( empty( $_REQUEST['canvas'] ) || empty( $_REQUEST['postId'] ) ) ) { + return add_query_arg( array( 'p' => '/template' ), remove_query_arg( 'postType' ) ); + } + + if ( isset( $_REQUEST['postType'] ) && 'wp_template' === $_REQUEST['postType'] && ! empty( $_REQUEST['postId'] ) ) { + return add_query_arg( array( 'p' => '/wp_template/' . $_REQUEST['postId'] ), remove_query_arg( array( 'postType', 'postId' ) ) ); + } + + if ( isset( $_REQUEST['postType'] ) && 'wp_block' === $_REQUEST['postType'] && ( empty( $_REQUEST['canvas'] ) || empty( $_REQUEST['postId'] ) ) ) { + return add_query_arg( array( 'p' => '/pattern' ), remove_query_arg( 'postType' ) ); + } + + if ( isset( $_REQUEST['postType'] ) && 'wp_block' === $_REQUEST['postType'] && ! empty( $_REQUEST['postId'] ) ) { + return add_query_arg( array( 'p' => '/wp_block/' . $_REQUEST['postId'] ), remove_query_arg( array( 'postType', 'postId' ) ) ); + } + + if ( isset( $_REQUEST['postType'] ) && 'wp_template_part' === $_REQUEST['postType'] && ( empty( $_REQUEST['canvas'] ) || empty( $_REQUEST['postId'] ) ) ) { + return add_query_arg( array( 'p' => '/pattern' ) ); + } + + if ( isset( $_REQUEST['postType'] ) && 'wp_template_part' === $_REQUEST['postType'] && ! empty( $_REQUEST['postId'] ) ) { + return add_query_arg( array( 'p' => '/wp_template_part/' . $_REQUEST['postId'] ), remove_query_arg( array( 'postType', 'postId' ) ) ); + } + + // The following redirects are for backward compatibility with the old site editor URLs. + if ( isset( $_REQUEST['path'] ) && '/wp_template_part/all' === $_REQUEST['path'] ) { + return add_query_arg( + array( + 'p' => '/pattern', + 'postType' => 'wp_template_part', + ), + remove_query_arg( 'path' ) + ); + } + + if ( isset( $_REQUEST['path'] ) && '/page' === $_REQUEST['path'] ) { + return add_query_arg( array( 'p' => '/page' ), remove_query_arg( 'path' ) ); + } + + if ( isset( $_REQUEST['path'] ) && '/wp_template' === $_REQUEST['path'] ) { + return add_query_arg( array( 'p' => '/template' ), remove_query_arg( 'path' ) ); + } + + if ( isset( $_REQUEST['path'] ) && '/patterns' === $_REQUEST['path'] ) { + return add_query_arg( array( 'p' => '/pattern' ), remove_query_arg( 'path' ) ); + } + + if ( isset( $_REQUEST['path'] ) && '/navigation' === $_REQUEST['path'] ) { + return add_query_arg( array( 'p' => '/navigation' ), remove_query_arg( 'path' ) ); + } + + return add_query_arg( array( 'p' => '/' ) ); +} + +function gutenberg_redirect_site_editor_deprecated_urls() { + $redirection = gutenberg_get_site_editor_redirection(); + if ( false !== $redirection ) { + wp_redirect( $redirection, 301 ); + exit; + } +} +add_action( 'admin_init', 'gutenberg_redirect_site_editor_deprecated_urls' ); + +/** + * Filter the `wp_die_handler` to allow access to the Site Editor's new pages page + * for Classic themes. + * + * site-editor.php's access is forbidden for hybrid/classic themes and only allowed with some very special query args (some very special pages like template parts...). + * The only way to disable this protection since we're changing the urls in Gutenberg is to override the wp_die_handler. + * + * @param callable $default_handler The default handler. + * @return callable The default handler or a custom handler. + */ +function gutenberg_styles_wp_die_handler( $default_handler ) { + if ( ! wp_is_block_theme() && str_contains( $_SERVER['REQUEST_URI'], 'site-editor.php' ) && current_user_can( 'edit_theme_options' ) ) { + return '__return_false'; + } + return $default_handler; +} +add_filter( 'wp_die_handler', 'gutenberg_styles_wp_die_handler' ); + +/** + * Add a Styles submenu under the Appearance menu + * for Classic themes. + * + * @global array $submenu + */ +function gutenberg_add_styles_submenu_item() { + if ( ! wp_is_block_theme() && ( current_theme_supports( 'editor-styles' ) || wp_theme_has_theme_json() ) ) { + global $submenu; + + $styles_menu_item = array( + __( 'Design', 'gutenberg' ), + 'edit_theme_options', + 'site-editor.php', + ); + // If $submenu exists, insert the Styles submenu item at position 2. + if ( $submenu && isset( $submenu['themes.php'] ) ) { + // This might not work as expected if the submenu has already been modified. + array_splice( $submenu['themes.php'], 1, 1, array( $styles_menu_item ) ); + } + } +} +add_action( 'admin_menu', 'gutenberg_add_styles_submenu_item' ); diff --git a/lib/demo.php b/lib/demo.php index 11272050bf4c2e..0e7ad5c2c8cc78 100644 --- a/lib/demo.php +++ b/lib/demo.php @@ -11,6 +11,8 @@ /** * Redirects the demo page to edit a new post. + * + * @global string $pagenow The name of the current admin page being viewed. */ function gutenberg_redirect_demo() { global $pagenow; diff --git a/lib/experimental/blocks.php b/lib/experimental/blocks.php index 68113276ec1c06..b4bf0d409b1596 100644 --- a/lib/experimental/blocks.php +++ b/lib/experimental/blocks.php @@ -117,3 +117,22 @@ function gutenberg_register_block_style( $block_name, $style_properties ) { return $result; } + +/** + * Additional data to expose to the view script module in the Form block. + */ +function gutenberg_block_core_form_view_script_module( $data ) { + if ( ! gutenberg_is_experiment_enabled( 'gutenberg-form-blocks' ) ) { + return $data; + } + + $data['nonce'] = wp_create_nonce( 'wp-block-form' ); + $data['ajaxUrl'] = admin_url( 'admin-ajax.php' ); + $data['action'] = 'wp_block_form_email_submit'; + + return $data; +} +add_filter( + 'script_module_data_@wordpress/block-library/form/view', + 'gutenberg_block_core_form_view_script_module' +); diff --git a/lib/experimental/editor-settings.php b/lib/experimental/editor-settings.php index afc6d7e220f676..5b36c32b3c8296 100644 --- a/lib/experimental/editor-settings.php +++ b/lib/experimental/editor-settings.php @@ -37,6 +37,9 @@ function gutenberg_enable_experiments() { if ( $gutenberg_experiments && array_key_exists( 'gutenberg-media-processing', $gutenberg_experiments ) ) { wp_add_inline_script( 'wp-block-editor', 'window.__experimentalMediaProcessing = true', 'before' ); } + if ( $gutenberg_experiments && array_key_exists( 'gutenberg-editor-write-mode', $gutenberg_experiments ) ) { + wp_add_inline_script( 'wp-block-editor', 'window.__experimentalEditorWriteMode = true', 'before' ); + } } add_action( 'admin_init', 'gutenberg_enable_experiments' ); diff --git a/lib/experimental/font-face/bc-layer/webfonts-deprecations.php b/lib/experimental/font-face/bc-layer/webfonts-deprecations.php index 2534d8db165273..fb5e6b315dbdaf 100644 --- a/lib/experimental/font-face/bc-layer/webfonts-deprecations.php +++ b/lib/experimental/font-face/bc-layer/webfonts-deprecations.php @@ -28,7 +28,7 @@ function wp_webfonts() { global $wp_webfonts; if ( ! ( $wp_webfonts instanceof WP_Webfonts ) ) { - $wp_webfonts = new WP_Webfonts( wp_fonts() ); + $wp_webfonts = new WP_Webfonts(); } return $wp_webfonts; diff --git a/lib/experimental/kses-allowed-html.php b/lib/experimental/kses-allowed-html.php index 122faef7b4ca2c..9a4f2e7c614b80 100644 --- a/lib/experimental/kses-allowed-html.php +++ b/lib/experimental/kses-allowed-html.php @@ -40,4 +40,4 @@ function gutenberg_kses_allowed_html( $allowedtags ) { ); return $allowedtags; } -add_filter( 'wp_kses_allowed_html', 'gutenberg_kses_allowed_html', 10, 2 ); +add_filter( 'wp_kses_allowed_html', 'gutenberg_kses_allowed_html' ); diff --git a/lib/experimental/media/load.php b/lib/experimental/media/load.php index bcb02accf62a6b..5e7b00173ca616 100644 --- a/lib/experimental/media/load.php +++ b/lib/experimental/media/load.php @@ -247,6 +247,8 @@ function gutenberg_set_up_cross_origin_isolation() { * Uses an output buffer to add crossorigin="anonymous" where needed. * * @link https://web.dev/coop-coep/ + * + * @global bool $is_safari */ function gutenberg_start_cross_origin_isolation_output_buffer(): void { global $is_safari; @@ -300,7 +302,7 @@ function gutenberg_add_crossorigin_attributes( string $html ): string { $processor->set_bookmark( 'resume' ); - $seeked = false; + $sought = false; $crossorigin = $processor->get_attribute( 'crossorigin' ); @@ -308,16 +310,16 @@ function gutenberg_add_crossorigin_attributes( string $html ): string { if ( is_string( $url ) && ! str_starts_with( $url, $site_url ) && ! str_starts_with( $url, '/' ) && ! is_string( $crossorigin ) ) { if ( 'SOURCE' === $tag ) { - $seeked = $processor->seek( 'audio-video-parent' ); + $sought = $processor->seek( 'audio-video-parent' ); - if ( $seeked ) { + if ( $sought ) { $processor->set_attribute( 'crossorigin', 'anonymous' ); } } else { $processor->set_attribute( 'crossorigin', 'anonymous' ); } - if ( $seeked ) { + if ( $sought ) { $processor->seek( 'resume' ); $processor->release_bookmark( 'audio-video-parent' ); } diff --git a/lib/experimental/posts/load.php b/lib/experimental/posts/load.php index 7321392b11a25d..b6dd9d55a8d7d8 100644 --- a/lib/experimental/posts/load.php +++ b/lib/experimental/posts/load.php @@ -51,7 +51,7 @@ function gutenberg_posts_dashboard() { do_action( 'enqueue_block_editor_assets' ); wp_register_style( 'wp-gutenberg-posts-dashboard', - gutenberg_url( 'build/edit-site/posts.css', __FILE__ ), + gutenberg_url( 'build/edit-site/posts.css' ), array( 'wp-components', 'wp-commands', 'wp-edit-site' ) ); wp_enqueue_style( 'wp-gutenberg-posts-dashboard' ); @@ -69,18 +69,6 @@ function gutenberg_posts_dashboard() { echo '
'; } -/** - * Redirects to the new posts dashboard page and adds the postType query arg. - */ -function gutenberg_add_post_type_arg() { - global $pagenow; - if ( 'admin.php' === $pagenow && isset( $_GET['page'] ) && 'gutenberg-posts-dashboard' === $_GET['page'] && empty( $_GET['postType'] ) ) { - wp_redirect( admin_url( '/admin.php?page=gutenberg-posts-dashboard&postType=post' ) ); - exit; - } -} -add_action( 'admin_init', 'gutenberg_add_post_type_arg' ); - /** * Replaces the default posts menu item with the new posts dashboard. */ diff --git a/lib/experimental/script-modules.php b/lib/experimental/script-modules.php index 5a14e1418ed6de..9657fdad4a7254 100644 --- a/lib/experimental/script-modules.php +++ b/lib/experimental/script-modules.php @@ -55,7 +55,7 @@ function gutenberg_filter_block_type_metadata_settings_register_view_module( $se * @param string $field_name Field name to pick from metadata. * @param int $index Optional. Index of the script to register when multiple items passed. * Default 0. - * @return string Module ID. + * @return string|false Module ID. */ function gutenberg_register_block_module_id( $metadata, $field_name, $index = 0 ) { if ( empty( $metadata[ $field_name ] ) ) { diff --git a/lib/experimental/sync/README.md b/lib/experimental/sync/README.md index 83a105adddf7aa..c8f09d7f1ca5af 100644 --- a/lib/experimental/sync/README.md +++ b/lib/experimental/sync/README.md @@ -2,7 +2,7 @@ The signaling server allows multiple clients to exchange messages with each other through various communication topics. -Topics are not defined upfront, but clients define them by subscribing to them. By subscribing to a given topic, the client tells the server to keep track of its unread messages in the given topic. By unsubscribing from a topic, the client tells the server to free the bookeeping it maintains for the given client and topic. +Topics are not defined upfront, but clients define them by subscribing to them. By subscribing to a given topic, the client tells the server to keep track of its unread messages in the given topic. By unsubscribing from a topic, the client tells the server to free the bookkeeping it maintains for the given client and topic. Every client communicates with the server via `GET` or `POST`. Clients must have a unique identifier, which can be randomly generated. This identifier should be included as a parameter named `subscriber_id` in every request. diff --git a/lib/experiments-page.php b/lib/experiments-page.php index 946b68283a3e0b..c308b7cb5d4039 100644 --- a/lib/experiments-page.php +++ b/lib/experiments-page.php @@ -44,146 +44,158 @@ function gutenberg_initialize_experiments_settings() { ); add_settings_field( - 'gutenberg-sync-collaboration', - __( 'Live Collaboration and offline persistence', 'gutenberg' ), + 'gutenberg-block-experiments', + __( 'Blocks: add experimental blocks', 'gutenberg' ), 'gutenberg_display_experiment_field', 'gutenberg-experiments', 'gutenberg_experiments_section', array( - 'label' => __( 'Enable the live collaboration and offline persistence between peers', 'gutenberg' ), - 'id' => 'gutenberg-sync-collaboration', + 'label' => __( 'Enables experimental blocks on a rolling basis as they are developed.

(Warning: these blocks may have significant changes during development that cause validation errors and display issues.)

', 'gutenberg' ), + 'id' => 'gutenberg-block-experiments', ) ); add_settings_field( - 'gutenberg-custom-dataviews', - __( 'Custom dataviews', 'gutenberg' ), + 'gutenberg-form-blocks', + __( 'Blocks: add Form and input blocks', 'gutenberg' ), 'gutenberg_display_experiment_field', 'gutenberg-experiments', 'gutenberg_experiments_section', array( - 'label' => __( 'Test the custom dataviews in the pages page.', 'gutenberg' ), - 'id' => 'gutenberg-custom-dataviews', + 'label' => __( 'Enables new blocks to allow building forms. You are likely to experience UX issues that are being addressed.', 'gutenberg' ), + 'id' => 'gutenberg-form-blocks', ) ); add_settings_field( - 'gutenberg-color-randomizer', - __( 'Color randomizer ', 'gutenberg' ), + 'gutenberg-grid-interactivity', + __( 'Blocks: add Grid interactivity', 'gutenberg' ), 'gutenberg_display_experiment_field', 'gutenberg-experiments', 'gutenberg_experiments_section', array( - 'label' => __( 'Test the Global Styles color randomizer; a utility that lets you mix the current color palette pseudo-randomly.', 'gutenberg' ), - 'id' => 'gutenberg-color-randomizer', + 'label' => __( 'Enables enhancements to the Grid block that let you move and resize items in the editor canvas.', 'gutenberg' ), + 'id' => 'gutenberg-grid-interactivity', ) ); add_settings_field( - 'gutenberg-block-experiments', - __( 'Experimental blocks', 'gutenberg' ), + 'gutenberg-no-tinymce', + __( 'Blocks: disable TinyMCE and Classic block', 'gutenberg' ), 'gutenberg_display_experiment_field', 'gutenberg-experiments', 'gutenberg_experiments_section', array( - 'label' => __( 'Enable experimental blocks.

(Warning: these blocks may have significant changes during development that cause validation errors and display issues.)

', 'gutenberg' ), - 'id' => 'gutenberg-block-experiments', + 'label' => __( 'Disables the TinyMCE and Classic block', 'gutenberg' ), + 'id' => 'gutenberg-no-tinymce', ) ); add_settings_field( - 'gutenberg-form-blocks', - __( 'Form and input blocks', 'gutenberg' ), + 'gutenberg-media-processing', + __( 'Client-side media processing', 'gutenberg' ), 'gutenberg_display_experiment_field', 'gutenberg-experiments', 'gutenberg_experiments_section', array( - 'label' => __( 'Test new blocks to allow building forms (Warning: The new feature is not ready. You may experience UX issues that are being addressed)', 'gutenberg' ), - 'id' => 'gutenberg-form-blocks', + 'label' => __( 'Enables client-side media processing to leverage the browser\'s capabilities to handle tasks like image resizing and compression.', 'gutenberg' ), + 'id' => 'gutenberg-media-processing', ) ); add_settings_field( - 'gutenberg-grid-interactivity', - __( 'Grid interactivity', 'gutenberg' ), + 'gutenberg-block-comment', + __( 'Collaboration: add block level comments', 'gutenberg' ), 'gutenberg_display_experiment_field', 'gutenberg-experiments', 'gutenberg_experiments_section', array( - 'label' => __( 'Test enhancements to the Grid block that let you move and resize items in the editor canvas.', 'gutenberg' ), - 'id' => 'gutenberg-grid-interactivity', + 'label' => __( 'Enables multi-user block level commenting.', 'gutenberg' ), + 'id' => 'gutenberg-block-comment', ) ); add_settings_field( - 'gutenberg-no-tinymce', - __( 'Disable TinyMCE and Classic block', 'gutenberg' ), + 'gutenberg-sync-collaboration', + __( 'Collaboration: add real time editing', 'gutenberg' ), 'gutenberg_display_experiment_field', 'gutenberg-experiments', 'gutenberg_experiments_section', array( - 'label' => __( 'Disable TinyMCE and Classic block', 'gutenberg' ), - 'id' => 'gutenberg-no-tinymce', + 'label' => __( 'Enables live collaboration and offline persistence between peers.', 'gutenberg' ), + 'id' => 'gutenberg-sync-collaboration', ) ); add_settings_field( - 'gutenberg-full-page-client-side-navigation', - __( 'Enable full page client-side navigation', 'gutenberg' ), + 'gutenberg-color-randomizer', + __( 'Color randomizer', 'gutenberg' ), 'gutenberg_display_experiment_field', 'gutenberg-experiments', 'gutenberg_experiments_section', array( - 'label' => __( 'Enable full page client-side navigation using the Interactivity API', 'gutenberg' ), - 'id' => 'gutenberg-full-page-client-side-navigation', + 'label' => __( 'Enables the Global Styles color randomizer in the Site Editor; a utility that lets you mix the current color palette pseudo-randomly.', 'gutenberg' ), + 'id' => 'gutenberg-color-randomizer', + ) + ); + + add_settings_field( + 'gutenberg-custom-dataviews', + __( 'Data Views: add Custom Views', 'gutenberg' ), + 'gutenberg_display_experiment_field', + 'gutenberg-experiments', + 'gutenberg_experiments_section', + array( + 'label' => __( 'Enables the ability to add, edit, and save custom views when in the Site Editor.', 'gutenberg' ), + 'id' => 'gutenberg-custom-dataviews', ) ); add_settings_field( 'gutenberg-new-posts-dashboard', - __( 'Redesigned posts dashboard', 'gutenberg' ), + __( 'Data Views: enable for Posts', 'gutenberg' ), 'gutenberg_display_experiment_field', 'gutenberg-experiments', 'gutenberg_experiments_section', array( - 'label' => __( 'Enable a redesigned posts dashboard.', 'gutenberg' ), + 'label' => __( 'Enables a redesigned posts dashboard accessible through a submenu item in the Gutenberg plugin.', 'gutenberg' ), 'id' => 'gutenberg-new-posts-dashboard', ) ); add_settings_field( 'gutenberg-quick-edit-dataviews', - __( 'Quick Edit in DataViews', 'gutenberg' ), + __( 'Data Views: add Quick Edit', 'gutenberg' ), 'gutenberg_display_experiment_field', 'gutenberg-experiments', 'gutenberg_experiments_section', array( - 'label' => __( 'Allow access to a quick edit panel in the pages data views.', 'gutenberg' ), + 'label' => __( 'Enables access to a Quick Edit panel in the Site Editor Pages experience.', 'gutenberg' ), 'id' => 'gutenberg-quick-edit-dataviews', ) ); add_settings_field( - 'gutenberg-block-comment', - __( 'Block Comments', 'gutenberg' ), + 'gutenberg-full-page-client-side-navigation', + __( 'iAPI: full page client side navigation', 'gutenberg' ), 'gutenberg_display_experiment_field', 'gutenberg-experiments', 'gutenberg_experiments_section', array( - 'label' => __( 'Enable multi-user commenting on blocks', 'gutenberg' ), - 'id' => 'gutenberg-block-comment', + 'label' => __( 'Enables full-page client-side navigation with the Interactivity API, updating HTML while preserving application state.', 'gutenberg' ), + 'id' => 'gutenberg-full-page-client-side-navigation', ) ); add_settings_field( - 'gutenberg-media-processing', - __( 'Client-side media processing', 'gutenberg' ), + 'gutenberg-editor-write-mode', + __( 'Simplified site editing', 'gutenberg' ), 'gutenberg_display_experiment_field', 'gutenberg-experiments', 'gutenberg_experiments_section', array( - 'label' => __( 'Enable client-side media processing.', 'gutenberg' ), - 'id' => 'gutenberg-media-processing', + 'label' => __( 'Enables Write mode in the Site Editor for a simplified editing experience.', 'gutenberg' ), + 'id' => 'gutenberg-editor-write-mode', ) ); diff --git a/lib/global-styles-and-settings.php b/lib/global-styles-and-settings.php index c4446cf29cf011..3ff5e6cb135e18 100644 --- a/lib/global-styles-and-settings.php +++ b/lib/global-styles-and-settings.php @@ -9,7 +9,7 @@ * Returns the stylesheet resulting of merging core, theme, and user data. * * @param array $types Types of styles to load. Optional. - * It accepts as values: 'variables', 'presets', 'styles', 'base-layout-styles. + * See {@see 'WP_Theme_JSON::get_stylesheet'} for all valid types. * If empty, it'll load the following: * - for themes without theme.json: 'variables', 'presets', 'base-layout-styles'. * - for themes with theme.json: 'variables', 'presets', 'styles'. @@ -142,6 +142,8 @@ function gutenberg_get_global_settings( $path = array(), $context = array() ) { /** * Gets the global styles custom css from theme.json. * + * @deprecated Gutenberg 18.6.0 Use {@see 'gutenberg_get_global_stylesheet'} instead for top-level custom CSS, or {@see 'WP_Theme_JSON_Gutenberg::get_styles_for_block'} for block-level custom CSS. + * * @return string */ function gutenberg_get_global_styles_custom_css() { diff --git a/lib/load.php b/lib/load.php index d7e4a33cd02c92..69ba59e3718842 100644 --- a/lib/load.php +++ b/lib/load.php @@ -35,11 +35,6 @@ function gutenberg_is_experiment_enabled( $name ) { require_once __DIR__ . '/experimental/class-wp-rest-block-editor-settings-controller.php'; } - // WordPress 6.6 compat. - require __DIR__ . '/compat/wordpress-6.6/class-gutenberg-rest-global-styles-revisions-controller-6-6.php'; - require __DIR__ . '/compat/wordpress-6.6/class-gutenberg-rest-templates-controller-6-6.php'; - require __DIR__ . '/compat/wordpress-6.6/rest-api.php'; - // WordPress 6.7 compat. require __DIR__ . '/compat/wordpress-6.7/class-gutenberg-rest-posts-controller-6-7.php'; require __DIR__ . '/compat/wordpress-6.7/class-gutenberg-rest-templates-controller-6-7.php'; @@ -49,6 +44,8 @@ function gutenberg_is_experiment_enabled( $name ) { // WordPress 6.8 compat. require __DIR__ . '/compat/wordpress-6.8/block-comments.php'; require __DIR__ . '/compat/wordpress-6.8/class-gutenberg-rest-comment-controller-6-8.php'; + require __DIR__ . '/compat/wordpress-6.8/class-gutenberg-hierarchical-sort.php'; + require __DIR__ . '/compat/wordpress-6.8/rest-api.php'; // Plugin specific code. require_once __DIR__ . '/class-wp-rest-global-styles-controller-gutenberg.php'; @@ -70,18 +67,9 @@ function gutenberg_is_experiment_enabled( $name ) { require __DIR__ . '/compat/plugin/edit-site-routes-backwards-compat.php'; require __DIR__ . '/compat/plugin/fonts.php'; -// The Token Map was created during 6.6 in order to support the HTML API. It must be loaded before it. -require __DIR__ . '/compat/wordpress-6.6/class-gutenberg-token-map-6-6.php'; +// The Token Map was created to support the HTML API. It must be loaded before it. require __DIR__ . '/compat/wordpress-6.7/class-gutenberg-token-map-6-7.php'; -require __DIR__ . '/compat/wordpress-6.6/html-api/gutenberg-html5-named-character-references-6-6.php'; -require __DIR__ . '/compat/wordpress-6.6/html-api/class-gutenberg-html-decoder-6-6.php'; -require __DIR__ . '/compat/wordpress-6.6/html-api/class-gutenberg-html-tag-processor-6-6.php'; -require __DIR__ . '/compat/wordpress-6.6/html-api/class-gutenberg-html-open-elements-6-6.php'; -require __DIR__ . '/compat/wordpress-6.6/html-api/class-gutenberg-html-stack-event-6-6.php'; -require __DIR__ . '/compat/wordpress-6.6/html-api/class-gutenberg-html-processor-state-6-6.php'; -require __DIR__ . '/compat/wordpress-6.6/html-api/class-gutenberg-html-processor-6-6.php'; - // Type annotations were added in 6.7 so every file is updated. require __DIR__ . '/compat/wordpress-6.7/html-api/class-gutenberg-html-active-formatting-elements-6-7.php'; require __DIR__ . '/compat/wordpress-6.7/html-api/class-gutenberg-html-attribute-token-6-7.php'; @@ -96,17 +84,6 @@ function gutenberg_is_experiment_enabled( $name ) { require __DIR__ . '/compat/wordpress-6.7/html-api/class-gutenberg-html-processor-state-6-7.php'; require __DIR__ . '/compat/wordpress-6.7/html-api/class-gutenberg-html-processor-6-7.php'; -// WordPress 6.6 compat. -require __DIR__ . '/compat/wordpress-6.6/admin-bar.php'; -require __DIR__ . '/compat/wordpress-6.6/blocks.php'; -require __DIR__ . '/compat/wordpress-6.6/block-editor.php'; -require __DIR__ . '/compat/wordpress-6.6/compat.php'; -require __DIR__ . '/compat/wordpress-6.6/resolve-patterns.php'; -require __DIR__ . '/compat/wordpress-6.6/block-bindings/pattern-overrides.php'; -require __DIR__ . '/compat/wordpress-6.6/block-template-utils.php'; -require __DIR__ . '/compat/wordpress-6.6/option.php'; -require __DIR__ . '/compat/wordpress-6.6/post.php'; - // WordPress 6.7 compat. require __DIR__ . '/compat/wordpress-6.7/block-templates.php'; require __DIR__ . '/compat/wordpress-6.7/blocks.php'; @@ -120,6 +97,9 @@ function gutenberg_is_experiment_enabled( $name ) { require __DIR__ . '/compat/wordpress-6.8/preload.php'; require __DIR__ . '/compat/wordpress-6.8/blocks.php'; require __DIR__ . '/compat/wordpress-6.8/functions.php'; +require __DIR__ . '/compat/wordpress-6.8/post.php'; +require __DIR__ . '/compat/wordpress-6.8/site-editor.php'; +require __DIR__ . '/compat/wordpress-6.8/class-gutenberg-rest-user-controller.php'; // Experimental features. require __DIR__ . '/experimental/block-editor-settings-mobile.php'; diff --git a/lib/rest-api.php b/lib/rest-api.php index 7570bb19737233..783abc24d3ee38 100644 --- a/lib/rest-api.php +++ b/lib/rest-api.php @@ -19,15 +19,14 @@ * @return array Array of arguments for registering a post type. */ function gutenberg_override_global_styles_endpoint( array $args ): array { - $args['rest_controller_class'] = 'WP_REST_Global_Styles_Controller_Gutenberg'; - $args['revisions_rest_controller_class'] = 'Gutenberg_REST_Global_Styles_Revisions_Controller_6_6'; - $args['late_route_registration'] = true; - $args['show_in_rest'] = true; - $args['rest_base'] = 'global-styles'; + $args['rest_controller_class'] = 'WP_REST_Global_Styles_Controller_Gutenberg'; + $args['late_route_registration'] = true; + $args['show_in_rest'] = true; + $args['rest_base'] = 'global-styles'; return $args; } -add_filter( 'register_wp_global_styles_post_type_args', 'gutenberg_override_global_styles_endpoint', 10, 2 ); +add_filter( 'register_wp_global_styles_post_type_args', 'gutenberg_override_global_styles_endpoint' ); /** * Registers the Edit Site Export REST API routes. diff --git a/lib/theme-i18n.json b/lib/theme-i18n.json index e4d14502132cbe..1b7a8d0d31190b 100644 --- a/lib/theme-i18n.json +++ b/lib/theme-i18n.json @@ -45,6 +45,13 @@ } ] }, + "shadow": { + "presets": [ + { + "name": "Shadow name" + } + ] + }, "blocks": { "*": { "typography": { @@ -69,6 +76,18 @@ { "name": "Gradient name" } + ], + "duotone": [ + { + "name": "Duotone name" + } + ] + }, + "dimensions": { + "aspectRatios": [ + { + "name": "Aspect ratio name" + } ] }, "spacing": { diff --git a/package-lock.json b/package-lock.json index 0bcf8aee842fd4..56fc08877bfa0e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "gutenberg", - "version": "19.7.0-rc.1", + "version": "20.1.0-rc.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "gutenberg", - "version": "19.7.0-rc.1", + "version": "20.1.0-rc.1", "hasInstallScript": true, "license": "GPL-2.0-or-later", "workspaces": [ @@ -16,33 +16,38 @@ "@actions/core": "1.9.1", "@actions/github": "5.0.0", "@apidevtools/json-schema-ref-parser": "11.6.4", - "@ariakit/test": "^0.4.2", + "@ariakit/test": "^0.4.7", "@babel/core": "7.25.7", "@babel/plugin-syntax-jsx": "7.25.7", "@babel/runtime-corejs3": "7.25.7", "@babel/traverse": "7.25.7", "@emotion/babel-plugin": "11.11.0", + "@emotion/is-prop-valid": "1.2.2", "@emotion/jest": "11.7.1", "@emotion/native": "11.0.0", - "@geometricpanda/storybook-addon-badges": "2.0.1", + "@geometricpanda/storybook-addon-badges": "2.0.5", + "@inquirer/prompts": "7.2.0", "@octokit/rest": "16.26.0", "@octokit/types": "6.34.0", "@octokit/webhooks-types": "5.8.0", - "@playwright/test": "1.48.1", + "@playwright/test": "1.49.1", "@pmmmwh/react-refresh-webpack-plugin": "0.5.11", "@react-native/babel-preset": "0.73.10", "@react-native/metro-babel-transformer": "0.73.10", "@react-native/metro-config": "0.73.4", - "@storybook/addon-a11y": "7.6.15", - "@storybook/addon-actions": "7.6.15", - "@storybook/addon-controls": "7.6.15", - "@storybook/addon-docs": "7.6.15", - "@storybook/addon-toolbars": "7.6.15", - "@storybook/addon-viewport": "7.6.15", - "@storybook/react": "7.6.15", - "@storybook/react-webpack5": "7.6.15", - "@storybook/source-loader": "7.6.15", - "@storybook/theming": "7.6.15", + "@storybook/addon-a11y": "8.4.7", + "@storybook/addon-actions": "8.4.7", + "@storybook/addon-controls": "8.4.7", + "@storybook/addon-docs": "8.4.7", + "@storybook/addon-toolbars": "8.4.7", + "@storybook/addon-viewport": "8.4.7", + "@storybook/addon-webpack5-compiler-babel": "3.0.3", + "@storybook/react": "8.4.7", + "@storybook/react-webpack5": "8.4.7", + "@storybook/source-loader": "8.4.7", + "@storybook/test": "8.4.7", + "@storybook/theming": "8.4.7", + "@storybook/types": "8.4.7", "@testing-library/jest-dom": "5.16.5", "@testing-library/react": "14.3.0", "@testing-library/react-native": "12.4.3", @@ -51,6 +56,7 @@ "@types/estree": "1.0.5", "@types/istanbul-lib-report": "3.0.0", "@types/mime": "2.0.3", + "@types/node": "20.17.10", "@types/npm-package-arg": "6.1.1", "@types/prettier": "2.4.4", "@types/qs": "6.9.7", @@ -78,19 +84,21 @@ "commander": "9.2.0", "concurrently": "3.5.0", "copy-webpack-plugin": "10.2.0", - "core-js-builder": "3.38.1", - "cross-env": "3.2.4", + "core-js-builder": "3.39.0", + "cross-env": "7.0.3", "css-loader": "6.2.0", "cssnano": "6.0.1", "deep-freeze": "0.0.1", "equivalent-key-map": "0.2.2", + "esbuild": "0.18.20", "escape-html": "1.0.3", "eslint-import-resolver-node": "0.3.4", "eslint-plugin-eslint-comments": "3.1.2", "eslint-plugin-import": "2.25.2", - "eslint-plugin-jest": "27.2.3", + "eslint-plugin-jest": "27.4.3", "eslint-plugin-jest-dom": "5.0.2", "eslint-plugin-prettier": "5.0.0", + "eslint-plugin-react-compiler": "19.0.0-beta-0dec889-20241115", "eslint-plugin-ssr-friendly": "1.0.6", "eslint-plugin-storybook": "0.6.13", "eslint-plugin-testing-library": "6.0.2", @@ -99,7 +107,6 @@ "filenamify": "4.2.0", "glob": "7.1.2", "husky": "7.0.0", - "inquirer": "7.1.0", "jest": "29.6.2", "jest-environment-jsdom": "^29.6.2", "jest-jasmine2": "29.6.2", @@ -107,7 +114,7 @@ "jest-message-util": "29.6.2", "jest-watch-typeahead": "2.2.2", "json2md": "2.0.1", - "lerna": "7.1.4", + "lerna": "8.1.9", "lint-staged": "10.0.2", "make-dir": "3.0.0", "mkdirp": "3.0.1", @@ -119,12 +126,11 @@ "npm-run-all": "4.1.5", "patch-package": "8.0.0", "postcss": "8.4.38", - "postcss-import": "16.1.0", "postcss-loader": "6.2.1", "postcss-local-keyframes": "^0.0.2", "prettier": "npm:wp-prettier@3.0.3", "progress": "2.0.3", - "puppeteer-core": "23.1.0", + "puppeteer-core": "23.10.1", "raw-loader": "4.0.2", "react": "18.3.1", "react-docgen-typescript": "2.2.2", @@ -137,25 +143,25 @@ "reassure": "0.7.1", "redux": "5.0.1", "resize-observer-polyfill": "1.5.1", - "rimraf": "3.0.2", - "rtlcss": "4.0.0", - "sass": "1.35.2", - "sass-loader": "12.1.0", + "rimraf": "5.0.10", + "rtlcss": "4.3.0", + "sass": "1.54.0", + "sass-loader": "16.0.3", "semver": "7.5.4", "simple-git": "3.24.0", "snapshot-diff": "0.10.0", "source-map-loader": "3.0.0", "sprintf-js": "1.1.1", - "storybook": "7.6.15", + "storybook": "8.4.7", "storybook-source-link": "2.0.9", "strip-json-comments": "5.0.0", "style-loader": "3.2.1", "terser": "5.32.0", "terser-webpack-plugin": "5.3.10", - "typescript": "5.5.3", + "typescript": "5.7.2", "uuid": "9.0.1", "webdriverio": "8.16.20", - "webpack": "5.95.0", + "webpack": "5.97.0", "webpack-bundle-analyzer": "4.9.1", "worker-farm": "1.7.0" }, @@ -497,6 +503,22 @@ "integrity": "sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==", "dev": true }, + "node_modules/@appium/base-driver/node_modules/qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/@appium/base-driver/node_modules/raw-body": { "version": "2.5.2", "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", @@ -596,20 +618,6 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@appium/docutils/node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/@appium/docutils/node_modules/diff": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/diff/-/diff-5.1.0.tgz", @@ -619,12 +627,6 @@ "node": ">=0.3.1" } }, - "node_modules/@appium/docutils/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, "node_modules/@appium/docutils/node_modules/figures": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", @@ -665,15 +667,6 @@ "node": ">=8" } }, - "node_modules/@appium/docutils/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/@appium/docutils/node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -725,20 +718,6 @@ "node": ">=10" } }, - "node_modules/@appium/docutils/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/@appium/docutils/node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -792,32 +771,6 @@ "node": ">=12.20" } }, - "node_modules/@appium/docutils/node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/@appium/docutils/node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, - "engines": { - "node": ">=10" - } - }, "node_modules/@appium/docutils/node_modules/yaml": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.3.tgz", @@ -827,24 +780,6 @@ "node": ">= 14" } }, - "node_modules/@appium/docutils/node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "dev": true, - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/@appium/docutils/node_modules/yargs-parser": { "version": "21.1.1", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", @@ -1168,6 +1103,25 @@ "node": ">=16" } }, + "node_modules/@appium/support/node_modules/jackspeak": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz", + "integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, "node_modules/@appium/support/node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -1432,17 +1386,51 @@ } }, "node_modules/@ariakit/core": { - "version": "0.4.9", - "resolved": "https://registry.npmjs.org/@ariakit/core/-/core-0.4.9.tgz", - "integrity": "sha512-nV0B/OTK/0iB+P9RC7fudznYZ8eR6rR1F912Zc54e3+wSW5RrRvNOiRxyMrgENidd4R7cCMDw77XJLSBLKgEPQ==" + "version": "0.4.14", + "resolved": "https://registry.npmjs.org/@ariakit/core/-/core-0.4.14.tgz", + "integrity": "sha512-hpzZvyYzGhP09S9jW1XGsU/FD5K3BKsH1eG/QJ8rfgEeUdPS7BvHPt5lHbOeJ2cMrRzBEvsEzLi1ivfDifHsVA==", + "license": "MIT" + }, + "node_modules/@ariakit/react": { + "version": "0.4.15", + "resolved": "https://registry.npmjs.org/@ariakit/react/-/react-0.4.15.tgz", + "integrity": "sha512-0V2LkNPFrGRT+SEIiObx/LQjR6v3rR+mKEDUu/3tq7jfCZ+7+6Q6EMR1rFaK+XMkaRY1RWUcj/rRDWAUWnsDww==", + "license": "MIT", + "dependencies": { + "@ariakit/react-core": "0.4.15" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/ariakit" + }, + "peerDependencies": { + "react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/@ariakit/react-core": { + "version": "0.4.15", + "resolved": "https://registry.npmjs.org/@ariakit/react-core/-/react-core-0.4.15.tgz", + "integrity": "sha512-Up8+U97nAPJdyUh9E8BCEhJYTA+eVztWpHoo1R9zZfHd4cnBWAg5RHxEmMH+MamlvuRxBQA71hFKY/735fDg+A==", + "license": "MIT", + "dependencies": { + "@ariakit/core": "0.4.14", + "@floating-ui/dom": "^1.0.0", + "use-sync-external-store": "^1.2.0" + }, + "peerDependencies": { + "react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" + } }, "node_modules/@ariakit/test": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/@ariakit/test/-/test-0.4.2.tgz", - "integrity": "sha512-WXAAiAyTaHV9klntOB81Y+YHyA5iGxy9wXCmjQOfYK5InsuIour+7TVXICUxn2NF0XD6j6OoEJbCVDJ2Y46xEA==", + "version": "0.4.7", + "resolved": "https://registry.npmjs.org/@ariakit/test/-/test-0.4.7.tgz", + "integrity": "sha512-Zb5bnulzYGjr6sDubxOeOhk5Es6BYQq5lbcIe8xNrWUlpRiHsje/FlXNFpHnI92/7ESxH6X4pHhbb+qFAho1lw==", "dev": true, + "license": "MIT", "dependencies": { - "@ariakit/core": "0.4.9", + "@ariakit/core": "0.4.14", "@testing-library/dom": "^8.0.0 || ^9.0.0 || ^10.0.0" }, "peerDependencies": { @@ -1462,18 +1450,6 @@ } } }, - "node_modules/@aw-web-design/x-default-browser": { - "version": "1.4.126", - "resolved": "https://registry.npmjs.org/@aw-web-design/x-default-browser/-/x-default-browser-1.4.126.tgz", - "integrity": "sha512-Xk1sIhyNC/esHGGVjL/niHLowM0csl/kFO5uawBy4IrWwy0o1G8LGt3jP6nmWGz+USxeeqbihAmp/oVZju6wug==", - "dev": true, - "dependencies": { - "default-browser-id": "3.0.0" - }, - "bin": { - "x-default-browser": "bin/x-default-browser.js" - } - }, "node_modules/@axe-core/puppeteer": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@axe-core/puppeteer/-/puppeteer-4.0.0.tgz", @@ -2479,6 +2455,23 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-proposal-private-methods": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.18.6.tgz", + "integrity": "sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-private-methods instead.", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/plugin-proposal-private-property-in-object": { "version": "7.21.0-placeholder-for-preset-env.2", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", @@ -4160,12 +4153,6 @@ "node": ">=6.9.0" } }, - "node_modules/@base2/pretty-print-object": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@base2/pretty-print-object/-/pretty-print-object-1.0.1.tgz", - "integrity": "sha512-4iri8i1AqYHJE2DstZYkyEprg6Pq6sKx3xn5FpySk9sNhH7qN2LLlHJCfDTZRILNwQNPD7mATWM0TBui7uC1pA==", - "dev": true - }, "node_modules/@bcoe/v8-coverage": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", @@ -4185,102 +4172,6 @@ "reassure": "lib/commonjs/bin.js" } }, - "node_modules/@callstack/reassure-cli/node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@callstack/reassure-cli/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "node_modules/@callstack/reassure-cli/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@callstack/reassure-cli/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@callstack/reassure-cli/node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/@callstack/reassure-cli/node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/@callstack/reassure-cli/node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "dev": true, - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@callstack/reassure-cli/node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true, - "engines": { - "node": ">=12" - } - }, "node_modules/@callstack/reassure-compare": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/@callstack/reassure-compare/-/reassure-compare-0.3.0.tgz", @@ -4453,6 +4344,37 @@ "node": ">=0.8.0" } }, + "node_modules/@emnapi/core": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.3.1.tgz", + "integrity": "sha512-pVGjBIt1Y6gg3EJN8jTcfpP/+uuRksIo055oE/OBkDNcjZqVbfkWCksG1Jp4yZnj3iKWyWX8fdG/j6UDYPbFog==", + "dev": true, + "license": "MIT", + "dependencies": { + "@emnapi/wasi-threads": "1.0.1", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.3.1.tgz", + "integrity": "sha512-kEBmG8KyqtxJZv+ygbEim+KCGtIq1fC22Ms3S4ziXmYKm8uyoLX0MHONVKwp+9opg390VaKRNt4a7A9NwmpNhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.0.1.tgz", + "integrity": "sha512-iIBu7mwkq4UQGeMEM8bLwNK962nXdhodeScX4slfQnRhEMMzvYivHhutCIk8uojvmASXXPC2WNEjwxFWk72Oqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@emotion/babel-plugin": { "version": "11.11.0", "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.11.0.tgz", @@ -4547,6 +4469,19 @@ "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.1.tgz", "integrity": "sha512-gJB6HLm5rYwSLI6PQa+X1t5CFGrv1J1TWG+sOyMCeKz2ojaj6Fnl/rZEspogG+cvqbt4AE/2eIyD2QfLKTBNlQ==" }, + "node_modules/@emotion/is-prop-valid": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.2.tgz", + "integrity": "sha512-uNsoYd37AFmaCdXlg6EYD1KaPOaRWRByMCYzbKUX4+hhMfrxdVSelShywL4JVaAeM/eHUOSprYBQls+/neX3pw==", + "dependencies": { + "@emotion/memoize": "^0.8.1" + } + }, + "node_modules/@emotion/is-prop-valid/node_modules/@emotion/memoize": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz", + "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==" + }, "node_modules/@emotion/jest": { "version": "11.7.1", "resolved": "https://registry.npmjs.org/@emotion/jest/-/jest-11.7.1.tgz", @@ -4698,33 +4633,11 @@ } } }, - "node_modules/@emotion/styled/node_modules/@emotion/is-prop-valid": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.2.tgz", - "integrity": "sha512-uNsoYd37AFmaCdXlg6EYD1KaPOaRWRByMCYzbKUX4+hhMfrxdVSelShywL4JVaAeM/eHUOSprYBQls+/neX3pw==", - "dependencies": { - "@emotion/memoize": "^0.8.1" - } - }, - "node_modules/@emotion/styled/node_modules/@emotion/memoize": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz", - "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==" - }, "node_modules/@emotion/unitless": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.1.tgz", "integrity": "sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==" }, - "node_modules/@emotion/use-insertion-effect-with-fallbacks": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.0.1.tgz", - "integrity": "sha512-jT/qyKZ9rzLErtrjGgdkMBn2OP8wl0G3sQlBb3YPryvKHsjvINUhVaPFfP+fpBcOkmrVOVEEHQFJ7nbj2TH2gw==", - "dev": true, - "peerDependencies": { - "react": ">=16.8.0" - } - }, "node_modules/@emotion/utils": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.2.1.tgz", @@ -5202,12 +5115,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@fal-works/esbuild-plugin-global-externals": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@fal-works/esbuild-plugin-global-externals/-/esbuild-plugin-global-externals-2.1.2.tgz", - "integrity": "sha512-cEee/Z+I12mZcFJshKcCqC8tuX5hG3s+d+9nZ3LabqKF1vKdF41B92pJVCBggjAGORAeOzyyDDKrZwIkLffeOQ==", - "dev": true - }, "node_modules/@fastify/busboy": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz", @@ -5239,38 +5146,74 @@ "integrity": "sha512-kym7SodPp8/wloecOpcmSnWJsK7M0E5Wg8UcFA+uO4B9s5d0ywXOEro/8HM9x0rW+TljRzul/14UYz3TleT3ig==", "license": "MIT" }, - "node_modules/@floating-ui/react-dom": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.2.tgz", - "integrity": "sha512-06okr5cgPzMNBy+Ycse2A6udMi4bqwW/zgBF/rwjcNqWkyr82Mcg8b0vjX8OJpZFy/FKjJmw6wV7t44kK6kW7A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@floating-ui/dom": "^1.0.0" - }, - "peerDependencies": { - "react": ">=16.8.0", - "react-dom": ">=16.8.0" - } - }, "node_modules/@floating-ui/utils": { "version": "0.1.6", "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.1.6.tgz", "integrity": "sha512-OfX7E2oUDYxtBvsuS4e/jSn4Q9Qb6DzgeYtsAdkPZ47znpoNsMgZw0+tVijiv3uGNR6dgNlty6r9rzIzHjtd/A==" }, + "node_modules/@formatjs/ecma402-abstract": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-2.2.4.tgz", + "integrity": "sha512-lFyiQDVvSbQOpU+WFd//ILolGj4UgA/qXrKeZxdV14uKiAUiPAtX6XAn7WBCRi7Mx6I7EybM9E5yYn4BIpZWYg==", + "license": "MIT", + "dependencies": { + "@formatjs/fast-memoize": "2.2.3", + "@formatjs/intl-localematcher": "0.5.8", + "tslib": "2" + } + }, + "node_modules/@formatjs/fast-memoize": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-2.2.3.tgz", + "integrity": "sha512-3jeJ+HyOfu8osl3GNSL4vVHUuWFXR03Iz9jjgI7RwjG6ysu/Ymdr0JRCPHfF5yGbTE6JCrd63EpvX1/WybYRbA==", + "license": "MIT", + "dependencies": { + "tslib": "2" + } + }, + "node_modules/@formatjs/icu-messageformat-parser": { + "version": "2.9.4", + "resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.9.4.tgz", + "integrity": "sha512-Tbvp5a9IWuxUcpWNIW6GlMQYEc4rwNHR259uUFoKWNN1jM9obf9Ul0e+7r7MvFOBNcN+13K7NuKCKqQiAn1QEg==", + "license": "MIT", + "dependencies": { + "@formatjs/ecma402-abstract": "2.2.4", + "@formatjs/icu-skeleton-parser": "1.8.8", + "tslib": "2" + } + }, + "node_modules/@formatjs/icu-skeleton-parser": { + "version": "1.8.8", + "resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.8.8.tgz", + "integrity": "sha512-vHwK3piXwamFcx5YQdCdJxUQ1WdTl6ANclt5xba5zLGDv5Bsur7qz8AD7BevaKxITwpgDeU0u8My3AIibW9ywA==", + "license": "MIT", + "dependencies": { + "@formatjs/ecma402-abstract": "2.2.4", + "tslib": "2" + } + }, + "node_modules/@formatjs/intl-localematcher": { + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.5.8.tgz", + "integrity": "sha512-I+WDNWWJFZie+jkfkiK5Mp4hEDyRSEvmyfYadflOno/mmKJKcB17fEpEH0oJu/OWhhCJ8kJBDz2YMd/6cDl7Mg==", + "license": "MIT", + "dependencies": { + "tslib": "2" + } + }, "node_modules/@geometricpanda/storybook-addon-badges": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@geometricpanda/storybook-addon-badges/-/storybook-addon-badges-2.0.1.tgz", - "integrity": "sha512-dCEK/xJewuFe1d+ndF0hQIAJRnUsV9q5kuDmp7zvO7fTd7cDz0X9Bjz0lNRn6n4Z9bL9/iFHKzJESDHFfs4ihQ==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@geometricpanda/storybook-addon-badges/-/storybook-addon-badges-2.0.5.tgz", + "integrity": "sha512-FH56ly6ZhltjyKQWxUKORP67BxhL9FMJRByS5lqKZpeP8J2MMsMXG7eQmFXKcZGQORfVQye+1uYYWXweDOiFTQ==", "dev": true, "peerDependencies": { - "@storybook/blocks": "^7.0.0", - "@storybook/components": "^7.0.0", - "@storybook/core-events": "^7.0.0", - "@storybook/manager-api": "^7.0.0", - "@storybook/preview-api": "^7.0.0", - "@storybook/theming": "^7.0.0", - "@storybook/types": "^7.0.0", + "@storybook/blocks": "^8.3.0", + "@storybook/components": "^8.3.0", + "@storybook/core-events": "^8.3.0", + "@storybook/manager-api": "^8.3.0", + "@storybook/preview-api": "^8.3.0", + "@storybook/theming": "^8.3.0", + "@storybook/types": "^8.3.0", "react": "^16.8.0 || ^17.0.0 || ^18.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" }, @@ -5319,15 +5262,273 @@ "resolved": "https://registry.npmjs.org/@hutson/parse-repository-url/-/parse-repository-url-3.0.2.tgz", "integrity": "sha512-H9XAx3hc0BQHY6l+IFSWHDySypcXsvsuLhgYLUGywmJ5pswRVQJUHpOsobnLYp2ZUaUlKiKDrgWWhosOwAEM8Q==", "dev": true, + "license": "Apache-2.0", "engines": { "node": ">=6.9.0" } }, + "node_modules/@inquirer/checkbox": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-4.0.3.tgz", + "integrity": "sha512-CEt9B4e8zFOGtc/LYeQx5m8nfqQeG/4oNNv0PUvXGG0mys+wR/WbJ3B4KfSQ4Fcr3AQfpiuFOi3fVvmPfvNbxw==", + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.1.1", + "@inquirer/figures": "^1.0.8", + "@inquirer/type": "^3.0.1", + "ansi-escapes": "^4.3.2", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + } + }, + "node_modules/@inquirer/confirm": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-5.1.0.tgz", + "integrity": "sha512-osaBbIMEqVFjTX5exoqPXs6PilWQdjaLhGtMDXMXg/yxkHXNq43GlxGyTA35lK2HpzUgDN+Cjh/2AmqCN0QJpw==", + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.1.1", + "@inquirer/type": "^3.0.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + } + }, + "node_modules/@inquirer/core": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.1.1.tgz", + "integrity": "sha512-rmZVXy9iZvO3ZStEe/ayuuwIJ23LSF13aPMlLMTQARX6lGUBDHGV8UB5i9MRrfy0+mZwt5/9bdy8llszSD3NQA==", + "license": "MIT", + "dependencies": { + "@inquirer/figures": "^1.0.8", + "@inquirer/type": "^3.0.1", + "ansi-escapes": "^4.3.2", + "cli-width": "^4.1.0", + "mute-stream": "^2.0.0", + "signal-exit": "^4.1.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^6.2.0", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/core/node_modules/mute-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-2.0.0.tgz", + "integrity": "sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==", + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@inquirer/core/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@inquirer/editor": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-4.2.0.tgz", + "integrity": "sha512-Z3LeGsD3WlItDqLxTPciZDbGtm0wrz7iJGS/uUxSiQxef33ZrBq7LhsXg30P7xrWz1kZX4iGzxxj5SKZmJ8W+w==", + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.1.1", + "@inquirer/type": "^3.0.1", + "external-editor": "^3.1.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + } + }, + "node_modules/@inquirer/expand": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-4.0.3.tgz", + "integrity": "sha512-MDszqW4HYBpVMmAoy/FA9laLrgo899UAga0itEjsYrBthKieDZNc0e16gdn7N3cQ0DSf/6zsTBZMuDYDQU4ktg==", + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.1.1", + "@inquirer/type": "^3.0.1", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + } + }, + "node_modules/@inquirer/figures": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.8.tgz", + "integrity": "sha512-tKd+jsmhq21AP1LhexC0pPwsCxEhGgAkg28byjJAd+xhmIs8LUX8JbUc3vBf3PhLxWiB5EvyBE5X7JSPAqMAqg==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/input": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-4.1.0.tgz", + "integrity": "sha512-16B8A9hY741yGXzd8UJ9R8su/fuuyO2e+idd7oVLYjP23wKJ6ILRIIHcnXe8/6AoYgwRS2zp4PNsW/u/iZ24yg==", + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.1.1", + "@inquirer/type": "^3.0.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + } + }, + "node_modules/@inquirer/number": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@inquirer/number/-/number-3.0.3.tgz", + "integrity": "sha512-HA/W4YV+5deKCehIutfGBzNxWH1nhvUC67O4fC9ufSijn72yrYnRmzvC61dwFvlXIG1fQaYWi+cqNE9PaB9n6Q==", + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.1.1", + "@inquirer/type": "^3.0.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + } + }, + "node_modules/@inquirer/password": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-4.0.3.tgz", + "integrity": "sha512-3qWjk6hS0iabG9xx0U1plwQLDBc/HA/hWzLFFatADpR6XfE62LqPr9GpFXBkLU0KQUaIXZ996bNG+2yUvocH8w==", + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.1.1", + "@inquirer/type": "^3.0.1", + "ansi-escapes": "^4.3.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + } + }, + "node_modules/@inquirer/prompts": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-7.2.0.tgz", + "integrity": "sha512-ZXYZ5oGVrb+hCzcglPeVerJ5SFwennmDOPfXq1WyeZIrPGySLbl4W6GaSsBFvu3WII36AOK5yB8RMIEEkBjf8w==", + "license": "MIT", + "dependencies": { + "@inquirer/checkbox": "^4.0.3", + "@inquirer/confirm": "^5.1.0", + "@inquirer/editor": "^4.2.0", + "@inquirer/expand": "^4.0.3", + "@inquirer/input": "^4.1.0", + "@inquirer/number": "^3.0.3", + "@inquirer/password": "^4.0.3", + "@inquirer/rawlist": "^4.0.3", + "@inquirer/search": "^3.0.3", + "@inquirer/select": "^4.0.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + } + }, + "node_modules/@inquirer/rawlist": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-4.0.3.tgz", + "integrity": "sha512-5MhinSzfmOiZlRoPezfbJdfVCZikZs38ja3IOoWe7H1dxL0l3Z2jAUgbBldeyhhOkELdGvPlBfQaNbeLslib1w==", + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.1.1", + "@inquirer/type": "^3.0.1", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + } + }, + "node_modules/@inquirer/search": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@inquirer/search/-/search-3.0.3.tgz", + "integrity": "sha512-mQTCbdNolTGvGGVCJSI6afDwiSGTV+fMLPEIMDJgIV6L/s3+RYRpxt6t0DYnqMQmemnZ/Zq0vTIRwoHT1RgcTg==", + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.1.1", + "@inquirer/figures": "^1.0.8", + "@inquirer/type": "^3.0.1", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + } + }, + "node_modules/@inquirer/select": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-4.0.3.tgz", + "integrity": "sha512-OZfKDtDE8+J54JYAFTUGZwvKNfC7W/gFCjDkcsO7HnTH/wljsZo9y/FJquOxMy++DY0+9l9o/MOZ8s5s1j5wmw==", + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.1.1", + "@inquirer/figures": "^1.0.8", + "@inquirer/type": "^3.0.1", + "ansi-escapes": "^4.3.2", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + } + }, + "node_modules/@inquirer/type": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.1.tgz", + "integrity": "sha512-+ksJMIy92sOAiAccGpcKZUc3bYO07cADnscIxHBknEm3uNts3movSmBofc1908BNy5edKscxYeAdaX1NXkHS6A==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + } + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", - "dev": true, "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", @@ -5344,7 +5545,6 @@ "version": "6.1.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", - "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -5357,7 +5557,6 @@ "version": "6.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", - "dev": true, "engines": { "node": ">=12" }, @@ -5368,14 +5567,12 @@ "node_modules/@isaacs/cliui/node_modules/emoji-regex": { "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" }, "node_modules/@isaacs/cliui/node_modules/string-width": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "dev": true, "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", @@ -5392,7 +5589,6 @@ "version": "7.1.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dev": true, "dependencies": { "ansi-regex": "^6.0.1" }, @@ -5407,7 +5603,6 @@ "version": "8.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "dev": true, "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", @@ -5420,6 +5615,13 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/@isaacs/string-locale-compare": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@isaacs/string-locale-compare/-/string-locale-compare-1.1.0.tgz", + "integrity": "sha512-SQ7Kzhh9+D+ZW9MA0zkYv3VXhIDNx+LzM6EJ+/65I3QY+enU6Itte7E5XX7EWrqLW2FN4n06GWzBnPoC3th2aQ==", + "dev": true, + "license": "ISC" + }, "node_modules/@isaacs/ttlcache": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/@isaacs/ttlcache/-/ttlcache-1.4.1.tgz", @@ -5891,9 +6093,9 @@ } }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.15", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.25", @@ -5910,12 +6112,6 @@ "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==", "dev": true }, - "node_modules/@juggle/resize-observer": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@juggle/resize-observer/-/resize-observer-3.4.0.tgz", - "integrity": "sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==", - "dev": true - }, "node_modules/@kwsites/file-exists": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@kwsites/file-exists/-/file-exists-1.1.1.tgz", @@ -5934,3370 +6130,2710 @@ "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz", "integrity": "sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==" }, - "node_modules/@lerna/child-process": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/@lerna/child-process/-/child-process-7.1.4.tgz", - "integrity": "sha512-cSiMDx9oI9vvVT+V/WHcbqrksNoc9PIPFiks1lPS7zrVWkEbgA6REQyYmRd2H71kihzqhX5TJ20f2dWv6oEPdA==", + "node_modules/@lerna/create": { + "version": "8.1.9", + "resolved": "https://registry.npmjs.org/@lerna/create/-/create-8.1.9.tgz", + "integrity": "sha512-DPnl5lPX4v49eVxEbJnAizrpMdMTBz1qykZrAbBul9rfgk531v8oAt+Pm6O/rpAleRombNM7FJb5rYGzBJatOQ==", "dev": true, + "license": "MIT", "dependencies": { - "chalk": "^4.1.0", - "execa": "^5.0.0", - "strong-log-transformer": "^2.1.0" + "@npmcli/arborist": "7.5.4", + "@npmcli/package-json": "5.2.0", + "@npmcli/run-script": "8.1.0", + "@nx/devkit": ">=17.1.2 < 21", + "@octokit/plugin-enterprise-rest": "6.0.1", + "@octokit/rest": "19.0.11", + "aproba": "2.0.0", + "byte-size": "8.1.1", + "chalk": "4.1.0", + "clone-deep": "4.0.1", + "cmd-shim": "6.0.3", + "color-support": "1.1.3", + "columnify": "1.6.0", + "console-control-strings": "^1.1.0", + "conventional-changelog-core": "5.0.1", + "conventional-recommended-bump": "7.0.1", + "cosmiconfig": "9.0.0", + "dedent": "1.5.3", + "execa": "5.0.0", + "fs-extra": "^11.2.0", + "get-stream": "6.0.0", + "git-url-parse": "14.0.0", + "glob-parent": "6.0.2", + "globby": "11.1.0", + "graceful-fs": "4.2.11", + "has-unicode": "2.0.1", + "ini": "^1.3.8", + "init-package-json": "6.0.3", + "inquirer": "^8.2.4", + "is-ci": "3.0.1", + "is-stream": "2.0.0", + "js-yaml": "4.1.0", + "libnpmpublish": "9.0.9", + "load-json-file": "6.2.0", + "lodash": "^4.17.21", + "make-dir": "4.0.0", + "minimatch": "3.0.5", + "multimatch": "5.0.0", + "node-fetch": "2.6.7", + "npm-package-arg": "11.0.2", + "npm-packlist": "8.0.2", + "npm-registry-fetch": "^17.1.0", + "nx": ">=17.1.2 < 21", + "p-map": "4.0.0", + "p-map-series": "2.1.0", + "p-queue": "6.6.2", + "p-reduce": "^2.1.0", + "pacote": "^18.0.6", + "pify": "5.0.0", + "read-cmd-shim": "4.0.0", + "resolve-from": "5.0.0", + "rimraf": "^4.4.1", + "semver": "^7.3.4", + "set-blocking": "^2.0.0", + "signal-exit": "3.0.7", + "slash": "^3.0.0", + "ssri": "^10.0.6", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "strong-log-transformer": "2.1.0", + "tar": "6.2.1", + "temp-dir": "1.0.0", + "upath": "2.0.1", + "uuid": "^10.0.0", + "validate-npm-package-license": "^3.0.4", + "validate-npm-package-name": "5.0.1", + "wide-align": "1.1.5", + "write-file-atomic": "5.0.1", + "write-pkg": "4.0.0", + "yargs": "17.7.2", + "yargs-parser": "21.1.1" }, "engines": { - "node": "^14.17.0 || >=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@lerna/child-process/node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "node_modules/@lerna/create/node_modules/@octokit/auth-token": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-3.0.4.tgz", + "integrity": "sha512-TWFX7cZF2LXoCvdmJWY7XVPi74aSY0+FfBZNSXEXFkMpjcqsQwDSYVv5FhRFaI0V1ECnwbz4j59T/G+rXNWaIQ==", "dev": true, - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, + "license": "MIT", "engines": { - "node": ">= 8" + "node": ">= 14" } }, - "node_modules/@lerna/child-process/node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "node_modules/@lerna/create/node_modules/@octokit/core": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@octokit/core/-/core-4.2.4.tgz", + "integrity": "sha512-rYKilwgzQ7/imScn3M9/pFfUf4I1AZEH3KhyJmtPdE2zfaXAn2mFfUy4FbKewzc2We5y/LlKLj36fWJLKC2SIQ==", "dev": true, + "license": "MIT", "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" + "@octokit/auth-token": "^3.0.0", + "@octokit/graphql": "^5.0.0", + "@octokit/request": "^6.0.0", + "@octokit/request-error": "^3.0.0", + "@octokit/types": "^9.0.0", + "before-after-hook": "^2.2.0", + "universal-user-agent": "^6.0.0" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" + "node": ">= 14" } }, - "node_modules/@lerna/child-process/node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "node_modules/@lerna/create/node_modules/@octokit/endpoint": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-7.0.6.tgz", + "integrity": "sha512-5L4fseVRUsDFGR00tMWD/Trdeeihn999rTMGRMC1G/Ldi1uWlWJzI98H4Iak5DB/RVvQuyMYKqSK/R6mbSOQyg==", "dev": true, - "engines": { - "node": ">=10" + "license": "MIT", + "dependencies": { + "@octokit/types": "^9.0.0", + "is-plain-object": "^5.0.0", + "universal-user-agent": "^6.0.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@lerna/child-process/node_modules/human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "dev": true, "engines": { - "node": ">=10.17.0" + "node": ">= 14" } }, - "node_modules/@lerna/child-process/node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "node_modules/@lerna/create/node_modules/@octokit/graphql": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-5.0.6.tgz", + "integrity": "sha512-Fxyxdy/JH0MnIB5h+UQ3yCoh1FG4kWXfFKkpWqjZHw/p+Kc8Y44Hu/kCgNBT6nU1shNumEchmW/sUO1JuQnPcw==", "dev": true, - "engines": { - "node": ">=8" + "license": "MIT", + "dependencies": { + "@octokit/request": "^6.0.0", + "@octokit/types": "^9.0.0", + "universal-user-agent": "^6.0.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": ">= 14" } }, - "node_modules/@lerna/child-process/node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "node_modules/@lerna/create/node_modules/@octokit/openapi-types": { + "version": "18.1.1", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-18.1.1.tgz", + "integrity": "sha512-VRaeH8nCDtF5aXWnjPuEMIYf1itK/s3JYyJcWFJT8X9pSNnBtriDf7wlEWsGuhPLl4QIH4xM8fqTXDwJ3Mu6sw==", "dev": true, - "engines": { - "node": ">=6" - } + "license": "MIT" }, - "node_modules/@lerna/child-process/node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "node_modules/@lerna/create/node_modules/@octokit/plugin-paginate-rest": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-6.1.2.tgz", + "integrity": "sha512-qhrmtQeHU/IivxucOV1bbI/xZyC/iOBhclokv7Sut5vnejAIAEXVcGQeRpQlU39E0WwK9lNvJHphHri/DB6lbQ==", "dev": true, + "license": "MIT", "dependencies": { - "path-key": "^3.0.0" + "@octokit/tsconfig": "^1.0.2", + "@octokit/types": "^9.2.3" }, "engines": { - "node": ">=8" + "node": ">= 14" + }, + "peerDependencies": { + "@octokit/core": ">=4" } }, - "node_modules/@lerna/child-process/node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "node_modules/@lerna/create/node_modules/@octokit/plugin-rest-endpoint-methods": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-7.2.3.tgz", + "integrity": "sha512-I5Gml6kTAkzVlN7KCtjOM+Ruwe/rQppp0QU372K1GP7kNOYEKe8Xn5BW4sE62JAHdwpq95OQK/qGNyKQMUzVgA==", "dev": true, + "license": "MIT", "dependencies": { - "mimic-fn": "^2.1.0" + "@octokit/types": "^10.0.0" }, "engines": { - "node": ">=6" + "node": ">= 14" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peerDependencies": { + "@octokit/core": ">=3" } }, - "node_modules/@lerna/child-process/node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "node_modules/@lerna/create/node_modules/@octokit/plugin-rest-endpoint-methods/node_modules/@octokit/types": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-10.0.0.tgz", + "integrity": "sha512-Vm8IddVmhCgU1fxC1eyinpwqzXPEYu0NrYzD3YZjlGjyftdLBTeqNblRC0jmJmgxbJIsQlyogVeGnrNaaMVzIg==", "dev": true, - "engines": { - "node": ">=8" + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^18.0.0" } }, - "node_modules/@lerna/child-process/node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "node_modules/@lerna/create/node_modules/@octokit/request": { + "version": "6.2.8", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-6.2.8.tgz", + "integrity": "sha512-ow4+pkVQ+6XVVsekSYBzJC0VTVvh/FCTUUgTsboGq+DTeWdyIFV8WSCdo0RIxk6wSkBTHqIK1mYuY7nOBXOchw==", "dev": true, + "license": "MIT", "dependencies": { - "shebang-regex": "^3.0.0" + "@octokit/endpoint": "^7.0.0", + "@octokit/request-error": "^3.0.0", + "@octokit/types": "^9.0.0", + "is-plain-object": "^5.0.0", + "node-fetch": "^2.6.7", + "universal-user-agent": "^6.0.0" }, "engines": { - "node": ">=8" - } - }, - "node_modules/@lerna/child-process/node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "engines": { - "node": ">=8" + "node": ">= 14" } }, - "node_modules/@lerna/child-process/node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "node_modules/@lerna/create/node_modules/@octokit/request-error": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-3.0.3.tgz", + "integrity": "sha512-crqw3V5Iy2uOU5Np+8M/YexTlT8zxCfI+qu+LxUB7SZpje4Qmx3mub5DfEKSO8Ylyk0aogi6TYdf6kxzh2BguQ==", "dev": true, + "license": "MIT", "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" + "@octokit/types": "^9.0.0", + "deprecation": "^2.0.0", + "once": "^1.4.0" }, "engines": { - "node": ">= 8" + "node": ">= 14" } }, - "node_modules/@lerna/create": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/@lerna/create/-/create-7.1.4.tgz", - "integrity": "sha512-D5YWXsXIxWb1aGqcbtttczg86zMzkNhcs00/BleFNxdNYlTRdjLIReELOGBGrq3Hij05UN+7Dv9EKnPFJVbqAw==", + "node_modules/@lerna/create/node_modules/@octokit/rest": { + "version": "19.0.11", + "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-19.0.11.tgz", + "integrity": "sha512-m2a9VhaP5/tUw8FwfnW2ICXlXpLPIqxtg3XcAiGMLj/Xhw3RSBfZ8le/466ktO1Gcjr8oXudGnHhxV1TXJgFxw==", "dev": true, + "license": "MIT", "dependencies": { - "@lerna/child-process": "7.1.4", - "dedent": "0.7.0", - "fs-extra": "^11.1.1", - "init-package-json": "5.0.0", - "npm-package-arg": "8.1.1", - "p-reduce": "^2.1.0", - "pacote": "^15.2.0", - "pify": "5.0.0", - "semver": "^7.3.4", - "slash": "^3.0.0", - "validate-npm-package-license": "^3.0.4", - "validate-npm-package-name": "5.0.0", - "yargs-parser": "20.2.4" + "@octokit/core": "^4.2.1", + "@octokit/plugin-paginate-rest": "^6.1.2", + "@octokit/plugin-request-log": "^1.0.4", + "@octokit/plugin-rest-endpoint-methods": "^7.1.2" }, "engines": { - "node": "^14.17.0 || >=16.0.0" + "node": ">= 14" } }, - "node_modules/@lerna/create/node_modules/fs-extra": { - "version": "11.2.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", - "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==", + "node_modules/@lerna/create/node_modules/@octokit/types": { + "version": "9.3.2", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-9.3.2.tgz", + "integrity": "sha512-D4iHGTdAnEEVsB8fl95m1hiz7D5YiRdQ9b/OEb3BYRVwbLsGHcRVPz+u+BgRLNk0Q0/4iZCBqDN96j2XNxfXrA==", "dev": true, + "license": "MIT", "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=14.14" + "@octokit/openapi-types": "^18.0.0" } }, - "node_modules/@lerna/create/node_modules/hosted-git-info": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-3.0.8.tgz", - "integrity": "sha512-aXpmwoOhRBrw6X3j0h5RloK4x1OzsxMPyxqIHyNfSe2pypkVTZFpEiRoSipPEPlMrh0HW/XsjkJ5WgnCirpNUw==", + "node_modules/@lerna/create/node_modules/aproba": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", + "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/@lerna/create/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/@lerna/create/node_modules/before-after-hook": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.3.tgz", + "integrity": "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/@lerna/create/node_modules/chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", "dev": true, + "license": "MIT", "dependencies": { - "lru-cache": "^6.0.0" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@lerna/create/node_modules/jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "node_modules/@lerna/create/node_modules/cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", "dev": true, + "license": "MIT", "dependencies": { - "universalify": "^2.0.0" + "restore-cursor": "^3.1.0" }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" + "engines": { + "node": ">=8" } }, - "node_modules/@lerna/create/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "node_modules/@lerna/create/node_modules/cli-width": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", + "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, + "license": "ISC", "engines": { - "node": ">=10" + "node": ">= 10" } }, - "node_modules/@lerna/create/node_modules/npm-package-arg": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-8.1.1.tgz", - "integrity": "sha512-CsP95FhWQDwNqiYS+Q0mZ7FAEDytDZAkNxQqea6IaAFJTAY9Lhhqyl0irU/6PMc7BGfUmnsbHcqxJD7XuVM/rg==", + "node_modules/@lerna/create/node_modules/cosmiconfig": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", + "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", "dev": true, + "license": "MIT", "dependencies": { - "hosted-git-info": "^3.0.6", - "semver": "^7.0.0", - "validate-npm-package-name": "^3.0.0" + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0" }, "engines": { - "node": ">=10" + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/@lerna/create/node_modules/npm-package-arg/node_modules/validate-npm-package-name": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-3.0.0.tgz", - "integrity": "sha512-M6w37eVCMMouJ9V/sdPGnC5H4uDr73/+xdq0FBLO3TFFX1+7wiUY6Es328NN+y43tmY+doUdN9g9J21vqB7iLw==", + "node_modules/@lerna/create/node_modules/dedent": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.3.tgz", + "integrity": "sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ==", "dev": true, - "dependencies": { - "builtins": "^1.0.3" + "license": "MIT", + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } } }, - "node_modules/@lerna/create/node_modules/pify": { + "node_modules/@lerna/create/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/@lerna/create/node_modules/execa": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-5.0.0.tgz", - "integrity": "sha512-eW/gHNMlxdSP6dmG6uJip6FXN0EQBwm2clYYd8Wul42Cwu/DK8HEftzsapcNdYe2MfLiIwZqsDk2RDEsTE79hA==", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.0.0.tgz", + "integrity": "sha512-ov6w/2LCiuyO4RLYGdpFGjkcs0wMTgGE8PrkTHikeUy5iJekXyPIKUjifk5CsE0pt7sMCrMZ3YNqoCj6idQOnQ==", "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, "engines": { "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, - "node_modules/@lerna/create/node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "node_modules/@lerna/create/node_modules/figures": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", + "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^1.0.5" + }, "engines": { - "node": ">= 10.0.0" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@lerna/create/node_modules/validate-npm-package-name": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-5.0.0.tgz", - "integrity": "sha512-YuKoXDAhBYxY7SfOKxHBDoSyENFeW5VvIIQp2TGQuit8gpK6MnWaQelBKxso72DoxTZfZdcP3W90LqpSkgPzLQ==", + "node_modules/@lerna/create/node_modules/fs-extra": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", + "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==", "dev": true, + "license": "MIT", "dependencies": { - "builtins": "^5.0.0" + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": ">=14.14" } }, - "node_modules/@lerna/create/node_modules/validate-npm-package-name/node_modules/builtins": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/builtins/-/builtins-5.1.0.tgz", - "integrity": "sha512-SW9lzGTLvWTP1AY8xeAMZimqDrIaSdLQUcVr9DMef51niJ022Ri87SwRRKYm4A6iHfkPaiVUu/Duw2Wc4J7kKg==", + "node_modules/@lerna/create/node_modules/get-stream": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.0.tgz", + "integrity": "sha512-A1B3Bh1UmL0bidM/YX2NsCOTnGJePL9rO/M+Mw3m9f2gUpfokS0hi5Eah0WSUEWZdZhIZtMjkIYS7mDfOqNHbg==", "dev": true, - "dependencies": { - "semver": "^7.0.0" + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@lerna/create/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/@mdx-js/react": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@mdx-js/react/-/react-2.3.0.tgz", - "integrity": "sha512-zQH//gdOmuu7nt2oJR29vFhDv88oGPmVw6BggmrHeMI+xgEkp1B2dX9/bMBSYtK0dyLX/aOmesKS09g222K1/g==", + "node_modules/@lerna/create/node_modules/glob": { + "version": "9.3.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-9.3.5.tgz", + "integrity": "sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q==", "dev": true, + "license": "ISC", "dependencies": { - "@types/mdx": "^2.0.0", - "@types/react": ">=16" + "fs.realpath": "^1.0.0", + "minimatch": "^8.0.2", + "minipass": "^4.2.4", + "path-scurry": "^1.6.1" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "engines": { + "node": ">=16 || 14 >=14.17" }, - "peerDependencies": { - "react": ">=16" + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@ndelangen/get-tarball": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/@ndelangen/get-tarball/-/get-tarball-3.0.9.tgz", - "integrity": "sha512-9JKTEik4vq+yGosHYhZ1tiH/3WpUS0Nh0kej4Agndhox8pAdWhEx5knFVRcb/ya9knCRCs1rPxNrSXTDdfVqpA==", + "node_modules/@lerna/create/node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", "dev": true, + "license": "ISC", "dependencies": { - "gunzip-maybe": "^1.4.2", - "pump": "^3.0.0", - "tar-fs": "^2.1.1" + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" } }, - "node_modules/@ndelangen/get-tarball/node_modules/pump": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz", - "integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==", + "node_modules/@lerna/create/node_modules/glob/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "dev": true, "license": "MIT", "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "node_modules/@nicolo-ribaudo/eslint-scope-5-internals": { - "version": "5.1.1-v1", - "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz", - "integrity": "sha512-54/JRvkLIzzDWshCWfuhadfrfZVPiElY8Fcgmg1HroEly/EDSszzhBAsarCux+D/kOslTRquNzuyGSmUSTTHGg==", - "license": "MIT", - "dependencies": { - "eslint-scope": "5.1.1" + "balanced-match": "^1.0.0" } }, - "node_modules/@nicolo-ribaudo/eslint-scope-5-internals/node_modules/eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "license": "BSD-2-Clause", + "node_modules/@lerna/create/node_modules/glob/node_modules/minimatch": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-8.0.4.tgz", + "integrity": "sha512-W0Wvr9HyFXZRGIDgCicunpQ299OKXs9RgZfaukz4qAW/pJhcpUfupc9c+OObPOFueNy8VSrZgEmDtk6Kh4WzDA==", + "dev": true, + "license": "ISC", "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" + "brace-expansion": "^2.0.1" }, "engines": { - "node": ">=8.0.0" + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.2.tgz", - "integrity": "sha512-wrIBsjA5pl13f0RN4Zx4FNWmU71lv03meGKnqRUoCyan17s4V3WL92f3w3AIuWbNnpcrQyFBU5qMavJoB8d27w==", - "dependencies": { - "@nodelib/fs.stat": "2.0.2", - "run-parallel": "^1.1.9" - }, + "node_modules/@lerna/create/node_modules/glob/node_modules/minipass": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-4.2.8.tgz", + "integrity": "sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ==", + "dev": true, + "license": "ISC", "engines": { - "node": ">= 8" + "node": ">=8" } }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.2.tgz", - "integrity": "sha512-z8+wGWV2dgUhLqrtRYa03yDx4HWMvXKi1z8g3m2JyxAx8F7xk74asqPk5LAETjqDSGLFML/6CDl0+yFunSYicw==", + "node_modules/@lerna/create/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", "engines": { - "node": ">= 8" + "node": ">=8" } }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.3.tgz", - "integrity": "sha512-l6t8xEhfK9Sa4YO5mIRdau7XSOADfmh3jCr0evNHdY+HNkW6xuQhgMH7D73VV6WpZOagrW0UludvMTiifiwTfA==", + "node_modules/@lerna/create/node_modules/hosted-git-info": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz", + "integrity": "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==", + "dev": true, + "license": "ISC", "dependencies": { - "@nodelib/fs.scandir": "2.1.2", - "fastq": "^1.6.0" + "lru-cache": "^10.0.1" }, "engines": { - "node": ">= 8" + "node": "^16.14.0 || >=18.0.0" } }, - "node_modules/@npmcli/fs": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-3.1.0.tgz", - "integrity": "sha512-7kZUAaLscfgbwBQRbvdMYaZOWyMEcPTH/tJjnyAWJ/dvvs9Ef+CERx/qJb9GExJpl1qipaDGn7KqHnFGGixd0w==", + "node_modules/@lerna/create/node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", "dev": true, - "dependencies": { - "semver": "^7.3.5" - }, + "license": "Apache-2.0", "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": ">=10.17.0" } }, - "node_modules/@npmcli/git": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-4.1.0.tgz", - "integrity": "sha512-9hwoB3gStVfa0N31ymBmrX+GuDGdVA/QWShZVqE0HK2Af+7QGGrCTbZia/SW0ImUTjTne7SP91qxDmtXvDHRPQ==", + "node_modules/@lerna/create/node_modules/ignore-walk": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-6.0.5.tgz", + "integrity": "sha512-VuuG0wCnjhnylG1ABXT3dAuIpTNDs/G8jlpmwXY03fXoXy/8ZK8/T+hMzt8L4WnrLCJgdybqgPagnF/f97cg3A==", "dev": true, + "license": "ISC", "dependencies": { - "@npmcli/promise-spawn": "^6.0.0", - "lru-cache": "^7.4.4", - "npm-pick-manifest": "^8.0.0", - "proc-log": "^3.0.0", - "promise-inflight": "^1.0.1", - "promise-retry": "^2.0.1", - "semver": "^7.3.5", - "which": "^3.0.0" + "minimatch": "^9.0.0" }, "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/@npmcli/git/node_modules/lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "node_modules/@lerna/create/node_modules/ignore-walk/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "dev": true, - "engines": { - "node": ">=12" + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" } }, - "node_modules/@npmcli/git/node_modules/which": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/which/-/which-3.0.1.tgz", - "integrity": "sha512-XA1b62dzQzLfaEOSQFTCOd5KFf/1VSzZo7/7TUjnya6u0vGGKzU96UQBZTAThCb2j4/xjBAyii1OhRLJEivHvg==", + "node_modules/@lerna/create/node_modules/ignore-walk/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "dev": true, + "license": "ISC", "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/which.js" + "brace-expansion": "^2.0.1" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@npmcli/installed-package-contents": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@npmcli/installed-package-contents/-/installed-package-contents-2.0.2.tgz", - "integrity": "sha512-xACzLPhnfD51GKvTOOuNX2/V4G4mz9/1I2MfDoye9kBM3RYe5g2YbscsaGoTlaWqkxeiapBWyseULVKpSVHtKQ==", + "node_modules/@lerna/create/node_modules/inquirer": { + "version": "8.2.6", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.6.tgz", + "integrity": "sha512-M1WuAmb7pn9zdFRtQYk26ZBoY043Sse0wVDdk4Bppr+JOXyQYybdtvK+l9wUibhtjdjvtoiNy8tk+EgsYIUqKg==", "dev": true, + "license": "MIT", "dependencies": { - "npm-bundled": "^3.0.0", - "npm-normalize-package-bin": "^3.0.0" - }, - "bin": { - "installed-package-contents": "lib/index.js" + "ansi-escapes": "^4.2.1", + "chalk": "^4.1.1", + "cli-cursor": "^3.1.0", + "cli-width": "^3.0.0", + "external-editor": "^3.0.3", + "figures": "^3.0.0", + "lodash": "^4.17.21", + "mute-stream": "0.0.8", + "ora": "^5.4.1", + "run-async": "^2.4.0", + "rxjs": "^7.5.5", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0", + "through": "^2.3.6", + "wrap-ansi": "^6.0.1" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": ">=12.0.0" } }, - "node_modules/@npmcli/installed-package-contents/node_modules/npm-bundled": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-3.0.1.tgz", - "integrity": "sha512-+AvaheE/ww1JEwRHOrn4WHNzOxGtVp+adrg2AeZS/7KuxGUYFuBta98wYpfHBbJp6Tg6j1NKSEVHNcfZzJHQwQ==", + "node_modules/@lerna/create/node_modules/inquirer/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, + "license": "MIT", "dependencies": { - "npm-normalize-package-bin": "^3.0.0" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@npmcli/installed-package-contents/node_modules/npm-normalize-package-bin": { + "node_modules/@lerna/create/node_modules/is-ci": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-3.0.1.tgz", - "integrity": "sha512-dMxCf+zZ+3zeQZXKxmyuCKlIDPGuv8EF940xbkC4kQVDTtqoh6rJFO+JTKSA6/Rwi0getWmtuy4Itup0AMcaDQ==", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-3.0.1.tgz", + "integrity": "sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==", "dev": true, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "license": "MIT", + "dependencies": { + "ci-info": "^3.2.0" + }, + "bin": { + "is-ci": "bin.js" } }, - "node_modules/@npmcli/node-gyp": { + "node_modules/@lerna/create/node_modules/is-fullwidth-code-point": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@npmcli/node-gyp/-/node-gyp-3.0.0.tgz", - "integrity": "sha512-gp8pRXC2oOxu0DUE1/M3bYtb1b3/DbJ5aM113+XJBgfXdussRAsX0YOrOhdd8WvnAR6auDBvJomGAkLKA5ydxA==", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "dev": true, + "license": "MIT", "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": ">=8" } }, - "node_modules/@npmcli/promise-spawn": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-6.0.2.tgz", - "integrity": "sha512-gGq0NJkIGSwdbUt4yhdF8ZrmkGKVz9vAdVzpOfnom+V8PLSmSOVhZwbNvZZS1EYcJN5hzzKBxmmVVAInM6HQLg==", + "node_modules/@lerna/create/node_modules/is-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", + "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", "dev": true, - "dependencies": { - "which": "^3.0.0" - }, + "license": "MIT", "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": ">=8" } }, - "node_modules/@npmcli/promise-spawn/node_modules/which": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/which/-/which-3.0.1.tgz", - "integrity": "sha512-XA1b62dzQzLfaEOSQFTCOd5KFf/1VSzZo7/7TUjnya6u0vGGKzU96UQBZTAThCb2j4/xjBAyii1OhRLJEivHvg==", + "node_modules/@lerna/create/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "dev": true, + "license": "MIT", "dependencies": { - "isexe": "^2.0.0" + "argparse": "^2.0.1" }, "bin": { - "node-which": "bin/which.js" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "js-yaml": "bin/js-yaml.js" } }, - "node_modules/@npmcli/run-script": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/@npmcli/run-script/-/run-script-6.0.2.tgz", - "integrity": "sha512-NCcr1uQo1k5U+SYlnIrbAh3cxy+OQT1VtqiAbxdymSlptbzBb62AjH2xXgjNCoP073hoa1CfCAcwoZ8k96C4nA==", + "node_modules/@lerna/create/node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", "dev": true, + "license": "MIT", "dependencies": { - "@npmcli/node-gyp": "^3.0.0", - "@npmcli/promise-spawn": "^6.0.0", - "node-gyp": "^9.0.0", - "read-package-json-fast": "^3.0.0", - "which": "^3.0.0" + "universalify": "^2.0.0" }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "optionalDependencies": { + "graceful-fs": "^4.1.6" } }, - "node_modules/@npmcli/run-script/node_modules/which": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/which/-/which-3.0.1.tgz", - "integrity": "sha512-XA1b62dzQzLfaEOSQFTCOd5KFf/1VSzZo7/7TUjnya6u0vGGKzU96UQBZTAThCb2j4/xjBAyii1OhRLJEivHvg==", + "node_modules/@lerna/create/node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", "dev": true, + "license": "MIT", "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/which.js" + "semver": "^7.5.3" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@nrwl/devkit": { - "version": "16.6.0", - "resolved": "https://registry.npmjs.org/@nrwl/devkit/-/devkit-16.6.0.tgz", - "integrity": "sha512-xZEN6wfA1uJwv+FVRQFOHsCcpvGvIYGx2zutbzungDodWkfzlJ3tzIGqYjIpPCBVT83erM6Gscnka2W46AuKfA==", + "node_modules/@lerna/create/node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", "dev": true, - "dependencies": { - "@nx/devkit": "16.6.0" + "license": "MIT", + "engines": { + "node": ">=6" } }, - "node_modules/@nrwl/tao": { - "version": "16.6.0", - "resolved": "https://registry.npmjs.org/@nrwl/tao/-/tao-16.6.0.tgz", - "integrity": "sha512-NQkDhmzlR1wMuYzzpl4XrKTYgyIzELdJ+dVrNKf4+p4z5WwKGucgRBj60xMQ3kdV25IX95/fmMDB8qVp/pNQ0Q==", + "node_modules/@lerna/create/node_modules/minimatch": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.5.tgz", + "integrity": "sha512-tUpxzX0VAzJHjLu0xUfFv1gwVp9ba3IOuRAVH2EGuRW8a5emA2FlACLqiT/lDVtS1W+TGNwqz3sWaNyLgDJWuw==", "dev": true, + "license": "ISC", "dependencies": { - "nx": "16.6.0", - "tslib": "^2.3.0" + "brace-expansion": "^1.1.7" }, - "bin": { - "tao": "index.js" + "engines": { + "node": "*" } }, - "node_modules/@nx/devkit": { - "version": "16.6.0", - "resolved": "https://registry.npmjs.org/@nx/devkit/-/devkit-16.6.0.tgz", - "integrity": "sha512-rhJ0y+MSPHDuoZPxsOYdj/n5ks+gK74TIMgTb8eZgPT/uR86a4oxf62wUQXgECedR5HzLE2HunbnoLhhJXmpJw==", + "node_modules/@lerna/create/node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/@lerna/create/node_modules/node-fetch": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", "dev": true, + "license": "MIT", "dependencies": { - "@nrwl/devkit": "16.6.0", - "ejs": "^3.1.7", - "ignore": "^5.0.4", - "semver": "7.5.3", - "tmp": "~0.2.1", - "tslib": "^2.3.0" + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" }, "peerDependencies": { - "nx": ">= 15 <= 17" + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } } }, - "node_modules/@nx/devkit/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "node_modules/@lerna/create/node_modules/npm-package-arg": { + "version": "11.0.2", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-11.0.2.tgz", + "integrity": "sha512-IGN0IAwmhDJwy13Wc8k+4PEbTPhpJnMtfR53ZbOyjkvmEcLS4nCwp6mvMWjS5sUjeiW3mpx6cHmuhKEu9XmcQw==", "dev": true, + "license": "ISC", "dependencies": { - "yallist": "^4.0.0" + "hosted-git-info": "^7.0.0", + "proc-log": "^4.0.0", + "semver": "^7.3.5", + "validate-npm-package-name": "^5.0.0" }, "engines": { - "node": ">=10" + "node": "^16.14.0 || >=18.0.0" } }, - "node_modules/@nx/devkit/node_modules/semver": { - "version": "7.5.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.3.tgz", - "integrity": "sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ==", + "node_modules/@lerna/create/node_modules/npm-packlist": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-8.0.2.tgz", + "integrity": "sha512-shYrPFIS/JLP4oQmAwDyk5HcyysKW8/JLTEA32S0Z5TzvpaeeX2yMFfoK1fjEBnCBvVyIB/Jj/GBFdm0wsgzbA==", "dev": true, + "license": "ISC", "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" + "ignore-walk": "^6.0.4" }, "engines": { - "node": ">=10" + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/@nx/devkit/node_modules/tmp": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz", - "integrity": "sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==", + "node_modules/@lerna/create/node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.0.0" + }, "engines": { - "node": ">=14.14" + "node": ">=8" } }, - "node_modules/@nx/devkit/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/@nx/nx-darwin-arm64": { - "version": "16.6.0", - "resolved": "https://registry.npmjs.org/@nx/nx-darwin-arm64/-/nx-darwin-arm64-16.6.0.tgz", - "integrity": "sha512-8nJuqcWG/Ob39rebgPLpv2h/V46b9Rqqm/AGH+bYV9fNJpxgMXclyincbMIWvfYN2tW+Vb9DusiTxV6RPrLapA==", - "cpu": [ - "arm64" - ], + "node_modules/@lerna/create/node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", "dev": true, - "optional": true, - "os": [ - "darwin" - ], + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, "engines": { - "node": ">= 10" + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@nx/nx-darwin-x64": { - "version": "16.6.0", - "resolved": "https://registry.npmjs.org/@nx/nx-darwin-x64/-/nx-darwin-x64-16.6.0.tgz", - "integrity": "sha512-T4DV0/2PkPZjzjmsmQEyjPDNBEKc4Rhf7mbIZlsHXj27BPoeNjEcbjtXKuOZHZDIpGFYECGT/sAF6C2NVYgmxw==", - "cpu": [ - "x64" - ], + "node_modules/@lerna/create/node_modules/ora": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", + "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", "dev": true, - "optional": true, - "os": [ - "darwin" - ], + "license": "MIT", + "dependencies": { + "bl": "^4.1.0", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "is-unicode-supported": "^0.1.0", + "log-symbols": "^4.1.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + }, "engines": { - "node": ">= 10" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@nx/nx-freebsd-x64": { - "version": "16.6.0", - "resolved": "https://registry.npmjs.org/@nx/nx-freebsd-x64/-/nx-freebsd-x64-16.6.0.tgz", - "integrity": "sha512-Ck/yejYgp65dH9pbExKN/X0m22+xS3rWF1DBr2LkP6j1zJaweRc3dT83BWgt5mCjmcmZVk3J8N01AxULAzUAqA==", - "cpu": [ - "x64" - ], + "node_modules/@lerna/create/node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "dev": true, - "optional": true, - "os": [ - "freebsd" - ], + "license": "MIT", "engines": { - "node": ">= 10" + "node": ">=8" } }, - "node_modules/@nx/nx-linux-arm-gnueabihf": { - "version": "16.6.0", - "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm-gnueabihf/-/nx-linux-arm-gnueabihf-16.6.0.tgz", - "integrity": "sha512-eyk/R1mBQ3X0PCSS+Cck3onvr3wmZVmM/+x0x9Ai02Vm6q9Eq6oZ1YtZGQsklNIyw1vk2WV9rJCStfu9mLecEw==", - "cpu": [ - "arm" - ], + "node_modules/@lerna/create/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", "dev": true, - "optional": true, - "os": [ - "linux" - ], + "license": "MIT", "engines": { - "node": ">= 10" + "node": ">=8" } }, - "node_modules/@nx/nx-linux-arm64-gnu": { - "version": "16.6.0", - "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm64-gnu/-/nx-linux-arm64-gnu-16.6.0.tgz", - "integrity": "sha512-S0qFFdQFDmBIEZqBAJl4K47V3YuMvDvthbYE0enXrXApWgDApmhtxINXSOjSus7DNq9kMrgtSDGkBmoBot61iw==", - "cpu": [ - "arm64" - ], + "node_modules/@lerna/create/node_modules/restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", "dev": true, - "optional": true, - "os": [ - "linux" - ], + "license": "MIT", + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, "engines": { - "node": ">= 10" + "node": ">=8" } }, - "node_modules/@nx/nx-linux-arm64-musl": { - "version": "16.6.0", - "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm64-musl/-/nx-linux-arm64-musl-16.6.0.tgz", - "integrity": "sha512-TXWY5VYtg2wX/LWxyrUkDVpqCyJHF7fWoVMUSlFe+XQnk9wp/yIbq2s0k3h8I4biYb6AgtcVqbR4ID86lSNuMA==", - "cpu": [ - "arm64" - ], + "node_modules/@lerna/create/node_modules/rimraf": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-4.4.1.tgz", + "integrity": "sha512-Gk8NlF062+T9CqNGn6h4tls3k6T1+/nXdOcSZVikNVtlRdYpA7wRJJMoXmuvOnLW844rPjdQ7JgXCYM6PPC/og==", "dev": true, - "optional": true, - "os": [ - "linux" - ], + "license": "ISC", + "dependencies": { + "glob": "^9.2.0" + }, + "bin": { + "rimraf": "dist/cjs/src/bin.js" + }, "engines": { - "node": ">= 10" + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@nx/nx-linux-x64-gnu": { - "version": "16.6.0", - "resolved": "https://registry.npmjs.org/@nx/nx-linux-x64-gnu/-/nx-linux-x64-gnu-16.6.0.tgz", - "integrity": "sha512-qQIpSVN8Ij4oOJ5v+U+YztWJ3YQkeCIevr4RdCE9rDilfq9RmBD94L4VDm7NRzYBuQL8uQxqWzGqb7ZW4mfHpw==", - "cpu": [ - "x64" - ], + "node_modules/@lerna/create/node_modules/rxjs": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" } }, - "node_modules/@nx/nx-linux-x64-musl": { - "version": "16.6.0", - "resolved": "https://registry.npmjs.org/@nx/nx-linux-x64-musl/-/nx-linux-x64-musl-16.6.0.tgz", - "integrity": "sha512-EYOHe11lfVfEfZqSAIa1c39mx2Obr4mqd36dBZx+0UKhjrcmWiOdsIVYMQSb3n0TqB33BprjI4p9ZcFSDuoNbA==", - "cpu": [ - "x64" - ], + "node_modules/@lerna/create/node_modules/ssri": { + "version": "10.0.6", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-10.0.6.tgz", + "integrity": "sha512-MGrFH9Z4NP9Iyhqn16sDtBpRRNJ0Y2hNa6D65h736fVSaPCHr4DM4sWUNvVaSuC+0OBGhwsrydQwmgfg5LncqQ==", "dev": true, - "optional": true, - "os": [ - "linux" - ], + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, "engines": { - "node": ">= 10" + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/@nx/nx-win32-arm64-msvc": { - "version": "16.6.0", - "resolved": "https://registry.npmjs.org/@nx/nx-win32-arm64-msvc/-/nx-win32-arm64-msvc-16.6.0.tgz", - "integrity": "sha512-f1BmuirOrsAGh5+h/utkAWNuqgohvBoekQgMxYcyJxSkFN+pxNG1U68P59Cidn0h9mkyonxGVCBvWwJa3svVFA==", - "cpu": [ - "arm64" - ], + "node_modules/@lerna/create/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, - "optional": true, - "os": [ - "win32" - ], + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, "engines": { - "node": ">= 10" + "node": ">=8" } }, - "node_modules/@nx/nx-win32-x64-msvc": { - "version": "16.6.0", - "resolved": "https://registry.npmjs.org/@nx/nx-win32-x64-msvc/-/nx-win32-x64-msvc-16.6.0.tgz", - "integrity": "sha512-UmTTjFLpv4poVZE3RdUHianU8/O9zZYBiAnTRq5spwSDwxJHnLTZBUxFFf3ztCxeHOUIfSyW9utpGfCMCptzvQ==", - "cpu": [ - "x64" - ], + "node_modules/@lerna/create/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, - "optional": true, - "os": [ - "win32" - ], + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, "engines": { - "node": ">= 10" + "node": ">=8" } }, - "node_modules/@octokit/auth-token": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-2.5.0.tgz", - "integrity": "sha512-r5FVUJCOLl19AxiuZD2VRZ/ORjp/4IN98Of6YJoJOkY75CIBuYfmiNHGrDwXr+aLGG55igl9QrxX3hbiXlLb+g==", - "dependencies": { - "@octokit/types": "^6.0.3" - } + "node_modules/@lerna/create/node_modules/universal-user-agent": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.1.tgz", + "integrity": "sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ==", + "dev": true, + "license": "ISC" }, - "node_modules/@octokit/core": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/@octokit/core/-/core-3.6.0.tgz", - "integrity": "sha512-7RKRKuA4xTjMhY+eG3jthb3hlZCsOwg3rztWh75Xc+ShDWOfDDATWbeZpAHBNRpm4Tv9WgBMOy1zEJYXG6NJ7Q==", - "dependencies": { - "@octokit/auth-token": "^2.4.4", - "@octokit/graphql": "^4.5.8", - "@octokit/request": "^5.6.3", - "@octokit/request-error": "^2.0.5", - "@octokit/types": "^6.0.3", - "before-after-hook": "^2.2.0", - "universal-user-agent": "^6.0.0" + "node_modules/@lerna/create/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" } }, - "node_modules/@octokit/core/node_modules/@octokit/endpoint": { - "version": "6.0.12", - "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-6.0.12.tgz", - "integrity": "sha512-lF3puPwkQWGfkMClXb4k/eUT/nZKQfxinRWJrdZaJO85Dqwo/G0yOC434Jr2ojwafWJMYqFGFa5ms4jJUgujdA==", - "dependencies": { - "@octokit/types": "^6.0.3", - "is-plain-object": "^5.0.0", - "universal-user-agent": "^6.0.0" + "node_modules/@lerna/create/node_modules/upath": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/upath/-/upath-2.0.1.tgz", + "integrity": "sha512-1uEe95xksV1O0CYKXo8vQvN1JEbtJp7lb7C5U9HMsIp6IVwntkH/oNUzyVNQSd4S1sYk2FpSSW44FqMc8qee5w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4", + "yarn": "*" } }, - "node_modules/@octokit/core/node_modules/@octokit/request": { - "version": "5.6.3", - "resolved": "https://registry.npmjs.org/@octokit/request/-/request-5.6.3.tgz", - "integrity": "sha512-bFJl0I1KVc9jYTe9tdGGpAMPy32dLBXXo1dS/YwSCTL/2nd9XeHsY616RE3HPXDVk+a+dBuzyz5YdlXwcDTr2A==", - "dependencies": { - "@octokit/endpoint": "^6.0.1", - "@octokit/request-error": "^2.1.0", - "@octokit/types": "^6.16.1", - "is-plain-object": "^5.0.0", - "node-fetch": "^2.6.7", - "universal-user-agent": "^6.0.0" + "node_modules/@lerna/create/node_modules/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" } }, - "node_modules/@octokit/core/node_modules/before-after-hook": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.3.tgz", - "integrity": "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==" - }, - "node_modules/@octokit/core/node_modules/universal-user-agent": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.1.tgz", - "integrity": "sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ==" - }, - "node_modules/@octokit/endpoint": { - "version": "5.5.3", - "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-5.5.3.tgz", - "integrity": "sha512-EzKwkwcxeegYYah5ukEeAI/gYRLv2Y9U5PpIsseGSFDk+G3RbipQGBs8GuYS1TLCtQaqoO66+aQGtITPalxsNQ==", + "node_modules/@lerna/create/node_modules/validate-npm-package-name": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-5.0.1.tgz", + "integrity": "sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==", "dev": true, - "dependencies": { - "@octokit/types": "^2.0.0", - "is-plain-object": "^3.0.0", - "universal-user-agent": "^5.0.0" + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/@octokit/endpoint/node_modules/@octokit/types": { - "version": "2.16.2", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-2.16.2.tgz", - "integrity": "sha512-O75k56TYvJ8WpAakWwYRN8Bgu60KrmX0z1KqFp1kNiFNkgW+JW+9EBKZ+S33PU6SLvbihqd+3drvPxKK68Ee8Q==", + "node_modules/@lerna/create/node_modules/write-file-atomic": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz", + "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==", "dev": true, + "license": "ISC", "dependencies": { - "@types/node": ">= 8" + "imurmurhash": "^0.1.4", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/@octokit/endpoint/node_modules/is-plain-object": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-3.0.1.tgz", - "integrity": "sha512-Xnpx182SBMrr/aBik8y+GuR4U1L9FqMSojwDQwPMmxyC6bvEqly9UBCxhauBF5vNh2gwWJNX6oDV7O+OM4z34g==", + "node_modules/@lerna/create/node_modules/write-file-atomic/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", "dev": true, + "license": "ISC", "engines": { - "node": ">=0.10.0" + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@octokit/endpoint/node_modules/universal-user-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-5.0.0.tgz", - "integrity": "sha512-B5TPtzZleXyPrUMKCpEHFmVhMN6EhmJYjG5PQna9s7mXeSqGTLap4OpqLl5FCEFUI3UBmllkETwKf/db66Y54Q==", + "node_modules/@lerna/create/node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", "dev": true, - "dependencies": { - "os-name": "^3.1.0" + "license": "ISC", + "engines": { + "node": ">=12" } }, - "node_modules/@octokit/graphql": { - "version": "4.8.0", - "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-4.8.0.tgz", - "integrity": "sha512-0gv+qLSBLKF0z8TKaSKTsS39scVKF9dbMxJpj3U0vC7wjNWFuIpL/z76Qe2fiuCbDRcJSavkXsVtMS6/dtQQsg==", + "node_modules/@mdx-js/react": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@mdx-js/react/-/react-3.1.0.tgz", + "integrity": "sha512-QjHtSaoameoalGnKDT3FoIl4+9RwyTmo9ZJGBdLOks/YOiWHoRDI3PUwEzOE7kEmGcV3AFcp9K6dYu9rEuKLAQ==", + "dev": true, "dependencies": { - "@octokit/request": "^5.6.0", - "@octokit/types": "^6.0.3", - "universal-user-agent": "^6.0.0" + "@types/mdx": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + }, + "peerDependencies": { + "@types/react": ">=16", + "react": ">=16" } }, - "node_modules/@octokit/graphql/node_modules/@octokit/endpoint": { - "version": "6.0.12", - "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-6.0.12.tgz", - "integrity": "sha512-lF3puPwkQWGfkMClXb4k/eUT/nZKQfxinRWJrdZaJO85Dqwo/G0yOC434Jr2ojwafWJMYqFGFa5ms4jJUgujdA==", + "node_modules/@napi-rs/wasm-runtime": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.4.tgz", + "integrity": "sha512-9zESzOO5aDByvhIAsOy9TbpZ0Ur2AJbUI7UT73kcUTS2mxAMHOBaa1st/jAymNoCtvrit99kkzT1FZuXVcgfIQ==", + "dev": true, + "license": "MIT", "dependencies": { - "@octokit/types": "^6.0.3", - "is-plain-object": "^5.0.0", - "universal-user-agent": "^6.0.0" + "@emnapi/core": "^1.1.0", + "@emnapi/runtime": "^1.1.0", + "@tybys/wasm-util": "^0.9.0" } }, - "node_modules/@octokit/graphql/node_modules/@octokit/request": { - "version": "5.6.3", - "resolved": "https://registry.npmjs.org/@octokit/request/-/request-5.6.3.tgz", - "integrity": "sha512-bFJl0I1KVc9jYTe9tdGGpAMPy32dLBXXo1dS/YwSCTL/2nd9XeHsY616RE3HPXDVk+a+dBuzyz5YdlXwcDTr2A==", + "node_modules/@nicolo-ribaudo/eslint-scope-5-internals": { + "version": "5.1.1-v1", + "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz", + "integrity": "sha512-54/JRvkLIzzDWshCWfuhadfrfZVPiElY8Fcgmg1HroEly/EDSszzhBAsarCux+D/kOslTRquNzuyGSmUSTTHGg==", + "license": "MIT", "dependencies": { - "@octokit/endpoint": "^6.0.1", - "@octokit/request-error": "^2.1.0", - "@octokit/types": "^6.16.1", - "is-plain-object": "^5.0.0", - "node-fetch": "^2.6.7", - "universal-user-agent": "^6.0.0" + "eslint-scope": "5.1.1" } }, - "node_modules/@octokit/graphql/node_modules/universal-user-agent": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.1.tgz", - "integrity": "sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ==" - }, - "node_modules/@octokit/openapi-types": { - "version": "11.2.0", - "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-11.2.0.tgz", - "integrity": "sha512-PBsVO+15KSlGmiI8QAzaqvsNlZlrDlyAJYcrXBCvVUxCp7VnXjkwPoFHgjEJXx3WF9BAwkA6nfCUA7i9sODzKA==" - }, - "node_modules/@octokit/plugin-enterprise-rest": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/@octokit/plugin-enterprise-rest/-/plugin-enterprise-rest-6.0.1.tgz", - "integrity": "sha512-93uGjlhUD+iNg1iWhUENAtJata6w5nE+V4urXOAlIXdco6xNZtUSfYY8dzp3Udy74aqO/B5UZL80x/YMa5PKRw==", - "dev": true - }, - "node_modules/@octokit/plugin-paginate-rest": { - "version": "2.21.3", - "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.21.3.tgz", - "integrity": "sha512-aCZTEf0y2h3OLbrgKkrfFdjRL6eSOo8komneVQJnYecAxIej7Bafor2xhuDJOIFau4pk0i/P28/XgtbyPF0ZHw==", + "node_modules/@nicolo-ribaudo/eslint-scope-5-internals/node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "license": "BSD-2-Clause", "dependencies": { - "@octokit/types": "^6.40.0" + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" }, - "peerDependencies": { - "@octokit/core": ">=2" + "engines": { + "node": ">=8.0.0" } }, - "node_modules/@octokit/plugin-paginate-rest/node_modules/@octokit/openapi-types": { - "version": "12.11.0", - "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-12.11.0.tgz", - "integrity": "sha512-VsXyi8peyRq9PqIz/tpqiL2w3w80OgVMwBHltTml3LmVvXiphgeqmY9mvBw9Wu7e0QWk/fqD37ux8yP5uVekyQ==" - }, - "node_modules/@octokit/plugin-paginate-rest/node_modules/@octokit/types": { - "version": "6.41.0", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.41.0.tgz", - "integrity": "sha512-eJ2jbzjdijiL3B4PrSQaSjuF2sPEQPVCPzBvTHJD9Nz+9dw2SGH4K4xeQJ77YfTq5bRQ+bD8wT11JbeDPmxmGg==", + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.2.tgz", + "integrity": "sha512-wrIBsjA5pl13f0RN4Zx4FNWmU71lv03meGKnqRUoCyan17s4V3WL92f3w3AIuWbNnpcrQyFBU5qMavJoB8d27w==", "dependencies": { - "@octokit/openapi-types": "^12.11.0" + "@nodelib/fs.stat": "2.0.2", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" } }, - "node_modules/@octokit/plugin-request-log": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-1.0.4.tgz", - "integrity": "sha512-mLUsMkgP7K/cnFEw07kWqXGF5LKrOkD+lhCrKvPHXWDywAwuDUeDwWBpc69XK3pNX0uKiVt8g5z96PJ6z9xCFA==", - "dev": true, - "peerDependencies": { - "@octokit/core": ">=3" + "node_modules/@nodelib/fs.stat": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.2.tgz", + "integrity": "sha512-z8+wGWV2dgUhLqrtRYa03yDx4HWMvXKi1z8g3m2JyxAx8F7xk74asqPk5LAETjqDSGLFML/6CDl0+yFunSYicw==", + "engines": { + "node": ">= 8" } }, - "node_modules/@octokit/plugin-rest-endpoint-methods": { - "version": "5.16.2", - "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-5.16.2.tgz", - "integrity": "sha512-8QFz29Fg5jDuTPXVtey05BLm7OB+M8fnvE64RNegzX7U+5NUXcOcnpTIK0YfSHBg8gYd0oxIq3IZTe9SfPZiRw==", + "node_modules/@nodelib/fs.walk": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.3.tgz", + "integrity": "sha512-l6t8xEhfK9Sa4YO5mIRdau7XSOADfmh3jCr0evNHdY+HNkW6xuQhgMH7D73VV6WpZOagrW0UludvMTiifiwTfA==", "dependencies": { - "@octokit/types": "^6.39.0", - "deprecation": "^2.3.1" + "@nodelib/fs.scandir": "2.1.2", + "fastq": "^1.6.0" }, - "peerDependencies": { - "@octokit/core": ">=3" - } - }, - "node_modules/@octokit/plugin-rest-endpoint-methods/node_modules/@octokit/openapi-types": { - "version": "12.11.0", - "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-12.11.0.tgz", - "integrity": "sha512-VsXyi8peyRq9PqIz/tpqiL2w3w80OgVMwBHltTml3LmVvXiphgeqmY9mvBw9Wu7e0QWk/fqD37ux8yP5uVekyQ==" - }, - "node_modules/@octokit/plugin-rest-endpoint-methods/node_modules/@octokit/types": { - "version": "6.41.0", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.41.0.tgz", - "integrity": "sha512-eJ2jbzjdijiL3B4PrSQaSjuF2sPEQPVCPzBvTHJD9Nz+9dw2SGH4K4xeQJ77YfTq5bRQ+bD8wT11JbeDPmxmGg==", - "dependencies": { - "@octokit/openapi-types": "^12.11.0" + "engines": { + "node": ">= 8" } }, - "node_modules/@octokit/request": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/@octokit/request/-/request-4.1.1.tgz", - "integrity": "sha512-LOyL0i3oxRo418EXRSJNk/3Q4I0/NKawTn6H/CQp+wnrG1UFLGu080gSsgnWobhPo5BpUNgSQ5BRk5FOOJhD1Q==", + "node_modules/@npmcli/agent": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/@npmcli/agent/-/agent-2.2.2.tgz", + "integrity": "sha512-OrcNPXdpSl9UX7qPVRWbmWMCSXrcDa2M9DvrbOTj7ao1S4PlqVFYv9/yLKMkrJKZ/V5A/kDBC690or307i26Og==", "dev": true, + "license": "ISC", "dependencies": { - "@octokit/endpoint": "^5.1.0", - "@octokit/request-error": "^1.0.1", - "deprecation": "^2.0.0", - "is-plain-object": "^3.0.0", - "node-fetch": "^2.3.0", - "once": "^1.4.0", - "universal-user-agent": "^2.1.0" + "agent-base": "^7.1.0", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.1", + "lru-cache": "^10.0.1", + "socks-proxy-agent": "^8.0.3" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" } }, - "node_modules/@octokit/request-error": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-2.1.0.tgz", - "integrity": "sha512-1VIvgXxs9WHSjicsRwq8PlR2LR2x6DwsJAaFgzdi0JfJoGSO8mYI/cHJQ+9FbN21aa+DrgNLnwObmyeSC8Rmpg==", + "node_modules/@npmcli/arborist": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/@npmcli/arborist/-/arborist-7.5.4.tgz", + "integrity": "sha512-nWtIc6QwwoUORCRNzKx4ypHqCk3drI+5aeYdMTQQiRCcn4lOOgfQh7WyZobGYTxXPSq1VwV53lkpN/BRlRk08g==", + "dev": true, + "license": "ISC", "dependencies": { - "@octokit/types": "^6.0.3", - "deprecation": "^2.0.0", - "once": "^1.4.0" + "@isaacs/string-locale-compare": "^1.1.0", + "@npmcli/fs": "^3.1.1", + "@npmcli/installed-package-contents": "^2.1.0", + "@npmcli/map-workspaces": "^3.0.2", + "@npmcli/metavuln-calculator": "^7.1.1", + "@npmcli/name-from-folder": "^2.0.0", + "@npmcli/node-gyp": "^3.0.0", + "@npmcli/package-json": "^5.1.0", + "@npmcli/query": "^3.1.0", + "@npmcli/redact": "^2.0.0", + "@npmcli/run-script": "^8.1.0", + "bin-links": "^4.0.4", + "cacache": "^18.0.3", + "common-ancestor-path": "^1.0.1", + "hosted-git-info": "^7.0.2", + "json-parse-even-better-errors": "^3.0.2", + "json-stringify-nice": "^1.1.4", + "lru-cache": "^10.2.2", + "minimatch": "^9.0.4", + "nopt": "^7.2.1", + "npm-install-checks": "^6.2.0", + "npm-package-arg": "^11.0.2", + "npm-pick-manifest": "^9.0.1", + "npm-registry-fetch": "^17.0.1", + "pacote": "^18.0.6", + "parse-conflict-json": "^3.0.0", + "proc-log": "^4.2.0", + "proggy": "^2.0.0", + "promise-all-reject-late": "^1.0.0", + "promise-call-limit": "^3.0.1", + "read-package-json-fast": "^3.0.2", + "semver": "^7.3.7", + "ssri": "^10.0.6", + "treeverse": "^3.0.0", + "walk-up-path": "^3.0.1" + }, + "bin": { + "arborist": "bin/index.js" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" } }, - "node_modules/@octokit/request/node_modules/@octokit/request-error": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-1.2.1.tgz", - "integrity": "sha512-+6yDyk1EES6WK+l3viRDElw96MvwfJxCt45GvmjDUKWjYIb3PJZQkq3i46TwGwoPD4h8NmTrENmtyA1FwbmhRA==", + "node_modules/@npmcli/arborist/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "dev": true, + "license": "MIT", "dependencies": { - "@octokit/types": "^2.0.0", - "deprecation": "^2.0.0", - "once": "^1.4.0" + "balanced-match": "^1.0.0" } }, - "node_modules/@octokit/request/node_modules/@octokit/types": { - "version": "2.16.2", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-2.16.2.tgz", - "integrity": "sha512-O75k56TYvJ8WpAakWwYRN8Bgu60KrmX0z1KqFp1kNiFNkgW+JW+9EBKZ+S33PU6SLvbihqd+3drvPxKK68Ee8Q==", + "node_modules/@npmcli/arborist/node_modules/cacache": { + "version": "18.0.4", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-18.0.4.tgz", + "integrity": "sha512-B+L5iIa9mgcjLbliir2th36yEwPftrzteHYujzsx3dFP/31GCHcIeS8f5MGd80odLOjaOvSpU3EEAmRQptkxLQ==", "dev": true, + "license": "ISC", "dependencies": { - "@types/node": ">= 8" + "@npmcli/fs": "^3.1.0", + "fs-minipass": "^3.0.0", + "glob": "^10.2.2", + "lru-cache": "^10.0.1", + "minipass": "^7.0.3", + "minipass-collect": "^2.0.1", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "p-map": "^4.0.0", + "ssri": "^10.0.0", + "tar": "^6.1.11", + "unique-filename": "^3.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" } }, - "node_modules/@octokit/request/node_modules/is-plain-object": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-3.0.1.tgz", - "integrity": "sha512-Xnpx182SBMrr/aBik8y+GuR4U1L9FqMSojwDQwPMmxyC6bvEqly9UBCxhauBF5vNh2gwWJNX6oDV7O+OM4z34g==", + "node_modules/@npmcli/arborist/node_modules/fs-minipass": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-3.0.3.tgz", + "integrity": "sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==", "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, "engines": { - "node": ">=0.10.0" + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/@octokit/rest": { - "version": "16.26.0", - "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-16.26.0.tgz", - "integrity": "sha512-NBpzre44ZAQWZhlH+zUYTgqI0pHN+c9rNj4d+pCydGEiKTGc1HKmoTghEUyr9GxazDyoAvmpx9nL0I7QS1Olvg==", + "node_modules/@npmcli/arborist/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", "dev": true, + "license": "ISC", "dependencies": { - "@octokit/request": "^4.0.1", - "@octokit/request-error": "^1.0.2", - "atob-lite": "^2.0.0", - "before-after-hook": "^1.4.0", - "btoa-lite": "^1.0.0", - "deprecation": "^2.0.0", - "lodash.get": "^4.4.2", - "lodash.set": "^4.3.2", - "lodash.uniq": "^4.5.0", - "octokit-pagination-methods": "^1.1.0", - "once": "^1.4.0", - "universal-user-agent": "^2.0.0", - "url-template": "^2.0.8" + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@octokit/rest/node_modules/@octokit/request-error": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-1.2.1.tgz", - "integrity": "sha512-+6yDyk1EES6WK+l3viRDElw96MvwfJxCt45GvmjDUKWjYIb3PJZQkq3i46TwGwoPD4h8NmTrENmtyA1FwbmhRA==", + "node_modules/@npmcli/arborist/node_modules/hosted-git-info": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz", + "integrity": "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==", "dev": true, + "license": "ISC", "dependencies": { - "@octokit/types": "^2.0.0", - "deprecation": "^2.0.0", - "once": "^1.4.0" + "lru-cache": "^10.0.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" } }, - "node_modules/@octokit/rest/node_modules/@octokit/types": { - "version": "2.16.2", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-2.16.2.tgz", - "integrity": "sha512-O75k56TYvJ8WpAakWwYRN8Bgu60KrmX0z1KqFp1kNiFNkgW+JW+9EBKZ+S33PU6SLvbihqd+3drvPxKK68Ee8Q==", + "node_modules/@npmcli/arborist/node_modules/json-parse-even-better-errors": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.2.tgz", + "integrity": "sha512-fi0NG4bPjCHunUJffmLd0gxssIgkNmArMvis4iNah6Owg1MCJjWhEcDLmsK6iGkJq3tHwbDkTlce70/tmXN4cQ==", "dev": true, - "dependencies": { - "@types/node": ">= 8" - } - }, - "node_modules/@octokit/tsconfig": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@octokit/tsconfig/-/tsconfig-1.0.2.tgz", - "integrity": "sha512-I0vDR0rdtP8p2lGMzvsJzbhdOWy405HcGovrspJ8RRibHnyRgggUSNO5AIox5LmqiwmatHKYsvj6VGFHkqS7lA==", - "dev": true - }, - "node_modules/@octokit/types": { - "version": "6.34.0", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.34.0.tgz", - "integrity": "sha512-s1zLBjWhdEI2zwaoSgyOFoKSl109CUcVBCc7biPJ3aAf6LGLU6szDvi31JPU7bxfla2lqfhjbbg/5DdFNxOwHw==", - "dependencies": { - "@octokit/openapi-types": "^11.2.0" + "license": "MIT", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/@octokit/webhooks-methods": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@octokit/webhooks-methods/-/webhooks-methods-2.0.0.tgz", - "integrity": "sha512-35cfQ4YWlnZnmZKmIxlGPUPLtbkF8lr/A/1Sk1eC0ddLMwQN06dOuLc+dI3YLQS+T+MoNt3DIQ0NynwgKPilig==", - "license": "MIT" - }, - "node_modules/@octokit/webhooks-types": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/@octokit/webhooks-types/-/webhooks-types-5.8.0.tgz", - "integrity": "sha512-8adktjIb76A7viIdayQSFuBEwOzwhDC+9yxZpKNHjfzrlostHCw0/N7JWpWMObfElwvJMk2fY2l1noENCk9wmw==", - "license": "MIT" - }, - "node_modules/@parcel/watcher": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.0.4.tgz", - "integrity": "sha512-cTDi+FUDBIUOBKEtj+nhiJ71AZVlkAsQFuGQTun5tV9mwQBQgZvhCzG+URPQc8myeN32yRVZEfVAPCs1RW+Jvg==", + "node_modules/@npmcli/arborist/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "dev": true, - "hasInstallScript": true, + "license": "ISC", "dependencies": { - "node-addon-api": "^3.2.1", - "node-gyp-build": "^4.3.0" + "brace-expansion": "^2.0.1" }, "engines": { - "node": ">= 10.0.0" + "node": ">=16 || 14 >=14.17" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@pkgjs/parseargs": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", - "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "node_modules/@npmcli/arborist/node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", "dev": true, - "optional": true, + "license": "ISC", "engines": { - "node": ">=14" + "node": ">=16 || 14 >=14.17" } }, - "node_modules/@pkgr/utils": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/@pkgr/utils/-/utils-2.4.2.tgz", - "integrity": "sha512-POgTXhjrTfbTV63DiFXav4lBHiICLKKwDeaKn9Nphwj7WH6m0hMMCaJkMyRWjgtPFyRKRVoMXXjczsTQRDEhYw==", + "node_modules/@npmcli/arborist/node_modules/npm-package-arg": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-11.0.3.tgz", + "integrity": "sha512-sHGJy8sOC1YraBywpzQlIKBE4pBbGbiF95U6Auspzyem956E0+FtDtsx1ZxlOJkQCZ1AFXAY/yuvtFYrOxF+Bw==", + "dev": true, + "license": "ISC", "dependencies": { - "cross-spawn": "^7.0.3", - "fast-glob": "^3.3.0", - "is-glob": "^4.0.3", - "open": "^9.1.0", - "picocolors": "^1.0.0", - "tslib": "^2.6.0" + "hosted-git-info": "^7.0.0", + "proc-log": "^4.0.0", + "semver": "^7.3.5", + "validate-npm-package-name": "^5.0.0" }, "engines": { - "node": "^12.20.0 || ^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/unts" + "node": "^16.14.0 || >=18.0.0" } }, - "node_modules/@pkgr/utils/node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "node_modules/@npmcli/arborist/node_modules/ssri": { + "version": "10.0.6", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-10.0.6.tgz", + "integrity": "sha512-MGrFH9Z4NP9Iyhqn16sDtBpRRNJ0Y2hNa6D65h736fVSaPCHr4DM4sWUNvVaSuC+0OBGhwsrydQwmgfg5LncqQ==", + "dev": true, + "license": "ISC", "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" + "minipass": "^7.0.3" }, "engines": { - "node": ">= 8" + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/@pkgr/utils/node_modules/define-lazy-prop": { + "node_modules/@npmcli/arborist/node_modules/unique-filename": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", - "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@pkgr/utils/node_modules/fast-glob": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", - "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-3.0.0.tgz", + "integrity": "sha512-afXhuC55wkAmZ0P18QsVE6kp8JaxrEokN2HGIoIVv2ijHQd419H0+6EigAFcIzXeMIkcIkNBpB3L/DXB3cTS/g==", + "dev": true, + "license": "ISC", "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" + "unique-slug": "^4.0.0" }, "engines": { - "node": ">=8.6.0" + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/@pkgr/utils/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "node_modules/@npmcli/arborist/node_modules/unique-slug": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-4.0.0.tgz", + "integrity": "sha512-WrcA6AyEfqDX5bWige/4NQfPZMtASNVxdmWR76WESYQVAACSgWcR6e9i0mofqqBxYFtL4oAxPIptY73/0YE1DQ==", + "dev": true, + "license": "ISC", "dependencies": { - "is-glob": "^4.0.1" + "imurmurhash": "^0.1.4" }, "engines": { - "node": ">= 6" + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/@pkgr/utils/node_modules/is-wsl": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", - "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", - "dependencies": { - "is-docker": "^2.0.0" - }, + "node_modules/@npmcli/arborist/node_modules/validate-npm-package-name": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-5.0.1.tgz", + "integrity": "sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==", + "dev": true, + "license": "ISC", "engines": { - "node": ">=8" + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/@pkgr/utils/node_modules/open": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/open/-/open-9.1.0.tgz", - "integrity": "sha512-OS+QTnw1/4vrf+9hh1jc1jnYjzSG4ttTBB8UxOwAnInG3Uo4ssetzC1ihqaIHjLJnA5GGlRl6QlZXOTQhRBUvg==", + "node_modules/@npmcli/fs": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-3.1.1.tgz", + "integrity": "sha512-q9CRWjpHCMIh5sVyefoD1cA7PkvILqCZsnSOEUUivORLjxCO/Irmue2DprETiNgEqktDBZaM1Bi+jrarx1XdCg==", + "dev": true, + "license": "ISC", "dependencies": { - "default-browser": "^4.0.0", - "define-lazy-prop": "^3.0.0", - "is-inside-container": "^1.0.0", - "is-wsl": "^2.2.0" + "semver": "^7.3.5" }, "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/@pkgr/utils/node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "node_modules/@npmcli/git": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-5.0.8.tgz", + "integrity": "sha512-liASfw5cqhjNW9UFd+ruwwdEf/lbOAQjLL2XY2dFW/bkJheXDYZgOyul/4gVvEV4BWkTXjYGmDqMw9uegdbJNQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/promise-spawn": "^7.0.0", + "ini": "^4.1.3", + "lru-cache": "^10.0.1", + "npm-pick-manifest": "^9.0.0", + "proc-log": "^4.0.0", + "promise-inflight": "^1.0.1", + "promise-retry": "^2.0.1", + "semver": "^7.3.5", + "which": "^4.0.0" + }, "engines": { - "node": ">=8" + "node": "^16.14.0 || >=18.0.0" } }, - "node_modules/@pkgr/utils/node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dependencies": { - "shebang-regex": "^3.0.0" - }, + "node_modules/@npmcli/git/node_modules/ini": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.3.tgz", + "integrity": "sha512-X7rqawQBvfdjS10YU1y1YVreA3SsLrW9dX2CewP2EbBJM4ypVNLDkO5y04gejPwKIY9lR+7r9gn3rFPt/kmWFg==", + "dev": true, + "license": "ISC", "engines": { - "node": ">=8" + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/@pkgr/utils/node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "node_modules/@npmcli/git/node_modules/isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "dev": true, + "license": "ISC", "engines": { - "node": ">=8" + "node": ">=16" } }, - "node_modules/@pkgr/utils/node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "node_modules/@npmcli/git/node_modules/which": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", + "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "dev": true, + "license": "ISC", "dependencies": { - "isexe": "^2.0.0" + "isexe": "^3.1.1" }, "bin": { - "node-which": "bin/node-which" + "node-which": "bin/which.js" }, "engines": { - "node": ">= 8" + "node": "^16.13.0 || >=18.0.0" } }, - "node_modules/@playwright/test": { - "version": "1.48.1", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.48.1.tgz", - "integrity": "sha512-s9RtWoxkOLmRJdw3oFvhFbs9OJS0BzrLUc8Hf6l2UdCNd1rqeEyD4BhCJkvzeEoD1FsK4mirsWwGerhVmYKtZg==", + "node_modules/@npmcli/installed-package-contents": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@npmcli/installed-package-contents/-/installed-package-contents-2.1.0.tgz", + "integrity": "sha512-c8UuGLeZpm69BryRykLuKRyKFZYJsZSCT4aVY5ds4omyZqJ172ApzgfKJ5eV/r3HgLdUYgFVe54KSFVjKoe27w==", "dev": true, + "license": "ISC", "dependencies": { - "playwright": "1.48.1" + "npm-bundled": "^3.0.0", + "npm-normalize-package-bin": "^3.0.0" }, "bin": { - "playwright": "cli.js" + "installed-package-contents": "bin/index.js" }, "engines": { - "node": ">=18" + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/@pmmmwh/react-refresh-webpack-plugin": { - "version": "0.5.11", - "resolved": "https://registry.npmjs.org/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.5.11.tgz", - "integrity": "sha512-7j/6vdTym0+qZ6u4XbSAxrWBGYSdCfTzySkj7WAFgDLmSyWlOrWvpyzxlFh5jtw9dn0oL/jtW+06XfFiisN3JQ==", + "node_modules/@npmcli/installed-package-contents/node_modules/npm-bundled": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-3.0.1.tgz", + "integrity": "sha512-+AvaheE/ww1JEwRHOrn4WHNzOxGtVp+adrg2AeZS/7KuxGUYFuBta98wYpfHBbJp6Tg6j1NKSEVHNcfZzJHQwQ==", + "dev": true, + "license": "ISC", "dependencies": { - "ansi-html-community": "^0.0.8", - "common-path-prefix": "^3.0.0", - "core-js-pure": "^3.23.3", - "error-stack-parser": "^2.0.6", - "find-up": "^5.0.0", - "html-entities": "^2.1.0", - "loader-utils": "^2.0.4", - "schema-utils": "^3.0.0", - "source-map": "^0.7.3" + "npm-normalize-package-bin": "^3.0.0" }, "engines": { - "node": ">= 10.13" - }, - "peerDependencies": { - "@types/webpack": "4.x || 5.x", - "react-refresh": ">=0.10.0 <1.0.0", - "sockjs-client": "^1.4.0", - "type-fest": ">=0.17.0 <5.0.0", - "webpack": ">=4.43.0 <6.0.0", - "webpack-dev-server": "3.x || 4.x", - "webpack-hot-middleware": "2.x", - "webpack-plugin-serve": "0.x || 1.x" + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/installed-package-contents/node_modules/npm-normalize-package-bin": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-3.0.1.tgz", + "integrity": "sha512-dMxCf+zZ+3zeQZXKxmyuCKlIDPGuv8EF940xbkC4kQVDTtqoh6rJFO+JTKSA6/Rwi0getWmtuy4Itup0AMcaDQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/map-workspaces": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@npmcli/map-workspaces/-/map-workspaces-3.0.6.tgz", + "integrity": "sha512-tkYs0OYnzQm6iIRdfy+LcLBjcKuQCeE5YLb8KnrIlutJfheNaPvPpgoFEyEFgbjzl5PLZ3IA/BWAwRU0eHuQDA==", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/name-from-folder": "^2.0.0", + "glob": "^10.2.2", + "minimatch": "^9.0.0", + "read-package-json-fast": "^3.0.0" }, - "peerDependenciesMeta": { - "@types/webpack": { - "optional": true - }, - "sockjs-client": { - "optional": true - }, - "type-fest": { - "optional": true - }, - "webpack-dev-server": { - "optional": true - }, - "webpack-hot-middleware": { - "optional": true - }, - "webpack-plugin-serve": { - "optional": true - } + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/@pmmmwh/react-refresh-webpack-plugin/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "node_modules/@npmcli/map-workspaces/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "license": "MIT", "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" + "balanced-match": "^1.0.0" + } + }, + "node_modules/@npmcli/map-workspaces/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@pmmmwh/react-refresh-webpack-plugin/node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "node_modules/@npmcli/map-workspaces/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" + "brace-expansion": "^2.0.1" }, "engines": { - "node": ">=10" + "node": ">=16 || 14 >=14.17" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@pmmmwh/react-refresh-webpack-plugin/node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "node_modules/@npmcli/map-workspaces/node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/@npmcli/metavuln-calculator": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@npmcli/metavuln-calculator/-/metavuln-calculator-7.1.1.tgz", + "integrity": "sha512-Nkxf96V0lAx3HCpVda7Vw4P23RILgdi/5K1fmj2tZkWIYLpXAN8k2UVVOsW16TsS5F8Ws2I7Cm+PU1/rsVF47g==", + "dev": true, + "license": "ISC", "dependencies": { - "p-locate": "^5.0.0" + "cacache": "^18.0.0", + "json-parse-even-better-errors": "^3.0.0", + "pacote": "^18.0.0", + "proc-log": "^4.1.0", + "semver": "^7.3.5" }, "engines": { - "node": ">=10" + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/metavuln-calculator/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@npmcli/metavuln-calculator/node_modules/cacache": { + "version": "18.0.4", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-18.0.4.tgz", + "integrity": "sha512-B+L5iIa9mgcjLbliir2th36yEwPftrzteHYujzsx3dFP/31GCHcIeS8f5MGd80odLOjaOvSpU3EEAmRQptkxLQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/fs": "^3.1.0", + "fs-minipass": "^3.0.0", + "glob": "^10.2.2", + "lru-cache": "^10.0.1", + "minipass": "^7.0.3", + "minipass-collect": "^2.0.1", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "p-map": "^4.0.0", + "ssri": "^10.0.0", + "tar": "^6.1.11", + "unique-filename": "^3.0.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": "^16.14.0 || >=18.0.0" } }, - "node_modules/@pmmmwh/react-refresh-webpack-plugin/node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "node_modules/@npmcli/metavuln-calculator/node_modules/fs-minipass": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-3.0.3.tgz", + "integrity": "sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==", + "dev": true, + "license": "ISC", "dependencies": { - "p-limit": "^3.0.2" + "minipass": "^7.0.3" }, "engines": { - "node": ">=10" + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/metavuln-calculator/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@pmmmwh/react-refresh-webpack-plugin/node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "node_modules/@npmcli/metavuln-calculator/node_modules/json-parse-even-better-errors": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.2.tgz", + "integrity": "sha512-fi0NG4bPjCHunUJffmLd0gxssIgkNmArMvis4iNah6Owg1MCJjWhEcDLmsK6iGkJq3tHwbDkTlce70/tmXN4cQ==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=8" + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/@pmmmwh/react-refresh-webpack-plugin/node_modules/schema-utils": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", - "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "node_modules/@npmcli/metavuln-calculator/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", "dependencies": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" + "brace-expansion": "^2.0.1" }, "engines": { - "node": ">= 10.13.0" + "node": ">=16 || 14 >=14.17" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@pmmmwh/react-refresh-webpack-plugin/node_modules/source-map": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", - "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "node_modules/@npmcli/metavuln-calculator/node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "license": "ISC", "engines": { - "node": ">= 8" + "node": ">=16 || 14 >=14.17" } }, - "node_modules/@polka/url": { - "version": "1.0.0-next.23", - "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.23.tgz", - "integrity": "sha512-C16M+IYz0rgRhWZdCmK+h58JMv8vijAA61gmz2rspCSwKwzBebpdcsiUmwrtJRdphuY30i6BSLEOP8ppbNLyLg==" - }, - "node_modules/@preact/signals": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@preact/signals/-/signals-1.3.0.tgz", - "integrity": "sha512-EOMeg42SlLS72dhoq6Vjq08havnLseWmPQ8A0YsgIAqMgWgx7V1a39+Pxo6i7SY5NwJtH4849JogFq3M67AzWg==", - "license": "MIT", + "node_modules/@npmcli/metavuln-calculator/node_modules/ssri": { + "version": "10.0.6", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-10.0.6.tgz", + "integrity": "sha512-MGrFH9Z4NP9Iyhqn16sDtBpRRNJ0Y2hNa6D65h736fVSaPCHr4DM4sWUNvVaSuC+0OBGhwsrydQwmgfg5LncqQ==", + "dev": true, + "license": "ISC", "dependencies": { - "@preact/signals-core": "^1.7.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/preact" + "minipass": "^7.0.3" }, - "peerDependencies": { - "preact": "10.x" - } - }, - "node_modules/@preact/signals-core": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/@preact/signals-core/-/signals-core-1.8.0.tgz", - "integrity": "sha512-OBvUsRZqNmjzCZXWLxkZfhcgT+Fk8DDcT/8vD6a1xhDemodyy87UJRJfASMuSD8FaAIeGgGm85ydXhm7lr4fyA==", - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/preact" + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/@puppeteer/browsers": { - "version": "1.4.6", - "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.4.6.tgz", - "integrity": "sha512-x4BEjr2SjOPowNeiguzjozQbsc6h437ovD/wu+JpaenxVLm3jkgzHY2xOslMTp50HoTvQreMjiexiGQw1sqZlQ==", + "node_modules/@npmcli/metavuln-calculator/node_modules/unique-filename": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-3.0.0.tgz", + "integrity": "sha512-afXhuC55wkAmZ0P18QsVE6kp8JaxrEokN2HGIoIVv2ijHQd419H0+6EigAFcIzXeMIkcIkNBpB3L/DXB3cTS/g==", + "dev": true, + "license": "ISC", "dependencies": { - "debug": "4.3.4", - "extract-zip": "2.0.1", - "progress": "2.0.3", - "proxy-agent": "6.3.0", - "tar-fs": "3.0.4", - "unbzip2-stream": "1.4.3", - "yargs": "17.7.1" - }, - "bin": { - "browsers": "lib/cjs/main-cli.js" + "unique-slug": "^4.0.0" }, "engines": { - "node": ">=16.3.0" - }, - "peerDependencies": { - "typescript": ">= 4.7.4" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/@puppeteer/browsers/node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "node_modules/@npmcli/metavuln-calculator/node_modules/unique-slug": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-4.0.0.tgz", + "integrity": "sha512-WrcA6AyEfqDX5bWige/4NQfPZMtASNVxdmWR76WESYQVAACSgWcR6e9i0mofqqBxYFtL4oAxPIptY73/0YE1DQ==", + "dev": true, + "license": "ISC", "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" + "imurmurhash": "^0.1.4" }, "engines": { - "node": ">=12" + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/@puppeteer/browsers/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" - }, - "node_modules/@puppeteer/browsers/node_modules/extract-zip": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", - "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", - "dependencies": { - "debug": "^4.1.1", - "get-stream": "^5.1.0", - "yauzl": "^2.10.0" - }, - "bin": { - "extract-zip": "cli.js" - }, + "node_modules/@npmcli/name-from-folder": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/name-from-folder/-/name-from-folder-2.0.0.tgz", + "integrity": "sha512-pwK+BfEBZJbKdNYpHHRTNBwBoqrN/iIMO0AiGvYsp3Hoaq0WbgGSWQR6SCldZovoDpY3yje5lkFUe6gsDgJ2vg==", + "dev": true, + "license": "ISC", "engines": { - "node": ">= 10.17.0" - }, - "optionalDependencies": { - "@types/yauzl": "^2.9.1" + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/@puppeteer/browsers/node_modules/is-fullwidth-code-point": { + "node_modules/@npmcli/node-gyp": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "resolved": "https://registry.npmjs.org/@npmcli/node-gyp/-/node-gyp-3.0.0.tgz", + "integrity": "sha512-gp8pRXC2oOxu0DUE1/M3bYtb1b3/DbJ5aM113+XJBgfXdussRAsX0YOrOhdd8WvnAR6auDBvJomGAkLKA5ydxA==", + "dev": true, + "license": "ISC", "engines": { - "node": ">=8" - } - }, - "node_modules/@puppeteer/browsers/node_modules/pump": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz", - "integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==", - "license": "MIT", - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/@puppeteer/browsers/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "node_modules/@npmcli/package-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@npmcli/package-json/-/package-json-5.2.0.tgz", + "integrity": "sha512-qe/kiqqkW0AGtvBjL8TJKZk/eBBSpnJkUWvHdQ9jM2lKHXRYYJuyNpJPlJw3c8QjC2ow6NZYiLExhUaeJelbxQ==", + "dev": true, + "license": "ISC", "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" + "@npmcli/git": "^5.0.0", + "glob": "^10.2.2", + "hosted-git-info": "^7.0.0", + "json-parse-even-better-errors": "^3.0.0", + "normalize-package-data": "^6.0.0", + "proc-log": "^4.0.0", + "semver": "^7.5.3" }, "engines": { - "node": ">=8" + "node": "^16.14.0 || >=18.0.0" } }, - "node_modules/@puppeteer/browsers/node_modules/tar-fs": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.4.tgz", - "integrity": "sha512-5AFQU8b9qLfZCX9zp2duONhPmZv0hGYiBPJsyUdqMjzq/mqVpy/rEUSeHk1+YitmxugaptgBh5oDGU3VsAJq4w==", + "node_modules/@npmcli/package-json/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "license": "MIT", "dependencies": { - "mkdirp-classic": "^0.5.2", - "pump": "^3.0.0", - "tar-stream": "^3.1.5" + "balanced-match": "^1.0.0" } }, - "node_modules/@puppeteer/browsers/node_modules/tar-stream": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", - "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", + "node_modules/@npmcli/package-json/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "license": "ISC", "dependencies": { - "b4a": "^1.6.4", - "fast-fifo": "^1.2.0", - "streamx": "^2.15.0" + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@puppeteer/browsers/node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "node_modules/@npmcli/package-json/node_modules/hosted-git-info": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz", + "integrity": "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==", + "dev": true, + "license": "ISC", "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" + "lru-cache": "^10.0.1" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + "node": "^16.14.0 || >=18.0.0" } }, - "node_modules/@puppeteer/browsers/node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "node_modules/@npmcli/package-json/node_modules/json-parse-even-better-errors": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.2.tgz", + "integrity": "sha512-fi0NG4bPjCHunUJffmLd0gxssIgkNmArMvis4iNah6Owg1MCJjWhEcDLmsK6iGkJq3tHwbDkTlce70/tmXN4cQ==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=10" + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/@puppeteer/browsers/node_modules/yargs": { - "version": "17.7.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.1.tgz", - "integrity": "sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw==", + "node_modules/@npmcli/package-json/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" + "brace-expansion": "^2.0.1" }, "engines": { - "node": ">=12" + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@puppeteer/browsers/node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "node_modules/@npmcli/package-json/node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "license": "ISC", "engines": { - "node": ">=12" + "node": ">=16 || 14 >=14.17" } }, - "node_modules/@radix-ui/number": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.0.1.tgz", - "integrity": "sha512-T5gIdVO2mmPW3NNhjNgEP3cqMXjXL9UbO0BzWcXfvdBs+BohbQxvd/K5hSVKmn9/lbTdsQVKbUcP5WLCwvUbBg==", + "node_modules/@npmcli/package-json/node_modules/normalize-package-data": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-6.0.2.tgz", + "integrity": "sha512-V6gygoYb/5EmNI+MEGrWkC+e6+Rr7mTmfHrxDbLzxQogBkgzo76rkok0Am6thgSF7Mv2nLOajAJj5vDJZEFn7g==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { - "@babel/runtime": "^7.13.10" + "hosted-git-info": "^7.0.0", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" } }, - "node_modules/@radix-ui/primitive": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.0.1.tgz", - "integrity": "sha512-yQ8oGX2GVsEYMWGxcovu1uGWPCxV5BFfeeYxqPmuAzUyLT9qmaMXSAhXpb0WrspIeqYzdJpkh2vHModJPgRIaw==", - "license": "MIT", + "node_modules/@npmcli/promise-spawn": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-7.0.2.tgz", + "integrity": "sha512-xhfYPXoV5Dy4UkY0D+v2KkwvnDfiA/8Mt3sWCGI/hM03NsYIH8ZaG6QzS9x7pje5vHZBZJ2v6VRFVTWACnqcmQ==", + "dev": true, + "license": "ISC", "dependencies": { - "@babel/runtime": "^7.13.10" + "which": "^4.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" } }, - "node_modules/@radix-ui/react-arrow": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.0.3.tgz", - "integrity": "sha512-wSP+pHsB/jQRaL6voubsQ/ZlrGBHHrOjmBnr19hxYgtS0WvAFwZhK2WP/YY5yF9uKECCEEDGxuLxq1NBK51wFA==", + "node_modules/@npmcli/promise-spawn/node_modules/isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", "dev": true, - "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/react-primitive": "1.0.3" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } + "license": "ISC", + "engines": { + "node": ">=16" } }, - "node_modules/@radix-ui/react-collection": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.0.3.tgz", - "integrity": "sha512-3SzW+0PW7yBBoQlT8wNcGtaxaD0XSu0uLUFgrtHY08Acx05TaHaOmVLR73c0j/cqpDy53KBMO7s0dx2wmOIDIA==", + "node_modules/@npmcli/promise-spawn/node_modules/which": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", + "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", "dev": true, + "license": "ISC", "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/react-compose-refs": "1.0.1", - "@radix-ui/react-context": "1.0.1", - "@radix-ui/react-primitive": "1.0.3", - "@radix-ui/react-slot": "1.0.2" + "isexe": "^3.1.1" }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" + "bin": { + "node-which": "bin/which.js" }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } + "engines": { + "node": "^16.13.0 || >=18.0.0" } }, - "node_modules/@radix-ui/react-compose-refs": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.0.1.tgz", - "integrity": "sha512-fDSBgd44FKHa1FRMU59qBMPFcl2PZE+2nmqunj+BWFyYYjnhIDWL2ItDs3rrbJDQOtzt5nIebLCQc4QRfz6LJw==", - "license": "MIT", + "node_modules/@npmcli/query": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@npmcli/query/-/query-3.1.0.tgz", + "integrity": "sha512-C/iR0tk7KSKGldibYIB9x8GtO/0Bd0I2mhOaDb8ucQL/bQVTmGoeREaFj64Z5+iCBRf3dQfed0CjJL7I8iTkiQ==", + "dev": true, + "license": "ISC", "dependencies": { - "@babel/runtime": "^7.13.10" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0" + "postcss-selector-parser": "^6.0.10" }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/@radix-ui/react-context": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.0.1.tgz", - "integrity": "sha512-ebbrdFoYTcuZ0v4wG5tedGnp9tzcV8awzsxYph7gXUyvnNLuTIcCk1q17JEbnVhXAKG9oX3KtchwiMIAYp9NLg==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.13.10" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } + "node_modules/@npmcli/redact": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@npmcli/redact/-/redact-2.0.1.tgz", + "integrity": "sha512-YgsR5jCQZhVmTJvjduTOIHph0L73pK8xwMVaDY0PatySqVM9AZj93jpoXYSJqfHFxFkN9dmqTw6OiqExsS3LPw==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^16.14.0 || >=18.0.0" } }, - "node_modules/@radix-ui/react-direction": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.0.1.tgz", - "integrity": "sha512-RXcvnXgyvYvBEOhCBuddKecVkoMiI10Jcm5cTI7abJRAHYfFxeu+FBQs/DvdxSYucxR5mna0dNsL6QFlds5TMA==", + "node_modules/@npmcli/run-script": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@npmcli/run-script/-/run-script-8.1.0.tgz", + "integrity": "sha512-y7efHHwghQfk28G2z3tlZ67pLG0XdfYbcVG26r7YIXALRsrVQcTq4/tdenSmdOrEsNahIYA/eh8aEVROWGFUDg==", "dev": true, + "license": "ISC", "dependencies": { - "@babel/runtime": "^7.13.10" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0" + "@npmcli/node-gyp": "^3.0.0", + "@npmcli/package-json": "^5.0.0", + "@npmcli/promise-spawn": "^7.0.0", + "node-gyp": "^10.0.0", + "proc-log": "^4.0.0", + "which": "^4.0.0" }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } + "engines": { + "node": "^16.14.0 || >=18.0.0" } }, - "node_modules/@radix-ui/react-dismissable-layer": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.0.4.tgz", - "integrity": "sha512-7UpBa/RKMoHJYjie1gkF1DlK8l1fdU/VKDpoS3rCCo8YBJR294GwcEHyxHw72yvphJ7ld0AXEcSLAzY2F/WyCg==", + "node_modules/@npmcli/run-script/node_modules/isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", "dev": true, - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/primitive": "1.0.1", - "@radix-ui/react-compose-refs": "1.0.1", - "@radix-ui/react-primitive": "1.0.3", - "@radix-ui/react-use-callback-ref": "1.0.1", - "@radix-ui/react-use-escape-keydown": "1.0.3" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } + "license": "ISC", + "engines": { + "node": ">=16" } }, - "node_modules/@radix-ui/react-focus-guards": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.0.1.tgz", - "integrity": "sha512-Rect2dWbQ8waGzhMavsIbmSVCgYxkXLxxR3ZvCX79JOglzdEy4JXMb98lq4hPxUbLr77nP0UOGf4rcMU+s1pUA==", - "license": "MIT", + "node_modules/@npmcli/run-script/node_modules/which": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", + "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "dev": true, + "license": "ISC", "dependencies": { - "@babel/runtime": "^7.13.10" + "isexe": "^3.1.1" }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0" + "bin": { + "node-which": "bin/which.js" }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } + "engines": { + "node": "^16.13.0 || >=18.0.0" } }, - "node_modules/@radix-ui/react-focus-scope": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.0.3.tgz", - "integrity": "sha512-upXdPfqI4islj2CslyfUBNlaJCPybbqRHAi1KER7Isel9Q2AtSJ0zRBZv8mWQiFXD2nyAJ4BhC3yXgZ6kMBSrQ==", + "node_modules/@nx/devkit": { + "version": "20.2.1", + "resolved": "https://registry.npmjs.org/@nx/devkit/-/devkit-20.2.1.tgz", + "integrity": "sha512-boNTu7Z7oHkYjrYg5Wzg+cQfbEJ2nntRj1eI99w8mp4qz2B4PEEjJOB0BZafR54ZcKpGEbyp/QBB945GsjTUbw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/react-compose-refs": "1.0.1", - "@radix-ui/react-primitive": "1.0.3", - "@radix-ui/react-use-callback-ref": "1.0.1" + "ejs": "^3.1.7", + "enquirer": "~2.3.6", + "ignore": "^5.0.4", + "minimatch": "9.0.3", + "semver": "^7.5.3", + "tmp": "~0.2.1", + "tslib": "^2.3.0", + "yargs-parser": "21.1.1" }, "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } + "nx": ">= 19 <= 21" } }, - "node_modules/@radix-ui/react-id": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.0.1.tgz", - "integrity": "sha512-tI7sT/kqYp8p96yGWY1OAnLHrqDgzHefRBKQ2YAkBS5ja7QLcZ9Z/uY7bEjPUatf8RomoXM8/1sMj1IJaE5UzQ==", + "node_modules/@nx/devkit/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, "license": "MIT", "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/react-use-layout-effect": "1.0.1" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } + "balanced-match": "^1.0.0" } }, - "node_modules/@radix-ui/react-popper": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.1.2.tgz", - "integrity": "sha512-1CnGGfFi/bbqtJZZ0P/NQY20xdG3E0LALJaLUEoKwPLwl6PPPfbeiCqMVQnhoFRAxjJj4RpBRJzDmUgsex2tSg==", + "node_modules/@nx/devkit/node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", "dev": true, + "license": "ISC", "dependencies": { - "@babel/runtime": "^7.13.10", - "@floating-ui/react-dom": "^2.0.0", - "@radix-ui/react-arrow": "1.0.3", - "@radix-ui/react-compose-refs": "1.0.1", - "@radix-ui/react-context": "1.0.1", - "@radix-ui/react-primitive": "1.0.3", - "@radix-ui/react-use-callback-ref": "1.0.1", - "@radix-ui/react-use-layout-effect": "1.0.1", - "@radix-ui/react-use-rect": "1.0.1", - "@radix-ui/react-use-size": "1.0.1", - "@radix-ui/rect": "1.0.1" + "brace-expansion": "^2.0.1" }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" + "engines": { + "node": ">=16 || 14 >=14.17" }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@radix-ui/react-portal": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.0.3.tgz", - "integrity": "sha512-xLYZeHrWoPmA5mEKEfZZevoVRK/Q43GfzRXkWV6qawIWWK8t6ifIiLQdd7rmQ4Vk1bmI21XhqF9BN3jWf+phpA==", + "node_modules/@nx/devkit/node_modules/tmp": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz", + "integrity": "sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==", "dev": true, "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/react-primitive": "1.0.3" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } + "engines": { + "node": ">=14.14" } }, - "node_modules/@radix-ui/react-primitive": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-1.0.3.tgz", - "integrity": "sha512-yi58uVyoAcK/Nq1inRY56ZSjKypBNKTa/1mcL8qdl6oJeEaDbOldlzrGn7P6Q3Id5d+SYNGc5AJgc4vGhjs5+g==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/react-slot": "1.0.2" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-roving-focus": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.0.4.tgz", - "integrity": "sha512-2mUg5Mgcu001VkGy+FfzZyzbmuUWzgWkj3rvv4yu+mLw03+mTzbxZHvfcGyFp2b8EkQeMkpRQ5FiA2Vr2O6TeQ==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/primitive": "1.0.1", - "@radix-ui/react-collection": "1.0.3", - "@radix-ui/react-compose-refs": "1.0.1", - "@radix-ui/react-context": "1.0.1", - "@radix-ui/react-direction": "1.0.1", - "@radix-ui/react-id": "1.0.1", - "@radix-ui/react-primitive": "1.0.3", - "@radix-ui/react-use-callback-ref": "1.0.1", - "@radix-ui/react-use-controllable-state": "1.0.1" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-select": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-1.2.2.tgz", - "integrity": "sha512-zI7McXr8fNaSrUY9mZe4x/HC0jTLY9fWNhO1oLWYMQGDXuV4UCivIGTxwioSzO0ZCYX9iSLyWmAh/1TOmX3Cnw==", + "node_modules/@nx/devkit/node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", "dev": true, - "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/number": "1.0.1", - "@radix-ui/primitive": "1.0.1", - "@radix-ui/react-collection": "1.0.3", - "@radix-ui/react-compose-refs": "1.0.1", - "@radix-ui/react-context": "1.0.1", - "@radix-ui/react-direction": "1.0.1", - "@radix-ui/react-dismissable-layer": "1.0.4", - "@radix-ui/react-focus-guards": "1.0.1", - "@radix-ui/react-focus-scope": "1.0.3", - "@radix-ui/react-id": "1.0.1", - "@radix-ui/react-popper": "1.1.2", - "@radix-ui/react-portal": "1.0.3", - "@radix-ui/react-primitive": "1.0.3", - "@radix-ui/react-slot": "1.0.2", - "@radix-ui/react-use-callback-ref": "1.0.1", - "@radix-ui/react-use-controllable-state": "1.0.1", - "@radix-ui/react-use-layout-effect": "1.0.1", - "@radix-ui/react-use-previous": "1.0.1", - "@radix-ui/react-visually-hidden": "1.0.3", - "aria-hidden": "^1.1.1", - "react-remove-scroll": "2.5.5" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } + "license": "ISC", + "engines": { + "node": ">=12" } }, - "node_modules/@radix-ui/react-separator": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.0.3.tgz", - "integrity": "sha512-itYmTy/kokS21aiV5+Z56MZB54KrhPgn6eHDKkFeOLR34HMN2s8PaN47qZZAGnvupcjxHaFZnW4pQEh0BvvVuw==", + "node_modules/@nx/nx-darwin-arm64": { + "version": "20.2.1", + "resolved": "https://registry.npmjs.org/@nx/nx-darwin-arm64/-/nx-darwin-arm64-20.2.1.tgz", + "integrity": "sha512-nJcyPZfH6Vq4cG6gRnQ8PcnVOLePeT3exzLnQu0I4I2EtCTPyCSRA3gxoGzZ3qZFMQTsCbwv4HYfdx42AXOTAQ==", + "cpu": [ + "arm64" + ], "dev": true, - "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/react-primitive": "1.0.3" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-slot": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.2.tgz", - "integrity": "sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg==", "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/react-compose-refs": "1.0.1" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-toggle": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle/-/react-toggle-1.0.3.tgz", - "integrity": "sha512-Pkqg3+Bc98ftZGsl60CLANXQBBQ4W3mTFS9EJvNxKMZ7magklKV69/id1mlAlOFDDfHvlCms0fx8fA4CMKDJHg==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/primitive": "1.0.1", - "@radix-ui/react-primitive": "1.0.3", - "@radix-ui/react-use-controllable-state": "1.0.1" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-toggle-group": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle-group/-/react-toggle-group-1.0.4.tgz", - "integrity": "sha512-Uaj/M/cMyiyT9Bx6fOZO0SAG4Cls0GptBWiBmBxofmDbNVnYYoyRWj/2M/6VCi/7qcXFWnHhRUfdfZFvvkuu8A==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/primitive": "1.0.1", - "@radix-ui/react-context": "1.0.1", - "@radix-ui/react-direction": "1.0.1", - "@radix-ui/react-primitive": "1.0.3", - "@radix-ui/react-roving-focus": "1.0.4", - "@radix-ui/react-toggle": "1.0.3", - "@radix-ui/react-use-controllable-state": "1.0.1" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" } }, - "node_modules/@radix-ui/react-toolbar": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@radix-ui/react-toolbar/-/react-toolbar-1.0.4.tgz", - "integrity": "sha512-tBgmM/O7a07xbaEkYJWYTXkIdU/1pW4/KZORR43toC/4XWyBCURK0ei9kMUdp+gTPPKBgYLxXmRSH1EVcIDp8Q==", + "node_modules/@nx/nx-darwin-x64": { + "version": "20.2.1", + "resolved": "https://registry.npmjs.org/@nx/nx-darwin-x64/-/nx-darwin-x64-20.2.1.tgz", + "integrity": "sha512-SEiN8fjEs010ME4PRP8O9f8qG8AMZBGz8hOkF6ZrdlC+iEi4iyAGpgWFq8PKBlpVW4G5gxR91Y7eVaTKAsgH5w==", + "cpu": [ + "x64" + ], "dev": true, - "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/primitive": "1.0.1", - "@radix-ui/react-context": "1.0.1", - "@radix-ui/react-direction": "1.0.1", - "@radix-ui/react-primitive": "1.0.3", - "@radix-ui/react-roving-focus": "1.0.4", - "@radix-ui/react-separator": "1.0.3", - "@radix-ui/react-toggle-group": "1.0.4" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-callback-ref": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.0.1.tgz", - "integrity": "sha512-D94LjX4Sp0xJFVaoQOd3OO9k7tpBYNOXdVhkltUbGv2Qb9OXdrg/CpsjlZv7ia14Sylv398LswWBVVu5nqKzAQ==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.13.10" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-controllable-state": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.0.1.tgz", - "integrity": "sha512-Svl5GY5FQeN758fWKrjM6Qb7asvXeiZltlT4U2gVfl8Gx5UAv2sMR0LWo8yhsIZh2oQ0eFdZ59aoOOMV7b47VA==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/react-use-callback-ref": "1.0.1" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-escape-keydown": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.0.3.tgz", - "integrity": "sha512-vyL82j40hcFicA+M4Ex7hVkB9vHgSse1ZWomAqV2Je3RleKGO5iM8KMOEtfoSB0PnIelMd2lATjTGMYqN5ylTg==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/react-use-callback-ref": "1.0.1" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-layout-effect": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.0.1.tgz", - "integrity": "sha512-v/5RegiJWYdoCvMnITBkNNx6bCj20fiaJnWtRkU18yITptraXjffz5Qbn05uOiQnOvi+dbkznkoaMltz1GnszQ==", "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.13.10" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-previous": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.0.1.tgz", - "integrity": "sha512-cV5La9DPwiQ7S0gf/0qiD6YgNqM5Fk97Kdrlc5yBcrF3jyEZQwm7vYFqMo4IfeHgJXsRaMvLABFtd0OVEmZhDw==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.13.10" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-rect": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.0.1.tgz", - "integrity": "sha512-Cq5DLuSiuYVKNU8orzJMbl15TXilTnJKUCltMVQg53BQOF1/C5toAaGrowkgksdBQ9H+SRL23g0HDmg9tvmxXw==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/rect": "1.0.1" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-size": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.0.1.tgz", - "integrity": "sha512-ibay+VqrgcaI6veAojjofPATwledXiSmX+C0KrBk/xgpX9rBzPV3OsfwlhQdUOFbh+LKQorLYT+xTXW9V8yd0g==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/react-use-layout-effect": "1.0.1" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-visually-hidden": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.0.3.tgz", - "integrity": "sha512-D4w41yN5YRKtu464TLnByKzMDG/JlMPHtfZgQAu9v6mNakUqGUI9vUrfQKz8NK41VMm/xbZbh76NUTVtIYqOMA==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/react-primitive": "1.0.3" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/rect": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.0.1.tgz", - "integrity": "sha512-fyrgCaedtvMg9NK3en0pnOYJdtfwxUcNolezkNPUsoX57X8oQk+NkqcvzHXD2uKNij6GXmWU9NDru2IWjrO4BQ==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.13.10" - } - }, - "node_modules/@react-native-clipboard/clipboard": { - "version": "1.11.2", - "resolved": "https://registry.npmjs.org/@react-native-clipboard/clipboard/-/clipboard-1.11.2.tgz", - "integrity": "sha512-bHyZVW62TuleiZsXNHS1Pv16fWc0fh8O9WvBzl4h2fykqZRW9a+Pv/RGTH56E3X2PqzHP38K5go8zmCZUoIsoQ==", - "peerDependencies": { - "react": ">=16.0", - "react-native": ">=0.57.0" - } - }, - "node_modules/@react-native-community/blur": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@react-native-community/blur/-/blur-4.2.0.tgz", - "integrity": "sha512-StgP5zQJOCHqDRjmcKnzVkJ920S6DYBKRJfigSUnlkNQp+HzZtVtyKq0j5a7x84NtHcV7j8Uy5mz1Lx9ZKRKfA==", - "peerDependencies": { - "react": "*", - "react-native": "*" - } - }, - "node_modules/@react-native-community/cli": { - "version": "12.3.2", - "resolved": "https://registry.npmjs.org/@react-native-community/cli/-/cli-12.3.2.tgz", - "integrity": "sha512-WgoUWwLDcf/G1Su2COUUVs3RzAwnV/vUTdISSpAUGgSc57mPabaAoUctKTnfYEhCnE3j02k3VtaVPwCAFRO3TQ==", - "dependencies": { - "@react-native-community/cli-clean": "12.3.2", - "@react-native-community/cli-config": "12.3.2", - "@react-native-community/cli-debugger-ui": "12.3.2", - "@react-native-community/cli-doctor": "12.3.2", - "@react-native-community/cli-hermes": "12.3.2", - "@react-native-community/cli-plugin-metro": "12.3.2", - "@react-native-community/cli-server-api": "12.3.2", - "@react-native-community/cli-tools": "12.3.2", - "@react-native-community/cli-types": "12.3.2", - "chalk": "^4.1.2", - "commander": "^9.4.1", - "deepmerge": "^4.3.0", - "execa": "^5.0.0", - "find-up": "^4.1.0", - "fs-extra": "^8.1.0", - "graceful-fs": "^4.1.3", - "prompts": "^2.4.2", - "semver": "^7.5.2" - }, - "bin": { - "react-native": "build/bin.js" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@react-native-community/cli-clean": { - "version": "12.3.2", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-clean/-/cli-clean-12.3.2.tgz", - "integrity": "sha512-90k2hCX0ddSFPT7EN7h5SZj0XZPXP0+y/++v262hssoey3nhurwF57NGWN0XAR0o9BSW7+mBfeInfabzDraO6A==", - "dependencies": { - "@react-native-community/cli-tools": "12.3.2", - "chalk": "^4.1.2", - "execa": "^5.0.0" - } - }, - "node_modules/@react-native-community/cli-clean/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@react-native-community/cli-clean/node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@react-native-community/cli-clean/node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/@react-native-community/cli-clean/node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@react-native-community/cli-clean/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/@react-native-community/cli-clean/node_modules/human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "engines": { - "node": ">=10.17.0" - } - }, - "node_modules/@react-native-community/cli-clean/node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@react-native-community/cli-clean/node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "engines": { - "node": ">=6" - } - }, - "node_modules/@react-native-community/cli-clean/node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@react-native-community/cli-clean/node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@react-native-community/cli-clean/node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "engines": { - "node": ">=8" - } - }, - "node_modules/@react-native-community/cli-clean/node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@react-native-community/cli-clean/node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "engines": { - "node": ">=8" - } - }, - "node_modules/@react-native-community/cli-clean/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@react-native-community/cli-clean/node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@react-native-community/cli-config": { - "version": "12.3.2", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-config/-/cli-config-12.3.2.tgz", - "integrity": "sha512-UUCzDjQgvAVL/57rL7eOuFUhd+d+6qfM7V8uOegQFeFEmSmvUUDLYoXpBa5vAK9JgQtSqMBJ1Shmwao+/oElxQ==", - "dependencies": { - "@react-native-community/cli-tools": "12.3.2", - "chalk": "^4.1.2", - "cosmiconfig": "^5.1.0", - "deepmerge": "^4.3.0", - "glob": "^7.1.3", - "joi": "^17.2.1" - } - }, - "node_modules/@react-native-community/cli-config/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "node": ">= 10" } }, - "node_modules/@react-native-community/cli-config/node_modules/cosmiconfig": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz", - "integrity": "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==", - "dependencies": { - "import-fresh": "^2.0.0", - "is-directory": "^0.3.1", - "js-yaml": "^3.13.1", - "parse-json": "^4.0.0" - }, + "node_modules/@nx/nx-freebsd-x64": { + "version": "20.2.1", + "resolved": "https://registry.npmjs.org/@nx/nx-freebsd-x64/-/nx-freebsd-x64-20.2.1.tgz", + "integrity": "sha512-/yEKS9q17EG2Ci130McvpZM5YUghH1ql9UXWbVmitufn+RQD90hoblkG/B+cxJeZonrdKAjdpLQ+hfVz+FBd/g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], "engines": { - "node": ">=4" + "node": ">= 10" } }, - "node_modules/@react-native-community/cli-config/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, + "node_modules/@nx/nx-linux-arm-gnueabihf": { + "version": "20.2.1", + "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm-gnueabihf/-/nx-linux-arm-gnueabihf-20.2.1.tgz", + "integrity": "sha512-DPtRjTCJ5++stTGtjqYftCb2c0CNed2s2EZZLQuDP+tikTsLm0d3S3ZaU5eHhqZW35tQuMOVweOfC1nJ3/DTSA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": ">= 10" } }, - "node_modules/@react-native-community/cli-config/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/@nx/nx-linux-arm64-gnu": { + "version": "20.2.1", + "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm64-gnu/-/nx-linux-arm64-gnu-20.2.1.tgz", + "integrity": "sha512-ggGwHOEP6UjXeqv6DtRxizeBnX/zRZi8BRJbEJBwAt1cAUnLlklk8d+Hmjs+j/FlFXBV9f+ylpAqoYkplFR8jg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=8" + "node": ">= 10" } }, - "node_modules/@react-native-community/cli-config/node_modules/import-fresh": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", - "integrity": "sha512-eZ5H8rcgYazHbKC3PG4ClHNykCSxtAhxSSEM+2mb+7evD2CKF5V7c0dNum7AdpDh0ZdICwZY9sRSn8f+KH96sg==", - "dependencies": { - "caller-path": "^2.0.0", - "resolve-from": "^3.0.0" - }, + "node_modules/@nx/nx-linux-arm64-musl": { + "version": "20.2.1", + "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm64-musl/-/nx-linux-arm64-musl-20.2.1.tgz", + "integrity": "sha512-HZBGxsBJUFbWVTiyJxqt0tS8tlvp+Tp0D533mGKW75cU0rv9dnmbtTwkkkx+LXqerjSRvNS3Qtj0Uh2w92Vtig==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=4" + "node": ">= 10" } }, - "node_modules/@react-native-community/cli-config/node_modules/parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", - "dependencies": { - "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1" - }, + "node_modules/@nx/nx-linux-x64-gnu": { + "version": "20.2.1", + "resolved": "https://registry.npmjs.org/@nx/nx-linux-x64-gnu/-/nx-linux-x64-gnu-20.2.1.tgz", + "integrity": "sha512-pTytPwGiPRakqz2PKiWTSRNm9taE1U9n0+kRAAFzbOtzeW+eIoebe5xY5QMoZ+XtIZ6pJM2BUOyMD+/TX57r8Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=4" + "node": ">= 10" } }, - "node_modules/@react-native-community/cli-config/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, + "node_modules/@nx/nx-linux-x64-musl": { + "version": "20.2.1", + "resolved": "https://registry.npmjs.org/@nx/nx-linux-x64-musl/-/nx-linux-x64-musl-20.2.1.tgz", + "integrity": "sha512-p3egqe5zmwiDl6xSwHi2K9UZWiKbZ/s/j4qV+pZttzMyNPfhohTeP+VwQqjTeQ1hPBl2YhwmmktEPsIPYJG7YA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=8" - } - }, - "node_modules/@react-native-community/cli-debugger-ui": { - "version": "12.3.2", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-debugger-ui/-/cli-debugger-ui-12.3.2.tgz", - "integrity": "sha512-nSWQUL+51J682DlfcC1bjkUbQbGvHCC25jpqTwHIjmmVjYCX1uHuhPSqQKgPNdvtfOkrkACxczd7kVMmetxY2Q==", - "dependencies": { - "serve-static": "^1.13.1" - } - }, - "node_modules/@react-native-community/cli-doctor": { - "version": "12.3.2", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-doctor/-/cli-doctor-12.3.2.tgz", - "integrity": "sha512-GrAabdY4qtBX49knHFvEAdLtCjkmndjTeqhYO6BhsbAeKOtspcLT/0WRgdLIaKODRa61ADNB3K5Zm4dU0QrZOg==", - "dependencies": { - "@react-native-community/cli-config": "12.3.2", - "@react-native-community/cli-platform-android": "12.3.2", - "@react-native-community/cli-platform-ios": "12.3.2", - "@react-native-community/cli-tools": "12.3.2", - "chalk": "^4.1.2", - "command-exists": "^1.2.8", - "deepmerge": "^4.3.0", - "envinfo": "^7.10.0", - "execa": "^5.0.0", - "hermes-profile-transformer": "^0.0.6", - "ip": "^1.1.5", - "node-stream-zip": "^1.9.1", - "ora": "^5.4.1", - "semver": "^7.5.2", - "strip-ansi": "^5.2.0", - "wcwidth": "^1.0.1", - "yaml": "^2.2.1" + "node": ">= 10" } }, - "node_modules/@react-native-community/cli-doctor/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, + "node_modules/@nx/nx-win32-arm64-msvc": { + "version": "20.2.1", + "resolved": "https://registry.npmjs.org/@nx/nx-win32-arm64-msvc/-/nx-win32-arm64-msvc-20.2.1.tgz", + "integrity": "sha512-Wujist6k08pjgWWQ1pjXrCArmMgnyIXNVmDP14cWo1KHecBuxNWa9i62PrxQ0K8MLYMcAzLHJxN9t54GzBbd+g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "node": ">= 10" } }, - "node_modules/@react-native-community/cli-doctor/node_modules/cli-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", - "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", - "dependencies": { - "restore-cursor": "^3.1.0" - }, + "node_modules/@nx/nx-win32-x64-msvc": { + "version": "20.2.1", + "resolved": "https://registry.npmjs.org/@nx/nx-win32-x64-msvc/-/nx-win32-x64-msvc-20.2.1.tgz", + "integrity": "sha512-tsEYfNV2+CWSQmbh9TM8cX5wk6F2QAH0tfvt4topyOOaR40eszW8qc/eDM/kkJ5nj87BbNEqPBQAYFE0AP1OMA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=8" + "node": ">= 10" } }, - "node_modules/@react-native-community/cli-doctor/node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "node_modules/@octokit/auth-token": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-2.5.0.tgz", + "integrity": "sha512-r5FVUJCOLl19AxiuZD2VRZ/ORjp/4IN98Of6YJoJOkY75CIBuYfmiNHGrDwXr+aLGG55igl9QrxX3hbiXlLb+g==", "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" + "@octokit/types": "^6.0.3" } }, - "node_modules/@react-native-community/cli-doctor/node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "node_modules/@octokit/core": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@octokit/core/-/core-3.6.0.tgz", + "integrity": "sha512-7RKRKuA4xTjMhY+eG3jthb3hlZCsOwg3rztWh75Xc+ShDWOfDDATWbeZpAHBNRpm4Tv9WgBMOy1zEJYXG6NJ7Q==", "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/@react-native-community/cli-doctor/node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@react-native-community/cli-doctor/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/@react-native-community/cli-doctor/node_modules/human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "engines": { - "node": ">=10.17.0" - } - }, - "node_modules/@react-native-community/cli-doctor/node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@react-native-community/cli-doctor/node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "engines": { - "node": ">=6" + "@octokit/auth-token": "^2.4.4", + "@octokit/graphql": "^4.5.8", + "@octokit/request": "^5.6.3", + "@octokit/request-error": "^2.0.5", + "@octokit/types": "^6.0.3", + "before-after-hook": "^2.2.0", + "universal-user-agent": "^6.0.0" } }, - "node_modules/@react-native-community/cli-doctor/node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "node_modules/@octokit/core/node_modules/@octokit/endpoint": { + "version": "6.0.12", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-6.0.12.tgz", + "integrity": "sha512-lF3puPwkQWGfkMClXb4k/eUT/nZKQfxinRWJrdZaJO85Dqwo/G0yOC434Jr2ojwafWJMYqFGFa5ms4jJUgujdA==", "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" + "@octokit/types": "^6.0.3", + "is-plain-object": "^5.0.0", + "universal-user-agent": "^6.0.0" } }, - "node_modules/@react-native-community/cli-doctor/node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "node_modules/@octokit/core/node_modules/@octokit/request": { + "version": "5.6.3", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-5.6.3.tgz", + "integrity": "sha512-bFJl0I1KVc9jYTe9tdGGpAMPy32dLBXXo1dS/YwSCTL/2nd9XeHsY616RE3HPXDVk+a+dBuzyz5YdlXwcDTr2A==", "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "@octokit/endpoint": "^6.0.1", + "@octokit/request-error": "^2.1.0", + "@octokit/types": "^6.16.1", + "is-plain-object": "^5.0.0", + "node-fetch": "^2.6.7", + "universal-user-agent": "^6.0.0" } }, - "node_modules/@react-native-community/cli-doctor/node_modules/ora": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", - "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", - "dependencies": { - "bl": "^4.1.0", - "chalk": "^4.1.0", - "cli-cursor": "^3.1.0", - "cli-spinners": "^2.5.0", - "is-interactive": "^1.0.0", - "is-unicode-supported": "^0.1.0", - "log-symbols": "^4.1.0", - "strip-ansi": "^6.0.0", - "wcwidth": "^1.0.1" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } + "node_modules/@octokit/core/node_modules/before-after-hook": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.3.tgz", + "integrity": "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==" }, - "node_modules/@react-native-community/cli-doctor/node_modules/ora/node_modules/strip-ansi": { + "node_modules/@octokit/core/node_modules/universal-user-agent": { "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@react-native-community/cli-doctor/node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "engines": { - "node": ">=8" - } + "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.1.tgz", + "integrity": "sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ==" }, - "node_modules/@react-native-community/cli-doctor/node_modules/restore-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", - "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "node_modules/@octokit/endpoint": { + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-5.5.3.tgz", + "integrity": "sha512-EzKwkwcxeegYYah5ukEeAI/gYRLv2Y9U5PpIsseGSFDk+G3RbipQGBs8GuYS1TLCtQaqoO66+aQGtITPalxsNQ==", + "dev": true, "dependencies": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" - }, - "engines": { - "node": ">=8" + "@octokit/types": "^2.0.0", + "is-plain-object": "^3.0.0", + "universal-user-agent": "^5.0.0" } }, - "node_modules/@react-native-community/cli-doctor/node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "node_modules/@octokit/endpoint/node_modules/@octokit/types": { + "version": "2.16.2", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-2.16.2.tgz", + "integrity": "sha512-O75k56TYvJ8WpAakWwYRN8Bgu60KrmX0z1KqFp1kNiFNkgW+JW+9EBKZ+S33PU6SLvbihqd+3drvPxKK68Ee8Q==", + "dev": true, "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" + "@types/node": ">= 8" } }, - "node_modules/@react-native-community/cli-doctor/node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "node_modules/@octokit/endpoint/node_modules/is-plain-object": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-3.0.1.tgz", + "integrity": "sha512-Xnpx182SBMrr/aBik8y+GuR4U1L9FqMSojwDQwPMmxyC6bvEqly9UBCxhauBF5vNh2gwWJNX6oDV7O+OM4z34g==", + "dev": true, "engines": { - "node": ">=8" + "node": ">=0.10.0" } }, - "node_modules/@react-native-community/cli-doctor/node_modules/strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "node_modules/@octokit/endpoint/node_modules/universal-user-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-5.0.0.tgz", + "integrity": "sha512-B5TPtzZleXyPrUMKCpEHFmVhMN6EhmJYjG5PQna9s7mXeSqGTLap4OpqLl5FCEFUI3UBmllkETwKf/db66Y54Q==", + "dev": true, "dependencies": { - "ansi-regex": "^4.1.0" - }, - "engines": { - "node": ">=6" + "os-name": "^3.1.0" } }, - "node_modules/@react-native-community/cli-doctor/node_modules/strip-ansi/node_modules/ansi-regex": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", - "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", - "engines": { - "node": ">=6" + "node_modules/@octokit/graphql": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-4.8.0.tgz", + "integrity": "sha512-0gv+qLSBLKF0z8TKaSKTsS39scVKF9dbMxJpj3U0vC7wjNWFuIpL/z76Qe2fiuCbDRcJSavkXsVtMS6/dtQQsg==", + "dependencies": { + "@octokit/request": "^5.6.0", + "@octokit/types": "^6.0.3", + "universal-user-agent": "^6.0.0" } }, - "node_modules/@react-native-community/cli-doctor/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/@octokit/graphql/node_modules/@octokit/endpoint": { + "version": "6.0.12", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-6.0.12.tgz", + "integrity": "sha512-lF3puPwkQWGfkMClXb4k/eUT/nZKQfxinRWJrdZaJO85Dqwo/G0yOC434Jr2ojwafWJMYqFGFa5ms4jJUgujdA==", "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" + "@octokit/types": "^6.0.3", + "is-plain-object": "^5.0.0", + "universal-user-agent": "^6.0.0" } }, - "node_modules/@react-native-community/cli-doctor/node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "node_modules/@octokit/graphql/node_modules/@octokit/request": { + "version": "5.6.3", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-5.6.3.tgz", + "integrity": "sha512-bFJl0I1KVc9jYTe9tdGGpAMPy32dLBXXo1dS/YwSCTL/2nd9XeHsY616RE3HPXDVk+a+dBuzyz5YdlXwcDTr2A==", "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" + "@octokit/endpoint": "^6.0.1", + "@octokit/request-error": "^2.1.0", + "@octokit/types": "^6.16.1", + "is-plain-object": "^5.0.0", + "node-fetch": "^2.6.7", + "universal-user-agent": "^6.0.0" } }, - "node_modules/@react-native-community/cli-doctor/node_modules/yaml": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.6.0.tgz", - "integrity": "sha512-a6ae//JvKDEra2kdi1qzCyrJW/WZCgFi8ydDV+eXExl95t+5R+ijnqHJbz9tmMh8FUjx3iv2fCQ4dclAQlO2UQ==", - "license": "ISC", - "bin": { - "yaml": "bin.mjs" - }, - "engines": { - "node": ">= 14" - } + "node_modules/@octokit/graphql/node_modules/universal-user-agent": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.1.tgz", + "integrity": "sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ==" }, - "node_modules/@react-native-community/cli-hermes": { - "version": "12.3.2", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-hermes/-/cli-hermes-12.3.2.tgz", - "integrity": "sha512-SL6F9O8ghp4ESBFH2YAPLtIN39jdnvGBKnK4FGKpDCjtB3DnUmDsGFlH46S+GGt5M6VzfG2eeKEOKf3pZ6jUzA==", - "dependencies": { - "@react-native-community/cli-platform-android": "12.3.2", - "@react-native-community/cli-tools": "12.3.2", - "chalk": "^4.1.2", - "hermes-profile-transformer": "^0.0.6", - "ip": "^1.1.5" - } + "node_modules/@octokit/openapi-types": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-11.2.0.tgz", + "integrity": "sha512-PBsVO+15KSlGmiI8QAzaqvsNlZlrDlyAJYcrXBCvVUxCp7VnXjkwPoFHgjEJXx3WF9BAwkA6nfCUA7i9sODzKA==" }, - "node_modules/@react-native-community/cli-hermes/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/@octokit/plugin-enterprise-rest": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@octokit/plugin-enterprise-rest/-/plugin-enterprise-rest-6.0.1.tgz", + "integrity": "sha512-93uGjlhUD+iNg1iWhUENAtJata6w5nE+V4urXOAlIXdco6xNZtUSfYY8dzp3Udy74aqO/B5UZL80x/YMa5PKRw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@octokit/plugin-paginate-rest": { + "version": "2.21.3", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.21.3.tgz", + "integrity": "sha512-aCZTEf0y2h3OLbrgKkrfFdjRL6eSOo8komneVQJnYecAxIej7Bafor2xhuDJOIFau4pk0i/P28/XgtbyPF0ZHw==", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" + "@octokit/types": "^6.40.0" }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "peerDependencies": { + "@octokit/core": ">=2" } }, - "node_modules/@react-native-community/cli-hermes/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } + "node_modules/@octokit/plugin-paginate-rest/node_modules/@octokit/openapi-types": { + "version": "12.11.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-12.11.0.tgz", + "integrity": "sha512-VsXyi8peyRq9PqIz/tpqiL2w3w80OgVMwBHltTml3LmVvXiphgeqmY9mvBw9Wu7e0QWk/fqD37ux8yP5uVekyQ==" }, - "node_modules/@react-native-community/cli-hermes/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/@octokit/plugin-paginate-rest/node_modules/@octokit/types": { + "version": "6.41.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.41.0.tgz", + "integrity": "sha512-eJ2jbzjdijiL3B4PrSQaSjuF2sPEQPVCPzBvTHJD9Nz+9dw2SGH4K4xeQJ77YfTq5bRQ+bD8wT11JbeDPmxmGg==", "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" + "@octokit/openapi-types": "^12.11.0" } }, - "node_modules/@react-native-community/cli-platform-android": { - "version": "12.3.2", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-android/-/cli-platform-android-12.3.2.tgz", - "integrity": "sha512-MZ5nO8yi/N+Fj2i9BJcJ9C/ez+9/Ir7lQt49DWRo9YDmzye66mYLr/P2l/qxsixllbbDi7BXrlLpxaEhMrDopg==", - "dependencies": { - "@react-native-community/cli-tools": "12.3.2", - "chalk": "^4.1.2", - "execa": "^5.0.0", - "fast-xml-parser": "^4.2.4", - "glob": "^7.1.3", - "logkitty": "^0.7.1" + "node_modules/@octokit/plugin-request-log": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-1.0.4.tgz", + "integrity": "sha512-mLUsMkgP7K/cnFEw07kWqXGF5LKrOkD+lhCrKvPHXWDywAwuDUeDwWBpc69XK3pNX0uKiVt8g5z96PJ6z9xCFA==", + "dev": true, + "peerDependencies": { + "@octokit/core": ">=3" } }, - "node_modules/@react-native-community/cli-platform-android/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/@octokit/plugin-rest-endpoint-methods": { + "version": "5.16.2", + "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-5.16.2.tgz", + "integrity": "sha512-8QFz29Fg5jDuTPXVtey05BLm7OB+M8fnvE64RNegzX7U+5NUXcOcnpTIK0YfSHBg8gYd0oxIq3IZTe9SfPZiRw==", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" + "@octokit/types": "^6.39.0", + "deprecation": "^2.3.1" }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "peerDependencies": { + "@octokit/core": ">=3" } }, - "node_modules/@react-native-community/cli-platform-android/node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } + "node_modules/@octokit/plugin-rest-endpoint-methods/node_modules/@octokit/openapi-types": { + "version": "12.11.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-12.11.0.tgz", + "integrity": "sha512-VsXyi8peyRq9PqIz/tpqiL2w3w80OgVMwBHltTml3LmVvXiphgeqmY9mvBw9Wu7e0QWk/fqD37ux8yP5uVekyQ==" }, - "node_modules/@react-native-community/cli-platform-android/node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "node_modules/@octokit/plugin-rest-endpoint-methods/node_modules/@octokit/types": { + "version": "6.41.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.41.0.tgz", + "integrity": "sha512-eJ2jbzjdijiL3B4PrSQaSjuF2sPEQPVCPzBvTHJD9Nz+9dw2SGH4K4xeQJ77YfTq5bRQ+bD8wT11JbeDPmxmGg==", "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" + "@octokit/openapi-types": "^12.11.0" } }, - "node_modules/@react-native-community/cli-platform-android/node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node_modules/@octokit/request": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-4.1.1.tgz", + "integrity": "sha512-LOyL0i3oxRo418EXRSJNk/3Q4I0/NKawTn6H/CQp+wnrG1UFLGu080gSsgnWobhPo5BpUNgSQ5BRk5FOOJhD1Q==", + "dev": true, + "dependencies": { + "@octokit/endpoint": "^5.1.0", + "@octokit/request-error": "^1.0.1", + "deprecation": "^2.0.0", + "is-plain-object": "^3.0.0", + "node-fetch": "^2.3.0", + "once": "^1.4.0", + "universal-user-agent": "^2.1.0" } }, - "node_modules/@react-native-community/cli-platform-android/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "node_modules/@octokit/request-error": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-2.1.0.tgz", + "integrity": "sha512-1VIvgXxs9WHSjicsRwq8PlR2LR2x6DwsJAaFgzdi0JfJoGSO8mYI/cHJQ+9FbN21aa+DrgNLnwObmyeSC8Rmpg==", "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "@octokit/types": "^6.0.3", + "deprecation": "^2.0.0", + "once": "^1.4.0" } }, - "node_modules/@react-native-community/cli-platform-android/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" + "node_modules/@octokit/request/node_modules/@octokit/request-error": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-1.2.1.tgz", + "integrity": "sha512-+6yDyk1EES6WK+l3viRDElw96MvwfJxCt45GvmjDUKWjYIb3PJZQkq3i46TwGwoPD4h8NmTrENmtyA1FwbmhRA==", + "dev": true, + "dependencies": { + "@octokit/types": "^2.0.0", + "deprecation": "^2.0.0", + "once": "^1.4.0" } }, - "node_modules/@react-native-community/cli-platform-android/node_modules/human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "engines": { - "node": ">=10.17.0" + "node_modules/@octokit/request/node_modules/@octokit/types": { + "version": "2.16.2", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-2.16.2.tgz", + "integrity": "sha512-O75k56TYvJ8WpAakWwYRN8Bgu60KrmX0z1KqFp1kNiFNkgW+JW+9EBKZ+S33PU6SLvbihqd+3drvPxKK68Ee8Q==", + "dev": true, + "dependencies": { + "@types/node": ">= 8" } }, - "node_modules/@react-native-community/cli-platform-android/node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "node_modules/@octokit/request/node_modules/is-plain-object": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-3.0.1.tgz", + "integrity": "sha512-Xnpx182SBMrr/aBik8y+GuR4U1L9FqMSojwDQwPMmxyC6bvEqly9UBCxhauBF5vNh2gwWJNX6oDV7O+OM4z34g==", + "dev": true, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=0.10.0" } }, - "node_modules/@react-native-community/cli-platform-android/node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "engines": { - "node": ">=6" + "node_modules/@octokit/rest": { + "version": "16.26.0", + "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-16.26.0.tgz", + "integrity": "sha512-NBpzre44ZAQWZhlH+zUYTgqI0pHN+c9rNj4d+pCydGEiKTGc1HKmoTghEUyr9GxazDyoAvmpx9nL0I7QS1Olvg==", + "dev": true, + "dependencies": { + "@octokit/request": "^4.0.1", + "@octokit/request-error": "^1.0.2", + "atob-lite": "^2.0.0", + "before-after-hook": "^1.4.0", + "btoa-lite": "^1.0.0", + "deprecation": "^2.0.0", + "lodash.get": "^4.4.2", + "lodash.set": "^4.3.2", + "lodash.uniq": "^4.5.0", + "octokit-pagination-methods": "^1.1.0", + "once": "^1.4.0", + "universal-user-agent": "^2.0.0", + "url-template": "^2.0.8" } }, - "node_modules/@react-native-community/cli-platform-android/node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "node_modules/@octokit/rest/node_modules/@octokit/request-error": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-1.2.1.tgz", + "integrity": "sha512-+6yDyk1EES6WK+l3viRDElw96MvwfJxCt45GvmjDUKWjYIb3PJZQkq3i46TwGwoPD4h8NmTrENmtyA1FwbmhRA==", + "dev": true, "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" + "@octokit/types": "^2.0.0", + "deprecation": "^2.0.0", + "once": "^1.4.0" } }, - "node_modules/@react-native-community/cli-platform-android/node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "node_modules/@octokit/rest/node_modules/@octokit/types": { + "version": "2.16.2", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-2.16.2.tgz", + "integrity": "sha512-O75k56TYvJ8WpAakWwYRN8Bgu60KrmX0z1KqFp1kNiFNkgW+JW+9EBKZ+S33PU6SLvbihqd+3drvPxKK68Ee8Q==", + "dev": true, "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "@types/node": ">= 8" } }, - "node_modules/@react-native-community/cli-platform-android/node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "engines": { - "node": ">=8" - } + "node_modules/@octokit/tsconfig": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@octokit/tsconfig/-/tsconfig-1.0.2.tgz", + "integrity": "sha512-I0vDR0rdtP8p2lGMzvsJzbhdOWy405HcGovrspJ8RRibHnyRgggUSNO5AIox5LmqiwmatHKYsvj6VGFHkqS7lA==", + "dev": true }, - "node_modules/@react-native-community/cli-platform-android/node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "node_modules/@octokit/types": { + "version": "6.34.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.34.0.tgz", + "integrity": "sha512-s1zLBjWhdEI2zwaoSgyOFoKSl109CUcVBCc7biPJ3aAf6LGLU6szDvi31JPU7bxfla2lqfhjbbg/5DdFNxOwHw==", "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" + "@octokit/openapi-types": "^11.2.0" } }, - "node_modules/@react-native-community/cli-platform-android/node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "engines": { - "node": ">=8" - } + "node_modules/@octokit/webhooks-methods": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@octokit/webhooks-methods/-/webhooks-methods-2.0.0.tgz", + "integrity": "sha512-35cfQ4YWlnZnmZKmIxlGPUPLtbkF8lr/A/1Sk1eC0ddLMwQN06dOuLc+dI3YLQS+T+MoNt3DIQ0NynwgKPilig==", + "license": "MIT" }, - "node_modules/@react-native-community/cli-platform-android/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, + "node_modules/@octokit/webhooks-types": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@octokit/webhooks-types/-/webhooks-types-5.8.0.tgz", + "integrity": "sha512-8adktjIb76A7viIdayQSFuBEwOzwhDC+9yxZpKNHjfzrlostHCw0/N7JWpWMObfElwvJMk2fY2l1noENCk9wmw==", + "license": "MIT" + }, + "node_modules/@paulirish/trace_engine": { + "version": "0.0.32", + "resolved": "https://registry.npmjs.org/@paulirish/trace_engine/-/trace_engine-0.0.32.tgz", + "integrity": "sha512-KxWFdRNbv13U8bhYaQvH6gLG9CVEt2jKeosyOOYILVntWEVWhovbgDrbOiZ12pJO3vjZs0Zgbd3/Zgde98woEA==", + "license": "BSD-3-Clause" + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "optional": true, "engines": { - "node": ">=8" + "node": ">=14" } }, - "node_modules/@react-native-community/cli-platform-android/node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "node_modules/@pkgr/utils": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@pkgr/utils/-/utils-2.4.2.tgz", + "integrity": "sha512-POgTXhjrTfbTV63DiFXav4lBHiICLKKwDeaKn9Nphwj7WH6m0hMMCaJkMyRWjgtPFyRKRVoMXXjczsTQRDEhYw==", "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" + "cross-spawn": "^7.0.3", + "fast-glob": "^3.3.0", + "is-glob": "^4.0.3", + "open": "^9.1.0", + "picocolors": "^1.0.0", + "tslib": "^2.6.0" }, "engines": { - "node": ">= 8" - } - }, - "node_modules/@react-native-community/cli-platform-ios": { - "version": "12.3.2", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-ios/-/cli-platform-ios-12.3.2.tgz", - "integrity": "sha512-OcWEAbkev1IL6SUiQnM6DQdsvfsKZhRZtoBNSj9MfdmwotVZSOEZJ+IjZ1FR9ChvMWayO9ns/o8LgoQxr1ZXeg==", - "dependencies": { - "@react-native-community/cli-tools": "12.3.2", - "chalk": "^4.1.2", - "execa": "^5.0.0", - "fast-xml-parser": "^4.0.12", - "glob": "^7.1.3", - "ora": "^5.4.1" + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" } }, - "node_modules/@react-native-community/cli-platform-ios/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, + "node_modules/@pkgr/utils/node_modules/define-lazy-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", "engines": { - "node": ">=10" + "node": ">=12" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@react-native-community/cli-platform-ios/node_modules/cli-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", - "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "node_modules/@pkgr/utils/node_modules/fast-glob": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", "dependencies": { - "restore-cursor": "^3.1.0" + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" }, "engines": { - "node": ">=8" + "node": ">=8.6.0" } }, - "node_modules/@react-native-community/cli-platform-ios/node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "node_modules/@pkgr/utils/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" + "is-glob": "^4.0.1" }, "engines": { - "node": ">= 8" + "node": ">= 6" } }, - "node_modules/@react-native-community/cli-platform-ios/node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "node_modules/@pkgr/utils/node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" + "is-docker": "^2.0.0" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" + "node": ">=8" } }, - "node_modules/@react-native-community/cli-platform-ios/node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "node_modules/@pkgr/utils/node_modules/open": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/open/-/open-9.1.0.tgz", + "integrity": "sha512-OS+QTnw1/4vrf+9hh1jc1jnYjzSG4ttTBB8UxOwAnInG3Uo4ssetzC1ihqaIHjLJnA5GGlRl6QlZXOTQhRBUvg==", + "dependencies": { + "default-browser": "^4.0.0", + "define-lazy-prop": "^3.0.0", + "is-inside-container": "^1.0.0", + "is-wsl": "^2.2.0" + }, "engines": { - "node": ">=10" + "node": ">=14.16" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@react-native-community/cli-platform-ios/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "node_modules/@playwright/test": { + "version": "1.49.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.49.1.tgz", + "integrity": "sha512-Ky+BVzPz8pL6PQxHqNRW1k3mIyv933LML7HktS8uik0bUXNCdPhoS/kLihiO1tMf/egaJb4IutXd7UywvXEW+g==", + "dev": true, + "license": "Apache-2.0", "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "playwright": "1.49.1" }, - "engines": { - "node": "*" + "bin": { + "playwright": "cli.js" }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@react-native-community/cli-platform-ios/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "engines": { - "node": ">=8" + "node": ">=18" } }, - "node_modules/@react-native-community/cli-platform-ios/node_modules/human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "node_modules/@pmmmwh/react-refresh-webpack-plugin": { + "version": "0.5.11", + "resolved": "https://registry.npmjs.org/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.5.11.tgz", + "integrity": "sha512-7j/6vdTym0+qZ6u4XbSAxrWBGYSdCfTzySkj7WAFgDLmSyWlOrWvpyzxlFh5jtw9dn0oL/jtW+06XfFiisN3JQ==", + "dependencies": { + "ansi-html-community": "^0.0.8", + "common-path-prefix": "^3.0.0", + "core-js-pure": "^3.23.3", + "error-stack-parser": "^2.0.6", + "find-up": "^5.0.0", + "html-entities": "^2.1.0", + "loader-utils": "^2.0.4", + "schema-utils": "^3.0.0", + "source-map": "^0.7.3" + }, "engines": { - "node": ">=10.17.0" + "node": ">= 10.13" + }, + "peerDependencies": { + "@types/webpack": "4.x || 5.x", + "react-refresh": ">=0.10.0 <1.0.0", + "sockjs-client": "^1.4.0", + "type-fest": ">=0.17.0 <5.0.0", + "webpack": ">=4.43.0 <6.0.0", + "webpack-dev-server": "3.x || 4.x", + "webpack-hot-middleware": "2.x", + "webpack-plugin-serve": "0.x || 1.x" + }, + "peerDependenciesMeta": { + "@types/webpack": { + "optional": true + }, + "sockjs-client": { + "optional": true + }, + "type-fest": { + "optional": true + }, + "webpack-dev-server": { + "optional": true + }, + "webpack-hot-middleware": { + "optional": true + }, + "webpack-plugin-serve": { + "optional": true + } } }, - "node_modules/@react-native-community/cli-platform-ios/node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "engines": { - "node": ">=8" + "node_modules/@pmmmwh/react-refresh-webpack-plugin/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@react-native-community/cli-platform-ios/node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "engines": { - "node": ">=6" + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/@react-native-community/cli-platform-ios/node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "node_modules/@pmmmwh/react-refresh-webpack-plugin/node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "dependencies": { - "path-key": "^3.0.0" + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" }, "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@react-native-community/cli-platform-ios/node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "node_modules/@pmmmwh/react-refresh-webpack-plugin/node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dependencies": { - "mimic-fn": "^2.1.0" + "p-locate": "^5.0.0" }, "engines": { - "node": ">=6" + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@react-native-community/cli-platform-ios/node_modules/ora": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", - "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", + "node_modules/@pmmmwh/react-refresh-webpack-plugin/node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", "dependencies": { - "bl": "^4.1.0", - "chalk": "^4.1.0", - "cli-cursor": "^3.1.0", - "cli-spinners": "^2.5.0", - "is-interactive": "^1.0.0", - "is-unicode-supported": "^0.1.0", - "log-symbols": "^4.1.0", - "strip-ansi": "^6.0.0", - "wcwidth": "^1.0.1" + "p-limit": "^3.0.2" }, "engines": { "node": ">=10" @@ -9306,322 +8842,427 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@react-native-community/cli-platform-ios/node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "node_modules/@pmmmwh/react-refresh-webpack-plugin/node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "engines": { "node": ">=8" } }, - "node_modules/@react-native-community/cli-platform-ios/node_modules/restore-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", - "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "node_modules/@pmmmwh/react-refresh-webpack-plugin/node_modules/schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", "dependencies": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" }, "engines": { - "node": ">=8" + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" } }, - "node_modules/@react-native-community/cli-platform-ios/node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "node_modules/@pmmmwh/react-refresh-webpack-plugin/node_modules/source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@polka/url": { + "version": "1.0.0-next.23", + "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.23.tgz", + "integrity": "sha512-C16M+IYz0rgRhWZdCmK+h58JMv8vijAA61gmz2rspCSwKwzBebpdcsiUmwrtJRdphuY30i6BSLEOP8ppbNLyLg==" + }, + "node_modules/@preact/signals": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@preact/signals/-/signals-1.3.0.tgz", + "integrity": "sha512-EOMeg42SlLS72dhoq6Vjq08havnLseWmPQ8A0YsgIAqMgWgx7V1a39+Pxo6i7SY5NwJtH4849JogFq3M67AzWg==", + "license": "MIT", "dependencies": { - "shebang-regex": "^3.0.0" + "@preact/signals-core": "^1.7.0" }, - "engines": { - "node": ">=8" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + }, + "peerDependencies": { + "preact": "10.x" } }, - "node_modules/@react-native-community/cli-platform-ios/node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "engines": { - "node": ">=8" + "node_modules/@preact/signals-core": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@preact/signals-core/-/signals-core-1.8.0.tgz", + "integrity": "sha512-OBvUsRZqNmjzCZXWLxkZfhcgT+Fk8DDcT/8vD6a1xhDemodyy87UJRJfASMuSD8FaAIeGgGm85ydXhm7lr4fyA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" } }, - "node_modules/@react-native-community/cli-platform-ios/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/@promptbook/utils": { + "version": "0.69.5", + "resolved": "https://registry.npmjs.org/@promptbook/utils/-/utils-0.69.5.tgz", + "integrity": "sha512-xm5Ti/Hp3o4xHrsK9Yy3MS6KbDxYbq485hDsFvxqaNA7equHLPdo8H8faTitTeb14QCDfLW4iwCxdVYu5sn6YQ==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://buymeacoffee.com/hejny" + }, + { + "type": "github", + "url": "https://github.com/webgptorg/promptbook/blob/main/README.md#%EF%B8%8F-contributing" + } + ], + "license": "CC-BY-4.0", "dependencies": { - "has-flag": "^4.0.0" + "spacetrim": "0.11.59" + } + }, + "node_modules/@puppeteer/browsers": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.9.1.tgz", + "integrity": "sha512-PuvK6xZzGhKPvlx3fpfdM2kYY3P/hB1URtK8wA7XUJ6prn6pp22zvJHu48th0SGcHL9SutbPHrFuQgfXTFobWA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "debug": "4.3.4", + "extract-zip": "2.0.1", + "progress": "2.0.3", + "proxy-agent": "6.3.1", + "tar-fs": "3.0.4", + "unbzip2-stream": "1.4.3", + "yargs": "17.7.2" + }, + "bin": { + "browsers": "lib/cjs/main-cli.js" }, "engines": { - "node": ">=8" + "node": ">=16.3.0" } }, - "node_modules/@react-native-community/cli-platform-ios/node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "node_modules/@puppeteer/browsers/node_modules/extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "dev": true, + "license": "BSD-2-Clause", "dependencies": { - "isexe": "^2.0.0" + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" }, "bin": { - "node-which": "bin/node-which" + "extract-zip": "cli.js" }, "engines": { - "node": ">= 8" + "node": ">= 10.17.0" + }, + "optionalDependencies": { + "@types/yauzl": "^2.9.1" } }, - "node_modules/@react-native-community/cli-plugin-metro": { - "version": "12.3.2", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-plugin-metro/-/cli-plugin-metro-12.3.2.tgz", - "integrity": "sha512-FpFBwu+d2E7KRhYPTkKvQsWb2/JKsJv+t1tcqgQkn+oByhp+qGyXBobFB8/R3yYvRRDCSDhS+atWTJzk9TjM8g==" + "node_modules/@puppeteer/browsers/node_modules/pump": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz", + "integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==", + "dev": true, + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } }, - "node_modules/@react-native-community/cli-server-api": { - "version": "12.3.2", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-server-api/-/cli-server-api-12.3.2.tgz", - "integrity": "sha512-iwa7EO9XFA/OjI5pPLLpI/6mFVqv8L73kNck3CNOJIUCCveGXBKK0VMyOkXaf/BYnihgQrXh+x5cxbDbggr7+Q==", + "node_modules/@puppeteer/browsers/node_modules/tar-fs": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.4.tgz", + "integrity": "sha512-5AFQU8b9qLfZCX9zp2duONhPmZv0hGYiBPJsyUdqMjzq/mqVpy/rEUSeHk1+YitmxugaptgBh5oDGU3VsAJq4w==", + "dev": true, + "license": "MIT", "dependencies": { - "@react-native-community/cli-debugger-ui": "12.3.2", - "@react-native-community/cli-tools": "12.3.2", - "compression": "^1.7.1", - "connect": "^3.6.5", - "errorhandler": "^1.5.1", - "nocache": "^3.0.1", - "pretty-format": "^26.6.2", - "serve-static": "^1.13.1", - "ws": "^7.5.1" + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^3.1.5" } }, - "node_modules/@react-native-community/cli-server-api/node_modules/@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", + "node_modules/@puppeteer/browsers/node_modules/tar-stream": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", + "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", + "dev": true, + "license": "MIT", "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": ">= 10.14.2" + "b4a": "^1.6.4", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" } }, - "node_modules/@react-native-community/cli-server-api/node_modules/@types/yargs": { - "version": "15.0.19", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.19.tgz", - "integrity": "sha512-2XUaGVmyQjgyAZldf0D0c14vvo/yv0MhQBSTJcejMMaitsn3nxCB6TmH4G0ZQf+uxROOa9mpanoSm8h6SG/1ZA==", + "node_modules/@radix-ui/primitive": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.0.1.tgz", + "integrity": "sha512-yQ8oGX2GVsEYMWGxcovu1uGWPCxV5BFfeeYxqPmuAzUyLT9qmaMXSAhXpb0WrspIeqYzdJpkh2vHModJPgRIaw==", + "license": "MIT", "dependencies": { - "@types/yargs-parser": "*" + "@babel/runtime": "^7.13.10" } }, - "node_modules/@react-native-community/cli-server-api/node_modules/pretty-format": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", - "integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==", + "node_modules/@radix-ui/react-compose-refs": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.0.1.tgz", + "integrity": "sha512-fDSBgd44FKHa1FRMU59qBMPFcl2PZE+2nmqunj+BWFyYYjnhIDWL2ItDs3rrbJDQOtzt5nIebLCQc4QRfz6LJw==", + "license": "MIT", "dependencies": { - "@jest/types": "^26.6.2", - "ansi-regex": "^5.0.0", - "ansi-styles": "^4.0.0", - "react-is": "^17.0.1" + "@babel/runtime": "^7.13.10" }, - "engines": { - "node": ">= 10" + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } } }, - "node_modules/@react-native-community/cli-server-api/node_modules/react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" - }, - "node_modules/@react-native-community/cli-tools": { - "version": "12.3.2", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-tools/-/cli-tools-12.3.2.tgz", - "integrity": "sha512-nDH7vuEicHI2TI0jac/DjT3fr977iWXRdgVAqPZFFczlbs7A8GQvEdGnZ1G8dqRUmg+kptw0e4hwczAOG89JzQ==", + "node_modules/@radix-ui/react-context": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.0.1.tgz", + "integrity": "sha512-ebbrdFoYTcuZ0v4wG5tedGnp9tzcV8awzsxYph7gXUyvnNLuTIcCk1q17JEbnVhXAKG9oX3KtchwiMIAYp9NLg==", + "license": "MIT", "dependencies": { - "appdirsjs": "^1.2.4", - "chalk": "^4.1.2", - "find-up": "^5.0.0", - "mime": "^2.4.1", - "node-fetch": "^2.6.0", - "open": "^6.2.0", - "ora": "^5.4.1", - "semver": "^7.5.2", - "shell-quote": "^1.7.3", - "sudo-prompt": "^9.0.0" + "@babel/runtime": "^7.13.10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } } }, - "node_modules/@react-native-community/cli-tools/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/@radix-ui/react-focus-guards": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.0.1.tgz", + "integrity": "sha512-Rect2dWbQ8waGzhMavsIbmSVCgYxkXLxxR3ZvCX79JOglzdEy4JXMb98lq4hPxUbLr77nP0UOGf4rcMU+s1pUA==", + "license": "MIT", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "@babel/runtime": "^7.13.10" }, - "engines": { - "node": ">=10" + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0" }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } } }, - "node_modules/@react-native-community/cli-tools/node_modules/cli-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", - "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "node_modules/@radix-ui/react-id": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.0.1.tgz", + "integrity": "sha512-tI7sT/kqYp8p96yGWY1OAnLHrqDgzHefRBKQ2YAkBS5ja7QLcZ9Z/uY7bEjPUatf8RomoXM8/1sMj1IJaE5UzQ==", + "license": "MIT", "dependencies": { - "restore-cursor": "^3.1.0" + "@babel/runtime": "^7.13.10", + "@radix-ui/react-use-layout-effect": "1.0.1" }, - "engines": { - "node": ">=8" + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } } }, - "node_modules/@react-native-community/cli-tools/node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "node_modules/@radix-ui/react-primitive": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-1.0.3.tgz", + "integrity": "sha512-yi58uVyoAcK/Nq1inRY56ZSjKypBNKTa/1mcL8qdl6oJeEaDbOldlzrGn7P6Q3Id5d+SYNGc5AJgc4vGhjs5+g==", + "license": "MIT", "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" + "@babel/runtime": "^7.13.10", + "@radix-ui/react-slot": "1.0.2" }, - "engines": { - "node": ">=10" + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@react-native-community/cli-tools/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } } }, - "node_modules/@react-native-community/cli-tools/node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "node_modules/@radix-ui/react-slot": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.2.tgz", + "integrity": "sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg==", + "license": "MIT", "dependencies": { - "p-locate": "^5.0.0" + "@babel/runtime": "^7.13.10", + "@radix-ui/react-compose-refs": "1.0.1" }, - "engines": { - "node": ">=10" + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } } }, - "node_modules/@react-native-community/cli-tools/node_modules/mime": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", - "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", - "bin": { - "mime": "cli.js" + "node_modules/@radix-ui/react-use-callback-ref": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.0.1.tgz", + "integrity": "sha512-D94LjX4Sp0xJFVaoQOd3OO9k7tpBYNOXdVhkltUbGv2Qb9OXdrg/CpsjlZv7ia14Sylv398LswWBVVu5nqKzAQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.13.10" }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/@react-native-community/cli-tools/node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "engines": { - "node": ">=6" + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } } }, - "node_modules/@react-native-community/cli-tools/node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.0.1.tgz", + "integrity": "sha512-Svl5GY5FQeN758fWKrjM6Qb7asvXeiZltlT4U2gVfl8Gx5UAv2sMR0LWo8yhsIZh2oQ0eFdZ59aoOOMV7b47VA==", + "license": "MIT", "dependencies": { - "mimic-fn": "^2.1.0" + "@babel/runtime": "^7.13.10", + "@radix-ui/react-use-callback-ref": "1.0.1" }, - "engines": { - "node": ">=6" + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } } }, - "node_modules/@react-native-community/cli-tools/node_modules/ora": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", - "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", + "node_modules/@radix-ui/react-use-escape-keydown": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.0.3.tgz", + "integrity": "sha512-vyL82j40hcFicA+M4Ex7hVkB9vHgSse1ZWomAqV2Je3RleKGO5iM8KMOEtfoSB0PnIelMd2lATjTGMYqN5ylTg==", + "license": "MIT", "dependencies": { - "bl": "^4.1.0", - "chalk": "^4.1.0", - "cli-cursor": "^3.1.0", - "cli-spinners": "^2.5.0", - "is-interactive": "^1.0.0", - "is-unicode-supported": "^0.1.0", - "log-symbols": "^4.1.0", - "strip-ansi": "^6.0.0", - "wcwidth": "^1.0.1" + "@babel/runtime": "^7.13.10", + "@radix-ui/react-use-callback-ref": "1.0.1" }, - "engines": { - "node": ">=10" + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } } }, - "node_modules/@react-native-community/cli-tools/node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.0.1.tgz", + "integrity": "sha512-v/5RegiJWYdoCvMnITBkNNx6bCj20fiaJnWtRkU18yITptraXjffz5Qbn05uOiQnOvi+dbkznkoaMltz1GnszQ==", + "license": "MIT", "dependencies": { - "p-limit": "^3.0.2" + "@babel/runtime": "^7.13.10" }, - "engines": { - "node": ">=10" + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } } }, - "node_modules/@react-native-community/cli-tools/node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "engines": { - "node": ">=8" + "node_modules/@react-native-clipboard/clipboard": { + "version": "1.11.2", + "resolved": "https://registry.npmjs.org/@react-native-clipboard/clipboard/-/clipboard-1.11.2.tgz", + "integrity": "sha512-bHyZVW62TuleiZsXNHS1Pv16fWc0fh8O9WvBzl4h2fykqZRW9a+Pv/RGTH56E3X2PqzHP38K5go8zmCZUoIsoQ==", + "peerDependencies": { + "react": ">=16.0", + "react-native": ">=0.57.0" } }, - "node_modules/@react-native-community/cli-tools/node_modules/restore-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", - "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", - "dependencies": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" - }, - "engines": { - "node": ">=8" + "node_modules/@react-native-community/blur": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@react-native-community/blur/-/blur-4.2.0.tgz", + "integrity": "sha512-StgP5zQJOCHqDRjmcKnzVkJ920S6DYBKRJfigSUnlkNQp+HzZtVtyKq0j5a7x84NtHcV7j8Uy5mz1Lx9ZKRKfA==", + "peerDependencies": { + "react": "*", + "react-native": "*" } }, - "node_modules/@react-native-community/cli-tools/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/@react-native-community/cli": { + "version": "12.3.2", + "resolved": "https://registry.npmjs.org/@react-native-community/cli/-/cli-12.3.2.tgz", + "integrity": "sha512-WgoUWwLDcf/G1Su2COUUVs3RzAwnV/vUTdISSpAUGgSc57mPabaAoUctKTnfYEhCnE3j02k3VtaVPwCAFRO3TQ==", "dependencies": { - "has-flag": "^4.0.0" + "@react-native-community/cli-clean": "12.3.2", + "@react-native-community/cli-config": "12.3.2", + "@react-native-community/cli-debugger-ui": "12.3.2", + "@react-native-community/cli-doctor": "12.3.2", + "@react-native-community/cli-hermes": "12.3.2", + "@react-native-community/cli-plugin-metro": "12.3.2", + "@react-native-community/cli-server-api": "12.3.2", + "@react-native-community/cli-tools": "12.3.2", + "@react-native-community/cli-types": "12.3.2", + "chalk": "^4.1.2", + "commander": "^9.4.1", + "deepmerge": "^4.3.0", + "execa": "^5.0.0", + "find-up": "^4.1.0", + "fs-extra": "^8.1.0", + "graceful-fs": "^4.1.3", + "prompts": "^2.4.2", + "semver": "^7.5.2" + }, + "bin": { + "react-native": "build/bin.js" }, "engines": { - "node": ">=8" + "node": ">=18" } }, - "node_modules/@react-native-community/cli-types": { + "node_modules/@react-native-community/cli-clean": { "version": "12.3.2", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-types/-/cli-types-12.3.2.tgz", - "integrity": "sha512-9D0UEFqLW8JmS16mjHJxUJWX8E+zJddrHILSH8AJHZ0NNHv4u2DXKdb0wFLMobFxGNxPT+VSOjc60fGvXzWHog==", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-clean/-/cli-clean-12.3.2.tgz", + "integrity": "sha512-90k2hCX0ddSFPT7EN7h5SZj0XZPXP0+y/++v262hssoey3nhurwF57NGWN0XAR0o9BSW7+mBfeInfabzDraO6A==", "dependencies": { - "joi": "^17.2.1" + "@react-native-community/cli-tools": "12.3.2", + "chalk": "^4.1.2", + "execa": "^5.0.0" } }, - "node_modules/@react-native-community/cli/node_modules/chalk": { + "node_modules/@react-native-community/cli-clean/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", @@ -9636,28 +9277,7 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@react-native-community/cli/node_modules/commander": { - "version": "9.5.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", - "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", - "engines": { - "node": "^12.20.0 || >=14" - } - }, - "node_modules/@react-native-community/cli/node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@react-native-community/cli/node_modules/execa": { + "node_modules/@react-native-community/cli-clean/node_modules/execa": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", @@ -9679,19 +9299,7 @@ "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, - "node_modules/@react-native-community/cli/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@react-native-community/cli/node_modules/get-stream": { + "node_modules/@react-native-community/cli-clean/node_modules/get-stream": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", @@ -9702,7 +9310,7 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@react-native-community/cli/node_modules/has-flag": { + "node_modules/@react-native-community/cli-clean/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", @@ -9710,7 +9318,7 @@ "node": ">=8" } }, - "node_modules/@react-native-community/cli/node_modules/human-signals": { + "node_modules/@react-native-community/cli-clean/node_modules/human-signals": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", @@ -9718,7 +9326,7 @@ "node": ">=10.17.0" } }, - "node_modules/@react-native-community/cli/node_modules/is-stream": { + "node_modules/@react-native-community/cli-clean/node_modules/is-stream": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", @@ -9729,18 +9337,7 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@react-native-community/cli/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@react-native-community/cli/node_modules/mimic-fn": { + "node_modules/@react-native-community/cli-clean/node_modules/mimic-fn": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", @@ -9748,7 +9345,7 @@ "node": ">=6" } }, - "node_modules/@react-native-community/cli/node_modules/npm-run-path": { + "node_modules/@react-native-community/cli-clean/node_modules/npm-run-path": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", @@ -9759,7 +9356,7 @@ "node": ">=8" } }, - "node_modules/@react-native-community/cli/node_modules/onetime": { + "node_modules/@react-native-community/cli-clean/node_modules/onetime": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", @@ -9773,48 +9370,7 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@react-native-community/cli/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@react-native-community/cli/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@react-native-community/cli/node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "engines": { - "node": ">=6" - } - }, - "node_modules/@react-native-community/cli/node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "engines": { - "node": ">=8" - } - }, - "node_modules/@react-native-community/cli/node_modules/path-key": { + "node_modules/@react-native-community/cli-clean/node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", @@ -9822,26 +9378,7 @@ "node": ">=8" } }, - "node_modules/@react-native-community/cli/node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@react-native-community/cli/node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "engines": { - "node": ">=8" - } - }, - "node_modules/@react-native-community/cli/node_modules/supports-color": { + "node_modules/@react-native-community/cli-clean/node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", @@ -9852,257 +9389,169 @@ "node": ">=8" } }, - "node_modules/@react-native-community/cli/node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "node_modules/@react-native-community/cli-config": { + "version": "12.3.2", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-config/-/cli-config-12.3.2.tgz", + "integrity": "sha512-UUCzDjQgvAVL/57rL7eOuFUhd+d+6qfM7V8uOegQFeFEmSmvUUDLYoXpBa5vAK9JgQtSqMBJ1Shmwao+/oElxQ==", "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@react-native-community/slider": { - "version": "3.0.2-wp-5", - "resolved": "https://raw.githubusercontent.com/wordpress-mobile/react-native-slider/v3.0.2-wp-5/react-native-community-slider-3.0.2-wp-5.tgz", - "integrity": "sha512-19yl9Em1mFKFLB9o3IO3VpBFw+U7cswjRk6cZ7kkhDCpDD3u0cpzkaIZK6NuFIPCLqOwU0MPtP+gonanN1JQKw==", - "workspaces": [ - "src", - "example" - ] - }, - "node_modules/@react-native-masked-view/masked-view": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@react-native-masked-view/masked-view/-/masked-view-0.3.0.tgz", - "integrity": "sha512-qLyoObcjzrkpNcoJjXquUePXfL1dXjHtuv+yX0zZ0Q4kG5yvVqd620+tSh7WbRoHkjpXhFBfLwvGhcWB2I0Lpw==", - "peerDependencies": { - "react": ">=16", - "react-native": ">=0.57" - } - }, - "node_modules/@react-native/assets-registry": { - "version": "0.73.1", - "resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.73.1.tgz", - "integrity": "sha512-2FgAbU7uKM5SbbW9QptPPZx8N9Ke2L7bsHb+EhAanZjFZunA9PaYtyjUQ1s7HD+zDVqOQIvjkpXSv7Kejd2tqg==", - "engines": { - "node": ">=18" + "@react-native-community/cli-tools": "12.3.2", + "chalk": "^4.1.2", + "cosmiconfig": "^5.1.0", + "deepmerge": "^4.3.0", + "glob": "^7.1.3", + "joi": "^17.2.1" } }, - "node_modules/@react-native/babel-plugin-codegen": { - "version": "0.73.3", - "resolved": "https://registry.npmjs.org/@react-native/babel-plugin-codegen/-/babel-plugin-codegen-0.73.3.tgz", - "integrity": "sha512-+zQrDDbz6lB48LyzFHxNCgXDCBHH+oTRdXAjikRcBUdeG9St9ABbYFLtb799zSxLOrCqFVyXqhJR2vlgLLEbcg==", + "node_modules/@react-native-community/cli-config/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dependencies": { - "@react-native/codegen": "0.73.2" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { - "node": ">=18" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@react-native/babel-preset": { - "version": "0.73.10", - "resolved": "https://registry.npmjs.org/@react-native/babel-preset/-/babel-preset-0.73.10.tgz", - "integrity": "sha512-LPxR9DZK1EtVSEm2+fpei/AWwzgyHfaY7fuX8wDiy2THRYc/56+v28IihN8g0IAbQH+gdjXVcshJRYfV6bh2gg==", - "dev": true, + "node_modules/@react-native-community/cli-config/node_modules/cosmiconfig": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz", + "integrity": "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==", "dependencies": { - "@babel/core": "^7.20.0", - "@babel/plugin-proposal-async-generator-functions": "^7.0.0", - "@babel/plugin-proposal-class-properties": "^7.18.0", - "@babel/plugin-proposal-export-default-from": "^7.0.0", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.0", - "@babel/plugin-proposal-numeric-separator": "^7.0.0", - "@babel/plugin-proposal-object-rest-spread": "^7.20.0", - "@babel/plugin-proposal-optional-catch-binding": "^7.0.0", - "@babel/plugin-proposal-optional-chaining": "^7.20.0", - "@babel/plugin-syntax-dynamic-import": "^7.8.0", - "@babel/plugin-syntax-export-default-from": "^7.0.0", - "@babel/plugin-syntax-flow": "^7.18.0", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.0.0", - "@babel/plugin-syntax-optional-chaining": "^7.0.0", - "@babel/plugin-transform-arrow-functions": "^7.0.0", - "@babel/plugin-transform-async-to-generator": "^7.20.0", - "@babel/plugin-transform-block-scoping": "^7.0.0", - "@babel/plugin-transform-classes": "^7.0.0", - "@babel/plugin-transform-computed-properties": "^7.0.0", - "@babel/plugin-transform-destructuring": "^7.20.0", - "@babel/plugin-transform-flow-strip-types": "^7.20.0", - "@babel/plugin-transform-function-name": "^7.0.0", - "@babel/plugin-transform-literals": "^7.0.0", - "@babel/plugin-transform-modules-commonjs": "^7.0.0", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.0.0", - "@babel/plugin-transform-parameters": "^7.0.0", - "@babel/plugin-transform-react-display-name": "^7.0.0", - "@babel/plugin-transform-react-jsx": "^7.0.0", - "@babel/plugin-transform-react-jsx-self": "^7.0.0", - "@babel/plugin-transform-react-jsx-source": "^7.0.0", - "@babel/plugin-transform-runtime": "^7.0.0", - "@babel/plugin-transform-shorthand-properties": "^7.0.0", - "@babel/plugin-transform-spread": "^7.0.0", - "@babel/plugin-transform-sticky-regex": "^7.0.0", - "@babel/plugin-transform-typescript": "^7.5.0", - "@babel/plugin-transform-unicode-regex": "^7.0.0", - "@babel/template": "^7.0.0", - "babel-plugin-transform-flow-enums": "^0.0.2", - "react-refresh": "^0.4.0" - }, - "engines": { - "node": ">=18" + "import-fresh": "^2.0.0", + "is-directory": "^0.3.1", + "js-yaml": "^3.13.1", + "parse-json": "^4.0.0" }, - "peerDependencies": { - "@babel/core": "*" - } - }, - "node_modules/@react-native/babel-preset/node_modules/react-refresh": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.4.3.tgz", - "integrity": "sha512-Hwln1VNuGl/6bVwnd0Xdn1e84gT/8T9aYNL+HAKDArLCS7LWjwr7StE30IEYbIkx0Vi3vs+coQxe+SQDbGbbpA==", - "dev": true, "engines": { - "node": ">=0.10.0" + "node": ">=4" } }, - "node_modules/@react-native/codegen": { - "version": "0.73.2", - "resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.73.2.tgz", - "integrity": "sha512-lfy8S7umhE3QLQG5ViC4wg5N1Z+E6RnaeIw8w1voroQsXXGPB72IBozh8dAHR3+ceTxIU0KX3A8OpJI8e1+HpQ==", + "node_modules/@react-native-community/cli-config/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", "dependencies": { - "@babel/parser": "^7.20.0", - "flow-parser": "^0.206.0", - "glob": "^7.1.1", - "invariant": "^2.2.4", - "jscodeshift": "^0.14.0", - "mkdirp": "^0.5.1", - "nullthrows": "^1.1.1" + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" }, "engines": { - "node": ">=18" + "node": "*" }, - "peerDependencies": { - "@babel/preset-env": "^7.1.6" + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@react-native-community/cli-config/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" } }, - "node_modules/@react-native/codegen/node_modules/mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "node_modules/@react-native-community/cli-config/node_modules/import-fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", + "integrity": "sha512-eZ5H8rcgYazHbKC3PG4ClHNykCSxtAhxSSEM+2mb+7evD2CKF5V7c0dNum7AdpDh0ZdICwZY9sRSn8f+KH96sg==", "dependencies": { - "minimist": "^1.2.6" + "caller-path": "^2.0.0", + "resolve-from": "^3.0.0" }, - "bin": { - "mkdirp": "bin/cmd.js" + "engines": { + "node": ">=4" } }, - "node_modules/@react-native/community-cli-plugin": { - "version": "0.73.14", - "resolved": "https://registry.npmjs.org/@react-native/community-cli-plugin/-/community-cli-plugin-0.73.14.tgz", - "integrity": "sha512-KzIwsTvAJrXPtwhGOSm+OcJH1B8TpY8cS4xxzu/e2qv3a2n4VLePHTPAfco1tmvekV8OHWvvD9JSIX7i2fB1gg==", + "node_modules/@react-native-community/cli-config/node_modules/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", "dependencies": { - "@react-native-community/cli-server-api": "12.3.2", - "@react-native-community/cli-tools": "12.3.2", - "@react-native/dev-middleware": "0.73.7", - "@react-native/metro-babel-transformer": "0.73.14", - "chalk": "^4.0.0", - "execa": "^5.1.1", - "metro": "^0.80.3", - "metro-config": "^0.80.3", - "metro-core": "^0.80.3", - "node-fetch": "^2.2.0", - "readline": "^1.3.0" + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" }, "engines": { - "node": ">=18" + "node": ">=4" } }, - "node_modules/@react-native/community-cli-plugin/node_modules/@react-native/babel-preset": { - "version": "0.73.20", - "resolved": "https://registry.npmjs.org/@react-native/babel-preset/-/babel-preset-0.73.20.tgz", - "integrity": "sha512-fU9NqkusbfFq71l4BWQfqqD/lLcLC0MZ++UYgieA3j8lIEppJTLVauv2RwtD2yltBkjebgYEC5Rwvt1l0MUBXw==", + "node_modules/@react-native-community/cli-config/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dependencies": { - "@babel/core": "^7.20.0", - "@babel/plugin-proposal-async-generator-functions": "^7.0.0", - "@babel/plugin-proposal-class-properties": "^7.18.0", - "@babel/plugin-proposal-export-default-from": "^7.0.0", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.0", - "@babel/plugin-proposal-numeric-separator": "^7.0.0", - "@babel/plugin-proposal-object-rest-spread": "^7.20.0", - "@babel/plugin-proposal-optional-catch-binding": "^7.0.0", - "@babel/plugin-proposal-optional-chaining": "^7.20.0", - "@babel/plugin-syntax-dynamic-import": "^7.8.0", - "@babel/plugin-syntax-export-default-from": "^7.0.0", - "@babel/plugin-syntax-flow": "^7.18.0", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.0.0", - "@babel/plugin-syntax-optional-chaining": "^7.0.0", - "@babel/plugin-transform-arrow-functions": "^7.0.0", - "@babel/plugin-transform-async-to-generator": "^7.20.0", - "@babel/plugin-transform-block-scoping": "^7.0.0", - "@babel/plugin-transform-classes": "^7.0.0", - "@babel/plugin-transform-computed-properties": "^7.0.0", - "@babel/plugin-transform-destructuring": "^7.20.0", - "@babel/plugin-transform-flow-strip-types": "^7.20.0", - "@babel/plugin-transform-function-name": "^7.0.0", - "@babel/plugin-transform-literals": "^7.0.0", - "@babel/plugin-transform-modules-commonjs": "^7.0.0", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.0.0", - "@babel/plugin-transform-parameters": "^7.0.0", - "@babel/plugin-transform-private-methods": "^7.22.5", - "@babel/plugin-transform-private-property-in-object": "^7.22.11", - "@babel/plugin-transform-react-display-name": "^7.0.0", - "@babel/plugin-transform-react-jsx": "^7.0.0", - "@babel/plugin-transform-react-jsx-self": "^7.0.0", - "@babel/plugin-transform-react-jsx-source": "^7.0.0", - "@babel/plugin-transform-runtime": "^7.0.0", - "@babel/plugin-transform-shorthand-properties": "^7.0.0", - "@babel/plugin-transform-spread": "^7.0.0", - "@babel/plugin-transform-sticky-regex": "^7.0.0", - "@babel/plugin-transform-typescript": "^7.5.0", - "@babel/plugin-transform-unicode-regex": "^7.0.0", - "@babel/template": "^7.0.0", - "@react-native/babel-plugin-codegen": "0.73.3", - "babel-plugin-transform-flow-enums": "^0.0.2", - "react-refresh": "^0.14.0" + "has-flag": "^4.0.0" }, "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@babel/core": "*" + "node": ">=8" } }, - "node_modules/@react-native/community-cli-plugin/node_modules/@react-native/metro-babel-transformer": { - "version": "0.73.14", - "resolved": "https://registry.npmjs.org/@react-native/metro-babel-transformer/-/metro-babel-transformer-0.73.14.tgz", - "integrity": "sha512-5wLeYw/lormpSqYfI9H/geZ/EtPmi+x5qLkEit15Q/70hkzYo/M+aWztUtbOITfgTEOP8d6ybROzoGsqgyZLcw==", + "node_modules/@react-native-community/cli-debugger-ui": { + "version": "12.3.2", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-debugger-ui/-/cli-debugger-ui-12.3.2.tgz", + "integrity": "sha512-nSWQUL+51J682DlfcC1bjkUbQbGvHCC25jpqTwHIjmmVjYCX1uHuhPSqQKgPNdvtfOkrkACxczd7kVMmetxY2Q==", "dependencies": { - "@babel/core": "^7.20.0", - "@react-native/babel-preset": "0.73.20", - "hermes-parser": "0.15.0", - "nullthrows": "^1.1.1" + "serve-static": "^1.13.1" + } + }, + "node_modules/@react-native-community/cli-doctor": { + "version": "12.3.2", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-doctor/-/cli-doctor-12.3.2.tgz", + "integrity": "sha512-GrAabdY4qtBX49knHFvEAdLtCjkmndjTeqhYO6BhsbAeKOtspcLT/0WRgdLIaKODRa61ADNB3K5Zm4dU0QrZOg==", + "dependencies": { + "@react-native-community/cli-config": "12.3.2", + "@react-native-community/cli-platform-android": "12.3.2", + "@react-native-community/cli-platform-ios": "12.3.2", + "@react-native-community/cli-tools": "12.3.2", + "chalk": "^4.1.2", + "command-exists": "^1.2.8", + "deepmerge": "^4.3.0", + "envinfo": "^7.10.0", + "execa": "^5.0.0", + "hermes-profile-transformer": "^0.0.6", + "ip": "^1.1.5", + "node-stream-zip": "^1.9.1", + "ora": "^5.4.1", + "semver": "^7.5.2", + "strip-ansi": "^5.2.0", + "wcwidth": "^1.0.1", + "yaml": "^2.2.1" + } + }, + "node_modules/@react-native-community/cli-doctor/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { - "node": ">=18" + "node": ">=10" }, - "peerDependencies": { - "@babel/core": "*" + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@react-native/community-cli-plugin/node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "node_modules/@react-native-community/cli-doctor/node_modules/cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" + "restore-cursor": "^3.1.0" }, "engines": { - "node": ">= 8" + "node": ">=8" } }, - "node_modules/@react-native/community-cli-plugin/node_modules/execa": { + "node_modules/@react-native-community/cli-doctor/node_modules/execa": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", @@ -10124,7 +9573,7 @@ "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, - "node_modules/@react-native/community-cli-plugin/node_modules/get-stream": { + "node_modules/@react-native-community/cli-doctor/node_modules/get-stream": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", @@ -10135,7 +9584,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@react-native/community-cli-plugin/node_modules/human-signals": { + "node_modules/@react-native-community/cli-doctor/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/@react-native-community/cli-doctor/node_modules/human-signals": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", @@ -10143,7 +9600,7 @@ "node": ">=10.17.0" } }, - "node_modules/@react-native/community-cli-plugin/node_modules/is-stream": { + "node_modules/@react-native-community/cli-doctor/node_modules/is-stream": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", @@ -10154,7 +9611,7 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@react-native/community-cli-plugin/node_modules/mimic-fn": { + "node_modules/@react-native-community/cli-doctor/node_modules/mimic-fn": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", @@ -10162,7 +9619,7 @@ "node": ">=6" } }, - "node_modules/@react-native/community-cli-plugin/node_modules/npm-run-path": { + "node_modules/@react-native-community/cli-doctor/node_modules/npm-run-path": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", @@ -10173,7 +9630,7 @@ "node": ">=8" } }, - "node_modules/@react-native/community-cli-plugin/node_modules/onetime": { + "node_modules/@react-native-community/cli-doctor/node_modules/onetime": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", @@ -10187,348 +9644,201 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@react-native/community-cli-plugin/node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "engines": { - "node": ">=8" - } - }, - "node_modules/@react-native/community-cli-plugin/node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "node_modules/@react-native-community/cli-doctor/node_modules/ora": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", + "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", "dependencies": { - "shebang-regex": "^3.0.0" + "bl": "^4.1.0", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "is-unicode-supported": "^0.1.0", + "log-symbols": "^4.1.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" }, "engines": { - "node": ">=8" - } - }, - "node_modules/@react-native/community-cli-plugin/node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "engines": { - "node": ">=8" - } - }, - "node_modules/@react-native/community-cli-plugin/node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" + "node": ">=10" }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@react-native/debugger-frontend": { - "version": "0.73.3", - "resolved": "https://registry.npmjs.org/@react-native/debugger-frontend/-/debugger-frontend-0.73.3.tgz", - "integrity": "sha512-RgEKnWuoo54dh7gQhV7kvzKhXZEhpF9LlMdZolyhGxHsBqZ2gXdibfDlfcARFFifPIiaZ3lXuOVVa4ei+uPgTw==", - "engines": { - "node": ">=18" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@react-native/dev-middleware": { - "version": "0.73.7", - "resolved": "https://registry.npmjs.org/@react-native/dev-middleware/-/dev-middleware-0.73.7.tgz", - "integrity": "sha512-BZXpn+qKp/dNdr4+TkZxXDttfx8YobDh8MFHsMk9usouLm22pKgFIPkGBV0X8Do4LBkFNPGtrnsKkWk/yuUXKg==", + "node_modules/@react-native-community/cli-doctor/node_modules/ora/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dependencies": { - "@isaacs/ttlcache": "^1.4.1", - "@react-native/debugger-frontend": "0.73.3", - "chrome-launcher": "^0.15.2", - "chromium-edge-launcher": "^1.0.0", - "connect": "^3.6.5", - "debug": "^2.2.0", - "node-fetch": "^2.2.0", - "open": "^7.0.3", - "serve-static": "^1.13.1", - "temp-dir": "^2.0.0" + "ansi-regex": "^5.0.1" }, "engines": { - "node": ">=18" - } - }, - "node_modules/@react-native/dev-middleware/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" + "node": ">=8" } }, - "node_modules/@react-native/dev-middleware/node_modules/is-wsl": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", - "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", - "dependencies": { - "is-docker": "^2.0.0" - }, + "node_modules/@react-native-community/cli-doctor/node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "engines": { "node": ">=8" } }, - "node_modules/@react-native/dev-middleware/node_modules/open": { - "version": "7.4.2", - "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz", - "integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==", + "node_modules/@react-native-community/cli-doctor/node_modules/restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", "dependencies": { - "is-docker": "^2.0.0", - "is-wsl": "^2.1.1" - }, - "engines": { - "node": ">=8" + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@react-native/dev-middleware/node_modules/temp-dir": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-2.0.0.tgz", - "integrity": "sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg==", "engines": { "node": ">=8" } }, - "node_modules/@react-native/gradle-plugin": { - "version": "0.73.4", - "resolved": "https://registry.npmjs.org/@react-native/gradle-plugin/-/gradle-plugin-0.73.4.tgz", - "integrity": "sha512-PMDnbsZa+tD55Ug+W8CfqXiGoGneSSyrBZCMb5JfiB3AFST3Uj5e6lw8SgI/B6SKZF7lG0BhZ6YHZsRZ5MlXmg==", + "node_modules/@react-native-community/cli-doctor/node_modules/strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dependencies": { + "ansi-regex": "^4.1.0" + }, "engines": { - "node": ">=18" + "node": ">=6" } }, - "node_modules/@react-native/js-polyfills": { - "version": "0.73.1", - "resolved": "https://registry.npmjs.org/@react-native/js-polyfills/-/js-polyfills-0.73.1.tgz", - "integrity": "sha512-ewMwGcumrilnF87H4jjrnvGZEaPFCAC4ebraEK+CurDDmwST/bIicI4hrOAv+0Z0F7DEK4O4H7r8q9vH7IbN4g==", + "node_modules/@react-native-community/cli-doctor/node_modules/strip-ansi/node_modules/ansi-regex": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", "engines": { - "node": ">=18" + "node": ">=6" } }, - "node_modules/@react-native/metro-babel-transformer": { - "version": "0.73.10", - "resolved": "https://registry.npmjs.org/@react-native/metro-babel-transformer/-/metro-babel-transformer-0.73.10.tgz", - "integrity": "sha512-g0q12s1VcrhI4ab9ZQzFHmvjkDW5PYdJd3LiyhIO3OAWLD7iNLWL5tJe9iA3n8rxbanmf1oauqlNY1UmkxhAvQ==", - "dev": true, + "node_modules/@react-native-community/cli-doctor/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dependencies": { - "@babel/core": "^7.20.0", - "@react-native/babel-preset": "*", - "babel-preset-fbjs": "^3.4.0", - "hermes-parser": "0.15.0", - "nullthrows": "^1.1.1" + "has-flag": "^4.0.0" }, "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@babel/core": "*" + "node": ">=8" } }, - "node_modules/@react-native/metro-config": { - "version": "0.73.4", - "resolved": "https://registry.npmjs.org/@react-native/metro-config/-/metro-config-0.73.4.tgz", - "integrity": "sha512-4IpWb9InOY23ssua6z/ho2B4uRqF4QaNHGg4aV3D/og5yiVF39GEm/REHU36i+KoHRO3GcB6DrI7N9KrcvgGBw==", - "dev": true, - "dependencies": { - "@react-native/js-polyfills": "0.73.1", - "@react-native/metro-babel-transformer": "0.73.14", - "metro-config": "^0.80.3", - "metro-runtime": "^0.80.3" + "node_modules/@react-native-community/cli-doctor/node_modules/yaml": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.6.0.tgz", + "integrity": "sha512-a6ae//JvKDEra2kdi1qzCyrJW/WZCgFi8ydDV+eXExl95t+5R+ijnqHJbz9tmMh8FUjx3iv2fCQ4dclAQlO2UQ==", + "license": "ISC", + "bin": { + "yaml": "bin.mjs" }, "engines": { - "node": ">=18" + "node": ">= 14" } }, - "node_modules/@react-native/metro-config/node_modules/@react-native/babel-preset": { - "version": "0.73.20", - "resolved": "https://registry.npmjs.org/@react-native/babel-preset/-/babel-preset-0.73.20.tgz", - "integrity": "sha512-fU9NqkusbfFq71l4BWQfqqD/lLcLC0MZ++UYgieA3j8lIEppJTLVauv2RwtD2yltBkjebgYEC5Rwvt1l0MUBXw==", - "dev": true, + "node_modules/@react-native-community/cli-hermes": { + "version": "12.3.2", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-hermes/-/cli-hermes-12.3.2.tgz", + "integrity": "sha512-SL6F9O8ghp4ESBFH2YAPLtIN39jdnvGBKnK4FGKpDCjtB3DnUmDsGFlH46S+GGt5M6VzfG2eeKEOKf3pZ6jUzA==", "dependencies": { - "@babel/core": "^7.20.0", - "@babel/plugin-proposal-async-generator-functions": "^7.0.0", - "@babel/plugin-proposal-class-properties": "^7.18.0", - "@babel/plugin-proposal-export-default-from": "^7.0.0", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.0", - "@babel/plugin-proposal-numeric-separator": "^7.0.0", - "@babel/plugin-proposal-object-rest-spread": "^7.20.0", - "@babel/plugin-proposal-optional-catch-binding": "^7.0.0", - "@babel/plugin-proposal-optional-chaining": "^7.20.0", - "@babel/plugin-syntax-dynamic-import": "^7.8.0", - "@babel/plugin-syntax-export-default-from": "^7.0.0", - "@babel/plugin-syntax-flow": "^7.18.0", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.0.0", - "@babel/plugin-syntax-optional-chaining": "^7.0.0", - "@babel/plugin-transform-arrow-functions": "^7.0.0", - "@babel/plugin-transform-async-to-generator": "^7.20.0", - "@babel/plugin-transform-block-scoping": "^7.0.0", - "@babel/plugin-transform-classes": "^7.0.0", - "@babel/plugin-transform-computed-properties": "^7.0.0", - "@babel/plugin-transform-destructuring": "^7.20.0", - "@babel/plugin-transform-flow-strip-types": "^7.20.0", - "@babel/plugin-transform-function-name": "^7.0.0", - "@babel/plugin-transform-literals": "^7.0.0", - "@babel/plugin-transform-modules-commonjs": "^7.0.0", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.0.0", - "@babel/plugin-transform-parameters": "^7.0.0", - "@babel/plugin-transform-private-methods": "^7.22.5", - "@babel/plugin-transform-private-property-in-object": "^7.22.11", - "@babel/plugin-transform-react-display-name": "^7.0.0", - "@babel/plugin-transform-react-jsx": "^7.0.0", - "@babel/plugin-transform-react-jsx-self": "^7.0.0", - "@babel/plugin-transform-react-jsx-source": "^7.0.0", - "@babel/plugin-transform-runtime": "^7.0.0", - "@babel/plugin-transform-shorthand-properties": "^7.0.0", - "@babel/plugin-transform-spread": "^7.0.0", - "@babel/plugin-transform-sticky-regex": "^7.0.0", - "@babel/plugin-transform-typescript": "^7.5.0", - "@babel/plugin-transform-unicode-regex": "^7.0.0", - "@babel/template": "^7.0.0", - "@react-native/babel-plugin-codegen": "0.73.3", - "babel-plugin-transform-flow-enums": "^0.0.2", - "react-refresh": "^0.14.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@babel/core": "*" + "@react-native-community/cli-platform-android": "12.3.2", + "@react-native-community/cli-tools": "12.3.2", + "chalk": "^4.1.2", + "hermes-profile-transformer": "^0.0.6", + "ip": "^1.1.5" } }, - "node_modules/@react-native/metro-config/node_modules/@react-native/metro-babel-transformer": { - "version": "0.73.14", - "resolved": "https://registry.npmjs.org/@react-native/metro-babel-transformer/-/metro-babel-transformer-0.73.14.tgz", - "integrity": "sha512-5wLeYw/lormpSqYfI9H/geZ/EtPmi+x5qLkEit15Q/70hkzYo/M+aWztUtbOITfgTEOP8d6ybROzoGsqgyZLcw==", - "dev": true, + "node_modules/@react-native-community/cli-hermes/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dependencies": { - "@babel/core": "^7.20.0", - "@react-native/babel-preset": "0.73.20", - "hermes-parser": "0.15.0", - "nullthrows": "^1.1.1" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { - "node": ">=18" + "node": ">=10" }, - "peerDependencies": { - "@babel/core": "*" + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@react-native/normalize-color": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@react-native/normalize-color/-/normalize-color-2.1.0.tgz", - "integrity": "sha512-Z1jQI2NpdFJCVgpY+8Dq/Bt3d+YUi1928Q+/CZm/oh66fzM0RUl54vvuXlPJKybH4pdCZey1eDTPaLHkMPNgWA==" - }, - "node_modules/@react-native/normalize-colors": { - "version": "0.73.2", - "resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.73.2.tgz", - "integrity": "sha512-bRBcb2T+I88aG74LMVHaKms2p/T8aQd8+BZ7LuuzXlRfog1bMWWn/C5i0HVuvW4RPtXQYgIlGiXVDy9Ir1So/w==" - }, - "node_modules/@react-native/virtualized-lists": { - "version": "0.73.4", - "resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.73.4.tgz", - "integrity": "sha512-HpmLg1FrEiDtrtAbXiwCgXFYyloK/dOIPIuWW3fsqukwJEWAiTzm1nXGJ7xPU5XTHiWZ4sKup5Ebaj8z7iyWog==", - "dependencies": { - "invariant": "^2.2.4", - "nullthrows": "^1.1.1" - }, + "node_modules/@react-native-community/cli-hermes/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "engines": { - "node": ">=18" - }, - "peerDependencies": { - "react-native": "*" + "node": ">=8" } }, - "node_modules/@react-navigation/core": { - "version": "5.12.0", - "resolved": "https://registry.npmjs.org/@react-navigation/core/-/core-5.12.0.tgz", - "integrity": "sha512-CTmYrFXCZwInN40CpEzkPxhrpzujj20qvsUgpH05+oO1flwsnaJsyBfYawIcTS62/1/Z6SAM7iW5PbKk+qw9iQ==", + "node_modules/@react-native-community/cli-hermes/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dependencies": { - "@react-navigation/routers": "^5.4.9", - "escape-string-regexp": "^4.0.0", - "nanoid": "^3.1.9", - "query-string": "^6.13.1", - "react-is": "^16.13.0", - "use-subscription": "^1.4.0" + "has-flag": "^4.0.0" }, - "peerDependencies": { - "react": "*" - } - }, - "node_modules/@react-navigation/core/node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@react-navigation/elements": { - "version": "1.3.18", - "resolved": "https://registry.npmjs.org/@react-navigation/elements/-/elements-1.3.18.tgz", - "integrity": "sha512-/0hwnJkrr415yP0Hf4PjUKgGyfshrvNUKFXN85Mrt1gY49hy9IwxZgrrxlh0THXkPeq8q4VWw44eHDfAcQf20Q==", - "peerDependencies": { - "@react-navigation/native": "^6.0.0", - "react": "*", - "react-native": "*", - "react-native-safe-area-context": ">= 3.0.0" + "node": ">=8" } }, - "node_modules/@react-navigation/native": { - "version": "6.0.14", - "resolved": "https://registry.npmjs.org/@react-navigation/native/-/native-6.0.14.tgz", - "integrity": "sha512-Z95bJrRkZerBJq6Qc/xjA/kibPpB+UvPeMWS1CBhRF8FaX1483UdHqPqSbW879tPwjP2R4XfoA4dtoEHswrOjA==", + "node_modules/@react-native-community/cli-platform-android": { + "version": "12.3.2", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-android/-/cli-platform-android-12.3.2.tgz", + "integrity": "sha512-MZ5nO8yi/N+Fj2i9BJcJ9C/ez+9/Ir7lQt49DWRo9YDmzye66mYLr/P2l/qxsixllbbDi7BXrlLpxaEhMrDopg==", "dependencies": { - "@react-navigation/core": "^6.4.1", - "escape-string-regexp": "^4.0.0", - "fast-deep-equal": "^3.1.3", - "nanoid": "^3.1.23" - }, - "peerDependencies": { - "react": "*", - "react-native": "*" + "@react-native-community/cli-tools": "12.3.2", + "chalk": "^4.1.2", + "execa": "^5.0.0", + "fast-xml-parser": "^4.2.4", + "glob": "^7.1.3", + "logkitty": "^0.7.1" } }, - "node_modules/@react-navigation/native/node_modules/@react-navigation/core": { - "version": "6.4.17", - "resolved": "https://registry.npmjs.org/@react-navigation/core/-/core-6.4.17.tgz", - "integrity": "sha512-Nd76EpomzChWAosGqWOYE3ItayhDzIEzzZsT7PfGcRFDgW5miHV2t4MZcq9YIK4tzxZjVVpYbIynOOQQd1e0Cg==", - "license": "MIT", + "node_modules/@react-native-community/cli-platform-android/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dependencies": { - "@react-navigation/routers": "^6.1.9", - "escape-string-regexp": "^4.0.0", - "nanoid": "^3.1.23", - "query-string": "^7.1.3", - "react-is": "^16.13.0", - "use-latest-callback": "^0.2.1" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, - "peerDependencies": { - "react": "*" + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@react-navigation/native/node_modules/@react-navigation/routers": { - "version": "6.1.9", - "resolved": "https://registry.npmjs.org/@react-navigation/routers/-/routers-6.1.9.tgz", - "integrity": "sha512-lTM8gSFHSfkJvQkxacGM6VJtBt61ip2XO54aNfswD+KMw6eeZ4oehl7m0me3CR9hnDE4+60iAZR8sAhvCiI3NA==", + "node_modules/@react-native-community/cli-platform-android/node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", "dependencies": { - "nanoid": "^3.1.23" + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, - "node_modules/@react-navigation/native/node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "node_modules/@react-native-community/cli-platform-android/node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", "engines": { "node": ">=10" }, @@ -10536,1095 +9846,985 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@react-navigation/native/node_modules/query-string": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/query-string/-/query-string-7.1.3.tgz", - "integrity": "sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg==", + "node_modules/@react-native-community/cli-platform-android/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", "dependencies": { - "decode-uri-component": "^0.2.2", - "filter-obj": "^1.1.0", - "split-on-first": "^1.0.0", - "strict-uri-encode": "^2.0.0" + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" }, "engines": { - "node": ">=6" + "node": "*" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@react-navigation/routers": { - "version": "5.4.9", - "resolved": "https://registry.npmjs.org/@react-navigation/routers/-/routers-5.4.9.tgz", - "integrity": "sha512-dYD5qrIKUmuBEp+O98hB0tDYpEsGQgCQFQgMEoFKBmVVhx2JnJJ1zxRjU7xWcCU4VdBA8IOowgHQHJsVNKYyrg==", - "dependencies": { - "nanoid": "^3.1.9" + "node_modules/@react-native-community/cli-platform-android/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" } }, - "node_modules/@react-navigation/stack": { - "version": "6.3.5", - "resolved": "https://registry.npmjs.org/@react-navigation/stack/-/stack-6.3.5.tgz", - "integrity": "sha512-G706Ow+8fhiLT5Cf48566YsjboU5h5ll9nva90xX3br7V6x0JMoGMvXjvbORZyzLuUytQO3mf0T3nVUuO52vZQ==", - "dependencies": { - "@react-navigation/elements": "^1.3.7", - "color": "^4.2.3", - "warn-once": "^0.1.0" + "node_modules/@react-native-community/cli-platform-android/node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/@react-native-community/cli-platform-android/node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "engines": { + "node": ">=8" }, - "peerDependencies": { - "@react-navigation/native": "^6.0.0", - "react": "*", - "react-native": "*", - "react-native-gesture-handler": ">= 1.0.0", - "react-native-safe-area-context": ">= 3.0.0", - "react-native-screens": ">= 3.0.0" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@react-spring/animated": { - "version": "9.5.5", - "resolved": "https://registry.npmjs.org/@react-spring/animated/-/animated-9.5.5.tgz", - "integrity": "sha512-glzViz7syQ3CE6BQOwAyr75cgh0qsihm5lkaf24I0DfU63cMm/3+br299UEYkuaHNmfDfM414uktiPlZCNJbQA==", + "node_modules/@react-native-community/cli-platform-android/node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/@react-native-community/cli-platform-android/node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", "dependencies": { - "@react-spring/shared": "~9.5.5", - "@react-spring/types": "~9.5.5" + "path-key": "^3.0.0" }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + "engines": { + "node": ">=8" } }, - "node_modules/@react-spring/core": { - "version": "9.5.5", - "resolved": "https://registry.npmjs.org/@react-spring/core/-/core-9.5.5.tgz", - "integrity": "sha512-shaJYb3iX18Au6gkk8ahaF0qx0LpS0Yd+ajb4asBaAQf6WPGuEdJsbsNSgei1/O13JyEATsJl20lkjeslJPMYA==", + "node_modules/@react-native-community/cli-platform-android/node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", "dependencies": { - "@react-spring/animated": "~9.5.5", - "@react-spring/rafz": "~9.5.5", - "@react-spring/shared": "~9.5.5", - "@react-spring/types": "~9.5.5" + "mimic-fn": "^2.1.0" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/react-spring/donate" + "engines": { + "node": ">=6" }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@react-spring/rafz": { - "version": "9.5.5", - "resolved": "https://registry.npmjs.org/@react-spring/rafz/-/rafz-9.5.5.tgz", - "integrity": "sha512-F/CLwB0d10jL6My5vgzRQxCNY2RNyDJZedRBK7FsngdCmzoq3V4OqqNc/9voJb9qRC2wd55oGXUeXv2eIaFmsw==" + "node_modules/@react-native-community/cli-platform-android/node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "engines": { + "node": ">=8" + } }, - "node_modules/@react-spring/shared": { - "version": "9.5.5", - "resolved": "https://registry.npmjs.org/@react-spring/shared/-/shared-9.5.5.tgz", - "integrity": "sha512-YwW70Pa/YXPOwTutExHZmMQSHcNC90kJOnNR4G4mCDNV99hE98jWkIPDOsgqbYx3amIglcFPiYKMaQuGdr8dyQ==", + "node_modules/@react-native-community/cli-platform-android/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dependencies": { - "@react-spring/rafz": "~9.5.5", - "@react-spring/types": "~9.5.5" + "has-flag": "^4.0.0" }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + "engines": { + "node": ">=8" } }, - "node_modules/@react-spring/types": { - "version": "9.5.5", - "resolved": "https://registry.npmjs.org/@react-spring/types/-/types-9.5.5.tgz", - "integrity": "sha512-7I/qY8H7Enwasxr4jU6WmtNK+RZ4Z/XvSlDvjXFVe7ii1x0MoSlkw6pD7xuac8qrHQRm9BTcbZNyeeKApYsvCg==" - }, - "node_modules/@react-spring/web": { - "version": "9.5.5", - "resolved": "https://registry.npmjs.org/@react-spring/web/-/web-9.5.5.tgz", - "integrity": "sha512-+moT8aDX/ho/XAhU+HRY9m0LVV9y9CK6NjSRaI+30Re150pB3iEip6QfnF4qnhSCQ5drpMF0XRXHgOTY/xbtFw==", + "node_modules/@react-native-community/cli-platform-ios": { + "version": "12.3.2", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-ios/-/cli-platform-ios-12.3.2.tgz", + "integrity": "sha512-OcWEAbkev1IL6SUiQnM6DQdsvfsKZhRZtoBNSj9MfdmwotVZSOEZJ+IjZ1FR9ChvMWayO9ns/o8LgoQxr1ZXeg==", "dependencies": { - "@react-spring/animated": "~9.5.5", - "@react-spring/core": "~9.5.5", - "@react-spring/shared": "~9.5.5", - "@react-spring/types": "~9.5.5" - }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + "@react-native-community/cli-tools": "12.3.2", + "chalk": "^4.1.2", + "execa": "^5.0.0", + "fast-xml-parser": "^4.0.12", + "glob": "^7.1.3", + "ora": "^5.4.1" } }, - "node_modules/@samverschueren/stream-to-observable": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@samverschueren/stream-to-observable/-/stream-to-observable-0.3.1.tgz", - "integrity": "sha512-c/qwwcHyafOQuVQJj0IlBjf5yYgBI7YPJ77k4fOJYesb41jio65eaJODRUmfYKhTOFBrIZ66kgvGPlNbjuoRdQ==", - "dev": true, + "node_modules/@react-native-community/cli-platform-ios/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dependencies": { - "any-observable": "^0.3.0" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { - "node": ">=6" + "node": ">=10" }, - "peerDependenciesMeta": { - "rxjs": { - "optional": true - }, - "zen-observable": { - "optional": true - } + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@sentry/core": { - "version": "6.19.7", - "resolved": "https://registry.npmjs.org/@sentry/core/-/core-6.19.7.tgz", - "integrity": "sha512-tOfZ/umqB2AcHPGbIrsFLcvApdTm9ggpi/kQZFkej7kMphjT+SGBiQfYtjyg9jcRW+ilAR4JXC9BGKsdEQ+8Vw==", + "node_modules/@react-native-community/cli-platform-ios/node_modules/cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", "dependencies": { - "@sentry/hub": "6.19.7", - "@sentry/minimal": "6.19.7", - "@sentry/types": "6.19.7", - "@sentry/utils": "6.19.7", - "tslib": "^1.9.3" + "restore-cursor": "^3.1.0" }, "engines": { - "node": ">=6" + "node": ">=8" } }, - "node_modules/@sentry/core/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" - }, - "node_modules/@sentry/hub": { - "version": "6.19.7", - "resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-6.19.7.tgz", - "integrity": "sha512-y3OtbYFAqKHCWezF0EGGr5lcyI2KbaXW2Ik7Xp8Mu9TxbSTuwTe4rTntwg8ngPjUQU3SUHzgjqVB8qjiGqFXCA==", + "node_modules/@react-native-community/cli-platform-ios/node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", "dependencies": { - "@sentry/types": "6.19.7", - "@sentry/utils": "6.19.7", - "tslib": "^1.9.3" + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" }, "engines": { - "node": ">=6" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, - "node_modules/@sentry/hub/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" - }, - "node_modules/@sentry/minimal": { - "version": "6.19.7", - "resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-6.19.7.tgz", - "integrity": "sha512-wcYmSJOdvk6VAPx8IcmZgN08XTXRwRtB1aOLZm+MVHjIZIhHoBGZJYTVQS/BWjldsamj2cX3YGbGXNunaCfYJQ==", - "dependencies": { - "@sentry/hub": "6.19.7", - "@sentry/types": "6.19.7", - "tslib": "^1.9.3" - }, + "node_modules/@react-native-community/cli-platform-ios/node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", "engines": { - "node": ">=6" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@sentry/minimal/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" - }, - "node_modules/@sentry/node": { - "version": "6.19.7", - "resolved": "https://registry.npmjs.org/@sentry/node/-/node-6.19.7.tgz", - "integrity": "sha512-gtmRC4dAXKODMpHXKfrkfvyBL3cI8y64vEi3fDD046uqYcrWdgoQsffuBbxMAizc6Ez1ia+f0Flue6p15Qaltg==", + "node_modules/@react-native-community/cli-platform-ios/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", "dependencies": { - "@sentry/core": "6.19.7", - "@sentry/hub": "6.19.7", - "@sentry/types": "6.19.7", - "@sentry/utils": "6.19.7", - "cookie": "^0.4.1", - "https-proxy-agent": "^5.0.0", - "lru_map": "^0.3.3", - "tslib": "^1.9.3" + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" }, "engines": { - "node": ">=6" + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@sentry/node/node_modules/cookie": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", - "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", + "node_modules/@react-native-community/cli-platform-ios/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "engines": { - "node": ">= 0.6" + "node": ">=8" } }, - "node_modules/@sentry/node/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" - }, - "node_modules/@sentry/types": { - "version": "6.19.7", - "resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.19.7.tgz", - "integrity": "sha512-jH84pDYE+hHIbVnab3Hr+ZXr1v8QABfhx39KknxqKWr2l0oEItzepV0URvbEhB446lk/S/59230dlUUIBGsXbg==", + "node_modules/@react-native-community/cli-platform-ios/node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", "engines": { - "node": ">=6" + "node": ">=10.17.0" } }, - "node_modules/@sentry/utils": { - "version": "6.19.7", - "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.19.7.tgz", - "integrity": "sha512-z95ECmE3i9pbWoXQrD/7PgkBAzJYR+iXtPuTkpBjDKs86O3mT+PXOT3BAn79w2wkn7/i3vOGD2xVr1uiMl26dA==", - "dependencies": { - "@sentry/types": "6.19.7", - "tslib": "^1.9.3" + "node_modules/@react-native-community/cli-platform-ios/node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "engines": { + "node": ">=8" }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@react-native-community/cli-platform-ios/node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", "engines": { "node": ">=6" } }, - "node_modules/@sentry/utils/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" - }, - "node_modules/@sideway/address": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", - "integrity": "sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==", + "node_modules/@react-native-community/cli-platform-ios/node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", "dependencies": { - "@hapi/hoek": "^9.0.0" + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" } }, - "node_modules/@sideway/formula": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz", - "integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==" - }, - "node_modules/@sideway/pinpoint": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", - "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==" - }, - "node_modules/@sidvind/better-ajv-errors": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@sidvind/better-ajv-errors/-/better-ajv-errors-2.1.0.tgz", - "integrity": "sha512-JuIb009FhHuL9priFBho2kv7QmZOydj0LgYvj+h1t0mMCmhM/YmQNRlJR5wVtBZya6wrVFK5Hi5TIbv5BKEx7w==", - "dev": true, + "node_modules/@react-native-community/cli-platform-ios/node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", "dependencies": { - "@babel/code-frame": "^7.16.0", - "chalk": "^4.1.0" + "mimic-fn": "^2.1.0" }, "engines": { - "node": ">= 14.0.0" + "node": ">=6" }, - "peerDependencies": { - "ajv": "4.11.8 - 8" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@sigstore/bundle": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@sigstore/bundle/-/bundle-1.0.0.tgz", - "integrity": "sha512-yLvrWDOh6uMOUlFCTJIZEnwOT9Xte7NPXUqVexEKGSF5XtBAuSg5du0kn3dRR0p47a4ah10Y0mNt8+uyeQXrBQ==", - "dev": true, + "node_modules/@react-native-community/cli-platform-ios/node_modules/ora": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", + "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", "dependencies": { - "@sigstore/protobuf-specs": "^0.2.0" + "bl": "^4.1.0", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "is-unicode-supported": "^0.1.0", + "log-symbols": "^4.1.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@sigstore/protobuf-specs": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@sigstore/protobuf-specs/-/protobuf-specs-0.2.0.tgz", - "integrity": "sha512-8ZhZKAVfXjIspDWwm3D3Kvj0ddbJ0HqDZ/pOs5cx88HpT8mVsotFrg7H1UMnXOuDHz6Zykwxn4mxG3QLuN+RUg==", - "dev": true, + "node_modules/@react-native-community/cli-platform-ios/node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": ">=8" } }, - "node_modules/@sigstore/tuf": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@sigstore/tuf/-/tuf-1.0.3.tgz", - "integrity": "sha512-2bRovzs0nJZFlCN3rXirE4gwxCn97JNjMmwpecqlbgV9WcxX7WRuIrgzx/X7Ib7MYRbyUTpBYE0s2x6AmZXnlg==", - "dev": true, + "node_modules/@react-native-community/cli-platform-ios/node_modules/restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", "dependencies": { - "@sigstore/protobuf-specs": "^0.2.0", - "tuf-js": "^1.1.7" + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": ">=8" } }, - "node_modules/@sinclair/typebox": { - "version": "0.27.8", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", - "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==" - }, - "node_modules/@sindresorhus/is": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", - "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", - "engines": { - "node": ">=10" + "node_modules/@react-native-community/cli-platform-ios/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" }, - "funding": { - "url": "https://github.com/sindresorhus/is?sponsor=1" + "engines": { + "node": ">=8" } }, - "node_modules/@sinonjs/commons": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.0.tgz", - "integrity": "sha512-jXBtWAF4vmdNmZgD5FoKsVLv3rPgDnLgPbU84LIJ3otV44vJlDRokVng5v8NFJdCf/da9legHcKaRuZs4L7faA==", - "dependencies": { - "type-detect": "4.0.8" - } + "node_modules/@react-native-community/cli-plugin-metro": { + "version": "12.3.2", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-plugin-metro/-/cli-plugin-metro-12.3.2.tgz", + "integrity": "sha512-FpFBwu+d2E7KRhYPTkKvQsWb2/JKsJv+t1tcqgQkn+oByhp+qGyXBobFB8/R3yYvRRDCSDhS+atWTJzk9TjM8g==" }, - "node_modules/@sinonjs/fake-timers": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", - "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "node_modules/@react-native-community/cli-server-api": { + "version": "12.3.2", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-server-api/-/cli-server-api-12.3.2.tgz", + "integrity": "sha512-iwa7EO9XFA/OjI5pPLLpI/6mFVqv8L73kNck3CNOJIUCCveGXBKK0VMyOkXaf/BYnihgQrXh+x5cxbDbggr7+Q==", "dependencies": { - "@sinonjs/commons": "^3.0.0" + "@react-native-community/cli-debugger-ui": "12.3.2", + "@react-native-community/cli-tools": "12.3.2", + "compression": "^1.7.1", + "connect": "^3.6.5", + "errorhandler": "^1.5.1", + "nocache": "^3.0.1", + "pretty-format": "^26.6.2", + "serve-static": "^1.13.1", + "ws": "^7.5.1" } }, - "node_modules/@sliphua/lilconfig-ts-loader": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/@sliphua/lilconfig-ts-loader/-/lilconfig-ts-loader-3.2.2.tgz", - "integrity": "sha512-nX2aBwAykiG50fSUzK9eyA5UvWcrEKzA0ZzCq9mLwHMwpKxM+U05YH8PHba1LJrbeZ7R1HSjJagWKMqFyq8cxw==", - "dev": true, - "dependencies": { - "lodash.get": "^4", - "make-error": "^1", - "ts-node": "^9", - "tslib": "^2" + "node_modules/@react-native-community/cli-server-api/node_modules/@jest/types": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", + "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^15.0.0", + "chalk": "^4.0.0" }, "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "lilconfig": ">=2" + "node": ">= 10.14.2" } }, - "node_modules/@storybook/addon-a11y": { - "version": "7.6.15", - "resolved": "https://registry.npmjs.org/@storybook/addon-a11y/-/addon-a11y-7.6.15.tgz", - "integrity": "sha512-8PxRMBJUSxNoceo2IYXFyZp3VU+/ONK/DsD0dj/fVrv7izFrS8aw2GWSsSMK8xAbEUpANXWMKGaSyvrRFVgsVQ==", - "dev": true, + "node_modules/@react-native-community/cli-server-api/node_modules/@types/yargs": { + "version": "15.0.19", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.19.tgz", + "integrity": "sha512-2XUaGVmyQjgyAZldf0D0c14vvo/yv0MhQBSTJcejMMaitsn3nxCB6TmH4G0ZQf+uxROOa9mpanoSm8h6SG/1ZA==", "dependencies": { - "@storybook/addon-highlight": "7.6.15", - "axe-core": "^4.2.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" + "@types/yargs-parser": "*" } }, - "node_modules/@storybook/addon-actions": { - "version": "7.6.15", - "resolved": "https://registry.npmjs.org/@storybook/addon-actions/-/addon-actions-7.6.15.tgz", - "integrity": "sha512-2Jfvbahe/tmq1iNnNxmcP0JnX0rqCuijjXXai9yMDV3koIMawn6t88MPVrdcso5ch/fxE45522nZqA3SZJbM4g==", - "dev": true, + "node_modules/@react-native-community/cli-server-api/node_modules/pretty-format": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", + "integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==", "dependencies": { - "@storybook/core-events": "7.6.15", - "@storybook/global": "^5.0.0", - "@types/uuid": "^9.0.1", - "dequal": "^2.0.2", - "polished": "^4.2.2", - "uuid": "^9.0.0" + "@jest/types": "^26.6.2", + "ansi-regex": "^5.0.0", + "ansi-styles": "^4.0.0", + "react-is": "^17.0.1" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" + "engines": { + "node": ">= 10" } }, - "node_modules/@storybook/addon-actions/node_modules/@types/uuid": { - "version": "9.0.8", - "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.8.tgz", - "integrity": "sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==", - "dev": true + "node_modules/@react-native-community/cli-server-api/node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" }, - "node_modules/@storybook/addon-controls": { - "version": "7.6.15", - "resolved": "https://registry.npmjs.org/@storybook/addon-controls/-/addon-controls-7.6.15.tgz", - "integrity": "sha512-HXcG/Lr4ri7WUFz14Y5lEBTA1XmKy0E/DepW88XVy6YNsTpERVWEBcvjKoLAU1smKrfhVto96hK2AVFL3A8EBQ==", - "dev": true, + "node_modules/@react-native-community/cli-tools": { + "version": "12.3.2", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-tools/-/cli-tools-12.3.2.tgz", + "integrity": "sha512-nDH7vuEicHI2TI0jac/DjT3fr977iWXRdgVAqPZFFczlbs7A8GQvEdGnZ1G8dqRUmg+kptw0e4hwczAOG89JzQ==", "dependencies": { - "@storybook/blocks": "7.6.15", - "lodash": "^4.17.21", - "ts-dedent": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" + "appdirsjs": "^1.2.4", + "chalk": "^4.1.2", + "find-up": "^5.0.0", + "mime": "^2.4.1", + "node-fetch": "^2.6.0", + "open": "^6.2.0", + "ora": "^5.4.1", + "semver": "^7.5.2", + "shell-quote": "^1.7.3", + "sudo-prompt": "^9.0.0" } }, - "node_modules/@storybook/addon-docs": { - "version": "7.6.15", - "resolved": "https://registry.npmjs.org/@storybook/addon-docs/-/addon-docs-7.6.15.tgz", - "integrity": "sha512-UPODqO+mrYaKyTSAtfRslxOFgSP/v/5vfDx896pbNTC4Sf8xLytoudw4I14hzkHmRdXiOnd21FqXJfmF/Onsvw==", - "dev": true, - "dependencies": { - "@jest/transform": "^29.3.1", - "@mdx-js/react": "^2.1.5", - "@storybook/blocks": "7.6.15", - "@storybook/client-logger": "7.6.15", - "@storybook/components": "7.6.15", - "@storybook/csf-plugin": "7.6.15", - "@storybook/csf-tools": "7.6.15", - "@storybook/global": "^5.0.0", - "@storybook/mdx2-csf": "^1.0.0", - "@storybook/node-logger": "7.6.15", - "@storybook/postinstall": "7.6.15", - "@storybook/preview-api": "7.6.15", - "@storybook/react-dom-shim": "7.6.15", - "@storybook/theming": "7.6.15", - "@storybook/types": "7.6.15", - "fs-extra": "^11.1.0", - "remark-external-links": "^8.0.0", - "remark-slug": "^6.0.0", - "ts-dedent": "^2.0.0" + "node_modules/@react-native-community/cli-tools/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" + "engines": { + "node": ">=10" }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@storybook/addon-docs/node_modules/fs-extra": { - "version": "11.2.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", - "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==", - "dev": true, + "node_modules/@react-native-community/cli-tools/node_modules/cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" + "restore-cursor": "^3.1.0" }, "engines": { - "node": ">=14.14" + "node": ">=8" } }, - "node_modules/@storybook/addon-docs/node_modules/jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dev": true, + "node_modules/@react-native-community/cli-tools/node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "dependencies": { - "universalify": "^2.0.0" + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@storybook/addon-docs/node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "dev": true, + "node_modules/@react-native-community/cli-tools/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "engines": { - "node": ">= 10.0.0" + "node": ">=8" } }, - "node_modules/@storybook/addon-highlight": { - "version": "7.6.15", - "resolved": "https://registry.npmjs.org/@storybook/addon-highlight/-/addon-highlight-7.6.15.tgz", - "integrity": "sha512-ptidWZJJcEM83YsxCjf+m1q8Rr9sN8piJ4PJlM2vyc4MLZY4q6htb1JJFeq3ov1Iz6SY9KjKc/zOkWo4L54nxw==", - "dev": true, + "node_modules/@react-native-community/cli-tools/node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dependencies": { - "@storybook/global": "^5.0.0" + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@storybook/addon-toolbars": { - "version": "7.6.15", - "resolved": "https://registry.npmjs.org/@storybook/addon-toolbars/-/addon-toolbars-7.6.15.tgz", - "integrity": "sha512-QougKS2eABB5Jd332i9tBpKgh2lN4aaqXkvmVC5egT5dOuJ9IeuZbGwiALef/uf1f3IuyUP41So9l2dI4u19aw==", - "dev": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" + "node_modules/@react-native-community/cli-tools/node_modules/mime": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4.0.0" } }, - "node_modules/@storybook/addon-viewport": { - "version": "7.6.15", - "resolved": "https://registry.npmjs.org/@storybook/addon-viewport/-/addon-viewport-7.6.15.tgz", - "integrity": "sha512-0esg0+onJftU2prD3n/sbxBTrTOIGQnZhbrKPP+/S26dVHuYaR/65XdwpRgXNY5PHK2yjU78HxiJP+Kyu75ntw==", - "dev": true, - "dependencies": { - "memoizerific": "^1.11.3" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" + "node_modules/@react-native-community/cli-tools/node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "engines": { + "node": ">=6" } }, - "node_modules/@storybook/blocks": { - "version": "7.6.15", - "resolved": "https://registry.npmjs.org/@storybook/blocks/-/blocks-7.6.15.tgz", - "integrity": "sha512-ODP7AVh2iIGblI5WKGokWSHbp9YQHc+Uce7JCGcnDbNavoy64Z6R6G+wXzF5jfl7xQlbhQ8yQCuSSL4GNdYTeA==", - "dev": true, + "node_modules/@react-native-community/cli-tools/node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", "dependencies": { - "@storybook/channels": "7.6.15", - "@storybook/client-logger": "7.6.15", - "@storybook/components": "7.6.15", - "@storybook/core-events": "7.6.15", - "@storybook/csf": "^0.1.2", - "@storybook/docs-tools": "7.6.15", - "@storybook/global": "^5.0.0", - "@storybook/manager-api": "7.6.15", - "@storybook/preview-api": "7.6.15", - "@storybook/theming": "7.6.15", - "@storybook/types": "7.6.15", - "@types/lodash": "^4.14.167", - "color-convert": "^2.0.1", - "dequal": "^2.0.2", - "lodash": "^4.17.21", - "markdown-to-jsx": "^7.1.8", - "memoizerific": "^1.11.3", - "polished": "^4.2.2", - "react-colorful": "^5.1.2", - "telejson": "^7.2.0", - "tocbot": "^4.20.1", - "ts-dedent": "^2.0.0", - "util-deprecate": "^1.0.2" + "mimic-fn": "^2.1.0" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" + "engines": { + "node": ">=6" }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@storybook/blocks/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, + "node_modules/@react-native-community/cli-tools/node_modules/ora": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", + "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", "dependencies": { - "color-name": "~1.1.4" + "bl": "^4.1.0", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "is-unicode-supported": "^0.1.0", + "log-symbols": "^4.1.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" }, "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@storybook/blocks/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/@storybook/builder-manager": { - "version": "7.6.15", - "resolved": "https://registry.npmjs.org/@storybook/builder-manager/-/builder-manager-7.6.15.tgz", - "integrity": "sha512-vfpfCywiasyP7vtbgLJhjssBEwUjZhBsRsubDAzumgOochPiKKPNwsSc5NU/4ZIGaC5zRO26kUaUqFIbJdTEUQ==", - "dev": true, - "dependencies": { - "@fal-works/esbuild-plugin-global-externals": "^2.1.2", - "@storybook/core-common": "7.6.15", - "@storybook/manager": "7.6.15", - "@storybook/node-logger": "7.6.15", - "@types/ejs": "^3.1.1", - "@types/find-cache-dir": "^3.2.1", - "@yarnpkg/esbuild-plugin-pnp": "^3.0.0-rc.10", - "browser-assert": "^1.2.1", - "ejs": "^3.1.8", - "esbuild": "^0.18.0", - "esbuild-plugin-alias": "^0.2.1", - "express": "^4.17.3", - "find-cache-dir": "^3.0.0", - "fs-extra": "^11.1.0", - "process": "^0.11.10", - "util": "^0.12.4" + "node": ">=10" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@storybook/builder-manager/node_modules/find-cache-dir": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", - "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", - "dev": true, + "node_modules/@react-native-community/cli-tools/node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", "dependencies": { - "commondir": "^1.0.1", - "make-dir": "^3.0.2", - "pkg-dir": "^4.1.0" + "p-limit": "^3.0.2" }, "engines": { - "node": ">=8" + "node": ">=10" }, "funding": { - "url": "https://github.com/avajs/find-cache-dir?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@storybook/builder-manager/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, + "node_modules/@react-native-community/cli-tools/node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "engines": { "node": ">=8" } }, - "node_modules/@storybook/builder-manager/node_modules/fs-extra": { - "version": "11.2.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", - "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==", - "dev": true, + "node_modules/@react-native-community/cli-tools/node_modules/restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" }, "engines": { - "node": ">=14.14" + "node": ">=8" } }, - "node_modules/@storybook/builder-manager/node_modules/jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dev": true, + "node_modules/@react-native-community/cli-tools/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dependencies": { - "universalify": "^2.0.0" + "has-flag": "^4.0.0" }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" + "engines": { + "node": ">=8" } }, - "node_modules/@storybook/builder-manager/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, + "node_modules/@react-native-community/cli-types": { + "version": "12.3.2", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-types/-/cli-types-12.3.2.tgz", + "integrity": "sha512-9D0UEFqLW8JmS16mjHJxUJWX8E+zJddrHILSH8AJHZ0NNHv4u2DXKdb0wFLMobFxGNxPT+VSOjc60fGvXzWHog==", "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" + "joi": "^17.2.1" } }, - "node_modules/@storybook/builder-manager/node_modules/make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dev": true, + "node_modules/@react-native-community/cli/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dependencies": { - "semver": "^6.0.0" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { - "node": ">=8" + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@storybook/builder-manager/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, + "node_modules/@react-native-community/cli/node_modules/commander": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "engines": { + "node": "^12.20.0 || >=14" + } + }, + "node_modules/@react-native-community/cli/node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", "dependencies": { - "p-try": "^2.0.0" + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" }, "engines": { - "node": ">=6" + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, - "node_modules/@storybook/builder-manager/node_modules/p-locate": { + "node_modules/@react-native-community/cli/node_modules/find-up": { "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "dependencies": { - "p-limit": "^2.2.0" + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" }, "engines": { "node": ">=8" } }, - "node_modules/@storybook/builder-manager/node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true, + "node_modules/@react-native-community/cli/node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", "engines": { - "node": ">=6" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@storybook/builder-manager/node_modules/path-exists": { + "node_modules/@react-native-community/cli/node_modules/has-flag": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "engines": { "node": ">=8" } }, - "node_modules/@storybook/builder-manager/node_modules/pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "dependencies": { - "find-up": "^4.0.0" - }, + "node_modules/@react-native-community/cli/node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", "engines": { - "node": ">=8" - } - }, - "node_modules/@storybook/builder-manager/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" + "node": ">=10.17.0" } }, - "node_modules/@storybook/builder-manager/node_modules/universalify": { + "node_modules/@react-native-community/cli/node_modules/is-stream": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "dev": true, + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", "engines": { - "node": ">= 10.0.0" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@storybook/builder-manager/node_modules/util": { - "version": "0.12.5", - "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", - "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", - "dev": true, + "node_modules/@react-native-community/cli/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", "dependencies": { - "inherits": "^2.0.3", - "is-arguments": "^1.0.4", - "is-generator-function": "^1.0.7", - "is-typed-array": "^1.1.3", - "which-typed-array": "^1.1.2" + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" } }, - "node_modules/@storybook/builder-webpack5": { - "version": "7.6.15", - "resolved": "https://registry.npmjs.org/@storybook/builder-webpack5/-/builder-webpack5-7.6.15.tgz", - "integrity": "sha512-HF+TSK/eU2ld8uQ8VWgcAIzOQ2hjnEkzup363vGZkYUfsHsVbjMpZgf+foDjI4LZNfQ/RjcVEZxqJqIbpM0Sjg==", - "dev": true, - "dependencies": { - "@babel/core": "^7.23.2", - "@storybook/channels": "7.6.15", - "@storybook/client-logger": "7.6.15", - "@storybook/core-common": "7.6.15", - "@storybook/core-events": "7.6.15", - "@storybook/core-webpack": "7.6.15", - "@storybook/node-logger": "7.6.15", - "@storybook/preview": "7.6.15", - "@storybook/preview-api": "7.6.15", - "@swc/core": "^1.3.82", - "@types/node": "^18.0.0", - "@types/semver": "^7.3.4", - "babel-loader": "^9.0.0", - "browser-assert": "^1.2.1", - "case-sensitive-paths-webpack-plugin": "^2.4.0", - "cjs-module-lexer": "^1.2.3", - "constants-browserify": "^1.0.0", - "css-loader": "^6.7.1", - "es-module-lexer": "^1.4.1", - "express": "^4.17.3", - "fork-ts-checker-webpack-plugin": "^8.0.0", - "fs-extra": "^11.1.0", - "html-webpack-plugin": "^5.5.0", - "magic-string": "^0.30.5", - "path-browserify": "^1.0.1", - "process": "^0.11.10", - "semver": "^7.3.7", - "style-loader": "^3.3.1", - "swc-loader": "^0.2.3", - "terser-webpack-plugin": "^5.3.1", - "ts-dedent": "^2.0.0", - "url": "^0.11.0", - "util": "^0.12.4", - "util-deprecate": "^1.0.2", - "webpack": "5", - "webpack-dev-middleware": "^6.1.1", - "webpack-hot-middleware": "^2.25.1", - "webpack-virtual-modules": "^0.5.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "node_modules/@react-native-community/cli/node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "engines": { + "node": ">=6" } }, - "node_modules/@storybook/builder-webpack5/node_modules/css-loader": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.11.0.tgz", - "integrity": "sha512-CTJ+AEQJjq5NzLga5pE39qdiSV56F8ywCIsqNIRF0r7BDgWsN25aazToqAFg7ZrtA/U016xudB3ffgweORxX7g==", - "dev": true, + "node_modules/@react-native-community/cli/node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", "dependencies": { - "icss-utils": "^5.1.0", - "postcss": "^8.4.33", - "postcss-modules-extract-imports": "^3.1.0", - "postcss-modules-local-by-default": "^4.0.5", - "postcss-modules-scope": "^3.2.0", - "postcss-modules-values": "^4.0.0", - "postcss-value-parser": "^4.2.0", - "semver": "^7.5.4" + "path-key": "^3.0.0" }, "engines": { - "node": ">= 12.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "@rspack/core": "0.x || 1.x", - "webpack": "^5.0.0" + "node": ">=8" + } + }, + "node_modules/@react-native-community/cli/node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dependencies": { + "mimic-fn": "^2.1.0" }, - "peerDependenciesMeta": { - "@rspack/core": { - "optional": true - }, - "webpack": { - "optional": true - } + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@storybook/builder-webpack5/node_modules/fs-extra": { - "version": "11.2.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", - "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==", - "dev": true, + "node_modules/@react-native-community/cli/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" + "p-try": "^2.0.0" }, "engines": { - "node": ">=14.14" + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@storybook/builder-webpack5/node_modules/jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dev": true, + "node_modules/@react-native-community/cli/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", "dependencies": { - "universalify": "^2.0.0" + "p-limit": "^2.2.0" }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" + "engines": { + "node": ">=8" } }, - "node_modules/@storybook/builder-webpack5/node_modules/path-browserify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", - "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", - "dev": true + "node_modules/@react-native-community/cli/node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "engines": { + "node": ">=6" + } }, - "node_modules/@storybook/builder-webpack5/node_modules/postcss-value-parser": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", - "dev": true + "node_modules/@react-native-community/cli/node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "engines": { + "node": ">=8" + } }, - "node_modules/@storybook/builder-webpack5/node_modules/style-loader": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-3.3.4.tgz", - "integrity": "sha512-0WqXzrsMTyb8yjZJHDqwmnwRJvhALK9LfRtRc6B4UTWe8AijYLZYZ9thuJTZc2VfQWINADW/j+LiJnfy2RoC1w==", - "dev": true, + "node_modules/@react-native-community/cli/node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "engines": { - "node": ">= 12.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" + "node": ">=8" + } + }, + "node_modules/@react-native-community/cli/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@react-native-community/slider": { + "version": "3.0.2-wp-5", + "resolved": "https://raw.githubusercontent.com/wordpress-mobile/react-native-slider/v3.0.2-wp-5/react-native-community-slider-3.0.2-wp-5.tgz", + "integrity": "sha512-19yl9Em1mFKFLB9o3IO3VpBFw+U7cswjRk6cZ7kkhDCpDD3u0cpzkaIZK6NuFIPCLqOwU0MPtP+gonanN1JQKw==", + "workspaces": [ + "src", + "example" + ] + }, + "node_modules/@react-native-masked-view/masked-view": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@react-native-masked-view/masked-view/-/masked-view-0.3.0.tgz", + "integrity": "sha512-qLyoObcjzrkpNcoJjXquUePXfL1dXjHtuv+yX0zZ0Q4kG5yvVqd620+tSh7WbRoHkjpXhFBfLwvGhcWB2I0Lpw==", "peerDependencies": { - "webpack": "^5.0.0" + "react": ">=16", + "react-native": ">=0.57" } }, - "node_modules/@storybook/builder-webpack5/node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "dev": true, + "node_modules/@react-native/assets-registry": { + "version": "0.73.1", + "resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.73.1.tgz", + "integrity": "sha512-2FgAbU7uKM5SbbW9QptPPZx8N9Ke2L7bsHb+EhAanZjFZunA9PaYtyjUQ1s7HD+zDVqOQIvjkpXSv7Kejd2tqg==", "engines": { - "node": ">= 10.0.0" + "node": ">=18" } }, - "node_modules/@storybook/builder-webpack5/node_modules/util": { - "version": "0.12.5", - "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", - "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", - "dev": true, + "node_modules/@react-native/babel-plugin-codegen": { + "version": "0.73.3", + "resolved": "https://registry.npmjs.org/@react-native/babel-plugin-codegen/-/babel-plugin-codegen-0.73.3.tgz", + "integrity": "sha512-+zQrDDbz6lB48LyzFHxNCgXDCBHH+oTRdXAjikRcBUdeG9St9ABbYFLtb799zSxLOrCqFVyXqhJR2vlgLLEbcg==", "dependencies": { - "inherits": "^2.0.3", - "is-arguments": "^1.0.4", - "is-generator-function": "^1.0.7", - "is-typed-array": "^1.1.3", - "which-typed-array": "^1.1.2" + "@react-native/codegen": "0.73.2" + }, + "engines": { + "node": ">=18" } }, - "node_modules/@storybook/channels": { - "version": "7.6.15", - "resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-7.6.15.tgz", - "integrity": "sha512-UPDYRzGkygYFa8QUpEiumWrvZm4u4RKVzgiBt9C4RmHORqkkZzL9LXhaZJp2SmIz1ND5gx6KR5ze8ZnAdwxxoQ==", + "node_modules/@react-native/babel-preset": { + "version": "0.73.10", + "resolved": "https://registry.npmjs.org/@react-native/babel-preset/-/babel-preset-0.73.10.tgz", + "integrity": "sha512-LPxR9DZK1EtVSEm2+fpei/AWwzgyHfaY7fuX8wDiy2THRYc/56+v28IihN8g0IAbQH+gdjXVcshJRYfV6bh2gg==", "dev": true, "dependencies": { - "@storybook/client-logger": "7.6.15", - "@storybook/core-events": "7.6.15", - "@storybook/global": "^5.0.0", - "qs": "^6.10.0", - "telejson": "^7.2.0", - "tiny-invariant": "^1.3.1" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" - } - }, - "node_modules/@storybook/cli": { - "version": "7.6.15", - "resolved": "https://registry.npmjs.org/@storybook/cli/-/cli-7.6.15.tgz", - "integrity": "sha512-2QRqCyVGDSkraHxX2JPYkkFccbu5Uo+JYFaFJo4vmMXzDurjWON+Ga2B8FCTd4A8P4C02Ca/79jgQoyBB3xoew==", - "dev": true, - "dependencies": { - "@babel/core": "^7.23.2", - "@babel/preset-env": "^7.23.2", - "@babel/types": "^7.23.0", - "@ndelangen/get-tarball": "^3.0.7", - "@storybook/codemod": "7.6.15", - "@storybook/core-common": "7.6.15", - "@storybook/core-events": "7.6.15", - "@storybook/core-server": "7.6.15", - "@storybook/csf-tools": "7.6.15", - "@storybook/node-logger": "7.6.15", - "@storybook/telemetry": "7.6.15", - "@storybook/types": "7.6.15", - "@types/semver": "^7.3.4", - "@yarnpkg/fslib": "2.10.3", - "@yarnpkg/libzip": "2.3.0", - "chalk": "^4.1.0", - "commander": "^6.2.1", - "cross-spawn": "^7.0.3", - "detect-indent": "^6.1.0", - "envinfo": "^7.7.3", - "execa": "^5.0.0", - "express": "^4.17.3", - "find-up": "^5.0.0", - "fs-extra": "^11.1.0", - "get-npm-tarball-url": "^2.0.3", - "get-port": "^5.1.1", - "giget": "^1.0.0", - "globby": "^11.0.2", - "jscodeshift": "^0.15.1", - "leven": "^3.1.0", - "ora": "^5.4.1", - "prettier": "^2.8.0", - "prompts": "^2.4.0", - "puppeteer-core": "^2.1.1", - "read-pkg-up": "^7.0.1", - "semver": "^7.3.7", - "strip-json-comments": "^3.0.1", - "tempy": "^1.0.1", - "ts-dedent": "^2.0.0", - "util-deprecate": "^1.0.2" + "@babel/core": "^7.20.0", + "@babel/plugin-proposal-async-generator-functions": "^7.0.0", + "@babel/plugin-proposal-class-properties": "^7.18.0", + "@babel/plugin-proposal-export-default-from": "^7.0.0", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.0", + "@babel/plugin-proposal-numeric-separator": "^7.0.0", + "@babel/plugin-proposal-object-rest-spread": "^7.20.0", + "@babel/plugin-proposal-optional-catch-binding": "^7.0.0", + "@babel/plugin-proposal-optional-chaining": "^7.20.0", + "@babel/plugin-syntax-dynamic-import": "^7.8.0", + "@babel/plugin-syntax-export-default-from": "^7.0.0", + "@babel/plugin-syntax-flow": "^7.18.0", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.0.0", + "@babel/plugin-syntax-optional-chaining": "^7.0.0", + "@babel/plugin-transform-arrow-functions": "^7.0.0", + "@babel/plugin-transform-async-to-generator": "^7.20.0", + "@babel/plugin-transform-block-scoping": "^7.0.0", + "@babel/plugin-transform-classes": "^7.0.0", + "@babel/plugin-transform-computed-properties": "^7.0.0", + "@babel/plugin-transform-destructuring": "^7.20.0", + "@babel/plugin-transform-flow-strip-types": "^7.20.0", + "@babel/plugin-transform-function-name": "^7.0.0", + "@babel/plugin-transform-literals": "^7.0.0", + "@babel/plugin-transform-modules-commonjs": "^7.0.0", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.0.0", + "@babel/plugin-transform-parameters": "^7.0.0", + "@babel/plugin-transform-react-display-name": "^7.0.0", + "@babel/plugin-transform-react-jsx": "^7.0.0", + "@babel/plugin-transform-react-jsx-self": "^7.0.0", + "@babel/plugin-transform-react-jsx-source": "^7.0.0", + "@babel/plugin-transform-runtime": "^7.0.0", + "@babel/plugin-transform-shorthand-properties": "^7.0.0", + "@babel/plugin-transform-spread": "^7.0.0", + "@babel/plugin-transform-sticky-regex": "^7.0.0", + "@babel/plugin-transform-typescript": "^7.5.0", + "@babel/plugin-transform-unicode-regex": "^7.0.0", + "@babel/template": "^7.0.0", + "babel-plugin-transform-flow-enums": "^0.0.2", + "react-refresh": "^0.4.0" }, - "bin": { - "getstorybook": "bin/index.js", - "sb": "bin/index.js" + "engines": { + "node": ">=18" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" + "peerDependencies": { + "@babel/core": "*" } }, - "node_modules/@storybook/cli/node_modules/agent-base": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-5.1.1.tgz", - "integrity": "sha512-TMeqbNl2fMW0nMjTEPOwe3J/PRFP4vqeoNuQMG0HlMrtm5QxKqdvAkZ1pRBQ/ulIyDD5Yq0nJ7YbdD8ey0TO3g==", + "node_modules/@react-native/babel-preset/node_modules/react-refresh": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.4.3.tgz", + "integrity": "sha512-Hwln1VNuGl/6bVwnd0Xdn1e84gT/8T9aYNL+HAKDArLCS7LWjwr7StE30IEYbIkx0Vi3vs+coQxe+SQDbGbbpA==", "dev": true, "engines": { - "node": ">= 6.0.0" + "node": ">=0.10.0" } }, - "node_modules/@storybook/cli/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, + "node_modules/@react-native/codegen": { + "version": "0.73.2", + "resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.73.2.tgz", + "integrity": "sha512-lfy8S7umhE3QLQG5ViC4wg5N1Z+E6RnaeIw8w1voroQsXXGPB72IBozh8dAHR3+ceTxIU0KX3A8OpJI8e1+HpQ==", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "@babel/parser": "^7.20.0", + "flow-parser": "^0.206.0", + "glob": "^7.1.1", + "invariant": "^2.2.4", + "jscodeshift": "^0.14.0", + "mkdirp": "^0.5.1", + "nullthrows": "^1.1.1" }, "engines": { - "node": ">=10" + "node": ">=18" }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "peerDependencies": { + "@babel/preset-env": "^7.1.6" } }, - "node_modules/@storybook/cli/node_modules/cli-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", - "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", - "dev": true, + "node_modules/@react-native/codegen/node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", "dependencies": { - "restore-cursor": "^3.1.0" + "minimist": "^1.2.6" }, - "engines": { - "node": ">=8" + "bin": { + "mkdirp": "bin/cmd.js" } }, - "node_modules/@storybook/cli/node_modules/commander": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", - "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", - "dev": true, + "node_modules/@react-native/community-cli-plugin": { + "version": "0.73.14", + "resolved": "https://registry.npmjs.org/@react-native/community-cli-plugin/-/community-cli-plugin-0.73.14.tgz", + "integrity": "sha512-KzIwsTvAJrXPtwhGOSm+OcJH1B8TpY8cS4xxzu/e2qv3a2n4VLePHTPAfco1tmvekV8OHWvvD9JSIX7i2fB1gg==", + "dependencies": { + "@react-native-community/cli-server-api": "12.3.2", + "@react-native-community/cli-tools": "12.3.2", + "@react-native/dev-middleware": "0.73.7", + "@react-native/metro-babel-transformer": "0.73.14", + "chalk": "^4.0.0", + "execa": "^5.1.1", + "metro": "^0.80.3", + "metro-config": "^0.80.3", + "metro-core": "^0.80.3", + "node-fetch": "^2.2.0", + "readline": "^1.3.0" + }, "engines": { - "node": ">= 6" + "node": ">=18" } }, - "node_modules/@storybook/cli/node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, + "node_modules/@react-native/community-cli-plugin/node_modules/@react-native/babel-preset": { + "version": "0.73.20", + "resolved": "https://registry.npmjs.org/@react-native/babel-preset/-/babel-preset-0.73.20.tgz", + "integrity": "sha512-fU9NqkusbfFq71l4BWQfqqD/lLcLC0MZ++UYgieA3j8lIEppJTLVauv2RwtD2yltBkjebgYEC5Rwvt1l0MUBXw==", "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" + "@babel/core": "^7.20.0", + "@babel/plugin-proposal-async-generator-functions": "^7.0.0", + "@babel/plugin-proposal-class-properties": "^7.18.0", + "@babel/plugin-proposal-export-default-from": "^7.0.0", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.0", + "@babel/plugin-proposal-numeric-separator": "^7.0.0", + "@babel/plugin-proposal-object-rest-spread": "^7.20.0", + "@babel/plugin-proposal-optional-catch-binding": "^7.0.0", + "@babel/plugin-proposal-optional-chaining": "^7.20.0", + "@babel/plugin-syntax-dynamic-import": "^7.8.0", + "@babel/plugin-syntax-export-default-from": "^7.0.0", + "@babel/plugin-syntax-flow": "^7.18.0", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.0.0", + "@babel/plugin-syntax-optional-chaining": "^7.0.0", + "@babel/plugin-transform-arrow-functions": "^7.0.0", + "@babel/plugin-transform-async-to-generator": "^7.20.0", + "@babel/plugin-transform-block-scoping": "^7.0.0", + "@babel/plugin-transform-classes": "^7.0.0", + "@babel/plugin-transform-computed-properties": "^7.0.0", + "@babel/plugin-transform-destructuring": "^7.20.0", + "@babel/plugin-transform-flow-strip-types": "^7.20.0", + "@babel/plugin-transform-function-name": "^7.0.0", + "@babel/plugin-transform-literals": "^7.0.0", + "@babel/plugin-transform-modules-commonjs": "^7.0.0", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.0.0", + "@babel/plugin-transform-parameters": "^7.0.0", + "@babel/plugin-transform-private-methods": "^7.22.5", + "@babel/plugin-transform-private-property-in-object": "^7.22.11", + "@babel/plugin-transform-react-display-name": "^7.0.0", + "@babel/plugin-transform-react-jsx": "^7.0.0", + "@babel/plugin-transform-react-jsx-self": "^7.0.0", + "@babel/plugin-transform-react-jsx-source": "^7.0.0", + "@babel/plugin-transform-runtime": "^7.0.0", + "@babel/plugin-transform-shorthand-properties": "^7.0.0", + "@babel/plugin-transform-spread": "^7.0.0", + "@babel/plugin-transform-sticky-regex": "^7.0.0", + "@babel/plugin-transform-typescript": "^7.5.0", + "@babel/plugin-transform-unicode-regex": "^7.0.0", + "@babel/template": "^7.0.0", + "@react-native/babel-plugin-codegen": "0.73.3", + "babel-plugin-transform-flow-enums": "^0.0.2", + "react-refresh": "^0.14.0" }, "engines": { - "node": ">= 8" + "node": ">=18" + }, + "peerDependencies": { + "@babel/core": "*" } }, - "node_modules/@storybook/cli/node_modules/detect-indent": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-6.1.0.tgz", - "integrity": "sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==", - "dev": true, + "node_modules/@react-native/community-cli-plugin/node_modules/@react-native/metro-babel-transformer": { + "version": "0.73.14", + "resolved": "https://registry.npmjs.org/@react-native/metro-babel-transformer/-/metro-babel-transformer-0.73.14.tgz", + "integrity": "sha512-5wLeYw/lormpSqYfI9H/geZ/EtPmi+x5qLkEit15Q/70hkzYo/M+aWztUtbOITfgTEOP8d6ybROzoGsqgyZLcw==", + "dependencies": { + "@babel/core": "^7.20.0", + "@react-native/babel-preset": "0.73.20", + "hermes-parser": "0.15.0", + "nullthrows": "^1.1.1" + }, "engines": { - "node": ">=8" + "node": ">=18" + }, + "peerDependencies": { + "@babel/core": "*" } }, - "node_modules/@storybook/cli/node_modules/execa": { + "node_modules/@react-native/community-cli-plugin/node_modules/execa": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "dev": true, "dependencies": { "cross-spawn": "^7.0.3", "get-stream": "^6.0.0", @@ -11643,15 +10843,10 @@ "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, - "node_modules/@storybook/cli/node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, + "node_modules/@react-native/community-cli-plugin/node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", "engines": { "node": ">=10" }, @@ -11659,1137 +10854,1195 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@storybook/cli/node_modules/fs-extra": { - "version": "11.2.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", - "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, + "node_modules/@react-native/community-cli-plugin/node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", "engines": { - "node": ">=14.14" + "node": ">=10.17.0" } }, - "node_modules/@storybook/cli/node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true, + "node_modules/@react-native/community-cli-plugin/node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", "engines": { - "node": ">=10" + "node": ">=8" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@storybook/cli/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, + "node_modules/@react-native/community-cli-plugin/node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": ">=6" } }, - "node_modules/@storybook/cli/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, + "node_modules/@react-native/community-cli-plugin/node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dependencies": { + "path-key": "^3.0.0" + }, "engines": { "node": ">=8" } }, - "node_modules/@storybook/cli/node_modules/https-proxy-agent": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-4.0.0.tgz", - "integrity": "sha512-zoDhWrkR3of1l9QAL8/scJZyLu8j/gBkcwcaQOZh7Gyh/+uJQzGVETdgT30akuwkpL8HTRfssqI3BZuV18teDg==", - "dev": true, + "node_modules/@react-native/community-cli-plugin/node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", "dependencies": { - "agent-base": "5", - "debug": "4" + "mimic-fn": "^2.1.0" }, "engines": { - "node": ">= 6.0.0" + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@storybook/cli/node_modules/human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "dev": true, + "node_modules/@react-native/community-cli-plugin/node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "engines": { - "node": ">=10.17.0" + "node": ">=8" } }, - "node_modules/@storybook/cli/node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true, + "node_modules/@react-native/debugger-frontend": { + "version": "0.73.3", + "resolved": "https://registry.npmjs.org/@react-native/debugger-frontend/-/debugger-frontend-0.73.3.tgz", + "integrity": "sha512-RgEKnWuoo54dh7gQhV7kvzKhXZEhpF9LlMdZolyhGxHsBqZ2gXdibfDlfcARFFifPIiaZ3lXuOVVa4ei+uPgTw==", "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=18" } }, - "node_modules/@storybook/cli/node_modules/jscodeshift": { - "version": "0.15.2", - "resolved": "https://registry.npmjs.org/jscodeshift/-/jscodeshift-0.15.2.tgz", - "integrity": "sha512-FquR7Okgmc4Sd0aEDwqho3rEiKR3BdvuG9jfdHjLJ6JQoWSMpavug3AoIfnfWhxFlf+5pzQh8qjqz0DWFrNQzA==", - "dev": true, + "node_modules/@react-native/dev-middleware": { + "version": "0.73.7", + "resolved": "https://registry.npmjs.org/@react-native/dev-middleware/-/dev-middleware-0.73.7.tgz", + "integrity": "sha512-BZXpn+qKp/dNdr4+TkZxXDttfx8YobDh8MFHsMk9usouLm22pKgFIPkGBV0X8Do4LBkFNPGtrnsKkWk/yuUXKg==", "dependencies": { - "@babel/core": "^7.23.0", - "@babel/parser": "^7.23.0", - "@babel/plugin-transform-class-properties": "^7.22.5", - "@babel/plugin-transform-modules-commonjs": "^7.23.0", - "@babel/plugin-transform-nullish-coalescing-operator": "^7.22.11", - "@babel/plugin-transform-optional-chaining": "^7.23.0", - "@babel/plugin-transform-private-methods": "^7.22.5", - "@babel/preset-flow": "^7.22.15", - "@babel/preset-typescript": "^7.23.0", - "@babel/register": "^7.22.15", - "babel-core": "^7.0.0-bridge.0", - "chalk": "^4.1.2", - "flow-parser": "0.*", - "graceful-fs": "^4.2.4", - "micromatch": "^4.0.4", - "neo-async": "^2.5.0", - "node-dir": "^0.1.17", - "recast": "^0.23.3", - "temp": "^0.8.4", - "write-file-atomic": "^2.3.0" - }, - "bin": { - "jscodeshift": "bin/jscodeshift.js" - }, - "peerDependencies": { - "@babel/preset-env": "^7.1.6" + "@isaacs/ttlcache": "^1.4.1", + "@react-native/debugger-frontend": "0.73.3", + "chrome-launcher": "^0.15.2", + "chromium-edge-launcher": "^1.0.0", + "connect": "^3.6.5", + "debug": "^2.2.0", + "node-fetch": "^2.2.0", + "open": "^7.0.3", + "serve-static": "^1.13.1", + "temp-dir": "^2.0.0" }, - "peerDependenciesMeta": { - "@babel/preset-env": { - "optional": true - } + "engines": { + "node": ">=18" } }, - "node_modules/@storybook/cli/node_modules/jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dev": true, + "node_modules/@react-native/dev-middleware/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" + "ms": "2.0.0" } }, - "node_modules/@storybook/cli/node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, + "node_modules/@react-native/dev-middleware/node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@react-native/dev-middleware/node_modules/open": { + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz", + "integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==", "dependencies": { - "p-locate": "^5.0.0" + "is-docker": "^2.0.0", + "is-wsl": "^2.1.1" }, "engines": { - "node": ">=10" + "node": ">=8" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@storybook/cli/node_modules/mime": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", - "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", - "dev": true, - "bin": { - "mime": "cli.js" - }, + "node_modules/@react-native/dev-middleware/node_modules/temp-dir": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-2.0.0.tgz", + "integrity": "sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg==", "engines": { - "node": ">=4.0.0" + "node": ">=8" } }, - "node_modules/@storybook/cli/node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true, + "node_modules/@react-native/gradle-plugin": { + "version": "0.73.4", + "resolved": "https://registry.npmjs.org/@react-native/gradle-plugin/-/gradle-plugin-0.73.4.tgz", + "integrity": "sha512-PMDnbsZa+tD55Ug+W8CfqXiGoGneSSyrBZCMb5JfiB3AFST3Uj5e6lw8SgI/B6SKZF7lG0BhZ6YHZsRZ5MlXmg==", "engines": { - "node": ">=6" + "node": ">=18" } }, - "node_modules/@storybook/cli/node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "dependencies": { - "path-key": "^3.0.0" - }, + "node_modules/@react-native/js-polyfills": { + "version": "0.73.1", + "resolved": "https://registry.npmjs.org/@react-native/js-polyfills/-/js-polyfills-0.73.1.tgz", + "integrity": "sha512-ewMwGcumrilnF87H4jjrnvGZEaPFCAC4ebraEK+CurDDmwST/bIicI4hrOAv+0Z0F7DEK4O4H7r8q9vH7IbN4g==", "engines": { - "node": ">=8" + "node": ">=18" } }, - "node_modules/@storybook/cli/node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "node_modules/@react-native/metro-babel-transformer": { + "version": "0.73.10", + "resolved": "https://registry.npmjs.org/@react-native/metro-babel-transformer/-/metro-babel-transformer-0.73.10.tgz", + "integrity": "sha512-g0q12s1VcrhI4ab9ZQzFHmvjkDW5PYdJd3LiyhIO3OAWLD7iNLWL5tJe9iA3n8rxbanmf1oauqlNY1UmkxhAvQ==", "dev": true, "dependencies": { - "mimic-fn": "^2.1.0" + "@babel/core": "^7.20.0", + "@react-native/babel-preset": "*", + "babel-preset-fbjs": "^3.4.0", + "hermes-parser": "0.15.0", + "nullthrows": "^1.1.1" }, "engines": { - "node": ">=6" + "node": ">=18" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peerDependencies": { + "@babel/core": "*" } }, - "node_modules/@storybook/cli/node_modules/ora": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", - "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", + "node_modules/@react-native/metro-config": { + "version": "0.73.4", + "resolved": "https://registry.npmjs.org/@react-native/metro-config/-/metro-config-0.73.4.tgz", + "integrity": "sha512-4IpWb9InOY23ssua6z/ho2B4uRqF4QaNHGg4aV3D/og5yiVF39GEm/REHU36i+KoHRO3GcB6DrI7N9KrcvgGBw==", "dev": true, "dependencies": { - "bl": "^4.1.0", - "chalk": "^4.1.0", - "cli-cursor": "^3.1.0", - "cli-spinners": "^2.5.0", - "is-interactive": "^1.0.0", - "is-unicode-supported": "^0.1.0", - "log-symbols": "^4.1.0", - "strip-ansi": "^6.0.0", - "wcwidth": "^1.0.1" + "@react-native/js-polyfills": "0.73.1", + "@react-native/metro-babel-transformer": "0.73.14", + "metro-config": "^0.80.3", + "metro-runtime": "^0.80.3" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=18" } }, - "node_modules/@storybook/cli/node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "node_modules/@react-native/metro-config/node_modules/@react-native/babel-preset": { + "version": "0.73.20", + "resolved": "https://registry.npmjs.org/@react-native/babel-preset/-/babel-preset-0.73.20.tgz", + "integrity": "sha512-fU9NqkusbfFq71l4BWQfqqD/lLcLC0MZ++UYgieA3j8lIEppJTLVauv2RwtD2yltBkjebgYEC5Rwvt1l0MUBXw==", "dev": true, "dependencies": { - "p-limit": "^3.0.2" + "@babel/core": "^7.20.0", + "@babel/plugin-proposal-async-generator-functions": "^7.0.0", + "@babel/plugin-proposal-class-properties": "^7.18.0", + "@babel/plugin-proposal-export-default-from": "^7.0.0", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.0", + "@babel/plugin-proposal-numeric-separator": "^7.0.0", + "@babel/plugin-proposal-object-rest-spread": "^7.20.0", + "@babel/plugin-proposal-optional-catch-binding": "^7.0.0", + "@babel/plugin-proposal-optional-chaining": "^7.20.0", + "@babel/plugin-syntax-dynamic-import": "^7.8.0", + "@babel/plugin-syntax-export-default-from": "^7.0.0", + "@babel/plugin-syntax-flow": "^7.18.0", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.0.0", + "@babel/plugin-syntax-optional-chaining": "^7.0.0", + "@babel/plugin-transform-arrow-functions": "^7.0.0", + "@babel/plugin-transform-async-to-generator": "^7.20.0", + "@babel/plugin-transform-block-scoping": "^7.0.0", + "@babel/plugin-transform-classes": "^7.0.0", + "@babel/plugin-transform-computed-properties": "^7.0.0", + "@babel/plugin-transform-destructuring": "^7.20.0", + "@babel/plugin-transform-flow-strip-types": "^7.20.0", + "@babel/plugin-transform-function-name": "^7.0.0", + "@babel/plugin-transform-literals": "^7.0.0", + "@babel/plugin-transform-modules-commonjs": "^7.0.0", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.0.0", + "@babel/plugin-transform-parameters": "^7.0.0", + "@babel/plugin-transform-private-methods": "^7.22.5", + "@babel/plugin-transform-private-property-in-object": "^7.22.11", + "@babel/plugin-transform-react-display-name": "^7.0.0", + "@babel/plugin-transform-react-jsx": "^7.0.0", + "@babel/plugin-transform-react-jsx-self": "^7.0.0", + "@babel/plugin-transform-react-jsx-source": "^7.0.0", + "@babel/plugin-transform-runtime": "^7.0.0", + "@babel/plugin-transform-shorthand-properties": "^7.0.0", + "@babel/plugin-transform-spread": "^7.0.0", + "@babel/plugin-transform-sticky-regex": "^7.0.0", + "@babel/plugin-transform-typescript": "^7.5.0", + "@babel/plugin-transform-unicode-regex": "^7.0.0", + "@babel/template": "^7.0.0", + "@react-native/babel-plugin-codegen": "0.73.3", + "babel-plugin-transform-flow-enums": "^0.0.2", + "react-refresh": "^0.14.0" }, "engines": { - "node": ">=10" + "node": ">=18" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peerDependencies": { + "@babel/core": "*" } }, - "node_modules/@storybook/cli/node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "node_modules/@react-native/metro-config/node_modules/@react-native/metro-babel-transformer": { + "version": "0.73.14", + "resolved": "https://registry.npmjs.org/@react-native/metro-babel-transformer/-/metro-babel-transformer-0.73.14.tgz", + "integrity": "sha512-5wLeYw/lormpSqYfI9H/geZ/EtPmi+x5qLkEit15Q/70hkzYo/M+aWztUtbOITfgTEOP8d6ybROzoGsqgyZLcw==", "dev": true, + "dependencies": { + "@babel/core": "^7.20.0", + "@react-native/babel-preset": "0.73.20", + "hermes-parser": "0.15.0", + "nullthrows": "^1.1.1" + }, "engines": { - "node": ">=8" + "node": ">=18" + }, + "peerDependencies": { + "@babel/core": "*" } }, - "node_modules/@storybook/cli/node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "engines": { - "node": ">=8" - } + "node_modules/@react-native/normalize-color": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@react-native/normalize-color/-/normalize-color-2.1.0.tgz", + "integrity": "sha512-Z1jQI2NpdFJCVgpY+8Dq/Bt3d+YUi1928Q+/CZm/oh66fzM0RUl54vvuXlPJKybH4pdCZey1eDTPaLHkMPNgWA==" }, - "node_modules/@storybook/cli/node_modules/prettier": { - "version": "2.8.8", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", - "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", - "dev": true, - "bin": { - "prettier": "bin-prettier.js" + "node_modules/@react-native/normalize-colors": { + "version": "0.73.2", + "resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.73.2.tgz", + "integrity": "sha512-bRBcb2T+I88aG74LMVHaKms2p/T8aQd8+BZ7LuuzXlRfog1bMWWn/C5i0HVuvW4RPtXQYgIlGiXVDy9Ir1So/w==" + }, + "node_modules/@react-native/virtualized-lists": { + "version": "0.73.4", + "resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.73.4.tgz", + "integrity": "sha512-HpmLg1FrEiDtrtAbXiwCgXFYyloK/dOIPIuWW3fsqukwJEWAiTzm1nXGJ7xPU5XTHiWZ4sKup5Ebaj8z7iyWog==", + "dependencies": { + "invariant": "^2.2.4", + "nullthrows": "^1.1.1" }, "engines": { - "node": ">=10.13.0" + "node": ">=18" }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" + "peerDependencies": { + "react-native": "*" } }, - "node_modules/@storybook/cli/node_modules/puppeteer-core": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-2.1.1.tgz", - "integrity": "sha512-n13AWriBMPYxnpbb6bnaY5YoY6rGj8vPLrz6CZF3o0qJNEwlcfJVxBzYZ0NJsQ21UbdJoijPCDrM++SUVEz7+w==", - "dev": true, + "node_modules/@react-navigation/core": { + "version": "5.12.0", + "resolved": "https://registry.npmjs.org/@react-navigation/core/-/core-5.12.0.tgz", + "integrity": "sha512-CTmYrFXCZwInN40CpEzkPxhrpzujj20qvsUgpH05+oO1flwsnaJsyBfYawIcTS62/1/Z6SAM7iW5PbKk+qw9iQ==", "dependencies": { - "@types/mime-types": "^2.1.0", - "debug": "^4.1.0", - "extract-zip": "^1.6.6", - "https-proxy-agent": "^4.0.0", - "mime": "^2.0.3", - "mime-types": "^2.1.25", - "progress": "^2.0.1", - "proxy-from-env": "^1.0.0", - "rimraf": "^2.6.1", - "ws": "^6.1.0" + "@react-navigation/routers": "^5.4.9", + "escape-string-regexp": "^4.0.0", + "nanoid": "^3.1.9", + "query-string": "^6.13.1", + "react-is": "^16.13.0", + "use-subscription": "^1.4.0" }, - "engines": { - "node": ">=8.16.0" + "peerDependencies": { + "react": "*" } }, - "node_modules/@storybook/cli/node_modules/recast": { - "version": "0.23.9", - "resolved": "https://registry.npmjs.org/recast/-/recast-0.23.9.tgz", - "integrity": "sha512-Hx/BGIbwj+Des3+xy5uAtAbdCyqK9y9wbBcDFDYanLS9JnMqf7OeF87HQwUimE87OEc72mr6tkKUKMBBL+hF9Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "ast-types": "^0.16.1", - "esprima": "~4.0.0", - "source-map": "~0.6.1", - "tiny-invariant": "^1.3.3", - "tslib": "^2.0.1" - }, + "node_modules/@react-navigation/core/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "engines": { - "node": ">= 4" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@storybook/cli/node_modules/restore-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", - "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", - "dev": true, - "dependencies": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" - }, - "engines": { - "node": ">=8" + "node_modules/@react-navigation/elements": { + "version": "1.3.18", + "resolved": "https://registry.npmjs.org/@react-navigation/elements/-/elements-1.3.18.tgz", + "integrity": "sha512-/0hwnJkrr415yP0Hf4PjUKgGyfshrvNUKFXN85Mrt1gY49hy9IwxZgrrxlh0THXkPeq8q4VWw44eHDfAcQf20Q==", + "peerDependencies": { + "@react-navigation/native": "^6.0.0", + "react": "*", + "react-native": "*", + "react-native-safe-area-context": ">= 3.0.0" } }, - "node_modules/@storybook/cli/node_modules/rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "dev": true, + "node_modules/@react-navigation/native": { + "version": "6.0.14", + "resolved": "https://registry.npmjs.org/@react-navigation/native/-/native-6.0.14.tgz", + "integrity": "sha512-Z95bJrRkZerBJq6Qc/xjA/kibPpB+UvPeMWS1CBhRF8FaX1483UdHqPqSbW879tPwjP2R4XfoA4dtoEHswrOjA==", "dependencies": { - "glob": "^7.1.3" + "@react-navigation/core": "^6.4.1", + "escape-string-regexp": "^4.0.0", + "fast-deep-equal": "^3.1.3", + "nanoid": "^3.1.23" }, - "bin": { - "rimraf": "bin.js" + "peerDependencies": { + "react": "*", + "react-native": "*" } }, - "node_modules/@storybook/cli/node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, + "node_modules/@react-navigation/native/node_modules/@react-navigation/core": { + "version": "6.4.17", + "resolved": "https://registry.npmjs.org/@react-navigation/core/-/core-6.4.17.tgz", + "integrity": "sha512-Nd76EpomzChWAosGqWOYE3ItayhDzIEzzZsT7PfGcRFDgW5miHV2t4MZcq9YIK4tzxZjVVpYbIynOOQQd1e0Cg==", + "license": "MIT", "dependencies": { - "shebang-regex": "^3.0.0" + "@react-navigation/routers": "^6.1.9", + "escape-string-regexp": "^4.0.0", + "nanoid": "^3.1.23", + "query-string": "^7.1.3", + "react-is": "^16.13.0", + "use-latest-callback": "^0.2.1" }, - "engines": { - "node": ">=8" + "peerDependencies": { + "react": "*" } }, - "node_modules/@storybook/cli/node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "engines": { - "node": ">=8" + "node_modules/@react-navigation/native/node_modules/@react-navigation/routers": { + "version": "6.1.9", + "resolved": "https://registry.npmjs.org/@react-navigation/routers/-/routers-6.1.9.tgz", + "integrity": "sha512-lTM8gSFHSfkJvQkxacGM6VJtBt61ip2XO54aNfswD+KMw6eeZ4oehl7m0me3CR9hnDE4+60iAZR8sAhvCiI3NA==", + "dependencies": { + "nanoid": "^3.1.23" } }, - "node_modules/@storybook/cli/node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, + "node_modules/@react-navigation/native/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "engines": { - "node": ">=8" + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@storybook/cli/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, + "node_modules/@react-navigation/native/node_modules/query-string": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-7.1.3.tgz", + "integrity": "sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg==", "dependencies": { - "has-flag": "^4.0.0" + "decode-uri-component": "^0.2.2", + "filter-obj": "^1.1.0", + "split-on-first": "^1.0.0", + "strict-uri-encode": "^2.0.0" }, "engines": { - "node": ">=8" - } - }, - "node_modules/@storybook/cli/node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "dev": true, - "engines": { - "node": ">= 10.0.0" + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@storybook/cli/node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, + "node_modules/@react-navigation/routers": { + "version": "5.4.9", + "resolved": "https://registry.npmjs.org/@react-navigation/routers/-/routers-5.4.9.tgz", + "integrity": "sha512-dYD5qrIKUmuBEp+O98hB0tDYpEsGQgCQFQgMEoFKBmVVhx2JnJJ1zxRjU7xWcCU4VdBA8IOowgHQHJsVNKYyrg==", "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" + "nanoid": "^3.1.9" } }, - "node_modules/@storybook/cli/node_modules/ws": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.3.tgz", - "integrity": "sha512-jmTjYU0j60B+vHey6TfR3Z7RD61z/hmxBS3VMSGIrroOWXQEneK1zNuotOUrGyBHQj0yrpsLHPWtigEFd13ndA==", - "dev": true, - "license": "MIT", + "node_modules/@react-navigation/stack": { + "version": "6.3.5", + "resolved": "https://registry.npmjs.org/@react-navigation/stack/-/stack-6.3.5.tgz", + "integrity": "sha512-G706Ow+8fhiLT5Cf48566YsjboU5h5ll9nva90xX3br7V6x0JMoGMvXjvbORZyzLuUytQO3mf0T3nVUuO52vZQ==", "dependencies": { - "async-limiter": "~1.0.0" + "@react-navigation/elements": "^1.3.7", + "color": "^4.2.3", + "warn-once": "^0.1.0" + }, + "peerDependencies": { + "@react-navigation/native": "^6.0.0", + "react": "*", + "react-native": "*", + "react-native-gesture-handler": ">= 1.0.0", + "react-native-safe-area-context": ">= 3.0.0", + "react-native-screens": ">= 3.0.0" } }, - "node_modules/@storybook/client-logger": { - "version": "7.6.15", - "resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-7.6.15.tgz", - "integrity": "sha512-n+K8IqnombqiQNnywVovS+lK61tvv/XSfgPt0cgvoF/hJZB0VDOMRjWsV+v9qQpj1TQEl1lLWeJwZMthTWupJA==", - "dev": true, + "node_modules/@react-spring/animated": { + "version": "9.5.5", + "resolved": "https://registry.npmjs.org/@react-spring/animated/-/animated-9.5.5.tgz", + "integrity": "sha512-glzViz7syQ3CE6BQOwAyr75cgh0qsihm5lkaf24I0DfU63cMm/3+br299UEYkuaHNmfDfM414uktiPlZCNJbQA==", "dependencies": { - "@storybook/global": "^5.0.0" + "@react-spring/shared": "~9.5.5", + "@react-spring/types": "~9.5.5" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" } }, - "node_modules/@storybook/codemod": { - "version": "7.6.15", - "resolved": "https://registry.npmjs.org/@storybook/codemod/-/codemod-7.6.15.tgz", - "integrity": "sha512-NiEbTLCdacj6TMxC7G49IImXeMzkG8wpPr8Ayxm9HeG6q5UkiF5/DiZdqbJm2zaosOsOKWwvXg1t6Pq6Nivytg==", - "dev": true, + "node_modules/@react-spring/core": { + "version": "9.5.5", + "resolved": "https://registry.npmjs.org/@react-spring/core/-/core-9.5.5.tgz", + "integrity": "sha512-shaJYb3iX18Au6gkk8ahaF0qx0LpS0Yd+ajb4asBaAQf6WPGuEdJsbsNSgei1/O13JyEATsJl20lkjeslJPMYA==", "dependencies": { - "@babel/core": "^7.23.2", - "@babel/preset-env": "^7.23.2", - "@babel/types": "^7.23.0", - "@storybook/csf": "^0.1.2", - "@storybook/csf-tools": "7.6.15", - "@storybook/node-logger": "7.6.15", - "@storybook/types": "7.6.15", - "@types/cross-spawn": "^6.0.2", - "cross-spawn": "^7.0.3", - "globby": "^11.0.2", - "jscodeshift": "^0.15.1", - "lodash": "^4.17.21", - "prettier": "^2.8.0", - "recast": "^0.23.1" + "@react-spring/animated": "~9.5.5", + "@react-spring/rafz": "~9.5.5", + "@react-spring/shared": "~9.5.5", + "@react-spring/types": "~9.5.5" }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/storybook" + "url": "https://opencollective.com/react-spring/donate" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" } }, - "node_modules/@storybook/codemod/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, + "node_modules/@react-spring/rafz": { + "version": "9.5.5", + "resolved": "https://registry.npmjs.org/@react-spring/rafz/-/rafz-9.5.5.tgz", + "integrity": "sha512-F/CLwB0d10jL6My5vgzRQxCNY2RNyDJZedRBK7FsngdCmzoq3V4OqqNc/9voJb9qRC2wd55oGXUeXv2eIaFmsw==" + }, + "node_modules/@react-spring/shared": { + "version": "9.5.5", + "resolved": "https://registry.npmjs.org/@react-spring/shared/-/shared-9.5.5.tgz", + "integrity": "sha512-YwW70Pa/YXPOwTutExHZmMQSHcNC90kJOnNR4G4mCDNV99hE98jWkIPDOsgqbYx3amIglcFPiYKMaQuGdr8dyQ==", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" + "@react-spring/rafz": "~9.5.5", + "@react-spring/types": "~9.5.5" }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" } }, - "node_modules/@storybook/codemod/node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, + "node_modules/@react-spring/types": { + "version": "9.5.5", + "resolved": "https://registry.npmjs.org/@react-spring/types/-/types-9.5.5.tgz", + "integrity": "sha512-7I/qY8H7Enwasxr4jU6WmtNK+RZ4Z/XvSlDvjXFVe7ii1x0MoSlkw6pD7xuac8qrHQRm9BTcbZNyeeKApYsvCg==" + }, + "node_modules/@react-spring/web": { + "version": "9.5.5", + "resolved": "https://registry.npmjs.org/@react-spring/web/-/web-9.5.5.tgz", + "integrity": "sha512-+moT8aDX/ho/XAhU+HRY9m0LVV9y9CK6NjSRaI+30Re150pB3iEip6QfnF4qnhSCQ5drpMF0XRXHgOTY/xbtFw==", "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" + "@react-spring/animated": "~9.5.5", + "@react-spring/core": "~9.5.5", + "@react-spring/shared": "~9.5.5", + "@react-spring/types": "~9.5.5" }, - "engines": { - "node": ">= 8" + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" } }, - "node_modules/@storybook/codemod/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } + "node_modules/@remote-ui/rpc": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/@remote-ui/rpc/-/rpc-1.4.5.tgz", + "integrity": "sha512-Cr+06niG/vmE4A9YsmaKngRuuVSWKMY42NMwtZfy+gctRWGu6Wj9BWuMJg5CEp+JTkRBPToqT5rqnrg1G/Wvow==", + "license": "MIT" }, - "node_modules/@storybook/codemod/node_modules/jscodeshift": { - "version": "0.15.2", - "resolved": "https://registry.npmjs.org/jscodeshift/-/jscodeshift-0.15.2.tgz", - "integrity": "sha512-FquR7Okgmc4Sd0aEDwqho3rEiKR3BdvuG9jfdHjLJ6JQoWSMpavug3AoIfnfWhxFlf+5pzQh8qjqz0DWFrNQzA==", + "node_modules/@samverschueren/stream-to-observable": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@samverschueren/stream-to-observable/-/stream-to-observable-0.3.1.tgz", + "integrity": "sha512-c/qwwcHyafOQuVQJj0IlBjf5yYgBI7YPJ77k4fOJYesb41jio65eaJODRUmfYKhTOFBrIZ66kgvGPlNbjuoRdQ==", "dev": true, "dependencies": { - "@babel/core": "^7.23.0", - "@babel/parser": "^7.23.0", - "@babel/plugin-transform-class-properties": "^7.22.5", - "@babel/plugin-transform-modules-commonjs": "^7.23.0", - "@babel/plugin-transform-nullish-coalescing-operator": "^7.22.11", - "@babel/plugin-transform-optional-chaining": "^7.23.0", - "@babel/plugin-transform-private-methods": "^7.22.5", - "@babel/preset-flow": "^7.22.15", - "@babel/preset-typescript": "^7.23.0", - "@babel/register": "^7.22.15", - "babel-core": "^7.0.0-bridge.0", - "chalk": "^4.1.2", - "flow-parser": "0.*", - "graceful-fs": "^4.2.4", - "micromatch": "^4.0.4", - "neo-async": "^2.5.0", - "node-dir": "^0.1.17", - "recast": "^0.23.3", - "temp": "^0.8.4", - "write-file-atomic": "^2.3.0" - }, - "bin": { - "jscodeshift": "bin/jscodeshift.js" + "any-observable": "^0.3.0" }, - "peerDependencies": { - "@babel/preset-env": "^7.1.6" + "engines": { + "node": ">=6" }, "peerDependenciesMeta": { - "@babel/preset-env": { + "rxjs": { + "optional": true + }, + "zen-observable": { "optional": true } } }, - "node_modules/@storybook/codemod/node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, + "node_modules/@sentry-internal/tracing": { + "version": "7.120.1", + "resolved": "https://registry.npmjs.org/@sentry-internal/tracing/-/tracing-7.120.1.tgz", + "integrity": "sha512-MwZlhQY27oM4V05m2Q46WB2F7jqFu8fewg14yRcjCuK3tdxvQoLsXOEPMZxLxpoXPTqPCm3Ig7mA4GwdlCL41w==", + "license": "MIT", + "dependencies": { + "@sentry/core": "7.120.1", + "@sentry/types": "7.120.1", + "@sentry/utils": "7.120.1" + }, "engines": { "node": ">=8" } }, - "node_modules/@storybook/codemod/node_modules/prettier": { - "version": "2.8.8", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", - "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", - "dev": true, - "bin": { - "prettier": "bin-prettier.js" + "node_modules/@sentry/core": { + "version": "7.120.1", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.120.1.tgz", + "integrity": "sha512-tXpJlf/8ngsSCpcRD+4DDvh4TqUbY0MlvE9Mpc/jO5GgYl/goAH2H1COw6W/UNfkr/l80P2jejS0HLPk0moi0A==", + "license": "MIT", + "dependencies": { + "@sentry/types": "7.120.1", + "@sentry/utils": "7.120.1" }, "engines": { - "node": ">=10.13.0" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" + "node": ">=8" } }, - "node_modules/@storybook/codemod/node_modules/recast": { - "version": "0.23.9", - "resolved": "https://registry.npmjs.org/recast/-/recast-0.23.9.tgz", - "integrity": "sha512-Hx/BGIbwj+Des3+xy5uAtAbdCyqK9y9wbBcDFDYanLS9JnMqf7OeF87HQwUimE87OEc72mr6tkKUKMBBL+hF9Q==", - "dev": true, + "node_modules/@sentry/integrations": { + "version": "7.120.1", + "resolved": "https://registry.npmjs.org/@sentry/integrations/-/integrations-7.120.1.tgz", + "integrity": "sha512-dshhLZUN+pYpyZiS5QRYKaYSqvWYtmsbwmBlH4SCGOnN9sbY4nZn0h8njr+xKT8UFnPxoTlbZmkcrVY3qPVMfg==", "license": "MIT", - "dependencies": { - "ast-types": "^0.16.1", - "esprima": "~4.0.0", - "source-map": "~0.6.1", - "tiny-invariant": "^1.3.3", - "tslib": "^2.0.1" + "dependencies": { + "@sentry/core": "7.120.1", + "@sentry/types": "7.120.1", + "@sentry/utils": "7.120.1", + "localforage": "^1.8.1" }, "engines": { - "node": ">= 4" + "node": ">=8" } }, - "node_modules/@storybook/codemod/node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, + "node_modules/@sentry/node": { + "version": "7.120.1", + "resolved": "https://registry.npmjs.org/@sentry/node/-/node-7.120.1.tgz", + "integrity": "sha512-YF/TDUCtUOQeUMwL4vcUWGNv/8Qz9624xBnaL8nXW888xNBoSRr2vH/zMrmTup5zfmWAh9lVbp98BZFF6F0WJg==", + "license": "MIT", "dependencies": { - "shebang-regex": "^3.0.0" + "@sentry-internal/tracing": "7.120.1", + "@sentry/core": "7.120.1", + "@sentry/integrations": "7.120.1", + "@sentry/types": "7.120.1", + "@sentry/utils": "7.120.1" }, "engines": { "node": ">=8" } }, - "node_modules/@storybook/codemod/node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, + "node_modules/@sentry/types": { + "version": "7.120.1", + "resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.120.1.tgz", + "integrity": "sha512-f/WT7YUH8SA2Jhez/hYz/dA351AJqr1Eht/URUdYsqMFecXr/blAcNKRVFccSsvQeTqWVV9HVQ9BXUSjPJOvFA==", + "license": "MIT", "engines": { "node": ">=8" } }, - "node_modules/@storybook/codemod/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, + "node_modules/@sentry/utils": { + "version": "7.120.1", + "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.120.1.tgz", + "integrity": "sha512-4boeo5Y3zw3gFrWZmPHsYOIlTh//eBaGBgWL25FqLbLObO23gFE86G6O6knP1Gamm1DGX2IWH7w4MChYuBm6tA==", + "license": "MIT", "dependencies": { - "has-flag": "^4.0.0" + "@sentry/types": "7.120.1" }, "engines": { "node": ">=8" } }, - "node_modules/@storybook/codemod/node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, + "node_modules/@shopify/web-worker": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/@shopify/web-worker/-/web-worker-6.4.0.tgz", + "integrity": "sha512-RvY1mgRyAqawFiYBvsBkek2pVK4GVpV9mmhWFCZXwx01usxXd2HMhKNTFeRYhSp29uoUcfBlKZAwCwQzt826tg==", + "license": "MIT", "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" + "@remote-ui/rpc": "^1.2.5" }, "engines": { - "node": ">= 8" + "node": ">=18.12.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0", + "webpack": "^5.38.0", + "webpack-virtual-modules": "^0.4.3 || ^0.5.0 || ^0.6.0" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "webpack": { + "optional": true + }, + "webpack-virtual-modules": { + "optional": true + } } }, - "node_modules/@storybook/components": { - "version": "7.6.15", - "resolved": "https://registry.npmjs.org/@storybook/components/-/components-7.6.15.tgz", - "integrity": "sha512-xD+maP7+C9HeZXi2vJ+uK9hXN4S4spP4uDj9pyZ9yViKb+ztEO6WpovUMT8WRQ0mMegWyLXkx3zqu43hZvXM1g==", + "node_modules/@sideway/address": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", + "integrity": "sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==", + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, + "node_modules/@sideway/formula": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz", + "integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==" + }, + "node_modules/@sideway/pinpoint": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", + "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==" + }, + "node_modules/@sidvind/better-ajv-errors": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@sidvind/better-ajv-errors/-/better-ajv-errors-2.1.0.tgz", + "integrity": "sha512-JuIb009FhHuL9priFBho2kv7QmZOydj0LgYvj+h1t0mMCmhM/YmQNRlJR5wVtBZya6wrVFK5Hi5TIbv5BKEx7w==", "dev": true, "dependencies": { - "@radix-ui/react-select": "^1.2.2", - "@radix-ui/react-toolbar": "^1.0.4", - "@storybook/client-logger": "7.6.15", - "@storybook/csf": "^0.1.2", - "@storybook/global": "^5.0.0", - "@storybook/theming": "7.6.15", - "@storybook/types": "7.6.15", - "memoizerific": "^1.11.3", - "use-resize-observer": "^9.1.0", - "util-deprecate": "^1.0.2" + "@babel/code-frame": "^7.16.0", + "chalk": "^4.1.0" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" + "engines": { + "node": ">= 14.0.0" }, "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + "ajv": "4.11.8 - 8" } }, - "node_modules/@storybook/core-client": { - "version": "7.6.15", - "resolved": "https://registry.npmjs.org/@storybook/core-client/-/core-client-7.6.15.tgz", - "integrity": "sha512-jwWol+zo+ItKBzPm9i80bEL6seHMsV0wKSaViVMQ4TqHtEbNeFE8sFEc2NTr18VNBnQOdlQPnEWmdboXBUrGcA==", + "node_modules/@sigstore/bundle": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/@sigstore/bundle/-/bundle-2.3.2.tgz", + "integrity": "sha512-wueKWDk70QixNLB363yHc2D2ItTgYiMTdPwK8D9dKQMR3ZQ0c35IxP5xnwQ8cNLoCgCRcHf14kE+CLIvNX1zmA==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@storybook/client-logger": "7.6.15", - "@storybook/preview-api": "7.6.15" + "@sigstore/protobuf-specs": "^0.3.2" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" + "engines": { + "node": "^16.14.0 || >=18.0.0" } }, - "node_modules/@storybook/core-common": { - "version": "7.6.15", - "resolved": "https://registry.npmjs.org/@storybook/core-common/-/core-common-7.6.15.tgz", - "integrity": "sha512-VGmcLJ5U1r1s8/YnLbKcyB4GnNL+/sZIPqwlcSKzDXO76HoVFv1kywf7PbASote7P3gdhLSxBdg95LH2bdIbmw==", + "node_modules/@sigstore/core": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@sigstore/core/-/core-1.1.0.tgz", + "integrity": "sha512-JzBqdVIyqm2FRQCulY6nbQzMpJJpSiJ8XXWMhtOX9eKgaXXpfNOF53lzQEjIydlStnd/eFtuC1dW4VYdD93oRg==", "dev": true, - "dependencies": { - "@storybook/core-events": "7.6.15", - "@storybook/node-logger": "7.6.15", - "@storybook/types": "7.6.15", - "@types/find-cache-dir": "^3.2.1", - "@types/node": "^18.0.0", - "@types/node-fetch": "^2.6.4", - "@types/pretty-hrtime": "^1.0.0", - "chalk": "^4.1.0", - "esbuild": "^0.18.0", - "esbuild-register": "^3.5.0", - "file-system-cache": "2.3.0", - "find-cache-dir": "^3.0.0", - "find-up": "^5.0.0", - "fs-extra": "^11.1.0", - "glob": "^10.0.0", - "handlebars": "^4.7.7", - "lazy-universal-dotenv": "^4.0.0", - "node-fetch": "^2.0.0", - "picomatch": "^2.3.0", - "pkg-dir": "^5.0.0", - "pretty-hrtime": "^1.0.3", - "resolve-from": "^5.0.0", - "ts-dedent": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" + "license": "Apache-2.0", + "engines": { + "node": "^16.14.0 || >=18.0.0" } }, - "node_modules/@storybook/core-common/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "node_modules/@sigstore/protobuf-specs": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@sigstore/protobuf-specs/-/protobuf-specs-0.3.2.tgz", + "integrity": "sha512-c6B0ehIWxMI8wiS/bj6rHMPqeFvngFV7cDU/MY+B16P9Z3Mp9k8L93eYZ7BYzSickzuqAQqAq0V956b3Ju6mLw==", "dev": true, - "dependencies": { - "balanced-match": "^1.0.0" + "license": "Apache-2.0", + "engines": { + "node": "^16.14.0 || >=18.0.0" } }, - "node_modules/@storybook/core-common/node_modules/find-cache-dir": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", - "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", + "node_modules/@sigstore/sign": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/@sigstore/sign/-/sign-2.3.2.tgz", + "integrity": "sha512-5Vz5dPVuunIIvC5vBb0APwo7qKA4G9yM48kPWJT+OEERs40md5GoUR1yedwpekWZ4m0Hhw44m6zU+ObsON+iDA==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "commondir": "^1.0.1", - "make-dir": "^3.0.2", - "pkg-dir": "^4.1.0" + "@sigstore/bundle": "^2.3.2", + "@sigstore/core": "^1.0.0", + "@sigstore/protobuf-specs": "^0.3.2", + "make-fetch-happen": "^13.0.1", + "proc-log": "^4.2.0", + "promise-retry": "^2.0.1" }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/avajs/find-cache-dir?sponsor=1" + "node": "^16.14.0 || >=18.0.0" } }, - "node_modules/@storybook/core-common/node_modules/find-cache-dir/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "node_modules/@sigstore/tuf": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/@sigstore/tuf/-/tuf-2.3.4.tgz", + "integrity": "sha512-44vtsveTPUpqhm9NCrbU8CWLe3Vck2HO1PNLw7RIajbB7xhtn5RBPm1VNSCMwqGYHhDsBJG8gDF0q4lgydsJvw==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" + "@sigstore/protobuf-specs": "^0.3.2", + "tuf-js": "^2.2.1" }, "engines": { - "node": ">=8" + "node": "^16.14.0 || >=18.0.0" } }, - "node_modules/@storybook/core-common/node_modules/find-cache-dir/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "node_modules/@sigstore/verify": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@sigstore/verify/-/verify-1.2.1.tgz", + "integrity": "sha512-8iKx79/F73DKbGfRf7+t4dqrc0bRr0thdPrxAtCKWRm/F0tG71i6O1rvlnScncJLLBZHn3h8M3c1BSUAb9yu8g==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "p-locate": "^4.1.0" + "@sigstore/bundle": "^2.3.2", + "@sigstore/core": "^1.1.0", + "@sigstore/protobuf-specs": "^0.3.2" }, "engines": { - "node": ">=8" + "node": "^16.14.0 || >=18.0.0" } }, - "node_modules/@storybook/core-common/node_modules/find-cache-dir/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "dependencies": { - "p-try": "^2.0.0" - }, + "node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==" + }, + "node_modules/@sindresorhus/is": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", + "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", "engines": { - "node": ">=6" + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sindresorhus/is?sponsor=1" } }, - "node_modules/@storybook/core-common/node_modules/find-cache-dir/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, + "node_modules/@sinonjs/commons": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.0.tgz", + "integrity": "sha512-jXBtWAF4vmdNmZgD5FoKsVLv3rPgDnLgPbU84LIJ3otV44vJlDRokVng5v8NFJdCf/da9legHcKaRuZs4L7faA==", "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" + "type-detect": "4.0.8" } }, - "node_modules/@storybook/core-common/node_modules/find-cache-dir/node_modules/pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, + "node_modules/@sinonjs/fake-timers": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", "dependencies": { - "find-up": "^4.0.0" - }, - "engines": { - "node": ">=8" + "@sinonjs/commons": "^3.0.0" } }, - "node_modules/@storybook/core-common/node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "node_modules/@sliphua/lilconfig-ts-loader": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@sliphua/lilconfig-ts-loader/-/lilconfig-ts-loader-3.2.2.tgz", + "integrity": "sha512-nX2aBwAykiG50fSUzK9eyA5UvWcrEKzA0ZzCq9mLwHMwpKxM+U05YH8PHba1LJrbeZ7R1HSjJagWKMqFyq8cxw==", "dev": true, "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" + "lodash.get": "^4", + "make-error": "^1", + "ts-node": "^9", + "tslib": "^2" }, "engines": { - "node": ">=10" + "node": ">=10.0.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peerDependencies": { + "lilconfig": ">=2" } }, - "node_modules/@storybook/core-common/node_modules/fs-extra": { - "version": "11.2.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", - "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==", + "node_modules/@storybook/addon-a11y": { + "version": "8.4.7", + "resolved": "https://registry.npmjs.org/@storybook/addon-a11y/-/addon-a11y-8.4.7.tgz", + "integrity": "sha512-GpUvXp6n25U1ZSv+hmDC+05BEqxWdlWjQTb/GaboRXZQeMBlze6zckpVb66spjmmtQAIISo0eZxX1+mGcVR7lA==", "dev": true, "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" + "@storybook/addon-highlight": "8.4.7", + "axe-core": "^4.2.0" }, - "engines": { - "node": ">=14.14" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "storybook": "^8.4.7" } }, - "node_modules/@storybook/core-common/node_modules/glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "node_modules/@storybook/addon-actions": { + "version": "8.4.7", + "resolved": "https://registry.npmjs.org/@storybook/addon-actions/-/addon-actions-8.4.7.tgz", + "integrity": "sha512-mjtD5JxcPuW74T6h7nqMxWTvDneFtokg88p6kQ5OnC1M259iAXb//yiSZgu/quunMHPCXSiqn4FNOSgASTSbsA==", "dev": true, - "license": "ISC", "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" + "@storybook/global": "^5.0.0", + "@types/uuid": "^9.0.1", + "dequal": "^2.0.2", + "polished": "^4.2.2", + "uuid": "^9.0.0" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "storybook": "^8.4.7" } }, - "node_modules/@storybook/core-common/node_modules/jackspeak": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", - "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "node_modules/@storybook/addon-actions/node_modules/@types/uuid": { + "version": "9.0.8", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.8.tgz", + "integrity": "sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==", + "dev": true + }, + "node_modules/@storybook/addon-controls": { + "version": "8.4.7", + "resolved": "https://registry.npmjs.org/@storybook/addon-controls/-/addon-controls-8.4.7.tgz", + "integrity": "sha512-377uo5IsJgXLnQLJixa47+11V+7Wn9KcDEw+96aGCBCfLbWNH8S08tJHHnSu+jXg9zoqCAC23MetntVp6LetHA==", "dev": true, - "license": "BlueOak-1.0.0", "dependencies": { - "@isaacs/cliui": "^8.0.2" + "@storybook/global": "^5.0.0", + "dequal": "^2.0.2", + "ts-dedent": "^2.0.0" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "type": "opencollective", + "url": "https://opencollective.com/storybook" }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" + "peerDependencies": { + "storybook": "^8.4.7" } }, - "node_modules/@storybook/core-common/node_modules/jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "node_modules/@storybook/addon-docs": { + "version": "8.4.7", + "resolved": "https://registry.npmjs.org/@storybook/addon-docs/-/addon-docs-8.4.7.tgz", + "integrity": "sha512-NwWaiTDT5puCBSUOVuf6ME7Zsbwz7Y79WF5tMZBx/sLQ60vpmJVQsap6NSjvK1Ravhc21EsIXqemAcBjAWu80w==", "dev": true, "dependencies": { - "universalify": "^2.0.0" + "@mdx-js/react": "^3.0.0", + "@storybook/blocks": "8.4.7", + "@storybook/csf-plugin": "8.4.7", + "@storybook/react-dom-shim": "8.4.7", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0", + "ts-dedent": "^2.0.0" }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "storybook": "^8.4.7" } }, - "node_modules/@storybook/core-common/node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "node_modules/@storybook/addon-highlight": { + "version": "8.4.7", + "resolved": "https://registry.npmjs.org/@storybook/addon-highlight/-/addon-highlight-8.4.7.tgz", + "integrity": "sha512-whQIDBd3PfVwcUCrRXvCUHWClXe9mQ7XkTPCdPo4B/tZ6Z9c6zD8JUHT76ddyHivixFLowMnA8PxMU6kCMAiNw==", "dev": true, "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" + "@storybook/global": "^5.0.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "storybook": "^8.4.7" } }, - "node_modules/@storybook/core-common/node_modules/make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "node_modules/@storybook/addon-toolbars": { + "version": "8.4.7", + "resolved": "https://registry.npmjs.org/@storybook/addon-toolbars/-/addon-toolbars-8.4.7.tgz", + "integrity": "sha512-OSfdv5UZs+NdGB+nZmbafGUWimiweJ/56gShlw8Neo/4jOJl1R3rnRqqY7MYx8E4GwoX+i3GF5C3iWFNQqlDcw==", "dev": true, - "dependencies": { - "semver": "^6.0.0" - }, - "engines": { - "node": ">=8" - }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "storybook": "^8.4.7" } }, - "node_modules/@storybook/core-common/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "node_modules/@storybook/addon-viewport": { + "version": "8.4.7", + "resolved": "https://registry.npmjs.org/@storybook/addon-viewport/-/addon-viewport-8.4.7.tgz", + "integrity": "sha512-hvczh/jjuXXcOogih09a663sRDDSATXwbE866al1DXgbDFraYD/LxX/QDb38W9hdjU9+Qhx8VFIcNWoMQns5HQ==", "dev": true, - "license": "ISC", "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" + "memoizerific": "^1.11.3" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "storybook": "^8.4.7" } }, - "node_modules/@storybook/core-common/node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "node_modules/@storybook/addon-webpack5-compiler-babel": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@storybook/addon-webpack5-compiler-babel/-/addon-webpack5-compiler-babel-3.0.3.tgz", + "integrity": "sha512-rVQTTw+oxJltbVKaejIWSHwVKOBJs3au21f/pYXhV0aiNgNhxEa3vr79t/j0j8ox8uJtzM8XYOb7FlkvGfHlwQ==", "dev": true, - "license": "ISC", + "dependencies": { + "@babel/core": "^7.23.7", + "babel-loader": "^9.1.3" + }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": ">=18" } }, - "node_modules/@storybook/core-common/node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "node_modules/@storybook/blocks": { + "version": "8.4.7", + "resolved": "https://registry.npmjs.org/@storybook/blocks/-/blocks-8.4.7.tgz", + "integrity": "sha512-+QH7+JwXXXIyP3fRCxz/7E2VZepAanXJM7G8nbR3wWsqWgrRp4Wra6MvybxAYCxU7aNfJX5c+RW84SNikFpcIA==", "dev": true, "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" + "@storybook/csf": "^0.1.11", + "@storybook/icons": "^1.2.12", + "ts-dedent": "^2.0.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", + "storybook": "^8.4.7" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } } }, - "node_modules/@storybook/core-common/node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "node_modules/@storybook/builder-webpack5": { + "version": "8.4.7", + "resolved": "https://registry.npmjs.org/@storybook/builder-webpack5/-/builder-webpack5-8.4.7.tgz", + "integrity": "sha512-O8LpsQ+4g2x5kh7rI9+jEUdX8k1a5egBQU1lbudmHchqsV0IKiVqBD9LL5Gj3wpit4vB8coSW4ZWTFBw8FQb4Q==", "dev": true, - "engines": { - "node": ">=6" + "dependencies": { + "@storybook/core-webpack": "8.4.7", + "@types/node": "^22.0.0", + "@types/semver": "^7.3.4", + "browser-assert": "^1.2.1", + "case-sensitive-paths-webpack-plugin": "^2.4.0", + "cjs-module-lexer": "^1.2.3", + "constants-browserify": "^1.0.0", + "css-loader": "^6.7.1", + "es-module-lexer": "^1.5.0", + "fork-ts-checker-webpack-plugin": "^8.0.0", + "html-webpack-plugin": "^5.5.0", + "magic-string": "^0.30.5", + "path-browserify": "^1.0.1", + "process": "^0.11.10", + "semver": "^7.3.7", + "style-loader": "^3.3.1", + "terser-webpack-plugin": "^5.3.1", + "ts-dedent": "^2.0.0", + "url": "^0.11.0", + "util": "^0.12.4", + "util-deprecate": "^1.0.2", + "webpack": "5", + "webpack-dev-middleware": "^6.1.2", + "webpack-hot-middleware": "^2.25.1", + "webpack-virtual-modules": "^0.6.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "storybook": "^8.4.7" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/@storybook/core-common/node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "node_modules/@storybook/builder-webpack5/node_modules/@types/node": { + "version": "22.10.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.2.tgz", + "integrity": "sha512-Xxr6BBRCAOQixvonOye19wnzyDiUtTeqldOOmj3CkeblonbccA12PFwlufvRdrpjXxqnmUaeiU5EOA+7s5diUQ==", "dev": true, - "engines": { - "node": ">=8" + "license": "MIT", + "dependencies": { + "undici-types": "~6.20.0" } }, - "node_modules/@storybook/core-common/node_modules/pkg-dir": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-5.0.0.tgz", - "integrity": "sha512-NPE8TDbzl/3YQYY7CSS228s3g2ollTFnc+Qi3tqmqJp9Vg2ovUpixcJEo2HJScN2Ez+kEaal6y70c0ehqJBJeA==", + "node_modules/@storybook/builder-webpack5/node_modules/css-loader": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.11.0.tgz", + "integrity": "sha512-CTJ+AEQJjq5NzLga5pE39qdiSV56F8ywCIsqNIRF0r7BDgWsN25aazToqAFg7ZrtA/U016xudB3ffgweORxX7g==", "dev": true, "dependencies": { - "find-up": "^5.0.0" + "icss-utils": "^5.1.0", + "postcss": "^8.4.33", + "postcss-modules-extract-imports": "^3.1.0", + "postcss-modules-local-by-default": "^4.0.5", + "postcss-modules-scope": "^3.2.0", + "postcss-modules-values": "^4.0.0", + "postcss-value-parser": "^4.2.0", + "semver": "^7.5.4" }, "engines": { - "node": ">=10" + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "@rspack/core": "0.x || 1.x", + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } } }, - "node_modules/@storybook/core-common/node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true, - "engines": { - "node": ">=8" - } + "node_modules/@storybook/builder-webpack5/node_modules/path-browserify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", + "dev": true }, - "node_modules/@storybook/core-common/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } + "node_modules/@storybook/builder-webpack5/node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true }, - "node_modules/@storybook/core-common/node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "node_modules/@storybook/builder-webpack5/node_modules/style-loader": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-3.3.4.tgz", + "integrity": "sha512-0WqXzrsMTyb8yjZJHDqwmnwRJvhALK9LfRtRc6B4UTWe8AijYLZYZ9thuJTZc2VfQWINADW/j+LiJnfy2RoC1w==", "dev": true, "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/@storybook/core-events": { - "version": "7.6.15", - "resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-7.6.15.tgz", - "integrity": "sha512-i4YnjGecbpGyrFe0340sPhQ9QjZZEBqvMy6kF4XWt6DYLHxZmsTj1HEdvxVl4Ej7V49Vw0Dm8MepJ1d4Y8MKrQ==", - "dev": true, - "dependencies": { - "ts-dedent": "^2.0.0" + "node": ">= 12.13.0" }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/storybook" + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" } }, - "node_modules/@storybook/core-server": { - "version": "7.6.15", - "resolved": "https://registry.npmjs.org/@storybook/core-server/-/core-server-7.6.15.tgz", - "integrity": "sha512-iIlxEAkrmKTSA3iGNqt/4QG7hf5suxBGYIB3DZAOfBo8EdZogMYaEmuCm5dbuaJr0mcVwlqwdhQiWb1VsR/NhA==", + "node_modules/@storybook/builder-webpack5/node_modules/undici-types": { + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", + "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@storybook/builder-webpack5/node_modules/util": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", + "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", "dev": true, "dependencies": { - "@aw-web-design/x-default-browser": "1.4.126", - "@discoveryjs/json-ext": "^0.5.3", - "@storybook/builder-manager": "7.6.15", - "@storybook/channels": "7.6.15", - "@storybook/core-common": "7.6.15", - "@storybook/core-events": "7.6.15", - "@storybook/csf": "^0.1.2", - "@storybook/csf-tools": "7.6.15", - "@storybook/docs-mdx": "^0.1.0", - "@storybook/global": "^5.0.0", - "@storybook/manager": "7.6.15", - "@storybook/node-logger": "7.6.15", - "@storybook/preview-api": "7.6.15", - "@storybook/telemetry": "7.6.15", - "@storybook/types": "7.6.15", - "@types/detect-port": "^1.3.0", - "@types/node": "^18.0.0", - "@types/pretty-hrtime": "^1.0.0", - "@types/semver": "^7.3.4", - "better-opn": "^3.0.2", - "chalk": "^4.1.0", - "cli-table3": "^0.6.1", - "compression": "^1.7.4", - "detect-port": "^1.3.0", - "express": "^4.17.3", - "fs-extra": "^11.1.0", - "globby": "^11.0.2", - "ip": "^2.0.0", - "lodash": "^4.17.21", - "open": "^8.4.0", - "pretty-hrtime": "^1.0.3", - "prompts": "^2.4.0", - "read-pkg-up": "^7.0.1", - "semver": "^7.3.7", - "telejson": "^7.2.0", - "tiny-invariant": "^1.3.1", - "ts-dedent": "^2.0.0", - "util": "^0.12.4", - "util-deprecate": "^1.0.2", - "watchpack": "^2.2.0", - "ws": "^8.2.3" - }, + "inherits": "^2.0.3", + "is-arguments": "^1.0.4", + "is-generator-function": "^1.0.7", + "is-typed-array": "^1.1.3", + "which-typed-array": "^1.1.2" + } + }, + "node_modules/@storybook/components": { + "version": "8.4.7", + "resolved": "https://registry.npmjs.org/@storybook/components/-/components-8.4.7.tgz", + "integrity": "sha512-uyJIcoyeMWKAvjrG9tJBUCKxr2WZk+PomgrgrUwejkIfXMO76i6jw9BwLa0NZjYdlthDv30r9FfbYZyeNPmF0g==", + "dev": true, "funding": { "type": "opencollective", "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "storybook": "^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0" } }, - "node_modules/@storybook/core-server/node_modules/fs-extra": { - "version": "11.2.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", - "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==", + "node_modules/@storybook/core": { + "version": "8.4.7", + "resolved": "https://registry.npmjs.org/@storybook/core/-/core-8.4.7.tgz", + "integrity": "sha512-7Z8Z0A+1YnhrrSXoKKwFFI4gnsLbWzr8fnDCU6+6HlDukFYh8GHRcZ9zKfqmy6U3hw2h8H5DrHsxWfyaYUUOoA==", "dev": true, "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" + "@storybook/csf": "^0.1.11", + "better-opn": "^3.0.2", + "browser-assert": "^1.2.1", + "esbuild": "^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0", + "esbuild-register": "^3.5.0", + "jsdoc-type-pratt-parser": "^4.0.0", + "process": "^0.11.10", + "recast": "^0.23.5", + "semver": "^7.6.2", + "util": "^0.12.5", + "ws": "^8.2.3" }, - "engines": { - "node": ">=14.14" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "prettier": "^2 || ^3" + }, + "peerDependenciesMeta": { + "prettier": { + "optional": true + } } }, - "node_modules/@storybook/core-server/node_modules/ip": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.1.tgz", - "integrity": "sha512-lJUL9imLTNi1ZfXT+DU6rBBdbiKGBuay9B6xGSPVjUeQwaH1RIGqef8RZkUtHioLmSNpPR5M4HVKJGm1j8FWVQ==", - "dev": true - }, - "node_modules/@storybook/core-server/node_modules/is-wsl": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", - "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "node_modules/@storybook/core-webpack": { + "version": "8.4.7", + "resolved": "https://registry.npmjs.org/@storybook/core-webpack/-/core-webpack-8.4.7.tgz", + "integrity": "sha512-Tj+CjQLpFyBJxhhMms+vbPT3+gTRAiQlrhY3L1IEVwBa3wtRMS0qjozH26d1hK4G6mUIEdwu13L54HMU/w33Sg==", "dev": true, "dependencies": { - "is-docker": "^2.0.0" + "@types/node": "^22.0.0", + "ts-dedent": "^2.0.0" }, - "engines": { - "node": ">=8" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "storybook": "^8.4.7" } }, - "node_modules/@storybook/core-server/node_modules/jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "node_modules/@storybook/core-webpack/node_modules/@types/node": { + "version": "22.10.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.2.tgz", + "integrity": "sha512-Xxr6BBRCAOQixvonOye19wnzyDiUtTeqldOOmj3CkeblonbccA12PFwlufvRdrpjXxqnmUaeiU5EOA+7s5diUQ==", "dev": true, + "license": "MIT", "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" + "undici-types": "~6.20.0" } }, - "node_modules/@storybook/core-server/node_modules/open": { - "version": "8.4.2", - "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", - "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", + "node_modules/@storybook/core-webpack/node_modules/undici-types": { + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", + "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@storybook/core/node_modules/recast": { + "version": "0.23.9", + "resolved": "https://registry.npmjs.org/recast/-/recast-0.23.9.tgz", + "integrity": "sha512-Hx/BGIbwj+Des3+xy5uAtAbdCyqK9y9wbBcDFDYanLS9JnMqf7OeF87HQwUimE87OEc72mr6tkKUKMBBL+hF9Q==", "dev": true, "dependencies": { - "define-lazy-prop": "^2.0.0", - "is-docker": "^2.1.1", - "is-wsl": "^2.2.0" + "ast-types": "^0.16.1", + "esprima": "~4.0.0", + "source-map": "~0.6.1", + "tiny-invariant": "^1.3.3", + "tslib": "^2.0.1" }, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">= 4" } }, - "node_modules/@storybook/core-server/node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "node_modules/@storybook/core/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", "dev": true, + "bin": { + "semver": "bin/semver.js" + }, "engines": { - "node": ">= 10.0.0" + "node": ">=10" } }, - "node_modules/@storybook/core-server/node_modules/util": { + "node_modules/@storybook/core/node_modules/util": { "version": "0.12.5", "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", @@ -12802,26 +12055,11 @@ "which-typed-array": "^1.1.2" } }, - "node_modules/@storybook/core-server/node_modules/watchpack": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.2.tgz", - "integrity": "sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw==", - "dev": true, - "license": "MIT", - "dependencies": { - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.1.2" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/@storybook/core-server/node_modules/ws": { + "node_modules/@storybook/core/node_modules/ws": { "version": "8.18.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", "dev": true, - "license": "MIT", "engines": { "node": ">=10.0.0" }, @@ -12838,23 +12076,6 @@ } } }, - "node_modules/@storybook/core-webpack": { - "version": "7.6.15", - "resolved": "https://registry.npmjs.org/@storybook/core-webpack/-/core-webpack-7.6.15.tgz", - "integrity": "sha512-6Qk/kc7OKcy4jNowQFz6TFLWM2NYeLoJ73dIbFnN2o8DYS5WwmQLZhZ+MRvr92M+w1nlnc268kaqooYmAj8Mnw==", - "dev": true, - "dependencies": { - "@storybook/core-common": "7.6.15", - "@storybook/node-logger": "7.6.15", - "@storybook/types": "7.6.15", - "@types/node": "^18.0.0", - "ts-dedent": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" - } - }, "node_modules/@storybook/csf": { "version": "0.1.11", "resolved": "https://registry.npmjs.org/@storybook/csf/-/csf-0.1.11.tgz", @@ -12866,90 +12087,19 @@ } }, "node_modules/@storybook/csf-plugin": { - "version": "7.6.15", - "resolved": "https://registry.npmjs.org/@storybook/csf-plugin/-/csf-plugin-7.6.15.tgz", - "integrity": "sha512-5Pm2B8XKNdG3fHyItWKbWnXHSRDFSvetlML+sMWGWYIjwOsnvPqt+gAvLksWhv/uJgDujGxNcPEh+/Y5C8ZAjQ==", + "version": "8.4.7", + "resolved": "https://registry.npmjs.org/@storybook/csf-plugin/-/csf-plugin-8.4.7.tgz", + "integrity": "sha512-Fgogplu4HImgC+AYDcdGm1rmL6OR1rVdNX1Be9C/NEXwOCpbbBwi0BxTf/2ZxHRk9fCeaPEcOdP5S8QHfltc1g==", "dev": true, "dependencies": { - "@storybook/csf-tools": "7.6.15", "unplugin": "^1.3.1" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/storybook" - } - }, - "node_modules/@storybook/csf-tools": { - "version": "7.6.15", - "resolved": "https://registry.npmjs.org/@storybook/csf-tools/-/csf-tools-7.6.15.tgz", - "integrity": "sha512-8iKgg2cmbFTpVhRRJOqouhPcEh0c8ywabG4S8ICZvnJooSXUI9mD9p3tYCS7MYuSiHj0epa1Kkn9DtXJRo9o6g==", - "dev": true, - "dependencies": { - "@babel/generator": "^7.23.0", - "@babel/parser": "^7.23.0", - "@babel/traverse": "^7.23.2", - "@babel/types": "^7.23.0", - "@storybook/csf": "^0.1.2", - "@storybook/types": "7.6.15", - "fs-extra": "^11.1.0", - "recast": "^0.23.1", - "ts-dedent": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" - } - }, - "node_modules/@storybook/csf-tools/node_modules/fs-extra": { - "version": "11.2.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", - "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=14.14" - } - }, - "node_modules/@storybook/csf-tools/node_modules/jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dev": true, - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/@storybook/csf-tools/node_modules/recast": { - "version": "0.23.9", - "resolved": "https://registry.npmjs.org/recast/-/recast-0.23.9.tgz", - "integrity": "sha512-Hx/BGIbwj+Des3+xy5uAtAbdCyqK9y9wbBcDFDYanLS9JnMqf7OeF87HQwUimE87OEc72mr6tkKUKMBBL+hF9Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "ast-types": "^0.16.1", - "esprima": "~4.0.0", - "source-map": "~0.6.1", - "tiny-invariant": "^1.3.3", - "tslib": "^2.0.1" }, - "engines": { - "node": ">= 4" - } - }, - "node_modules/@storybook/csf-tools/node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "dev": true, - "engines": { - "node": ">= 10.0.0" + "peerDependencies": { + "storybook": "^8.4.7" } }, "node_modules/@storybook/csf/node_modules/type-fest": { @@ -12964,294 +12114,257 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@storybook/docs-mdx": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/@storybook/docs-mdx/-/docs-mdx-0.1.0.tgz", - "integrity": "sha512-JDaBR9lwVY4eSH5W8EGHrhODjygPd6QImRbwjAuJNEnY0Vw4ie3bPkeGfnacB3OBW6u/agqPv2aRlR46JcAQLg==", + "node_modules/@storybook/global": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@storybook/global/-/global-5.0.0.tgz", + "integrity": "sha512-FcOqPAXACP0I3oJ/ws6/rrPT9WGhu915Cg8D02a9YxLo0DE9zI+a9A5gRGvmQ09fiWPukqI8ZAEoQEdWUKMQdQ==", "dev": true }, - "node_modules/@storybook/docs-tools": { - "version": "7.6.15", - "resolved": "https://registry.npmjs.org/@storybook/docs-tools/-/docs-tools-7.6.15.tgz", - "integrity": "sha512-npZEaI9Wpn9uJcRXFElqyiRw8bSxt95mLywPiEEGMT2kE5FfXM8d5Uj5O64kzoXdRI9IhRPEEZZidOtA/UInfQ==", + "node_modules/@storybook/icons": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@storybook/icons/-/icons-1.3.0.tgz", + "integrity": "sha512-Nz/UzeYQdUZUhacrPyfkiiysSjydyjgg/p0P9HxB4p/WaJUUjMAcaoaLgy3EXx61zZJ3iD36WPuDkZs5QYrA0A==", "dev": true, - "dependencies": { - "@storybook/core-common": "7.6.15", - "@storybook/preview-api": "7.6.15", - "@storybook/types": "7.6.15", - "@types/doctrine": "^0.0.3", - "assert": "^2.1.0", - "doctrine": "^3.0.0", - "lodash": "^4.17.21" + "engines": { + "node": ">=14.0.0" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" - } - }, - "node_modules/@storybook/docs-tools/node_modules/assert": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/assert/-/assert-2.1.0.tgz", - "integrity": "sha512-eLHpSK/Y4nhMJ07gDaAzoX/XAKS8PSaojml3M0DM4JpV1LAi5JOJ/p6H/XWrl8L+DzVEvVCW1z3vWAaB9oTsQw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "is-nan": "^1.3.2", - "object-is": "^1.1.5", - "object.assign": "^4.1.4", - "util": "^0.12.5" + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta" } }, - "node_modules/@storybook/docs-tools/node_modules/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "node_modules/@storybook/instrumenter": { + "version": "8.4.7", + "resolved": "https://registry.npmjs.org/@storybook/instrumenter/-/instrumenter-8.4.7.tgz", + "integrity": "sha512-k6NSD3jaRCCHAFtqXZ7tw8jAzD/yTEWXGya+REgZqq5RCkmJ+9S4Ytp/6OhQMPtPFX23gAuJJzTQVLcCr+gjRg==", "dev": true, "dependencies": { - "esutils": "^2.0.2" + "@storybook/global": "^5.0.0", + "@vitest/utils": "^2.1.1" }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@storybook/docs-tools/node_modules/util": { - "version": "0.12.5", - "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", - "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", - "dev": true, - "dependencies": { - "inherits": "^2.0.3", - "is-arguments": "^1.0.4", - "is-generator-function": "^1.0.7", - "is-typed-array": "^1.1.3", - "which-typed-array": "^1.1.2" - } - }, - "node_modules/@storybook/global": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@storybook/global/-/global-5.0.0.tgz", - "integrity": "sha512-FcOqPAXACP0I3oJ/ws6/rrPT9WGhu915Cg8D02a9YxLo0DE9zI+a9A5gRGvmQ09fiWPukqI8ZAEoQEdWUKMQdQ==", - "dev": true - }, - "node_modules/@storybook/manager": { - "version": "7.6.15", - "resolved": "https://registry.npmjs.org/@storybook/manager/-/manager-7.6.15.tgz", - "integrity": "sha512-GGV2ElV5AOIApy/FSDzoSlLUbyd2VhQVD3TdNGRxNauYRjEO8ulXHw2tNbT6ludtpYpDTAILzI6zT/iag8hmPQ==", - "dev": true, "funding": { "type": "opencollective", "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "storybook": "^8.4.7" } }, - "node_modules/@storybook/manager-api": { - "version": "7.6.15", - "resolved": "https://registry.npmjs.org/@storybook/manager-api/-/manager-api-7.6.15.tgz", - "integrity": "sha512-cPBsXcnJiaO3QyaEum2JgdihYea3cI03FeV35JdrBYLIelT4oqbYFnzjznsFg9+Ia9iAbz7aOBNyyRsWnC/UKw==", + "node_modules/@storybook/instrumenter/node_modules/@vitest/pretty-format": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.8.tgz", + "integrity": "sha512-9HiSZ9zpqNLKlbIDRWOnAWqgcA7xu+8YxXSekhr0Ykab7PAYFkhkwoqVArPOtJhPmYeE2YHgKZlj3CP36z2AJQ==", "dev": true, "dependencies": { - "@storybook/channels": "7.6.15", - "@storybook/client-logger": "7.6.15", - "@storybook/core-events": "7.6.15", - "@storybook/csf": "^0.1.2", - "@storybook/global": "^5.0.0", - "@storybook/router": "7.6.15", - "@storybook/theming": "7.6.15", - "@storybook/types": "7.6.15", - "dequal": "^2.0.2", - "lodash": "^4.17.21", - "memoizerific": "^1.11.3", - "store2": "^2.14.2", - "telejson": "^7.2.0", - "ts-dedent": "^2.0.0" + "tinyrainbow": "^1.2.0" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" + "url": "https://opencollective.com/vitest" } }, - "node_modules/@storybook/mdx2-csf": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@storybook/mdx2-csf/-/mdx2-csf-1.1.0.tgz", - "integrity": "sha512-TXJJd5RAKakWx4BtpwvSNdgTDkKM6RkXU8GK34S/LhidQ5Pjz3wcnqb0TxEkfhK/ztbP8nKHqXFwLfa2CYkvQw==", - "dev": true - }, - "node_modules/@storybook/node-logger": { - "version": "7.6.15", - "resolved": "https://registry.npmjs.org/@storybook/node-logger/-/node-logger-7.6.15.tgz", - "integrity": "sha512-C+sCvRjR+5uVU3VTrfyv7/RlPBxesAjIucUAK0keGyIZ7sFQYCPdkm4m/C4s+TcubgAzVvuoUHlRrSppdA7WzQ==", + "node_modules/@storybook/instrumenter/node_modules/@vitest/utils": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.8.tgz", + "integrity": "sha512-dwSoui6djdwbfFmIgbIjX2ZhIoG7Ex/+xpxyiEgIGzjliY8xGkcpITKTlp6B4MgtGkF2ilvm97cPM96XZaAgcA==", "dev": true, + "dependencies": { + "@vitest/pretty-format": "2.1.8", + "loupe": "^3.1.2", + "tinyrainbow": "^1.2.0" + }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" + "url": "https://opencollective.com/vitest" } }, - "node_modules/@storybook/postinstall": { - "version": "7.6.15", - "resolved": "https://registry.npmjs.org/@storybook/postinstall/-/postinstall-7.6.15.tgz", - "integrity": "sha512-DXQQ4kjAbQ7BSd9M4lDI/12vEEciYMP8uYFDlrPFjwD9LezsxtRiORkazjNRRX4730faO5zZsnWhXxCVkxck0g==", + "node_modules/@storybook/manager-api": { + "version": "8.4.7", + "resolved": "https://registry.npmjs.org/@storybook/manager-api/-/manager-api-8.4.7.tgz", + "integrity": "sha512-ELqemTviCxAsZ5tqUz39sDmQkvhVAvAgiplYy9Uf15kO0SP2+HKsCMzlrm2ue2FfkUNyqbDayCPPCB0Cdn/mpQ==", "dev": true, "funding": { "type": "opencollective", "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "storybook": "^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0" } }, "node_modules/@storybook/preset-react-webpack": { - "version": "7.6.15", - "resolved": "https://registry.npmjs.org/@storybook/preset-react-webpack/-/preset-react-webpack-7.6.15.tgz", - "integrity": "sha512-Oo3J7RKO/tFUVnRXs16tZGcX6n90gTpHdlT2Z1fZ+y8wEd9o+VvvKFEIIeMcRxf3hHa49R6Kbc4AQaE9FAuDlw==", + "version": "8.4.7", + "resolved": "https://registry.npmjs.org/@storybook/preset-react-webpack/-/preset-react-webpack-8.4.7.tgz", + "integrity": "sha512-geTSBKyrBagVihil5MF7LkVFynbfHhCinvnbCZZqXW7M1vgcxvatunUENB+iV8eWg/0EJ+8O7scZL+BAxQ/2qg==", "dev": true, "dependencies": { - "@babel/preset-flow": "^7.22.15", - "@babel/preset-react": "^7.22.15", - "@pmmmwh/react-refresh-webpack-plugin": "^0.5.11", - "@storybook/core-webpack": "7.6.15", - "@storybook/docs-tools": "7.6.15", - "@storybook/node-logger": "7.6.15", - "@storybook/react": "7.6.15", + "@storybook/core-webpack": "8.4.7", + "@storybook/react": "8.4.7", "@storybook/react-docgen-typescript-plugin": "1.0.6--canary.9.0c3f3b7.0", - "@types/node": "^18.0.0", + "@types/node": "^22.0.0", "@types/semver": "^7.3.4", - "babel-plugin-add-react-displayname": "^0.0.5", - "fs-extra": "^11.1.0", + "find-up": "^5.0.0", "magic-string": "^0.30.5", "react-docgen": "^7.0.0", - "react-refresh": "^0.14.0", + "resolve": "^1.22.8", "semver": "^7.3.7", + "tsconfig-paths": "^4.2.0", "webpack": "5" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "@babel/core": "^7.22.0", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", + "storybook": "^8.4.7" }, "peerDependenciesMeta": { - "@babel/core": { - "optional": true - }, "typescript": { "optional": true } } }, - "node_modules/@storybook/preset-react-webpack/node_modules/fs-extra": { - "version": "11.2.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", - "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==", + "node_modules/@storybook/preset-react-webpack/node_modules/@types/node": { + "version": "22.10.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.2.tgz", + "integrity": "sha512-Xxr6BBRCAOQixvonOye19wnzyDiUtTeqldOOmj3CkeblonbccA12PFwlufvRdrpjXxqnmUaeiU5EOA+7s5diUQ==", "dev": true, + "license": "MIT", "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" + "undici-types": "~6.20.0" + } + }, + "node_modules/@storybook/preset-react-webpack/node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" }, "engines": { - "node": ">=14.14" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@storybook/preset-react-webpack/node_modules/jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "node_modules/@storybook/preset-react-webpack/node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dev": true, "dependencies": { - "universalify": "^2.0.0" + "p-locate": "^5.0.0" }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@storybook/preset-react-webpack/node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "node_modules/@storybook/preset-react-webpack/node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, "engines": { - "node": ">= 10.0.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@storybook/preview": { - "version": "7.6.15", - "resolved": "https://registry.npmjs.org/@storybook/preview/-/preview-7.6.15.tgz", - "integrity": "sha512-q8d9v0+Bo/DHLV68OyV3Klep4knf2GAbrlHhLW1X4jlPccuEDUojIfqfK7m48ayeIxJzO48fcO0JdKM1XABx7g==", + "node_modules/@storybook/preset-react-webpack/node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "dev": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" + "engines": { + "node": ">=8" } }, - "node_modules/@storybook/preview-api": { - "version": "7.6.15", - "resolved": "https://registry.npmjs.org/@storybook/preview-api/-/preview-api-7.6.15.tgz", - "integrity": "sha512-2KN9vlizF6sFlYsJEGnFqcQaJXs4TTdawC1VazVdtaMSHANDxxDu8F1cP+u7lpPH3DkNZUmTGQDBYfYY9xR0eQ==", + "node_modules/@storybook/preset-react-webpack/node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@storybook/preset-react-webpack/node_modules/tsconfig-paths": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz", + "integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==", "dev": true, "dependencies": { - "@storybook/channels": "7.6.15", - "@storybook/client-logger": "7.6.15", - "@storybook/core-events": "7.6.15", - "@storybook/csf": "^0.1.2", - "@storybook/global": "^5.0.0", - "@storybook/types": "7.6.15", - "@types/qs": "^6.9.5", - "dequal": "^2.0.2", - "lodash": "^4.17.21", - "memoizerific": "^1.11.3", - "qs": "^6.10.0", - "synchronous-promise": "^2.0.15", - "ts-dedent": "^2.0.0", - "util-deprecate": "^1.0.2" + "json5": "^2.2.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@storybook/preset-react-webpack/node_modules/undici-types": { + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", + "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@storybook/preview-api": { + "version": "8.4.7", + "resolved": "https://registry.npmjs.org/@storybook/preview-api/-/preview-api-8.4.7.tgz", + "integrity": "sha512-0QVQwHw+OyZGHAJEXo6Knx+6/4er7n2rTDE5RYJ9F2E2Lg42E19pfdLlq2Jhoods2Xrclo3wj6GWR//Ahi39Eg==", + "dev": true, "funding": { "type": "opencollective", "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "storybook": "^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0" } }, "node_modules/@storybook/react": { - "version": "7.6.15", - "resolved": "https://registry.npmjs.org/@storybook/react/-/react-7.6.15.tgz", - "integrity": "sha512-oJMSh4iTGu6OqCmj0LhkuPyMkxGMTCoohN4HcDpXd96jCSyWotVebRsg9xm5ddB7f54e6DY4XDoGH0WnVoR23g==", + "version": "8.4.7", + "resolved": "https://registry.npmjs.org/@storybook/react/-/react-8.4.7.tgz", + "integrity": "sha512-nQ0/7i2DkaCb7dy0NaT95llRVNYWQiPIVuhNfjr1mVhEP7XD090p0g7eqUmsx8vfdHh2BzWEo6CoBFRd3+EXxw==", "dev": true, "dependencies": { - "@storybook/client-logger": "7.6.15", - "@storybook/core-client": "7.6.15", - "@storybook/docs-tools": "7.6.15", + "@storybook/components": "8.4.7", "@storybook/global": "^5.0.0", - "@storybook/preview-api": "7.6.15", - "@storybook/react-dom-shim": "7.6.15", - "@storybook/types": "7.6.15", - "@types/escodegen": "^0.0.6", - "@types/estree": "^0.0.51", - "@types/node": "^18.0.0", - "acorn": "^7.4.1", - "acorn-jsx": "^5.3.1", - "acorn-walk": "^7.2.0", - "escodegen": "^2.1.0", - "html-tags": "^3.1.0", - "lodash": "^4.17.21", - "prop-types": "^15.7.2", - "react-element-to-jsx-string": "^15.0.0", - "ts-dedent": "^2.0.0", - "type-fest": "~2.19", - "util-deprecate": "^1.0.2" + "@storybook/manager-api": "8.4.7", + "@storybook/preview-api": "8.4.7", + "@storybook/react-dom-shim": "8.4.7", + "@storybook/theming": "8.4.7" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0", - "typescript": "*" + "@storybook/test": "8.4.7", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", + "storybook": "^8.4.7", + "typescript": ">= 4.2.x" }, "peerDependenciesMeta": { + "@storybook/test": { + "optional": true + }, "typescript": { "optional": true } @@ -13400,112 +12513,84 @@ } }, "node_modules/@storybook/react-dom-shim": { - "version": "7.6.15", - "resolved": "https://registry.npmjs.org/@storybook/react-dom-shim/-/react-dom-shim-7.6.15.tgz", - "integrity": "sha512-2+X0HIxIyvjfSKVyGGjSJJLEFJ2ox7Rr8FjlMiRo5QfoOJhohZuWH7p4Lw7JMwm5PotnjrwlfsZI3cCilYJeYA==", + "version": "8.4.7", + "resolved": "https://registry.npmjs.org/@storybook/react-dom-shim/-/react-dom-shim-8.4.7.tgz", + "integrity": "sha512-6bkG2jvKTmWrmVzCgwpTxwIugd7Lu+2btsLAqhQSzDyIj2/uhMNp8xIMr/NBDtLgq3nomt9gefNa9xxLwk/OMg==", "dev": true, "funding": { "type": "opencollective", "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", + "storybook": "^8.4.7" } }, "node_modules/@storybook/react-webpack5": { - "version": "7.6.15", - "resolved": "https://registry.npmjs.org/@storybook/react-webpack5/-/react-webpack5-7.6.15.tgz", - "integrity": "sha512-TyYYSDho+4cQRBCVMKu7XDTCrAsLWaeldCoZm910e4DTXZUV3NDG8hVJIXzweaCu1o7JtDOelxsA6iizR/22GQ==", + "version": "8.4.7", + "resolved": "https://registry.npmjs.org/@storybook/react-webpack5/-/react-webpack5-8.4.7.tgz", + "integrity": "sha512-T9GLqlsP4It4El7cC8rSkBPRWvORAsTDULeWlO36RST2TrYnmBOUytsi22mk7cAAAVhhD6rTrs1YdqWRMpfa1w==", "dev": true, "dependencies": { - "@storybook/builder-webpack5": "7.6.15", - "@storybook/preset-react-webpack": "7.6.15", - "@storybook/react": "7.6.15", - "@types/node": "^18.0.0" + "@storybook/builder-webpack5": "8.4.7", + "@storybook/preset-react-webpack": "8.4.7", + "@storybook/react": "8.4.7", + "@types/node": "^22.0.0" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "@babel/core": "^7.22.0", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0", - "typescript": "*" + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", + "storybook": "^8.4.7", + "typescript": ">= 4.2.x" }, "peerDependenciesMeta": { - "@babel/core": { - "optional": true - }, "typescript": { "optional": true } } }, - "node_modules/@storybook/react/node_modules/@types/estree": { - "version": "0.0.51", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.51.tgz", - "integrity": "sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==", - "dev": true - }, - "node_modules/@storybook/react/node_modules/acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/@storybook/react/node_modules/type-fest": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", - "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "node_modules/@storybook/react-webpack5/node_modules/@types/node": { + "version": "22.10.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.2.tgz", + "integrity": "sha512-Xxr6BBRCAOQixvonOye19wnzyDiUtTeqldOOmj3CkeblonbccA12PFwlufvRdrpjXxqnmUaeiU5EOA+7s5diUQ==", "dev": true, - "engines": { - "node": ">=12.20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "license": "MIT", + "dependencies": { + "undici-types": "~6.20.0" } }, - "node_modules/@storybook/router": { - "version": "7.6.15", - "resolved": "https://registry.npmjs.org/@storybook/router/-/router-7.6.15.tgz", - "integrity": "sha512-5yhXXoVZ1iKUgeZoO8PGqBclrLgoJisxIYVK/Y1iJMXZ2ZvwUiTswLALT6lu97tSrcoBVxmqSghg0+U0YEU4Fg==", + "node_modules/@storybook/react-webpack5/node_modules/undici-types": { + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", + "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", "dev": true, - "dependencies": { - "@storybook/client-logger": "7.6.15", - "memoizerific": "^1.11.3", - "qs": "^6.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" - } + "license": "MIT" }, "node_modules/@storybook/source-loader": { - "version": "7.6.15", - "resolved": "https://registry.npmjs.org/@storybook/source-loader/-/source-loader-7.6.15.tgz", - "integrity": "sha512-E7LqjfvEUs2dn8ZWc1OfqzXU3vyi2/yP7rPHPRFjDUIpz1QI4IUCUIFY+n3YWkbk8wlmf6dV/2QYzYZPp6RD0g==", + "version": "8.4.7", + "resolved": "https://registry.npmjs.org/@storybook/source-loader/-/source-loader-8.4.7.tgz", + "integrity": "sha512-DrsYGGfNbbqlMzkhbLoNyNqrPa4QIkZ6O7FJ8Z/8jWb0cerQH2N6JW6k12ZnXgs8dO2Z33+iSEDIV8odh0E0PA==", "dev": true, "dependencies": { - "@storybook/csf": "^0.1.2", - "@storybook/types": "7.6.15", + "@storybook/csf": "^0.1.11", + "es-toolkit": "^1.22.0", "estraverse": "^5.2.0", - "lodash": "^4.17.21", - "prettier": "^2.8.0" + "prettier": "^3.1.1" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "storybook": "^8.4.7" } }, "node_modules/@storybook/source-loader/node_modules/estraverse": { @@ -13518,109 +12603,200 @@ } }, "node_modules/@storybook/source-loader/node_modules/prettier": { - "version": "2.8.8", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", - "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.4.2.tgz", + "integrity": "sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==", "dev": true, "bin": { - "prettier": "bin-prettier.js" + "prettier": "bin/prettier.cjs" }, "engines": { - "node": ">=10.13.0" + "node": ">=14" }, "funding": { "url": "https://github.com/prettier/prettier?sponsor=1" } }, - "node_modules/@storybook/telemetry": { - "version": "7.6.15", - "resolved": "https://registry.npmjs.org/@storybook/telemetry/-/telemetry-7.6.15.tgz", - "integrity": "sha512-klhKXLUS3OXozGEtMbbhKZLDfm+m3nNk2jvGwD6kkBenzFUzb0P2m8awxU7h1pBcKZKH/27U9t3KVzNFzWoWPw==", + "node_modules/@storybook/test": { + "version": "8.4.7", + "resolved": "https://registry.npmjs.org/@storybook/test/-/test-8.4.7.tgz", + "integrity": "sha512-AhvJsu5zl3uG40itSQVuSy5WByp3UVhS6xAnme4FWRwgSxhvZjATJ3AZkkHWOYjnnk+P2/sbz/XuPli1FVCWoQ==", "dev": true, "dependencies": { - "@storybook/client-logger": "7.6.15", - "@storybook/core-common": "7.6.15", - "@storybook/csf-tools": "7.6.15", - "chalk": "^4.1.0", - "detect-package-manager": "^2.0.1", - "fetch-retry": "^5.0.2", - "fs-extra": "^11.1.0", - "read-pkg-up": "^7.0.1" + "@storybook/csf": "^0.1.11", + "@storybook/global": "^5.0.0", + "@storybook/instrumenter": "8.4.7", + "@testing-library/dom": "10.4.0", + "@testing-library/jest-dom": "6.5.0", + "@testing-library/user-event": "14.5.2", + "@vitest/expect": "2.0.5", + "@vitest/spy": "2.0.5" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "storybook": "^8.4.7" } }, - "node_modules/@storybook/telemetry/node_modules/fs-extra": { - "version": "11.2.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", - "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==", + "node_modules/@storybook/test/node_modules/@testing-library/dom": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.0.tgz", + "integrity": "sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==", "dev": true, "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^5.0.1", + "aria-query": "5.3.0", + "chalk": "^4.1.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.5.0", + "pretty-format": "^27.0.2" }, "engines": { - "node": ">=14.14" + "node": ">=18" } }, - "node_modules/@storybook/telemetry/node_modules/jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "node_modules/@storybook/test/node_modules/@testing-library/jest-dom": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.5.0.tgz", + "integrity": "sha512-xGGHpBXYSHUUr6XsKBfs85TWlYKpTc37cSBBVrXcib2MkHLboWlkClhWF37JKlDb9KEq3dHs+f2xR7XJEWGBxA==", "dev": true, "dependencies": { - "universalify": "^2.0.0" + "@adobe/css-tools": "^4.4.0", + "aria-query": "^5.0.0", + "chalk": "^3.0.0", + "css.escape": "^1.5.1", + "dom-accessibility-api": "^0.6.3", + "lodash": "^4.17.21", + "redent": "^3.0.0" }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" + "engines": { + "node": ">=14", + "npm": ">=6", + "yarn": ">=1" } }, - "node_modules/@storybook/telemetry/node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "node_modules/@storybook/test/node_modules/@testing-library/jest-dom/node_modules/chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, "engines": { - "node": ">= 10.0.0" + "node": ">=8" } }, - "node_modules/@storybook/theming": { - "version": "7.6.15", - "resolved": "https://registry.npmjs.org/@storybook/theming/-/theming-7.6.15.tgz", - "integrity": "sha512-9PpsHAbUf6o0w33/P3mnb7QheTmfGlTYCismj5HMM1O2/zY0kQK9XcG9W+Cyvu56D/lFC19fz9YHQY8W4AbfnQ==", + "node_modules/@storybook/test/node_modules/@testing-library/jest-dom/node_modules/dom-accessibility-api": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz", + "integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==", + "dev": true + }, + "node_modules/@storybook/test/node_modules/@testing-library/user-event": { + "version": "14.5.2", + "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.5.2.tgz", + "integrity": "sha512-YAh82Wh4TIrxYLmfGcixwD18oIjyC1pFQC2Y01F2lzV2HTMiYrI0nze0FD0ocB//CKS/7jIUgae+adPqxK5yCQ==", + "dev": true, + "engines": { + "node": ">=12", + "npm": ">=6" + }, + "peerDependencies": { + "@testing-library/dom": ">=7.21.4" + } + }, + "node_modules/@storybook/test/node_modules/aria-query": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", "dev": true, "dependencies": { - "@emotion/use-insertion-effect-with-fallbacks": "^1.0.0", - "@storybook/client-logger": "7.6.15", - "@storybook/global": "^5.0.0", - "memoizerific": "^1.11.3" + "dequal": "^2.0.3" + } + }, + "node_modules/@storybook/test/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@storybook/test/node_modules/pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@storybook/test/node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@storybook/test/node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true + }, + "node_modules/@storybook/test/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@storybook/theming": { + "version": "8.4.7", + "resolved": "https://registry.npmjs.org/@storybook/theming/-/theming-8.4.7.tgz", + "integrity": "sha512-99rgLEjf7iwfSEmdqlHkSG3AyLcK0sfExcr0jnc6rLiAkBhzuIsvcHjjUwkR210SOCgXqBPW0ZA6uhnuyppHLw==", + "dev": true, "funding": { "type": "opencollective", "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + "storybook": "^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0" } }, "node_modules/@storybook/types": { - "version": "7.6.15", - "resolved": "https://registry.npmjs.org/@storybook/types/-/types-7.6.15.tgz", - "integrity": "sha512-tLH0lK6SXECSfMpKin9bge+7XiHZII17n6jc9ZI1TfSBZJyq3M6VzWh2r1C2lC97FlkcKXjIwM3n8h1xNjnI+A==", + "version": "8.4.7", + "resolved": "https://registry.npmjs.org/@storybook/types/-/types-8.4.7.tgz", + "integrity": "sha512-zuf0uPFjODB9Ls9/lqXnb1YsDKFuaASLOpTzpRlz9amFtTepo1dB0nVF9ZWcseTgGs7UxA4+ZR2SZrduXw/ihw==", "dev": true, - "dependencies": { - "@storybook/channels": "7.6.15", - "@types/babel__core": "^7.0.0", - "@types/express": "^4.7.0", - "file-system-cache": "2.3.0" - }, "funding": { "type": "opencollective", "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "storybook": "^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0" } }, "node_modules/@stylistic/stylelint-plugin": { @@ -14012,216 +13188,6 @@ "url": "https://github.com/sponsors/gregberge" } }, - "node_modules/@swc/core": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.4.1.tgz", - "integrity": "sha512-3y+Y8js+e7BbM16iND+6Rcs3jdiL28q3iVtYsCviYSSpP2uUVKkp5sJnCY4pg8AaVvyN7CGQHO7gLEZQ5ByozQ==", - "dev": true, - "hasInstallScript": true, - "dependencies": { - "@swc/counter": "^0.1.2", - "@swc/types": "^0.1.5" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/swc" - }, - "optionalDependencies": { - "@swc/core-darwin-arm64": "1.4.1", - "@swc/core-darwin-x64": "1.4.1", - "@swc/core-linux-arm-gnueabihf": "1.4.1", - "@swc/core-linux-arm64-gnu": "1.4.1", - "@swc/core-linux-arm64-musl": "1.4.1", - "@swc/core-linux-x64-gnu": "1.4.1", - "@swc/core-linux-x64-musl": "1.4.1", - "@swc/core-win32-arm64-msvc": "1.4.1", - "@swc/core-win32-ia32-msvc": "1.4.1", - "@swc/core-win32-x64-msvc": "1.4.1" - }, - "peerDependencies": { - "@swc/helpers": "^0.5.0" - }, - "peerDependenciesMeta": { - "@swc/helpers": { - "optional": true - } - } - }, - "node_modules/@swc/core-darwin-arm64": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.4.1.tgz", - "integrity": "sha512-ePyfx0348UbR4DOAW24TedeJbafnzha8liXFGuQ4bdXtEVXhLfPngprrxKrAddCuv42F9aTxydlF6+adD3FBhA==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@swc/core-darwin-x64": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.4.1.tgz", - "integrity": "sha512-eLf4JSe6VkCMdDowjM8XNC5rO+BrgfbluEzAVtKR8L2HacNYukieumN7EzpYCi0uF1BYwu1ku6tLyG2r0VcGxA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@swc/core-linux-arm-gnueabihf": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.4.1.tgz", - "integrity": "sha512-K8VtTLWMw+rkN/jDC9o/Q9SMmzdiHwYo2CfgkwVT29NsGccwmNhCQx6XoYiPKyKGIFKt4tdQnJHKUFzxUqQVtQ==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@swc/core-linux-arm64-gnu": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.4.1.tgz", - "integrity": "sha512-0e8p4g0Bfkt8lkiWgcdiENH3RzkcqKtpRXIVNGOmVc0OBkvc2tpm2WTx/eoCnes2HpTT4CTtR3Zljj4knQ4Fvw==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@swc/core-linux-arm64-musl": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.4.1.tgz", - "integrity": "sha512-b/vWGQo2n7lZVUnSQ7NBq3Qrj85GrAPPiRbpqaIGwOytiFSk8VULFihbEUwDe0rXgY4LDm8z8wkgADZcLnmdUA==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@swc/core-linux-x64-gnu": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.4.1.tgz", - "integrity": "sha512-AFMQlvkKEdNi1Vk2GFTxxJzbICttBsOQaXa98kFTeWTnFFIyiIj2w7Sk8XRTEJ/AjF8ia8JPKb1zddBWr9+bEQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@swc/core-linux-x64-musl": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.4.1.tgz", - "integrity": "sha512-QX2MxIECX1gfvUVZY+jk528/oFkS9MAl76e3ZRvG2KC/aKlCQL0KSzcTSm13mOxkDKS30EaGRDRQWNukGpMeRg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@swc/core-win32-arm64-msvc": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.4.1.tgz", - "integrity": "sha512-OklkJYXXI/tntD2zaY8i3iZldpyDw5q+NAP3k9OlQ7wXXf37djRsHLV0NW4+ZNHBjE9xp2RsXJ0jlOJhfgGoFA==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@swc/core-win32-ia32-msvc": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.4.1.tgz", - "integrity": "sha512-MBuc3/QfKX9FnLOU7iGN+6yHRTQaPQ9WskiC8s8JFiKQ+7I2p25tay2RplR9dIEEGgVAu6L7auv96LbNTh+FaA==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@swc/core-win32-x64-msvc": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.4.1.tgz", - "integrity": "sha512-lu4h4wFBb/bOK6N2MuZwg7TrEpwYXgpQf5R7ObNSXL65BwZ9BG8XRzD+dLJmALu8l5N08rP/TrpoKRoGT4WSxw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@swc/counter": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", - "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", - "dev": true - }, - "node_modules/@swc/types": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.5.tgz", - "integrity": "sha512-myfUej5naTBWnqOCc/MdVOLVjXUXtIA+NpDrDBKJtLLg2shUjBu3cZmB/85RyitKc55+lUUyl7oRfLOvkr2hsw==", - "dev": true - }, "node_modules/@szmarczak/http-timer": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", @@ -14261,9 +13227,9 @@ "integrity": "sha512-oocsqY7g0cR+Gur5jRQLSrX2OtpMLMse1I10JQBm8CdGMrDkh1Mg2gjsiquMHRtBs4Qwu5wgEp5GgIYHk4SNPw==" }, "node_modules/@testing-library/dom": { - "version": "9.3.1", - "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-9.3.1.tgz", - "integrity": "sha512-0DGPd9AR3+iDTjGoMpxIkAsUihHZ3Ai6CneU6bRRrffXMgzCdlNk43jTrD2/5LT6CBb3MWTP8v510JzYtahD2w==", + "version": "9.3.4", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-9.3.4.tgz", + "integrity": "sha512-FlS4ZWlp97iiNWig0Muq8p+3rVDjRiYE+YKGbAqXOu9nwJFFOdL00kFpz42M+4huzYi86vAK1sOOfyOG45muIQ==", "dev": true, "dependencies": { "@babel/code-frame": "^7.10.4", @@ -14491,28 +13457,31 @@ "version": "14.1.0", "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-14.1.0.tgz", "integrity": "sha512-VmsCG04YR58ciHBeJKBDNMWWfYbyP8FekWVuTlpstaUPlat1D0x/tXzkWP7yCMU0eSz9V4OZU0LBWTFJ3xZf6w==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@tufjs/canonical-json": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@tufjs/canonical-json/-/canonical-json-1.0.0.tgz", - "integrity": "sha512-QTnf++uxunWvG2z3UFNzAoQPHxnSXOwtaI3iJ+AohhV+5vONuArPjJE7aPXPVXfXJsqrVbZBu9b81AJoSd09IQ==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tufjs/canonical-json/-/canonical-json-2.0.0.tgz", + "integrity": "sha512-yVtV8zsdo8qFHe+/3kw81dSLyF7D576A5cCFCi4X7B39tWT7SekaEFUnvnWJHz+9qO7qJTah1JbrDjWKqFtdWA==", "dev": true, + "license": "MIT", "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^16.14.0 || >=18.0.0" } }, "node_modules/@tufjs/models": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@tufjs/models/-/models-1.0.4.tgz", - "integrity": "sha512-qaGV9ltJP0EO25YfFUPhxRVK0evXFIAGicsVXuRim4Ed9cjPxYhNnNJ49SFmbeLgtxpslIkX317IgpfcHPVj/A==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@tufjs/models/-/models-2.0.1.tgz", + "integrity": "sha512-92F7/SFyufn4DXsha9+QfKnN03JGqtMFMXgSHbZOo8JG59WkTni7UzAouNQDf7AuP9OAMxVOPQcqG3sB7w+kkg==", "dev": true, + "license": "MIT", "dependencies": { - "@tufjs/canonical-json": "1.0.0", - "minimatch": "^9.0.0" + "@tufjs/canonical-json": "2.0.0", + "minimatch": "^9.0.4" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^16.14.0 || >=18.0.0" } }, "node_modules/@tufjs/models/node_modules/brace-expansion": { @@ -14520,6 +13489,7 @@ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "dev": true, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" } @@ -14540,11 +13510,22 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/@tybys/wasm-util": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.9.0.tgz", + "integrity": "sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@types/archiver": { "version": "5.3.3", "resolved": "https://registry.npmjs.org/@types/archiver/-/archiver-5.3.3.tgz", "integrity": "sha512-0ABdVcXL6jOwNGY+hjWPqrxUvKelBEwNLcuv/SV2vZ4YCH8w9NttFCt+/QqI5zgMX+iX/XqVy89/r7EmLJmMpQ==", "dev": true, + "license": "MIT", "dependencies": { "@types/readdir-glob": "*" } @@ -14565,7 +13546,8 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/@types/async-lock/-/async-lock-1.4.0.tgz", "integrity": "sha512-2+rYSaWrpdbQG3SA0LmMT6YxWLrI81AqpMlSkw3QtFc2HGDufkweQSn30Eiev7x9LL0oyFrBqk1PXOnB9IEgKg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@types/babel__core": { "version": "7.20.0", @@ -14609,6 +13591,7 @@ "resolved": "https://registry.npmjs.org/@types/base64-stream/-/base64-stream-1.0.3.tgz", "integrity": "sha512-cbLPBuRLRq7l2+syfKT3byML5C/UNK/6hkcDDMxwttE+NtpXvQxhgXj0PNbcLCwUCmfEWMm3eRBpDmpieLegDQ==", "dev": true, + "license": "MIT", "dependencies": { "@types/node": "*" } @@ -14629,9 +13612,10 @@ } }, "node_modules/@types/bonjour": { - "version": "3.5.11", - "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.11.tgz", - "integrity": "sha512-isGhjmBtLIxdHBDl2xGwUzEM8AOyOvWsADWq7rqirdi/ZQoHnLWErHvsThcEzTX8juDRiZtzp2Qkv5bgNh6mAg==", + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.13.tgz", + "integrity": "sha512-z9fJ5Im06zvUL548KvYNecEVlA7cVDkGUi6kZusb04mpyEFKCIZJvloCcmpmLaIahDpOQGHaHmG6imtPMmPXGQ==", + "license": "MIT", "dependencies": { "@types/node": "*" } @@ -14656,63 +13640,40 @@ } }, "node_modules/@types/connect-history-api-fallback": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.1.tgz", - "integrity": "sha512-iaQslNbARe8fctL5Lk+DsmgWOM83lM+7FzP0eQUJs1jd3kBE8NWqBTIT2S8SqQOJjxvt2eyIjpOuYeRXq2AdMw==", + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.4.tgz", + "integrity": "sha512-n6Cr2xS1h4uAulPRdlw6Jl6s1oG8KrVilPN2yUITEs+K48EzMJJ3W1xy8K5eWuFvjp3R74AOIGSmp2UfBJ8HFw==", + "license": "MIT", "dependencies": { "@types/express-serve-static-core": "*", "@types/node": "*" } }, - "node_modules/@types/cross-spawn": { - "version": "6.0.6", - "resolved": "https://registry.npmjs.org/@types/cross-spawn/-/cross-spawn-6.0.6.tgz", - "integrity": "sha512-fXRhhUkG4H3TQk5dBhQ7m/JDdSNHKwR2BBia62lhwEIq9xGiQKLxd6LymNhn47SjXhsUEPmxi+PKw2OkW4LLjA==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/detect-port": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/@types/detect-port/-/detect-port-1.3.5.tgz", - "integrity": "sha512-Rf3/lB9WkDfIL9eEKaSYKc+1L/rNVYBjThk22JTqQw0YozXarX8YljFAz+HCoC6h4B4KwCMsBPZHaFezwT4BNA==", - "dev": true - }, "node_modules/@types/doctrine": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/@types/doctrine/-/doctrine-0.0.3.tgz", - "integrity": "sha512-w5jZ0ee+HaPOaX25X2/2oGR/7rgAQSYII7X7pp0m9KgBfMP7uKfMfTvcpl5Dj+eDBbpxKGiqE+flqDr6XTd2RA==", - "dev": true - }, - "node_modules/@types/ejs": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/@types/ejs/-/ejs-3.1.5.tgz", - "integrity": "sha512-nv+GSx77ZtXiJzwKdsASqi+YQ5Z7vwHsTP0JY2SiQgjGckkBRKZnk8nIM+7oUZ1VCtuTz0+By4qVR7fqzp/Dfg==", - "dev": true - }, - "node_modules/@types/emscripten": { - "version": "1.39.10", - "resolved": "https://registry.npmjs.org/@types/emscripten/-/emscripten-1.39.10.tgz", - "integrity": "sha512-TB/6hBkYQJxsZHSqyeuO1Jt0AB/bW6G7rHt9g7lML7SOF6lbgcHvw/Lr+69iqN0qxgXLhWKScAon73JNnptuDw==", - "dev": true - }, - "node_modules/@types/escodegen": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/@types/escodegen/-/escodegen-0.0.6.tgz", - "integrity": "sha512-AjwI4MvWx3HAOaZqYsjKWyEObT9lcVV0Y0V8nXo6cXzN8ZiMxVhf6F3d/UNvXVGKrEzL/Dluc5p+y9GkzlTWig==", + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/@types/doctrine/-/doctrine-0.0.9.tgz", + "integrity": "sha512-eOIHzCUSH7SMfonMG1LsC2f8vxBFtho6NGBznK41R84YzPuvSBzrhEps33IsQiOW9+VL6NQ9DbjQJznk/S4uRA==", "dev": true }, "node_modules/@types/eslint": { "version": "8.56.9", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.9.tgz", "integrity": "sha512-W4W3KcqzjJ0sHg2vAq9vfml6OhsJ53TcUjUqfzzZf/EChUtwspszj/S0pzMxnfRcO55/iGq47dscXw71Fxc4Zg==", - "dev": true, "dependencies": { "@types/estree": "*", "@types/json-schema": "*" } }, + "node_modules/@types/eslint-scope": { + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", + "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", + "license": "MIT", + "dependencies": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, "node_modules/@types/estree": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", @@ -14751,17 +13712,12 @@ "integrity": "sha512-g39Vp8ZJ3D0gXhhkhDidVvdy4QajkF7/PV6HGn23FMaMqE/tLC1JNHUeQ7SshKLsBjucakZsXBLkWULbGLdL5g==", "dev": true }, - "node_modules/@types/find-cache-dir": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/@types/find-cache-dir/-/find-cache-dir-3.2.1.tgz", - "integrity": "sha512-frsJrz2t/CeGifcu/6uRo4b+SzAwT4NYCVPu1GN8IB9XTzrpPkGuV0tmh9mN+/L0PklAlsC3u5Fxt0ju00LXIw==", - "dev": true - }, "node_modules/@types/find-root": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@types/find-root/-/find-root-1.1.2.tgz", "integrity": "sha512-lGuMq71TL466jtCpvh7orGd+mrdBmo2h8ozvtOOTbq3ByfWpuN+UVxv4sOv3YpsD4NhW2k6ESGhnT/FIg4Ouzw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@types/glob": { "version": "7.1.1", @@ -14861,6 +13817,7 @@ "resolved": "https://registry.npmjs.org/@types/jsftp/-/jsftp-2.1.3.tgz", "integrity": "sha512-R5rP70tkPnH+rhz0s6VuMKqMrkA7ET1AfU3FDGKNhYBgU/XTIbzsYNCgTByo60eitl0mI5OoEA9+MW/t/5iVzA==", "dev": true, + "license": "MIT", "dependencies": { "@types/node": "*" } @@ -14888,6 +13845,7 @@ "resolved": "https://registry.npmjs.org/@types/klaw/-/klaw-3.0.4.tgz", "integrity": "sha512-0M5F/WMU9yu2MyRued1VTQvUSwZ3siqYsX6MU7JF7VXRF5RzL0FXWFUrmdrWuGDWmuN6W+SyLhhg1Wp/sXkjtg==", "dev": true, + "license": "MIT", "dependencies": { "@types/node": "*" } @@ -14896,18 +13854,20 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/@types/lockfile/-/lockfile-1.0.2.tgz", "integrity": "sha512-jD5VbvhfMhaYN4M3qPJuhMVUg3Dfc4tvPvLEAXn6GXbs/ajDFtCQahX37GIE65ipTI3I+hEvNaXS3MYAn9Ce3Q==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@types/lodash": { "version": "4.14.199", "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.199.tgz", "integrity": "sha512-Vrjz5N5Ia4SEzWWgIVwnHNEnb1UE1XMkvY5DGXrAeOGE9imk0hgTHh5GyDjLDJi9OTCn9oo9dXH1uToK1VRfrg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@types/mdx": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@types/mdx/-/mdx-2.0.6.tgz", - "integrity": "sha512-sVcwEG10aFU2KcM7cIA0M410UPv/DesOPyG8zMVk0QUDexHA3lYmGucpEpZ2dtWWhi2ip3CG+5g/iH0PwoW4Fw==", + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/@types/mdx/-/mdx-2.0.13.tgz", + "integrity": "sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw==", "dev": true }, "node_modules/@types/method-override": { @@ -14915,6 +13875,7 @@ "resolved": "https://registry.npmjs.org/@types/method-override/-/method-override-0.0.33.tgz", "integrity": "sha512-H6hK7AZdUOCmboTTUlhfDG3uT0XDljjrk3vIb+GJ3ylkogXu5s/NncGB85r3rtCz6sxZBWF62dlf7I04sIQA5A==", "dev": true, + "license": "MIT", "dependencies": { "@types/express": "*" } @@ -14922,12 +13883,7 @@ "node_modules/@types/mime": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-2.0.3.tgz", - "integrity": "sha512-Jus9s4CDbqwocc5pOAnh8ShfrnMcPHuJYzVcSUU7lrh8Ni5HuIqX3oilL86p3dlTrk0LzHRCgA/GQ7uNCw6l2Q==" - }, - "node_modules/@types/mime-types": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@types/mime-types/-/mime-types-2.1.4.tgz", - "integrity": "sha512-lfU4b34HOri+kAY5UheuFMWPDOI+OPceBSHZKp69gEyTL/mmJ4cnU6Y/rlme3UL3GyOn6Y42hyIEw0/q8sWx5w==", + "integrity": "sha512-Jus9s4CDbqwocc5pOAnh8ShfrnMcPHuJYzVcSUU7lrh8Ni5HuIqX3oilL86p3dlTrk0LzHRCgA/GQ7uNCw6l2Q==", "dev": true }, "node_modules/@types/minimatch": { @@ -14949,34 +13905,35 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/@types/mv/-/mv-2.1.2.tgz", "integrity": "sha512-IvAjPuiQ2exDicnTrMidt1m+tj3gZ60BM0PaoRsU0m9Cn+lrOyemuO9Tf8CvHFmXlxMjr1TVCfadi9sfwbSuKg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@types/ncp": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/@types/ncp/-/ncp-2.0.6.tgz", "integrity": "sha512-A5CcYVkrcbRD+5ghRzn+LjEYUNT+YpjcPfnpYte6fBNfq3rXJRYA03cATV0XfVbSnOZa2Yp5sXYI1fIkjAF/UA==", "dev": true, + "license": "MIT", "dependencies": { "@types/node": "*" } }, "node_modules/@types/node": { - "version": "18.19.59", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.59.tgz", - "integrity": "sha512-vizm2EqwV/7Zay+A6J3tGl9Lhr7CjZe2HmWS988sefiEmsyP9CeXEleho6i4hJk/8UtZAo0bWN4QPZZr83RxvQ==", + "version": "20.17.10", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.10.tgz", + "integrity": "sha512-/jrvh5h6NXhEauFFexRin69nA0uHJ5gwk4iDivp/DeoEua3uwCUto6PC86IpRITBOs4+6i2I56K5x5b6WYGXHA==", "license": "MIT", "dependencies": { - "undici-types": "~5.26.4" + "undici-types": "~6.19.2" } }, - "node_modules/@types/node-fetch": { - "version": "2.6.11", - "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.11.tgz", - "integrity": "sha512-24xFj9R5+rfQJLRyM56qh+wnVSYhyXC2tkoBndtY0U+vubqNsYXGjufB2nn8Q6gt0LrARwL6UBtMCSVCwl4B1g==", - "dev": true, + "node_modules/@types/node-forge": { + "version": "1.3.11", + "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.11.tgz", + "integrity": "sha512-FQx220y22OKNTqaByeBGqHWYz4cl94tpcxeFdvBo3wjG6XPBuZ0BNgNZRV5J5TFmmcsJ4IzsLkmGRiQbnYsBEQ==", + "license": "MIT", "dependencies": { - "@types/node": "*", - "form-data": "^4.0.0" + "@types/node": "*" } }, "node_modules/@types/normalize-package-data": { @@ -15010,7 +13967,8 @@ "version": "0.0.31", "resolved": "https://registry.npmjs.org/@types/pluralize/-/pluralize-0.0.31.tgz", "integrity": "sha512-MQh69PPwFlYAL2qz/Mw5Zc34VTdt7pTck0Xbb6pbPSzdt5oaLB87iyJJxEMS5Dco/s7lXHunEezAvQurZZdrsQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@types/prettier": { "version": "2.4.4", @@ -15018,12 +13976,6 @@ "integrity": "sha512-ReVR2rLTV1kvtlWFyuot+d1pkpG2Fw/XKE3PDAdj57rbM97ttSp9JZ2UsP+2EHTylra9cUf6JA7tGwW1INzUrA==", "dev": true }, - "node_modules/@types/pretty-hrtime": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@types/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", - "integrity": "sha512-nj39q0wAIdhwn7DGUyT9irmsKK1tV0bd5WFEhgpqNTMFZ8cE+jieuTphCW0tfdm47S2zVT5mr09B28b1chmQMA==", - "dev": true - }, "node_modules/@types/prop-types": { "version": "15.7.4", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.4.tgz", @@ -15077,10 +14029,11 @@ } }, "node_modules/@types/readdir-glob": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@types/readdir-glob/-/readdir-glob-1.1.2.tgz", - "integrity": "sha512-vwAYrNN/8yhp/FJRU6HUSD0yk6xfoOS8HrZa8ZL7j+X8hJpaC1hTcAiXX2IxaAkkvrz9mLyoEhYZTE3cEYvA9Q==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@types/readdir-glob/-/readdir-glob-1.1.5.tgz", + "integrity": "sha512-raiuEPUYqXu+nvtY2Pe8s8FEmZ3x5yAH4VkLdihcPdalvsHltomrRC9BzuStrJ9yk06470hS0Crw0f1pXqD+Hg==", "dev": true, + "license": "MIT", "dependencies": { "@types/node": "*" } @@ -15106,9 +14059,10 @@ } }, "node_modules/@types/retry": { - "version": "0.12.1", - "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.1.tgz", - "integrity": "sha512-xoDlM2S4ortawSWORYqsdU+2rxdh4LRW9ytc3zmT37RIKQh6IHyKwwtKhKis9ah8ol07DCkZxPt8BBvPjC6v4g==" + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", + "license": "MIT" }, "node_modules/@types/scheduler": { "version": "0.16.2", @@ -15140,33 +14094,37 @@ "resolved": "https://registry.npmjs.org/@types/serve-favicon/-/serve-favicon-2.5.5.tgz", "integrity": "sha512-E/P1MhsGcalASnOVUPr9QQ4BIXyqQoGtLscG4fcMcEpZ7Z7tl6S4uSJnBJzWj7bj6rRZLIFOv0dR1YcepLNFFA==", "dev": true, + "license": "MIT", "dependencies": { "@types/express": "*" } }, "node_modules/@types/serve-index": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.1.tgz", - "integrity": "sha512-d/Hs3nWDxNL2xAczmOVZNj92YZCS6RGxfBPjKzuu/XirCgXdpKEb88dYNbrYGint6IVWLNP+yonwVAuRC0T2Dg==", + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.4.tgz", + "integrity": "sha512-qLpGZ/c2fhSs5gnYsQxtDEq3Oy8SXPClIXkW5ghvAvsNuVSA8k+gCONcUCS/UjLEYvYps+e8uBtfgXgvhwfNug==", + "license": "MIT", "dependencies": { "@types/express": "*" } }, "node_modules/@types/serve-static": { - "version": "1.15.2", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.2.tgz", - "integrity": "sha512-J2LqtvFYCzaj8pVYKw8klQXrLLk7TBZmQ4ShlcdkELFKGwGMfevMLneMMRkMgZxotOD9wg497LpC7O8PcvAmfw==", + "version": "1.15.7", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz", + "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==", + "license": "MIT", "dependencies": { "@types/http-errors": "*", - "@types/mime": "*", - "@types/node": "*" + "@types/node": "*", + "@types/send": "*" } }, "node_modules/@types/shell-quote": { "version": "1.7.2", "resolved": "https://registry.npmjs.org/@types/shell-quote/-/shell-quote-1.7.2.tgz", "integrity": "sha512-p3SZxGp6LXB6RPdMpJmquKjaxQlN/ijyBLTKGPg9IJK6J2g2sJsMmtXP9kNR+Axxi6MDKN2e/76HCOUmvkIpcw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@types/simple-peer": { "version": "9.11.6", @@ -15177,9 +14135,10 @@ } }, "node_modules/@types/sockjs": { - "version": "0.3.33", - "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.33.tgz", - "integrity": "sha512-f0KEEe05NvUnat+boPTZ0dgaLZ4SfSouXUgv5noUiefG2ajgKjmETo9ZJyuqsl7dfl2aHlLJUiki6B4ZYldiiw==", + "version": "0.3.36", + "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.36.tgz", + "integrity": "sha512-MK9V6NzAS1+Ud7JV9lJLFqW85VbC9dq3LmwZCuBe4wBDgKC0Kj/jd8Xl+nSviU+Qc3+m7umHHyHg//2KSa0a0Q==", + "license": "MIT", "dependencies": { "@types/node": "*" } @@ -15204,7 +14163,8 @@ "version": "8.1.1", "resolved": "https://registry.npmjs.org/@types/supports-color/-/supports-color-8.1.1.tgz", "integrity": "sha512-dPWnWsf+kzIG140B8z2w3fr5D03TLWbOAFQl45xUpI3vcizeXriNR5VYkWZ+WTMsUHqZ9Xlt3hrxGNANFyNQfw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@types/tapable": { "version": "1.0.5", @@ -15305,7 +14265,8 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/@types/which/-/which-3.0.0.tgz", "integrity": "sha512-ASCxdbsrwNfSMXALlC3Decif9rwDMu+80KGp5zI2RLRotfMsTv7fHL8W8VDp24wymzDyIFudhUeSCugrgRFfHQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@types/wrap-ansi": { "version": "3.0.0", @@ -15871,11 +14832,66 @@ "react": ">= 16.8.0" } }, + "node_modules/@vitest/expect": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.0.5.tgz", + "integrity": "sha512-yHZtwuP7JZivj65Gxoi8upUN2OzHTi3zVfjwdpu2WrvCZPLwsJ2Ey5ILIPccoW23dd/zQBlJ4/dhi7DWNyXCpA==", + "dev": true, + "dependencies": { + "@vitest/spy": "2.0.5", + "@vitest/utils": "2.0.5", + "chai": "^5.1.1", + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/pretty-format": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.0.5.tgz", + "integrity": "sha512-h8k+1oWHfwTkyTkb9egzwNMfJAEx4veaPSnMeKbVSjp4euqGSbQlm5+6VHwTr7u4FJslVVsUG5nopCaAYdOmSQ==", + "dev": true, + "dependencies": { + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.0.5.tgz", + "integrity": "sha512-c/jdthAhvJdpfVuaexSrnawxZz6pywlTPe84LUB2m/4t3rl2fTo9NFGBG4oWgaD+FTgDDV8hJ/nibT7IfH3JfA==", + "dev": true, + "dependencies": { + "tinyspy": "^3.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.0.5.tgz", + "integrity": "sha512-d8HKbqIcya+GR67mkZbrzhS5kKhtp8dQLcmRZLGTscGVg7yImT82cIrhtn2L8+VujWcy6KZweApgNmPsTAO/UQ==", + "dev": true, + "dependencies": { + "@vitest/pretty-format": "2.0.5", + "estree-walker": "^3.0.3", + "loupe": "^3.1.1", + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, "node_modules/@wdio/config": { "version": "8.16.20", "resolved": "https://registry.npmjs.org/@wdio/config/-/config-8.16.20.tgz", "integrity": "sha512-JFD7aYs3nGF2kNhc0eV03mWFQMJku42NCBl+aedb1jzP3z6tBWV3n1a0ETS4MTLps8lFXBDZWvWEnl+ZvVrHZw==", "dev": true, + "license": "MIT", "dependencies": { "@wdio/logger": "8.16.17", "@wdio/types": "8.16.12", @@ -15895,6 +14911,7 @@ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "dev": true, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" } @@ -15904,6 +14921,7 @@ "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-6.0.0.tgz", "integrity": "sha512-Fv96DCsdOgB6mdGl67MT5JaTNKRzrzill5OH5s8bjYJXVlcXyPYGyPsUkWyGV5p1TXI5esYIYMMeDJL0hEIwaA==", "dev": true, + "license": "MIT", "engines": { "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, @@ -15916,6 +14934,7 @@ "resolved": "https://registry.npmjs.org/find-up/-/find-up-6.3.0.tgz", "integrity": "sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==", "dev": true, + "license": "MIT", "dependencies": { "locate-path": "^7.1.0", "path-exists": "^5.0.0" @@ -15953,6 +14972,7 @@ "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz", "integrity": "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==", "dev": true, + "license": "ISC", "dependencies": { "lru-cache": "^10.0.1" }, @@ -15960,27 +14980,12 @@ "node": "^16.14.0 || >=18.0.0" } }, - "node_modules/@wdio/config/node_modules/jackspeak": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", - "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" - } - }, "node_modules/@wdio/config/node_modules/json-parse-even-better-errors": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.2.tgz", "integrity": "sha512-fi0NG4bPjCHunUJffmLd0gxssIgkNmArMvis4iNah6Owg1MCJjWhEcDLmsK6iGkJq3tHwbDkTlce70/tmXN4cQ==", "dev": true, + "license": "MIT", "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } @@ -15990,6 +14995,7 @@ "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-2.0.4.tgz", "integrity": "sha512-wM1+Z03eypVAVUCE7QdSqpVIvelbOakn1M0bPDoA4SGWPx3sNDVUiMo3L6To6WWGClB7VyXnhQ4Sn7gxiJbE6A==", "dev": true, + "license": "MIT", "engines": { "node": "^12.20.0 || ^14.13.1 || >=16.0.0" } @@ -15999,6 +15005,7 @@ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz", "integrity": "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==", "dev": true, + "license": "MIT", "dependencies": { "p-locate": "^6.0.0" }, @@ -16009,13 +15016,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@wdio/config/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true, - "license": "ISC" - }, "node_modules/@wdio/config/node_modules/minimatch": { "version": "9.0.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", @@ -16062,6 +15062,7 @@ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", "dev": true, + "license": "MIT", "dependencies": { "yocto-queue": "^1.0.0" }, @@ -16077,6 +15078,7 @@ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz", "integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==", "dev": true, + "license": "MIT", "dependencies": { "p-limit": "^4.0.0" }, @@ -16092,6 +15094,7 @@ "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-7.1.1.tgz", "integrity": "sha512-SgOTCX/EZXtZxBE5eJ97P4yGM5n37BwRU+YMsH4vNzFqJV/oWFXXCmwFlgWUM4PrakybVOueJJ6pwHqSVhTFDw==", "dev": true, + "license": "MIT", "dependencies": { "@babel/code-frame": "^7.21.4", "error-ex": "^1.3.2", @@ -16111,6 +15114,7 @@ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-3.13.1.tgz", "integrity": "sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g==", "dev": true, + "license": "(MIT OR CC0-1.0)", "engines": { "node": ">=14.16" }, @@ -16123,6 +15127,7 @@ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz", "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==", "dev": true, + "license": "MIT", "engines": { "node": "^12.20.0 || ^14.13.1 || >=16.0.0" } @@ -16132,6 +15137,7 @@ "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-8.1.0.tgz", "integrity": "sha512-PORM8AgzXeskHO/WEv312k9U03B8K9JSiWF/8N9sUuFjBa+9SF2u6K7VClzXwDXab51jCd8Nd36CNM+zR97ScQ==", "dev": true, + "license": "MIT", "dependencies": { "@types/normalize-package-data": "^2.4.1", "normalize-package-data": "^6.0.0", @@ -16150,6 +15156,7 @@ "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-10.1.0.tgz", "integrity": "sha512-aNtBq4jR8NawpKJQldrQcSW9y/d+KWH4v24HWkHljOZ7H0av+YTGANBzRh9A5pw7v/bLVsLVPpOhJ7gHNVy8lA==", "dev": true, + "license": "MIT", "dependencies": { "find-up": "^6.3.0", "read-pkg": "^8.1.0", @@ -16163,9 +15170,9 @@ } }, "node_modules/@wdio/config/node_modules/type-fest": { - "version": "4.26.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.26.1.tgz", - "integrity": "sha512-yOGpmOAL7CkKe/91I5O3gPICmJNLJ1G4zFYVAsRHg7M64biSnPtRj0WNQt++bRkjYOqjWXrhnUw1utzmVErAdg==", + "version": "4.30.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.30.0.tgz", + "integrity": "sha512-G6zXWS1dLj6eagy6sVhOMQiLtJdxQBHIA9Z6HFUNLOlr6MFOgzV8wvmidtPONfPtEUv0uZsy77XJNzTAfwPDaA==", "dev": true, "license": "(MIT OR CC0-1.0)", "engines": { @@ -16193,6 +15200,7 @@ "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-8.16.17.tgz", "integrity": "sha512-zeQ41z3T+b4IsrriZZipayXxLNDuGsm7TdExaviNGojPVrIsQUCSd/FvlLHM32b7ZrMyInHenu/zx1cjAZO71g==", "dev": true, + "license": "MIT", "dependencies": { "chalk": "^5.1.2", "loglevel": "^1.6.0", @@ -16221,6 +15229,7 @@ "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", "dev": true, + "license": "MIT", "engines": { "node": "^12.17.0 || ^14.13 || >=16.0.0" }, @@ -16233,6 +15242,7 @@ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", "dev": true, + "license": "MIT", "dependencies": { "ansi-regex": "^6.0.1" }, @@ -16247,13 +15257,15 @@ "version": "8.16.5", "resolved": "https://registry.npmjs.org/@wdio/protocols/-/protocols-8.16.5.tgz", "integrity": "sha512-u9I57hIqmcOgrDH327ZCc2GTXv2YFN5bg6UaA3OUoJU7eJgGYHFB6RrjiNjLXer68iIx07wwVM70V/1xzijd3Q==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@wdio/repl": { "version": "8.10.1", "resolved": "https://registry.npmjs.org/@wdio/repl/-/repl-8.10.1.tgz", "integrity": "sha512-VZ1WFHTNKjR8Ga97TtV2SZM6fvRjWbYI2i/f4pJB4PtusorKvONAMJf2LQcUBIyzbVobqr7KSrcjmSwRolI+yw==", "dev": true, + "license": "MIT", "dependencies": { "@types/node": "^20.1.0" }, @@ -16261,28 +15273,12 @@ "node": "^16.13 || >=18" } }, - "node_modules/@wdio/repl/node_modules/@types/node": { - "version": "20.16.15", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.16.15.tgz", - "integrity": "sha512-DV58qQz9dBMqVVn+qnKwGa51QzCD4YM/tQM16qLKxdf5tqz5W4QwtrMzjSTbabN1cFTSuyxVYBy+QWHjWW8X/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~6.19.2" - } - }, - "node_modules/@wdio/repl/node_modules/undici-types": { - "version": "6.19.8", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", - "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", - "dev": true, - "license": "MIT" - }, "node_modules/@wdio/types": { "version": "8.16.12", "resolved": "https://registry.npmjs.org/@wdio/types/-/types-8.16.12.tgz", "integrity": "sha512-TjCZJ3P9ual21G0dRv0lC9QgHGd3Igv+guEINevBKf/oD4/N84PvQ2eZG1nSbZ3xh8X/dvi+O64A6VEv43gx2w==", "dev": true, + "license": "MIT", "dependencies": { "@types/node": "^20.1.0" }, @@ -16290,28 +15286,12 @@ "node": "^16.13 || >=18" } }, - "node_modules/@wdio/types/node_modules/@types/node": { - "version": "20.16.15", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.16.15.tgz", - "integrity": "sha512-DV58qQz9dBMqVVn+qnKwGa51QzCD4YM/tQM16qLKxdf5tqz5W4QwtrMzjSTbabN1cFTSuyxVYBy+QWHjWW8X/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~6.19.2" - } - }, - "node_modules/@wdio/types/node_modules/undici-types": { - "version": "6.19.8", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", - "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", - "dev": true, - "license": "MIT" - }, "node_modules/@wdio/utils": { "version": "8.16.17", "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-8.16.17.tgz", "integrity": "sha512-jDyOrxbQRDJO0OPt9UBgnwpUIKqtRn4+R0gR5VSDrIG/in5ZZg28yer8urrIVY4yY9ut5r/22VaMHZI9LEXF5w==", "dev": true, + "license": "MIT", "dependencies": { "@puppeteer/browsers": "^1.6.0", "@wdio/logger": "8.16.17", @@ -16332,32 +15312,12 @@ "node": "^16.13 || >=18" } }, - "node_modules/@wdio/utils/node_modules/@puppeteer/browsers": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.9.1.tgz", - "integrity": "sha512-PuvK6xZzGhKPvlx3fpfdM2kYY3P/hB1URtK8wA7XUJ6prn6pp22zvJHu48th0SGcHL9SutbPHrFuQgfXTFobWA==", - "dev": true, - "dependencies": { - "debug": "4.3.4", - "extract-zip": "2.0.1", - "progress": "2.0.3", - "proxy-agent": "6.3.1", - "tar-fs": "3.0.4", - "unbzip2-stream": "1.4.3", - "yargs": "17.7.2" - }, - "bin": { - "browsers": "lib/cjs/main-cli.js" - }, - "engines": { - "node": ">=16.3.0" - } - }, "node_modules/@wdio/utils/node_modules/@sindresorhus/is": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-5.6.0.tgz", "integrity": "sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==", "dev": true, + "license": "MIT", "engines": { "node": ">=14.16" }, @@ -16370,6 +15330,7 @@ "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-5.0.1.tgz", "integrity": "sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==", "dev": true, + "license": "MIT", "dependencies": { "defer-to-connect": "^2.0.1" }, @@ -16377,23 +15338,12 @@ "node": ">=14.16" } }, - "node_modules/@wdio/utils/node_modules/agent-base": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", - "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", - "dev": true, - "dependencies": { - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, "node_modules/@wdio/utils/node_modules/cacheable-lookup": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-7.0.0.tgz", "integrity": "sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w==", "dev": true, + "license": "MIT", "engines": { "node": ">=14.16" } @@ -16403,6 +15353,7 @@ "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-10.2.14.tgz", "integrity": "sha512-zkDT5WAF4hSSoUgyfg5tFIxz8XQK+25W/TLVojJTMKBaxevLBBtLxgqguAuVQB8PVW79FVjHcU+GJ9tVbDZ9mQ==", "dev": true, + "license": "MIT", "dependencies": { "@types/http-cache-semantics": "^4.0.2", "get-stream": "^6.0.1", @@ -16416,37 +15367,12 @@ "node": ">=14.16" } }, - "node_modules/@wdio/utils/node_modules/cacheable-request/node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@wdio/utils/node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/@wdio/utils/node_modules/decamelize": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-6.0.0.tgz", "integrity": "sha512-Fv96DCsdOgB6mdGl67MT5JaTNKRzrzill5OH5s8bjYJXVlcXyPYGyPsUkWyGV5p1TXI5esYIYMMeDJL0hEIwaA==", "dev": true, + "license": "MIT", "engines": { "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, @@ -16454,37 +15380,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@wdio/utils/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "node_modules/@wdio/utils/node_modules/extract-zip": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", - "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", - "dev": true, - "dependencies": { - "debug": "^4.1.1", - "get-stream": "^5.1.0", - "yauzl": "^2.10.0" - }, - "bin": { - "extract-zip": "cli.js" - }, - "engines": { - "node": ">= 10.17.0" - }, - "optionalDependencies": { - "@types/yauzl": "^2.9.1" - } - }, "node_modules/@wdio/utils/node_modules/get-port": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/get-port/-/get-port-7.1.0.tgz", "integrity": "sha512-QB9NKEeDg3xxVwCCwJQ9+xycaz6pBB6iQ76wiWMl1927n0Kir6alPiP+yuiICLLU4jpMe08dXfpebuQppFA2zw==", "dev": true, + "license": "MIT", "engines": { "node": ">=16" }, @@ -16492,11 +15393,25 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@wdio/utils/node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/@wdio/utils/node_modules/got": { "version": "13.0.0", "resolved": "https://registry.npmjs.org/got/-/got-13.0.0.tgz", "integrity": "sha512-XfBk1CxOOScDcMr9O1yKkNaQyy865NbYs+F7dr4H0LZMVgCj2Le59k6PqbNHoL5ToeaEQUYh6c6yMfVcc6SJxA==", "dev": true, + "license": "MIT", "dependencies": { "@sindresorhus/is": "^5.2.0", "@szmarczak/http-timer": "^5.0.1", @@ -16517,36 +15432,12 @@ "url": "https://github.com/sindresorhus/got?sponsor=1" } }, - "node_modules/@wdio/utils/node_modules/got/node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@wdio/utils/node_modules/http-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", - "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", - "dev": true, - "dependencies": { - "agent-base": "^7.1.0", - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, "node_modules/@wdio/utils/node_modules/http2-wrapper": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-2.2.1.tgz", "integrity": "sha512-V5nVw1PAOgfI3Lmeaj2Exmeg7fenjhRUgz1lPSezy1CuhPYbgQtbQj4jZfEAEMlaL+vupsvhjqCyjzob0yxsmQ==", "dev": true, + "license": "MIT", "dependencies": { "quick-lru": "^5.1.1", "resolve-alpn": "^1.2.0" @@ -16555,34 +15446,12 @@ "node": ">=10.19.0" } }, - "node_modules/@wdio/utils/node_modules/https-proxy-agent": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", - "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.0.2", - "debug": "4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/@wdio/utils/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/@wdio/utils/node_modules/lowercase-keys": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-3.0.0.tgz", "integrity": "sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==", "dev": true, + "license": "MIT", "engines": { "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, @@ -16590,20 +15459,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@wdio/utils/node_modules/lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", - "dev": true, - "engines": { - "node": ">=12" - } - }, "node_modules/@wdio/utils/node_modules/mimic-response": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-4.0.0.tgz", "integrity": "sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg==", "dev": true, + "license": "MIT", "engines": { "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, @@ -16616,6 +15477,7 @@ "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.0.1.tgz", "integrity": "sha512-IO9QvjUMWxPQQhs60oOu10CRkWCiZzSUkzbXGGV9pviYl1fXYcvkzQ5jV9z8Y6un8ARoVRl4EtC6v6jNqbaJ/w==", "dev": true, + "license": "MIT", "engines": { "node": ">=14.16" }, @@ -16628,45 +15490,17 @@ "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-3.0.0.tgz", "integrity": "sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==", "dev": true, + "license": "MIT", "engines": { "node": ">=12.20" } }, - "node_modules/@wdio/utils/node_modules/proxy-agent": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.3.1.tgz", - "integrity": "sha512-Rb5RVBy1iyqOtNl15Cw/llpeLH8bsb37gM1FUfKQ+Wck6xHlbAhWGUFiTRHtkjqGTA5pSHz6+0hrPW/oECihPQ==", - "dev": true, - "dependencies": { - "agent-base": "^7.0.2", - "debug": "^4.3.4", - "http-proxy-agent": "^7.0.0", - "https-proxy-agent": "^7.0.2", - "lru-cache": "^7.14.1", - "pac-proxy-agent": "^7.0.1", - "proxy-from-env": "^1.1.0", - "socks-proxy-agent": "^8.0.2" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/@wdio/utils/node_modules/pump": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz", - "integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==", - "dev": true, - "license": "MIT", - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, "node_modules/@wdio/utils/node_modules/quick-lru": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", "dev": true, + "license": "MIT", "engines": { "node": ">=10" }, @@ -16679,6 +15513,7 @@ "resolved": "https://registry.npmjs.org/responselike/-/responselike-3.0.0.tgz", "integrity": "sha512-40yHxbNcl2+rzXvZuVkrYohathsSJlMTXKryG5y8uciHv1+xDLHQpgjG64JUO9nrEq2jGLH6IZ8BcZyw3wrweg==", "dev": true, + "license": "MIT", "dependencies": { "lowercase-keys": "^3.0.0" }, @@ -16689,119 +15524,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@wdio/utils/node_modules/socks-proxy-agent": { - "version": "8.0.4", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.4.tgz", - "integrity": "sha512-GNAq/eg8Udq2x0eNiFkr9gRg5bA7PXEWagQdeRX4cPSG+X/8V38v637gim9bjFptMk1QWsCTr0ttrJEiXbNnRw==", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.1", - "debug": "^4.3.4", - "socks": "^2.8.3" - }, - "engines": { - "node": ">= 14" - } - }, "node_modules/@wdio/utils/node_modules/split2": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", "dev": true, + "license": "ISC", "engines": { "node": ">= 10.x" } }, - "node_modules/@wdio/utils/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@wdio/utils/node_modules/tar-fs": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.4.tgz", - "integrity": "sha512-5AFQU8b9qLfZCX9zp2duONhPmZv0hGYiBPJsyUdqMjzq/mqVpy/rEUSeHk1+YitmxugaptgBh5oDGU3VsAJq4w==", - "dev": true, - "dependencies": { - "mkdirp-classic": "^0.5.2", - "pump": "^3.0.0", - "tar-stream": "^3.1.5" - } - }, - "node_modules/@wdio/utils/node_modules/tar-stream": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", - "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", - "dev": true, - "dependencies": { - "b4a": "^1.6.4", - "fast-fifo": "^1.2.0", - "streamx": "^2.15.0" - } - }, - "node_modules/@wdio/utils/node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/@wdio/utils/node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/@wdio/utils/node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "dev": true, - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@wdio/utils/node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true, - "engines": { - "node": ">=12" - } - }, "node_modules/@webassemblyjs/ast": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.9.0.tgz", @@ -16856,24 +15588,27 @@ } }, "node_modules/@webassemblyjs/helper-numbers": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz", - "integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", + "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", + "license": "MIT", "dependencies": { - "@webassemblyjs/floating-point-hex-parser": "1.11.6", - "@webassemblyjs/helper-api-error": "1.11.6", + "@webassemblyjs/floating-point-hex-parser": "1.13.2", + "@webassemblyjs/helper-api-error": "1.13.2", "@xtuc/long": "4.2.2" } }, "node_modules/@webassemblyjs/helper-numbers/node_modules/@webassemblyjs/floating-point-hex-parser": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz", - "integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==" + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", + "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", + "license": "MIT" }, "node_modules/@webassemblyjs/helper-numbers/node_modules/@webassemblyjs/helper-api-error": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz", - "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==" + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", + "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", + "license": "MIT" }, "node_modules/@webassemblyjs/helper-wasm-bytecode": { "version": "1.9.0", @@ -17418,6 +16153,10 @@ "resolved": "packages/undo-manager", "link": true }, + "node_modules/@wordpress/upload-media": { + "resolved": "packages/upload-media", + "link": true + }, "node_modules/@wordpress/url": { "resolved": "packages/url", "link": true @@ -17461,59 +16200,6 @@ "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==" }, - "node_modules/@yarnpkg/esbuild-plugin-pnp": { - "version": "3.0.0-rc.15", - "resolved": "https://registry.npmjs.org/@yarnpkg/esbuild-plugin-pnp/-/esbuild-plugin-pnp-3.0.0-rc.15.tgz", - "integrity": "sha512-kYzDJO5CA9sy+on/s2aIW0411AklfCi8Ck/4QDivOqsMKpStZA2SsR+X27VTggGwpStWaLrjJcDcdDMowtG8MA==", - "dev": true, - "dependencies": { - "tslib": "^2.4.0" - }, - "engines": { - "node": ">=14.15.0" - }, - "peerDependencies": { - "esbuild": ">=0.10.0" - } - }, - "node_modules/@yarnpkg/fslib": { - "version": "2.10.3", - "resolved": "https://registry.npmjs.org/@yarnpkg/fslib/-/fslib-2.10.3.tgz", - "integrity": "sha512-41H+Ga78xT9sHvWLlFOZLIhtU6mTGZ20pZ29EiZa97vnxdohJD2AF42rCoAoWfqUz486xY6fhjMH+DYEM9r14A==", - "dev": true, - "dependencies": { - "@yarnpkg/libzip": "^2.3.0", - "tslib": "^1.13.0" - }, - "engines": { - "node": ">=12 <14 || 14.2 - 14.9 || >14.10.0" - } - }, - "node_modules/@yarnpkg/fslib/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - }, - "node_modules/@yarnpkg/libzip": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@yarnpkg/libzip/-/libzip-2.3.0.tgz", - "integrity": "sha512-6xm38yGVIa6mKm/DUCF2zFFJhERh/QWp1ufm4cNUvxsONBmfPg8uZ9pZBdOmF6qFGr/HlT6ABBkCSx/dlEtvWg==", - "dev": true, - "dependencies": { - "@types/emscripten": "^1.39.6", - "tslib": "^1.13.0" - }, - "engines": { - "node": ">=12 <14 || 14.2 - 14.9 || >14.10.0" - } - }, - "node_modules/@yarnpkg/libzip/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - }, "node_modules/@yarnpkg/lockfile": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz", @@ -17521,23 +16207,37 @@ "dev": true }, "node_modules/@yarnpkg/parsers": { - "version": "3.0.0-rc.46", - "resolved": "https://registry.npmjs.org/@yarnpkg/parsers/-/parsers-3.0.0-rc.46.tgz", - "integrity": "sha512-aiATs7pSutzda/rq8fnuPwTglyVwjM22bNnK2ZgjrpAjQHSSl3lztd2f9evst1W/qnC58DRz7T7QndUDumAR4Q==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@yarnpkg/parsers/-/parsers-3.0.2.tgz", + "integrity": "sha512-/HcYgtUSiJiot/XWGLOlGxPYUG65+/31V8oqk17vZLW1xlCoR4PampyePljOxY2n8/3jz9+tIFzICsyGujJZoA==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "js-yaml": "^3.10.0", "tslib": "^2.4.0" }, "engines": { - "node": ">=14.15.0" + "node": ">=18.12.0" + } + }, + "node_modules/@zip.js/zip.js": { + "version": "2.7.54", + "resolved": "https://registry.npmjs.org/@zip.js/zip.js/-/zip.js-2.7.54.tgz", + "integrity": "sha512-qMrJVg2hoEsZJjMJez9yI2+nZlBUxgYzGV3mqcb2B/6T1ihXp0fWBDYlVHlHquuorgNUQP5a8qSmX6HF5rFJNg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "bun": ">=0.7.0", + "deno": ">=1.0.0", + "node": ">=16.5.0" } }, "node_modules/@zkochan/js-yaml": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/@zkochan/js-yaml/-/js-yaml-0.0.6.tgz", - "integrity": "sha512-nzvgl3VfhcELQ8LyVrYOru+UtAy1nrygk2+AGbTm8a5YcO6o8lSjAT+pfg3vJWxIoZKOUhrK6UU7xW/+00kQrg==", + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/@zkochan/js-yaml/-/js-yaml-0.0.7.tgz", + "integrity": "sha512-nrUSn7hzt7J6JWgWGz78ZYI8wj+gdIJdk0Ynjpp8l+trkn58Uqsf6RYrYkEK+3X18EX+TNdtJI0WxAtc+L84SQ==", "dev": true, + "license": "MIT", "dependencies": { "argparse": "^2.0.1" }, @@ -17549,7 +16249,8 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true + "dev": true, + "license": "Python-2.0" }, "node_modules/abab": { "version": "2.0.6", @@ -17557,10 +16258,14 @@ "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==" }, "node_modules/abbrev": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", - "dev": true + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz", + "integrity": "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } }, "node_modules/abort-controller": { "version": "3.0.0", @@ -17597,14 +16302,6 @@ "node": ">=0.4.0" } }, - "node_modules/acorn-import-attributes": { - "version": "1.9.5", - "resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz", - "integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==", - "peerDependencies": { - "acorn": "^8" - } - }, "node_modules/acorn-jsx": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.1.tgz", @@ -17613,29 +16310,12 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, - "node_modules/acorn-walk": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", - "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/add-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/add-stream/-/add-stream-1.0.0.tgz", "integrity": "sha512-qQLMr+8o0WC4FZGQTcJiKBVC59JylcPSrTtk6usvmIDFUOCKegapy1VHQwRbFMOFyb/inzUVqHs+eMYKDM1YeQ==", - "dev": true - }, - "node_modules/address": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/address/-/address-1.2.2.tgz", - "integrity": "sha512-4B/qKCfeE/ODUaAUpSwfzazo5x29WD4r3vXiWsB7I2mSDAihwEqKO+g8GELZUQSSAo5e1XTYh3ZVfLyxBc12nA==", "dev": true, - "engines": { - "node": ">= 10.0.0" - } + "license": "MIT" }, "node_modules/adm-zip": { "version": "0.5.9", @@ -17646,26 +16326,15 @@ } }, "node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "dependencies": { - "debug": "4" - }, - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/agentkeepalive": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.5.0.tgz", - "integrity": "sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew==", - "dev": true, + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.2.tgz", + "integrity": "sha512-JVzqkCNRT+VfqzzgPWDPnwvDheSAUdiMUn3NoLXpDJF5lRqeJqyC9iGsAxIOAW+mzIdq+uP1TvcX6bMtrH0agg==", + "license": "MIT", "dependencies": { - "humanize-ms": "^1.2.1" + "debug": "^4.3.4" }, "engines": { - "node": ">= 8.0.0" + "node": ">= 14" } }, "node_modules/aggregate-error": { @@ -17909,12 +16578,6 @@ "node": ">= 8" } }, - "node_modules/app-root-dir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/app-root-dir/-/app-root-dir-1.0.2.tgz", - "integrity": "sha512-jlpIfsOoNoafl92Sz//64uQHGSyMrD2vYG5d8o2a4qGvyNCvXur7bzIsWtAC/6flI2RYAp3kv8rsfBtaLm7w0g==", - "dev": true - }, "node_modules/app-root-path": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/app-root-path/-/app-root-path-2.2.1.tgz", @@ -18049,38 +16712,6 @@ "node": ">=8" } }, - "node_modules/appium/node_modules/cross-env": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", - "integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==", - "dev": true, - "dependencies": { - "cross-spawn": "^7.0.1" - }, - "bin": { - "cross-env": "src/bin/cross-env.js", - "cross-env-shell": "src/bin/cross-env-shell.js" - }, - "engines": { - "node": ">=10.14", - "npm": ">=6", - "yarn": ">=1" - } - }, - "node_modules/appium/node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, "node_modules/appium/node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -18281,15 +16912,6 @@ "node": ">=8" } }, - "node_modules/appium/node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/appium/node_modules/resolve-from": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", @@ -18333,27 +16955,6 @@ "node": ">=10" } }, - "node_modules/appium/node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/appium/node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/appium/node_modules/signal-exit": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", @@ -18392,21 +16993,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/appium/node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, "node_modules/appium/node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", @@ -18424,12 +17010,6 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/appium/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, "node_modules/appium/node_modules/yaml": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.1.tgz", @@ -18450,6 +17030,7 @@ "resolved": "https://registry.npmjs.org/archiver/-/archiver-6.0.1.tgz", "integrity": "sha512-CXGy4poOLBKptiZH//VlWdFuUC1RESbdZjGjILwBuZ73P7WkAUN0htfSfBq/7k6FRFlpu7bg4JOkj1vU9G6jcQ==", "dev": true, + "license": "MIT", "dependencies": { "archiver-utils": "^4.0.1", "async": "^3.2.4", @@ -18468,6 +17049,7 @@ "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-4.0.1.tgz", "integrity": "sha512-Q4Q99idbvzmgCTEAAhi32BkOyq8iVI5EwdO0PmBDSGIzzjYNdcFn7Q7k3OzbLy4kLUPXfJtG6fO2RjftXbobBg==", "dev": true, + "license": "MIT", "dependencies": { "glob": "^8.0.0", "graceful-fs": "^4.2.0", @@ -18485,6 +17067,7 @@ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "dev": true, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" } @@ -18493,7 +17076,9 @@ "version": "8.1.0", "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "deprecated": "Glob versions prior to v9 are no longer supported", "dev": true, + "license": "ISC", "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -18513,6 +17098,7 @@ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", "dev": true, + "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" }, @@ -18525,6 +17111,7 @@ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", "dev": true, + "license": "MIT", "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -18539,6 +17126,7 @@ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", "dev": true, + "license": "MIT", "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -18553,6 +17141,7 @@ "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", "dev": true, + "license": "MIT", "dependencies": { "b4a": "^1.6.4", "fast-fifo": "^1.2.0", @@ -18567,33 +17156,6 @@ "node": ">=14" } }, - "node_modules/are-we-there-yet": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz", - "integrity": "sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==", - "dev": true, - "dependencies": { - "delegates": "^1.0.0", - "readable-stream": "^3.6.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/are-we-there-yet/node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dev": true, - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/arg": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", @@ -18679,6 +17241,7 @@ "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-3.0.0.tgz", "integrity": "sha512-THtfYS6KtME/yIAhKjZ2ul7XI96lQGHRputJQHO80LAWQnuGP4iCIN8vdMRboGbIEYBwU33q8Tch1os2+X0kMg==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -18692,7 +17255,8 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/array-ify/-/array-ify-1.0.0.tgz", "integrity": "sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/array-includes": { "version": "3.1.6", @@ -18848,6 +17412,15 @@ "inherits": "2.0.3" } }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "engines": { + "node": ">=12" + } + }, "node_modules/assign-symbols": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", @@ -18978,43 +17551,6 @@ "integrity": "sha512-LEeSAWeh2Gfa2FtlQE1shxQ8zi5F9GHarrGKz08TMdODD5T4eH6BMsvtnhbWZ+XQn+Gb6om/917ucvRu7l7ukw==", "dev": true }, - "node_modules/autoprefixer": { - "version": "10.4.14", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.14.tgz", - "integrity": "sha512-FQzyfOsTlwVzjHxKEqRIAdJx9niO6VCBCoEwax/VLSoQF29ggECcPuBqUMZ+u8jCZOPSy8b8/8KnuFbp0SaFZQ==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/autoprefixer" - } - ], - "dependencies": { - "browserslist": "^4.21.5", - "caniuse-lite": "^1.0.30001464", - "fraction.js": "^4.2.0", - "normalize-range": "^0.1.2", - "picocolors": "^1.0.0", - "postcss-value-parser": "^4.2.0" - }, - "bin": { - "autoprefixer": "bin/autoprefixer" - }, - "engines": { - "node": "^10 || ^12 || >=14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/autoprefixer/node_modules/postcss-value-parser": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" - }, "node_modules/autosize": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/autosize/-/autosize-4.0.2.tgz", @@ -19032,9 +17568,10 @@ } }, "node_modules/axe-core": { - "version": "4.7.2", - "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.7.2.tgz", - "integrity": "sha512-zIURGIS1E1Q4pcrMjp+nnEh+16G56eG/MUllJH8yEvw7asDo7Ac9uhC9KIH5jzpITueEZolfYglnCGIuSBz39g==", + "version": "4.10.2", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.10.2.tgz", + "integrity": "sha512-RE3mdQ7P3FRSe7eqCWoeQ/Z9QXrtniSjp1wUjt5nRC3WIpz5rSCve6o3fsZ2aCpJtrZjSZgjwXAoTO5k4tEI0w==", + "license": "MPL-2.0", "engines": { "node": ">=4" } @@ -19044,6 +17581,7 @@ "resolved": "https://registry.npmjs.org/axios/-/axios-1.5.1.tgz", "integrity": "sha512-Q28iYCWzNHjAm+yEAot5QaAMxhMghWLFVf7rRdwhUI+c2jix2DUXjAHXVi+s1ibs3mjPO/cCgbA++3BjD0vP/A==", "dev": true, + "license": "MIT", "dependencies": { "follow-redirects": "^1.15.0", "form-data": "^4.0.0", @@ -19092,7 +17630,6 @@ "version": "9.2.1", "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-9.2.1.tgz", "integrity": "sha512-fqe8naHt46e0yIdkjUZYqddSXfej3AHajX+CSO5X7oy0EmPc6o5Xh+RClNoHjnieWz9AW4kZxW9yyFMhVB1QLA==", - "dev": true, "license": "MIT", "dependencies": { "find-cache-dir": "^4.0.0", @@ -19110,7 +17647,6 @@ "version": "8.17.1", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", - "dev": true, "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3", @@ -19127,7 +17663,6 @@ "version": "5.1.0", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", - "dev": true, "dependencies": { "fast-deep-equal": "^3.1.3" }, @@ -19139,7 +17674,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-4.0.0.tgz", "integrity": "sha512-9ZonPT4ZAK4a+1pUPVPZJapbi7O5qbbJPdYw/NOQWZZbVLdDTYM3A4R9z/DpAM08IDaFGsvPgiGZ82WEwUDWjg==", - "dev": true, "dependencies": { "common-path-prefix": "^3.0.0", "pkg-dir": "^7.0.0" @@ -19155,7 +17689,6 @@ "version": "6.3.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-6.3.0.tgz", "integrity": "sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==", - "dev": true, "dependencies": { "locate-path": "^7.1.0", "path-exists": "^5.0.0" @@ -19170,14 +17703,12 @@ "node_modules/babel-loader/node_modules/json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" }, "node_modules/babel-loader/node_modules/locate-path": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz", "integrity": "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==", - "dev": true, "dependencies": { "p-locate": "^6.0.0" }, @@ -19192,7 +17723,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", - "dev": true, "dependencies": { "yocto-queue": "^1.0.0" }, @@ -19207,7 +17737,6 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz", "integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==", - "dev": true, "dependencies": { "p-limit": "^4.0.0" }, @@ -19222,7 +17751,6 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz", "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==", - "dev": true, "engines": { "node": "^12.20.0 || ^14.13.1 || >=16.0.0" } @@ -19231,7 +17759,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-7.0.0.tgz", "integrity": "sha512-Ie9z/WINcxxLp27BKOCHGde4ITq9UklYKDzVo1nhk5sqGEXU3FpkwP5GM2voTGJkGd9B3Otl+Q4uwSOeSUtOBA==", - "dev": true, "dependencies": { "find-up": "^6.3.0" }, @@ -19246,7 +17773,6 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.2.0.tgz", "integrity": "sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw==", - "dev": true, "dependencies": { "@types/json-schema": "^7.0.9", "ajv": "^8.9.0", @@ -19265,7 +17791,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.1.1.tgz", "integrity": "sha512-b4JR1PFR10y1mKjhHY9LaGo6tmrgjit7hxVIeAmyMw3jegXR4dhYqLaQF5zMXZxY7tLpMyJeLjr1C4rLmkVe8g==", - "dev": true, "license": "MIT", "engines": { "node": ">=12.20" @@ -19274,12 +17799,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/babel-plugin-add-react-displayname": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/babel-plugin-add-react-displayname/-/babel-plugin-add-react-displayname-0.0.5.tgz", - "integrity": "sha512-LY3+Y0XVDYcShHHorshrDbt4KFWL4bSeniCtl4SYZbask+Syngk1uMPCeN9+nSiZo6zX5s0RTq/J9Pnaaf/KHw==", - "dev": true - }, "node_modules/babel-plugin-inline-json-import": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/babel-plugin-inline-json-import/-/babel-plugin-inline-json-import-0.3.2.tgz", @@ -19490,27 +18009,6 @@ "@babel/core": "^7.0.0" } }, - "node_modules/babel-runtime": { - "version": "6.25.0", - "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.25.0.tgz", - "integrity": "sha512-zeCYxDePWYAT/DfmQWIHsMSFW2vv45UIwIAMjGvQVsTd47RwsiRH0uK1yzyWZ7LDBKdhnGDPM6NYEO5CZyhPrg==", - "dependencies": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.10.0" - } - }, - "node_modules/babel-runtime/node_modules/core-js": { - "version": "2.6.12", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz", - "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==", - "deprecated": "core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js.", - "hasInstallScript": true - }, - "node_modules/babel-runtime/node_modules/regenerator-runtime": { - "version": "0.10.5", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz", - "integrity": "sha512-02YopEIhAgiBHWeoTiA8aitHDt8z6w+rQqNuIftlM+ZtvSl/brTouaU7DW6GO/cHtvxJvS4Hwv2ibKdxIRi24w==" - }, "node_modules/bail": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/bail/-/bail-1.0.3.tgz", @@ -19720,17 +18218,57 @@ "node": "*" } }, - "node_modules/binary": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/binary/-/binary-0.3.0.tgz", - "integrity": "sha512-D4H1y5KYwpJgK8wk1Cue5LLPgmwHKYSChkbspQg5JtVuR5ulGckxfR62H3AE9UDkdMC8yyXlqYihuz3Aqg2XZg==", + "node_modules/bin-links": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/bin-links/-/bin-links-4.0.4.tgz", + "integrity": "sha512-cMtq4W5ZsEwcutJrVId+a/tjt8GSbS+h0oNkdl6+6rBuEv8Ot33Bevj5KPm40t309zuhVic8NjpuL42QCiJWWA==", "dev": true, + "license": "ISC", "dependencies": { - "buffers": "~0.1.1", - "chainsaw": "~0.1.0" + "cmd-shim": "^6.0.0", + "npm-normalize-package-bin": "^3.0.0", + "read-cmd-shim": "^4.0.0", + "write-file-atomic": "^5.0.0" }, "engines": { - "node": "*" + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/bin-links/node_modules/npm-normalize-package-bin": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-3.0.1.tgz", + "integrity": "sha512-dMxCf+zZ+3zeQZXKxmyuCKlIDPGuv8EF940xbkC4kQVDTtqoh6rJFO+JTKSA6/Rwi0getWmtuy4Itup0AMcaDQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/bin-links/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/bin-links/node_modules/write-file-atomic": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz", + "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, "node_modules/binary-extensions": { @@ -19821,6 +18359,7 @@ "version": "1.20.1", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", + "license": "MIT", "dependencies": { "bytes": "3.1.2", "content-type": "~1.0.4", @@ -19844,6 +18383,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -19852,6 +18392,7 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", "dependencies": { "ms": "2.0.0" } @@ -19860,6 +18401,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -19868,6 +18410,7 @@ "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3" }, @@ -19879,6 +18422,7 @@ "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", "dependencies": { "ee-first": "1.1.1" }, @@ -19886,22 +18430,31 @@ "node": ">= 0.8" } }, + "node_modules/body-parser/node_modules/qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/bonjour-service": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.1.1.tgz", - "integrity": "sha512-Z/5lQRMOG9k7W+FkeGTNjh7htqn/2LMnfOvBZ8pynNZCM9MwkQkI3zeI4oz09uWdcgmgHugVvBqxGg4VQJ5PCg==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.3.0.tgz", + "integrity": "sha512-3YuAUiSkWykd+2Azjgyxei8OWf8thdn8AITIog2M4UICzoqfjlqr64WIjEXZllf/W6vK1goqleSR6brGomxQqA==", + "license": "MIT", "dependencies": { - "array-flatten": "^2.1.2", - "dns-equal": "^1.0.0", "fast-deep-equal": "^3.1.3", "multicast-dns": "^7.2.5" } }, - "node_modules/bonjour-service/node_modules/array-flatten": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz", - "integrity": "sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==" - }, "node_modules/boolbase": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", @@ -20145,30 +18698,12 @@ "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" }, - "node_modules/buffer-indexof-polyfill": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/buffer-indexof-polyfill/-/buffer-indexof-polyfill-1.0.2.tgz", - "integrity": "sha512-I7wzHwA3t1/lwXQh+A5PbNvJxgfo5r3xulgpYDB5zckTu/Z9oUK9biouBKQUjEqzaz3HnAT6TYoovmE+GqSf7A==", - "dev": true, - "engines": { - "node": ">=0.10" - } - }, "node_modules/buffer-xor": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", "integrity": "sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ==", "dev": true }, - "node_modules/buffers": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/buffers/-/buffers-0.1.1.tgz", - "integrity": "sha512-9q/rDEGSb/Qsvv2qvzIzdluL5k7AaJOTrw23z9reQthrbF7is4CtlT0DXyO1oei2DCp4uojjzQ7igaSHp1kAEQ==", - "dev": true, - "engines": { - "node": ">=0.2.0" - } - }, "node_modules/builtin-modules": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", @@ -20210,6 +18745,7 @@ "resolved": "https://registry.npmjs.org/byte-size/-/byte-size-8.1.1.tgz", "integrity": "sha512-tUkzZWK0M/qdoLEqikxBWe4kumyuwjl3HO6zHTr4yEI23EojPtLYXdG1+AQY7MN0cGyNDvEaJ8wiYQm6P2bPxg==", "dev": true, + "license": "MIT", "engines": { "node": ">=12.17" } @@ -20517,25 +19053,20 @@ "resolved": "https://registry.npmjs.org/ccount/-/ccount-1.0.3.tgz", "integrity": "sha512-Jt9tIBkRc9POUof7QA/VwWd+58fKkEEfI+/t1/eOlxKM7ZhrczNzMFefge7Ai+39y1pR/pP6cI19guHy3FSLmw==" }, - "node_modules/chainsaw": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/chainsaw/-/chainsaw-0.1.0.tgz", - "integrity": "sha512-75kWfWt6MEKNC8xYXIdRpDehRYY/tNSgwKaJq+dbbDcxORuVrrQ+SEHoWsniVn9XPYfP4gmdWIeDk/4YNp1rNQ==", + "node_modules/chai": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.1.2.tgz", + "integrity": "sha512-aGtmf24DW6MLHHG5gCx4zaI3uBq3KRtxeVs0DjFH6Z0rDNbsvTxFASFvdj79pxjxZ8/5u3PIiN3IwEIQkiiuPw==", "dev": true, "dependencies": { - "traverse": ">=0.3.0 <0.4" + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" }, "engines": { - "node": "*" - } - }, - "node_modules/chainsaw/node_modules/traverse": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.3.9.tgz", - "integrity": "sha512-iawgk0hLP3SxGKDfnDJf8wTz4p2qImnyihM5Hh/sGvQ3K37dPi/w8sRhdNIxYA1TwFwc5mDhIJq+O0RsvXBKdQ==", - "dev": true, - "engines": { - "node": "*" + "node": ">=12" } }, "node_modules/chalk": { @@ -20624,6 +19155,15 @@ "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==" }, + "node_modules/check-error": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", + "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", + "dev": true, + "engines": { + "node": ">= 16" + } + }, "node_modules/check-node-version": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/check-node-version/-/check-node-version-4.1.0.tgz", @@ -20773,6 +19313,8 @@ "version": "0.4.16", "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.16.tgz", "integrity": "sha512-7ZbXdWERxRxSwo3txsBjjmc/NLxqb1Bk30mRb0BMS4YIaiV6zvKZqL/UAH+DdqcDYayDWk2n/y8klkBDODrPvA==", + "dev": true, + "license": "Apache-2.0", "dependencies": { "mitt": "3.0.0" }, @@ -20780,6 +19322,13 @@ "devtools-protocol": "*" } }, + "node_modules/chromium-bidi/node_modules/mitt": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.0.tgz", + "integrity": "sha512-7dX2/10ITVyqh4aOSVI9gdape+t9l2/8QxHrFmUXu4EEUpdlxl6RudZUPZoc+zuY2hk1j7XxVroIVIan/pD/SQ==", + "dev": true, + "license": "MIT" + }, "node_modules/chromium-edge-launcher": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/chromium-edge-launcher/-/chromium-edge-launcher-1.0.0.tgz", @@ -20804,6 +19353,27 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/chromium-edge-launcher/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/chromium-edge-launcher/node_modules/is-wsl": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", @@ -20826,6 +19396,22 @@ "node": ">=10" } }, + "node_modules/chromium-edge-launcher/node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/ci-info": { "version": "3.9.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", @@ -20850,24 +19436,6 @@ "safe-buffer": "^5.0.1" } }, - "node_modules/citty": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/citty/-/citty-0.1.6.tgz", - "integrity": "sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==", - "dev": true, - "dependencies": { - "consola": "^3.2.3" - } - }, - "node_modules/citty/node_modules/consola": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/consola/-/consola-3.2.3.tgz", - "integrity": "sha512-I5qxpzLv+sJhTVEoLYNcTW+bThDCPsit0vLNKShZx6rLtpilNpmmeTPaeqJb9ZE9dV3DGaeby6Vuhrw38WjeyQ==", - "dev": true, - "engines": { - "node": "^14.18.0 || >=16.10.0" - } - }, "node_modules/cjs-module-lexer": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz", @@ -20958,50 +19526,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/cli-table3": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.3.tgz", - "integrity": "sha512-w5Jac5SykAeZJKntOxJCrm63Eg5/4dhMWIcuTbo9rpE+brgaSZo0RuNJZeOyMgsUdhDeojvgyQLmjI+K50ZGyg==", - "dev": true, - "dependencies": { - "string-width": "^4.2.0" - }, - "engines": { - "node": "10.* || >= 12.*" - }, - "optionalDependencies": { - "@colors/colors": "1.5.0" - } - }, - "node_modules/cli-table3/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "node_modules/cli-table3/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/cli-table3/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/cli-truncate": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-0.2.1.tgz", @@ -21063,9 +19587,13 @@ } }, "node_modules/cli-width": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.1.tgz", - "integrity": "sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw==" + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", + "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", + "license": "ISC", + "engines": { + "node": ">= 12" + } }, "node_modules/client-zip": { "version": "2.4.5", @@ -21161,10 +19689,11 @@ } }, "node_modules/cmd-shim": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/cmd-shim/-/cmd-shim-6.0.1.tgz", - "integrity": "sha512-S9iI9y0nKR4hwEQsVWpyxld/6kRfGepGfzff83FcaiEBpmvlbA2nnGe7Cylgrx2f/p1P5S5wpRm9oL8z1PbS3Q==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/cmd-shim/-/cmd-shim-6.0.3.tgz", + "integrity": "sha512-FMabTRlc5t5zjdenF6mS0MBeFZm0XqHqeOkcskKFb/LYCcRQ5fVgLOHVc4Lq9CqABd9zhjwPjMBCJvMCziSVtA==", "dev": true, + "license": "ISC", "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } @@ -21304,6 +19833,7 @@ "resolved": "https://registry.npmjs.org/columnify/-/columnify-1.6.0.tgz", "integrity": "sha512-lomjuFZKfM6MSAnV9aCZC9sc0qGbmZdfygNv+nCpqVkSKdCxCklLtd16O0EILGkImHw9ZpHkAnHaB+8Zxq5W6Q==", "dev": true, + "license": "MIT", "dependencies": { "strip-ansi": "^6.0.1", "wcwidth": "^1.0.0" @@ -21344,6 +19874,13 @@ "node": ">= 12.0.0" } }, + "node_modules/common-ancestor-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/common-ancestor-path/-/common-ancestor-path-1.0.1.tgz", + "integrity": "sha512-L3sHRo1pXXEqX8VU28kfgUY+YGsk09hPqZiZmLacNib6XNTCM8ubYeT7ryXQw8asB1sKgcU5lkB7ONug08aB8w==", + "dev": true, + "license": "ISC" + }, "node_modules/common-path-prefix": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/common-path-prefix/-/common-path-prefix-3.0.0.tgz", @@ -21359,6 +19896,7 @@ "resolved": "https://registry.npmjs.org/compare-func/-/compare-func-2.0.0.tgz", "integrity": "sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==", "dev": true, + "license": "MIT", "dependencies": { "array-ify": "^1.0.0", "dot-prop": "^5.1.0" @@ -21384,10 +19922,11 @@ "dev": true }, "node_modules/compress-commons": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-5.0.1.tgz", - "integrity": "sha512-MPh//1cERdLtqwO3pOFLeXtpuai0Y2WCd5AhtKxznqM7WtaMYaOEMSgn45d9D10sIHSfIKE603HlOp8OPGrvag==", + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-5.0.3.tgz", + "integrity": "sha512-/UIcLWvwAQyVibgpQDPtfNM3SvqN7G9elAPAV7GM0L53EbNWwWiCsWtK8Fwed/APEbptPHXs5PuW+y8Bq8lFTA==", "dev": true, + "license": "MIT", "dependencies": { "crc-32": "^1.2.0", "crc32-stream": "^5.0.0", @@ -21403,6 +19942,7 @@ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", "dev": true, + "license": "MIT", "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -21737,15 +20277,16 @@ } }, "node_modules/conventional-changelog-angular": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-6.0.0.tgz", - "integrity": "sha512-6qLgrBF4gueoC7AFVHu51nHL9pF9FRjXrH+ceVf7WmAfH3gs+gEYOkvxhjMPjZu57I4AGUGoNTY8V7Hrgf1uqg==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-7.0.0.tgz", + "integrity": "sha512-ROjNchA9LgfNMTTFSIWPzebCwOGFdgkEq45EnvvrmSLvCtAw0HSmrCs7/ty+wAeYUZyNay0YMUNYFTRL72PkBQ==", "dev": true, + "license": "ISC", "dependencies": { "compare-func": "^2.0.0" }, "engines": { - "node": ">=14" + "node": ">=16" } }, "node_modules/conventional-changelog-core": { @@ -21753,6 +20294,7 @@ "resolved": "https://registry.npmjs.org/conventional-changelog-core/-/conventional-changelog-core-5.0.1.tgz", "integrity": "sha512-Rvi5pH+LvgsqGwZPZ3Cq/tz4ty7mjijhr3qR4m9IBXNbxGGYgTVVO+duXzz9aArmHxFtwZ+LRkrNIMDQzgoY4A==", "dev": true, + "license": "MIT", "dependencies": { "add-stream": "^1.0.0", "conventional-changelog-writer": "^6.0.0", @@ -21775,6 +20317,7 @@ "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", "integrity": "sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==", "dev": true, + "license": "MIT", "dependencies": { "graceful-fs": "^4.1.2", "parse-json": "^4.0.0", @@ -21790,6 +20333,7 @@ "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz", "integrity": "sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "hosted-git-info": "^4.0.1", "is-core-module": "^2.5.0", @@ -21805,6 +20349,7 @@ "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", "dev": true, + "license": "MIT", "dependencies": { "error-ex": "^1.3.1", "json-parse-better-errors": "^1.0.1" @@ -21818,6 +20363,7 @@ "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", "dev": true, + "license": "MIT", "engines": { "node": ">=4" } @@ -21827,6 +20373,7 @@ "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", "integrity": "sha512-BLq/cCO9two+lBgiTYNqD6GdtK8s4NpaWrl6/rCO9w0TUS8oJl7cmToOZfRYllKTISY6nt1U7jQ53brmKqY6BA==", "dev": true, + "license": "MIT", "dependencies": { "load-json-file": "^4.0.0", "normalize-package-data": "^2.3.2", @@ -21841,6 +20388,7 @@ "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-3.0.0.tgz", "integrity": "sha512-YFzFrVvpC6frF1sz8psoHDBGF7fLPc+llq/8NB43oagqWkx8ar5zYtsTORtOjw9W2RHLpWP+zTWwBvf1bCmcSw==", "dev": true, + "license": "MIT", "dependencies": { "find-up": "^2.0.0", "read-pkg": "^3.0.0" @@ -21853,13 +20401,15 @@ "version": "2.8.9", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/conventional-changelog-core/node_modules/read-pkg/node_modules/normalize-package-data": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "hosted-git-info": "^2.1.4", "resolve": "^1.10.0", @@ -21872,6 +20422,7 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", "dev": true, + "license": "ISC", "bin": { "semver": "bin/semver" } @@ -21881,6 +20432,7 @@ "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", "dev": true, + "license": "MIT", "engines": { "node": ">=4" } @@ -21890,6 +20442,7 @@ "resolved": "https://registry.npmjs.org/conventional-changelog-preset-loader/-/conventional-changelog-preset-loader-3.0.0.tgz", "integrity": "sha512-qy9XbdSLmVnwnvzEisjxdDiLA4OmV3o8db+Zdg4WiFw14fP3B6XNz98X0swPPpkTd/pc1K7+adKgEDM1JCUMiA==", "dev": true, + "license": "MIT", "engines": { "node": ">=14" } @@ -21899,6 +20452,7 @@ "resolved": "https://registry.npmjs.org/conventional-changelog-writer/-/conventional-changelog-writer-6.0.1.tgz", "integrity": "sha512-359t9aHorPw+U+nHzUXHS5ZnPBOizRxfQsWT5ZDHBfvfxQOAik+yfuhKXG66CN5LEWPpMNnIMHUTCKeYNprvHQ==", "dev": true, + "license": "MIT", "dependencies": { "conventional-commits-filter": "^3.0.0", "dateformat": "^3.0.3", @@ -21920,6 +20474,7 @@ "resolved": "https://registry.npmjs.org/conventional-commits-filter/-/conventional-commits-filter-3.0.0.tgz", "integrity": "sha512-1ymej8b5LouPx9Ox0Dw/qAO2dVdfpRFq28e5Y0jJEU8ZrLdy0vOSkkIInwmxErFGhg6SALro60ZrwYFVTUDo4Q==", "dev": true, + "license": "MIT", "dependencies": { "lodash.ismatch": "^4.4.0", "modify-values": "^1.0.1" @@ -21933,6 +20488,7 @@ "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-4.0.0.tgz", "integrity": "sha512-WRv5j1FsVM5FISJkoYMR6tPk07fkKT0UodruX4je86V4owk451yjXAKzKAPOs9l7y59E2viHUS9eQ+dfUA9NSg==", "dev": true, + "license": "MIT", "dependencies": { "is-text-path": "^1.0.1", "JSONStream": "^1.3.5", @@ -21951,6 +20507,7 @@ "resolved": "https://registry.npmjs.org/conventional-recommended-bump/-/conventional-recommended-bump-7.0.1.tgz", "integrity": "sha512-Ft79FF4SlOFvX4PkwFDRnaNiIVX7YbmqGU0RwccUaiGvgp3S0a8ipR2/Qxk31vclDNM+GSdJOVs2KrsUCjblVA==", "dev": true, + "license": "MIT", "dependencies": { "concat-stream": "^2.0.0", "conventional-changelog-preset-loader": "^3.0.0", @@ -21975,6 +20532,7 @@ "engines": [ "node >= 6.0" ], + "license": "MIT", "dependencies": { "buffer-from": "^1.0.0", "inherits": "^2.0.3", @@ -21987,6 +20545,7 @@ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", "dev": true, + "license": "MIT", "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -22005,6 +20564,7 @@ "version": "0.5.0", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -22220,23 +20780,25 @@ } }, "node_modules/core-js": { - "version": "3.38.1", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.38.1.tgz", - "integrity": "sha512-OP35aUorbU3Zvlx7pjsFdu1rGNnD4pgw/CWoYzRY3t2EzoVT7shKHY1dlAy3f41cGIO7ZDPQimhGFTlEYkG/Hw==", + "version": "3.39.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.39.0.tgz", + "integrity": "sha512-raM0ew0/jJUqkJ0E6e8UDtl+y/7ktFivgWvqw8dNSQeNWoSDLvQ1H/RN3aPXB9tBd4/FhyR4RDPGhsNIMsAn7g==", "hasInstallScript": true, + "license": "MIT", "funding": { "type": "opencollective", "url": "https://opencollective.com/core-js" } }, "node_modules/core-js-builder": { - "version": "3.38.1", - "resolved": "https://registry.npmjs.org/core-js-builder/-/core-js-builder-3.38.1.tgz", - "integrity": "sha512-SxxFz0xLPX+MMCC3QtRavu5rzhFe28QqiaQKko+25DrUEsPYbNasZACUkKDDGfInxJiUk/9/PhmHwIdyN4vSKA==", + "version": "3.39.0", + "resolved": "https://registry.npmjs.org/core-js-builder/-/core-js-builder-3.39.0.tgz", + "integrity": "sha512-XyqDy/xNDx3B+fSQmkBYmYlCYheI7YjxadD5Bje5MbrfjcZ/VMAQfKPPUBohEUUuLR/bdyYTjyXtbICkQeDHcQ==", "dev": true, + "license": "MIT", "dependencies": { - "core-js": "3.38.1", - "core-js-compat": "3.38.1", + "core-js": "3.39.0", + "core-js-compat": "3.39.0", "mkdirp": ">=0.5.6 <1", "webpack": ">=4.47.0 <5" }, @@ -22493,17 +21055,70 @@ } }, "node_modules/core-js-compat": { - "version": "3.38.1", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.38.1.tgz", - "integrity": "sha512-JRH6gfXxGmrzF3tZ57lFx97YARxCXPaMzPo6jELZhv88pBH5VXpQ+y0znKGlFnzuaihqhLbefxSJxWJMPtfDzw==", + "version": "3.39.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.39.0.tgz", + "integrity": "sha512-VgEUx3VwlExr5no0tXlBt+silBvhTryPwCXRI2Id1PN8WTKu7MreethvddqOubrYxkFdv/RnYrqlv1sFNAUelw==", + "license": "MIT", "dependencies": { - "browserslist": "^4.23.3" + "browserslist": "^4.24.2" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/core-js" } }, + "node_modules/core-js-compat/node_modules/browserslist": { + "version": "4.24.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.2.tgz", + "integrity": "sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "caniuse-lite": "^1.0.30001669", + "electron-to-chromium": "^1.5.41", + "node-releases": "^2.0.18", + "update-browserslist-db": "^1.1.1" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/core-js-compat/node_modules/caniuse-lite": { + "version": "1.0.30001687", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001687.tgz", + "integrity": "sha512-0S/FDhf4ZiqrTUiQ39dKeUjYRjkv7lOZU1Dgif2rIqrTzX/1wV2hfKu9TOm1IHkdSijfLswxTFzl/cvir+SLSQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, "node_modules/core-js-pure": { "version": "3.31.0", "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.31.0.tgz", @@ -22547,6 +21162,7 @@ "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", "dev": true, + "license": "Apache-2.0", "bin": { "crc32": "bin/crc32.njs" }, @@ -22555,10 +21171,11 @@ } }, "node_modules/crc32-stream": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-5.0.0.tgz", - "integrity": "sha512-B0EPa1UK+qnpBZpG+7FgPCu0J2ETLpXq09o9BkLkEAhdB6Z61Qo4pJ3JYu0c+Qi+/SAL7QThqnzS06pmSSyZaw==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-5.0.1.tgz", + "integrity": "sha512-lO1dFui+CEUh/ztYIpgpKItKW9Bb4NWakCRJrnqAbFIYD+OZAwb2VfD5T5eXMw2FNcsDHkQcNl/Wh3iVXYwU6g==", "dev": true, + "license": "MIT", "dependencies": { "crc-32": "^1.2.0", "readable-stream": "^3.4.0" @@ -22572,6 +21189,7 @@ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", "dev": true, + "license": "MIT", "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -22631,38 +21249,91 @@ "dev": true }, "node_modules/cross-env": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-3.2.4.tgz", - "integrity": "sha512-T8AFEAiuJ0w53ou6rnu3Fipaiu1W6ZO9GYfd33uxe1kAIiXM0fD8QnIm7orcJBOt7WQC5Ply63E7WZW/jSM+FA==", + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", + "integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==", "dev": true, + "license": "MIT", "dependencies": { - "cross-spawn": "^5.1.0", - "is-windows": "^1.0.0" + "cross-spawn": "^7.0.1" }, "bin": { - "cross-env": "dist/bin/cross-env.js" + "cross-env": "src/bin/cross-env.js", + "cross-env-shell": "src/bin/cross-env-shell.js" }, "engines": { - "node": ">=4.0" + "node": ">=10.14", + "npm": ">=6", + "yarn": ">=1" } }, "node_modules/cross-fetch": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz", "integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==", + "dev": true, "license": "MIT", "dependencies": { "node-fetch": "^2.6.12" } }, "node_modules/cross-spawn": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", - "integrity": "sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", "dependencies": { - "lru-cache": "^4.0.1", - "shebang-command": "^1.2.0", - "which": "^1.2.9" + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cross-spawn/node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cross-spawn/node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cross-spawn/node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cross-spawn/node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" } }, "node_modules/crypto-browserify": { @@ -23077,6 +21748,7 @@ "resolved": "https://registry.npmjs.org/dargs/-/dargs-7.0.0.tgz", "integrity": "sha512-2iy1EkLdlBzQGvbweYRFxmFath8+K7+AKB0TlhHWkNuH+TmovaMH/Wp7V7R4u7f4SnX3OgLsU9t1NI9ioDnUpg==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -23146,6 +21818,7 @@ "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz", "integrity": "sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==", "dev": true, + "license": "MIT", "engines": { "node": "*" } @@ -23249,6 +21922,15 @@ "integrity": "sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==", "dev": true }, + "node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/deep-extend": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", @@ -23281,6 +21963,7 @@ "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-5.1.0.tgz", "integrity": "sha512-eS8dRJOckyo9maw9Tu5O5RUi/4inFLrnoLkBe3cPfDMx3WZioXtmOew4TXQaxq7Rhl4xjDtR7c6x8nNTxOvbFw==", "dev": true, + "license": "BSD-3-Clause", "engines": { "node": ">=16.0.0" } @@ -23317,19 +22000,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/default-browser/node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, "node_modules/default-browser/node_modules/execa": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/execa/-/execa-7.2.0.tgz", @@ -23432,33 +22102,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/default-browser/node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "engines": { - "node": ">=8" - } - }, - "node_modules/default-browser/node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/default-browser/node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "engines": { - "node": ">=8" - } - }, "node_modules/default-browser/node_modules/strip-final-newline": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", @@ -23470,24 +22113,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/default-browser/node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, "node_modules/default-gateway": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-6.0.3.tgz", "integrity": "sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg==", + "license": "BSD-2-Clause", "dependencies": { "execa": "^5.0.0" }, @@ -23495,23 +22125,11 @@ "node": ">= 10" } }, - "node_modules/default-gateway/node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, "node_modules/default-gateway/node_modules/execa": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "license": "MIT", "dependencies": { "cross-spawn": "^7.0.3", "get-stream": "^6.0.0", @@ -23534,6 +22152,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "license": "MIT", "engines": { "node": ">=10" }, @@ -23545,6 +22164,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "license": "Apache-2.0", "engines": { "node": ">=10.17.0" } @@ -23553,6 +22173,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "license": "MIT", "engines": { "node": ">=8" }, @@ -23564,6 +22185,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "license": "MIT", "engines": { "node": ">=6" } @@ -23572,6 +22194,7 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "license": "MIT", "dependencies": { "path-key": "^3.0.0" }, @@ -23583,6 +22206,7 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "license": "MIT", "dependencies": { "mimic-fn": "^2.1.0" }, @@ -23597,43 +22221,11 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", "engines": { "node": ">=8" } }, - "node_modules/default-gateway/node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/default-gateway/node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "engines": { - "node": ">=8" - } - }, - "node_modules/default-gateway/node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, "node_modules/defaults": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz", @@ -23715,16 +22307,11 @@ "node": ">= 0.4" } }, - "node_modules/defu": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz", - "integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==", - "dev": true - }, "node_modules/degenerator": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz", "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==", + "license": "MIT", "dependencies": { "ast-types": "^0.13.4", "escodegen": "^2.1.0", @@ -23738,6 +22325,7 @@ "version": "0.13.4", "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", + "license": "MIT", "dependencies": { "tslib": "^2.0.1" }, @@ -23844,12 +22432,6 @@ "resolved": "https://registry.npmjs.org/delegate/-/delegate-3.2.0.tgz", "integrity": "sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw==" }, - "node_modules/delegates": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", - "dev": true - }, "node_modules/denodeify": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/denodeify/-/denodeify-1.2.1.tgz", @@ -23915,10 +22497,11 @@ } }, "node_modules/detect-libc": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.2.tgz", - "integrity": "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", + "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", "dev": true, + "license": "Apache-2.0", "optional": true, "engines": { "node": ">=8" @@ -23942,187 +22525,10 @@ "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==" }, - "node_modules/detect-package-manager": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/detect-package-manager/-/detect-package-manager-2.0.1.tgz", - "integrity": "sha512-j/lJHyoLlWi6G1LDdLgvUtz60Zo5GEj+sVYtTVXnYLDPuzgC3llMxonXym9zIwhhUII8vjdw0LXxavpLqTbl1A==", - "dev": true, - "dependencies": { - "execa": "^5.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/detect-package-manager/node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/detect-package-manager/node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "dev": true, - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/detect-package-manager/node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/detect-package-manager/node_modules/human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "dev": true, - "engines": { - "node": ">=10.17.0" - } - }, - "node_modules/detect-package-manager/node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/detect-package-manager/node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/detect-package-manager/node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/detect-package-manager/node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/detect-package-manager/node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/detect-package-manager/node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/detect-package-manager/node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/detect-package-manager/node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/detect-port": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/detect-port/-/detect-port-1.5.1.tgz", - "integrity": "sha512-aBzdj76lueB6uUst5iAs7+0H/oOjqI5D16XUWxlWMIMROhcM0rfsNVk93zTngq1dDNpoXRr++Sus7ETAExppAQ==", - "dev": true, - "dependencies": { - "address": "^1.0.1", - "debug": "4" - }, - "bin": { - "detect": "bin/detect-port.js", - "detect-port": "bin/detect-port.js" - } - }, "node_modules/devtools-protocol": { - "version": "0.0.1312386", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1312386.tgz", - "integrity": "sha512-DPnhUXvmvKT2dFA/j7B+riVLUt9Q6RKJlcppojL5CoRywJJKLDYnRlw0gTFKfgDPHP5E04UoB71SxoJlVZy8FA==", + "version": "0.0.1367902", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1367902.tgz", + "integrity": "sha512-XxtPuC3PGakY6PD7dG66/o8KwJ/LkH2/EKe19Dcw58w53dv4/vSQEkn/SzuyhHE2q4zPgCkxQBxus3VV4ql+Pg==", "license": "BSD-3-Clause" }, "node_modules/diff": { @@ -24183,11 +22589,6 @@ "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", "dev": true }, - "node_modules/dns-equal": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz", - "integrity": "sha512-z+paD6YUQsk+AbGCEM4PrOXSss5gd66QfcVBFTKR/HpFL9jCqikS94HYwKww6fQyO7IxrIIyUu+g0Ka9tUS2Cg==" - }, "node_modules/dns-packet": { "version": "5.6.1", "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz", @@ -24325,15 +22726,6 @@ "url": "https://dotenvx.com" } }, - "node_modules/dotenv-expand": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-10.0.0.tgz", - "integrity": "sha512-GopVGCpVS1UKH75VKHGuQFqS1Gusej0z4FyQkPdwjil2gNIv+LNsqBlboOzpJFZKVT95GkCyWJbBSdFEFUWI2A==", - "dev": true, - "engines": { - "node": ">=12" - } - }, "node_modules/dset": { "version": "3.1.4", "resolved": "https://registry.npmjs.org/dset/-/dset-3.1.4.tgz", @@ -24348,15 +22740,6 @@ "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==" }, - "node_modules/duplexer2": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", - "integrity": "sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==", - "dev": true, - "dependencies": { - "readable-stream": "^2.0.2" - } - }, "node_modules/duplexify": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.6.0.tgz", @@ -24372,14 +22755,14 @@ "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "dev": true + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" }, "node_modules/edge-paths": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/edge-paths/-/edge-paths-3.0.5.tgz", "integrity": "sha512-sB7vSrDnFa4ezWQk9nZ/n0FdpdUuC6R1EOrlU3DL+bovcNFK28rqu2emmAUjujYEJTWIgQGqgVVWUZXMnc8iWg==", "dev": true, + "license": "MIT", "dependencies": { "@types/which": "^2.0.1", "which": "^2.0.2" @@ -24395,13 +22778,15 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/@types/which/-/which-2.0.2.tgz", "integrity": "sha512-113D3mDkZDjo+EeUEHCFy0qniNc1ZpecGiAU7WSo7YDoSzolZIQKpYFHrPpjkB2nuyahcKfrmLXeQlh7gqJYdw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/edge-paths/node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, + "license": "ISC", "dependencies": { "isexe": "^2.0.0" }, @@ -24413,28 +22798,73 @@ } }, "node_modules/edgedriver": { - "version": "5.3.7", - "resolved": "https://registry.npmjs.org/edgedriver/-/edgedriver-5.3.7.tgz", - "integrity": "sha512-E1qNFEA9NbaCPSvGaeZhyd7mEZLar+oFS0NRAe5TehJcQ3cayoUdJE5uOFrbxdv/rM4NEPH7aK9a9kgG09rszA==", + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/edgedriver/-/edgedriver-5.6.1.tgz", + "integrity": "sha512-3Ve9cd5ziLByUdigw6zovVeWJjVs8QHVmqOB0sJ0WNeVPcwf4p18GnxMmVvlFmYRloUwf5suNuorea4QzwBIOA==", "dev": true, "hasInstallScript": true, + "license": "MIT", "dependencies": { - "@wdio/logger": "^8.11.0", + "@wdio/logger": "^8.38.0", + "@zip.js/zip.js": "^2.7.48", "decamelize": "^6.0.0", "edge-paths": "^3.0.5", + "fast-xml-parser": "^4.4.1", "node-fetch": "^3.3.2", - "unzipper": "^0.10.14", "which": "^4.0.0" }, "bin": { "edgedriver": "bin/edgedriver.js" } }, + "node_modules/edgedriver/node_modules/@wdio/logger": { + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-8.38.0.tgz", + "integrity": "sha512-kcHL86RmNbcQP+Gq/vQUGlArfU6IIcbbnNp32rRIraitomZow+iEoc519rdQmSVusDozMS5DZthkgDdxK+vz6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^5.1.2", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": "^16.13 || >=18" + } + }, + "node_modules/edgedriver/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/edgedriver/node_modules/chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/edgedriver/node_modules/data-uri-to-buffer": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", "dev": true, + "license": "MIT", "engines": { "node": ">= 12" } @@ -24444,6 +22874,7 @@ "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-6.0.0.tgz", "integrity": "sha512-Fv96DCsdOgB6mdGl67MT5JaTNKRzrzill5OH5s8bjYJXVlcXyPYGyPsUkWyGV5p1TXI5esYIYMMeDJL0hEIwaA==", "dev": true, + "license": "MIT", "engines": { "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, @@ -24456,6 +22887,7 @@ "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", "dev": true, + "license": "ISC", "engines": { "node": ">=16" } @@ -24465,6 +22897,7 @@ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", "dev": true, + "license": "MIT", "dependencies": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", @@ -24478,11 +22911,28 @@ "url": "https://opencollective.com/node-fetch" } }, + "node_modules/edgedriver/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, "node_modules/edgedriver/node_modules/which": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", "dev": true, + "license": "ISC", "dependencies": { "isexe": "^3.1.1" }, @@ -24515,9 +22965,10 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.33", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.33.tgz", - "integrity": "sha512-+cYTcFB1QqD4j4LegwLfpCNxifb6dDFUAwk6RsLusCwIaZI6or2f+q8rs5tTB2YC53HhOlIbEaqHMAAC8IOIwA==" + "version": "1.5.71", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.71.tgz", + "integrity": "sha512-dB68l59BI75W1BUGVTAEJy45CEVuEGy9qPVVQ8pnHyHMn36PLPPoE1mjLH+lo9rKulO3HC2OhbACI/8tCqJBcA==", + "license": "ISC" }, "node_modules/elegant-spinner": { "version": "1.0.1", @@ -24529,10 +22980,11 @@ } }, "node_modules/elliptic": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", - "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", + "version": "6.6.1", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.6.1.tgz", + "integrity": "sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g==", "dev": true, + "license": "MIT", "dependencies": { "bn.js": "^4.11.9", "brorand": "^1.1.0", @@ -24672,9 +23124,9 @@ } }, "node_modules/envinfo": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.14.0.tgz", - "integrity": "sha512-CO40UI41xDQzhLB1hWyqUKgFhs250pNcGbyGKe1l/e4FSaI/+YE4IMG76GDt0In67WLPACIITC+sOi08x4wIvg==", + "version": "7.13.0", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.13.0.tgz", + "integrity": "sha512-cvcaMr7KqXVh4nyzGTVqTum+gAiL265x5jUWQIDLq//zOGbW+gSW/C+OWLleY/rs9Qole6AZLMXPbtIFQbqu+Q==", "license": "MIT", "bin": { "envinfo": "dist/cli.js" @@ -24692,7 +23144,8 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/errno": { "version": "0.1.7", @@ -24832,9 +23285,9 @@ "dev": true }, "node_modules/es-module-lexer": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.4.1.tgz", - "integrity": "sha512-cXLGjP0c4T3flZJKQSuziYoq7MlT+rnvfZjfp7h+I7K9BNX54kP9nyWvdbwjQ4u1iWbOL4u96fgeZLToQlZC7w==" + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.4.tgz", + "integrity": "sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==" }, "node_modules/es-set-tostringtag": { "version": "2.0.1", @@ -24873,11 +23326,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/es-toolkit": { + "version": "1.29.0", + "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.29.0.tgz", + "integrity": "sha512-GjTll+E6APcfAQA09D89HdT8Qn2Yb+TeDSDBTMcxAo+V+w1amAtCI15LJu4YPH/UCPoSo/F47Gr1LIM0TE0lZA==", + "dev": true + }, "node_modules/es6-error": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/esbuild": { "version": "0.18.20", @@ -24916,16 +23376,10 @@ "@esbuild/win32-x64": "0.18.20" } }, - "node_modules/esbuild-plugin-alias": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/esbuild-plugin-alias/-/esbuild-plugin-alias-0.2.1.tgz", - "integrity": "sha512-jyfL/pwPqaFXyKnj8lP8iLk6Z0m099uXR45aSN8Av1XD4vhvQutxxPzgA2bTcAwQpa1zCXDcWOlhFgyP3GKqhQ==", - "dev": true - }, "node_modules/esbuild-register": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/esbuild-register/-/esbuild-register-3.5.0.tgz", - "integrity": "sha512-+4G/XmakeBAsvJuDugJvtyF1x+XJT4FMocynNpxrvEBViirpfUn2PgNpCHedfWhF4WokNsO/OvMKrmJOIJsI5A==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/esbuild-register/-/esbuild-register-3.6.0.tgz", + "integrity": "sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg==", "dev": true, "dependencies": { "debug": "^4.3.4" @@ -25173,9 +23627,10 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, "node_modules/eslint-plugin-jest": { - "version": "27.2.3", - "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-27.2.3.tgz", - "integrity": "sha512-sRLlSCpICzWuje66Gl9zvdF6mwD5X86I4u55hJyFBsxYOsBCmT5+kSUjf+fkFWVMMgpzNEupjW8WzUqi83hJAQ==", + "version": "27.4.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-27.4.3.tgz", + "integrity": "sha512-7S6SmmsHsgIm06BAGCAxL+ABd9/IB3MWkz2pudj6Qqor2y1qQpWPfuFU4SG9pWj4xDjF0e+D7Llh5useuSzAZw==", + "license": "MIT", "dependencies": { "@typescript-eslint/utils": "^5.10.0" }, @@ -25347,6 +23802,41 @@ "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8" } }, + "node_modules/eslint-plugin-react-compiler": { + "version": "19.0.0-beta-0dec889-20241115", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-compiler/-/eslint-plugin-react-compiler-19.0.0-beta-0dec889-20241115.tgz", + "integrity": "sha512-jTjEHuE8/R6qD/CD2d+5YvWMy1q9/tX3kft4WDyg42/HktjHtHXrEToyZ6THEQf8t/YWMY1RGeCkykePbACtFA==", + "dev": true, + "dependencies": { + "@babel/core": "^7.24.4", + "@babel/parser": "^7.24.4", + "@babel/plugin-proposal-private-methods": "^7.18.6", + "hermes-parser": "^0.20.1", + "zod": "^3.22.4", + "zod-validation-error": "^3.0.3" + }, + "engines": { + "node": "^14.17.0 || ^16.0.0 || >= 18.0.0" + }, + "peerDependencies": { + "eslint": ">=7" + } + }, + "node_modules/eslint-plugin-react-compiler/node_modules/hermes-estree": { + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.20.1.tgz", + "integrity": "sha512-SQpZK4BzR48kuOg0v4pb3EAGNclzIlqMj3Opu/mu7bbAoFw6oig6cEt/RAi0zTFW/iW6Iz9X9ggGuZTAZ/yZHg==", + "dev": true + }, + "node_modules/eslint-plugin-react-compiler/node_modules/hermes-parser": { + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.20.1.tgz", + "integrity": "sha512-BL5P83cwCogI8D7rrDCgsFY0tdYUtmFP9XaXtl2IQjC+2Xo+4okjfXintlTxcIwl4qeGddEl28Z11kbVIw0aNA==", + "dev": true, + "dependencies": { + "hermes-estree": "0.20.1" + } + }, "node_modules/eslint-plugin-react-hooks": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.3.0.tgz", @@ -25503,19 +23993,6 @@ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" }, - "node_modules/eslint/node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, "node_modules/eslint/node_modules/doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", @@ -25630,14 +24107,6 @@ "node": ">= 0.8.0" } }, - "node_modules/eslint/node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "engines": { - "node": ">=8" - } - }, "node_modules/eslint/node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -25646,25 +24115,6 @@ "node": ">= 0.8.0" } }, - "node_modules/eslint/node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/eslint/node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "engines": { - "node": ">=8" - } - }, "node_modules/eslint/node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -25687,20 +24137,6 @@ "node": ">= 0.8.0" } }, - "node_modules/eslint/node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, "node_modules/espree": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/espree/-/espree-9.1.0.tgz", @@ -25795,6 +24231,15 @@ "node": ">=4.0" } }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "dependencies": { + "@types/estree": "^1.0.0" + } + }, "node_modules/esutils": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", @@ -25869,19 +24314,6 @@ "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, - "node_modules/execa/node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, "node_modules/execa/node_modules/is-stream": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", @@ -25934,39 +24366,6 @@ "node": ">=8" } }, - "node_modules/execa/node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/execa/node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "engines": { - "node": ">=8" - } - }, - "node_modules/execa/node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, "node_modules/exit": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", @@ -26031,6 +24430,7 @@ "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", "dev": true, + "license": "(MIT OR WTFPL)", "optional": true, "engines": { "node": ">=6" @@ -26072,12 +24472,14 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.1.tgz", "integrity": "sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw==", - "dev": true + "dev": true, + "license": "Apache-2.0" }, "node_modules/express": { "version": "4.18.2", "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", + "license": "MIT", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", @@ -26119,6 +24521,7 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", "dependencies": { "ms": "2.0.0" } @@ -26135,6 +24538,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "license": "MIT", "dependencies": { "debug": "2.6.9", "encodeurl": "~1.0.2", @@ -26152,6 +24556,7 @@ "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", "dependencies": { "ee-first": "1.1.1" }, @@ -26159,6 +24564,21 @@ "node": ">= 0.8" } }, + "node_modules/express/node_modules/qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/express/node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -26182,6 +24602,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -26229,9 +24650,10 @@ } }, "node_modules/external-editor": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.0.3.tgz", - "integrity": "sha512-bn71H9+qWoOQKyZDo25mOMVpSmXROAsTJVVVYzrrtol3d4y+AsKjf4Iwl2Q+IuT0kFSQ1qo166UuIwqYq7mGnA==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", + "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", + "license": "MIT", "dependencies": { "chardet": "^0.7.0", "iconv-lite": "^0.4.24", @@ -26426,9 +24848,9 @@ "license": "MIT" }, "node_modules/fast-xml-parser": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.3.4.tgz", - "integrity": "sha512-utnwm92SyozgA3hhH2I8qldf2lBqm6qHOICawRNRFu1qMe3+oqr+GcXjGqTmXTMGE5T4eC03kr/rlh5C1IRdZA==", + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.5.0.tgz", + "integrity": "sha512-/PlTQCI96+fZMAOLMZK4CWG1ItCbfZ/0jx7UIJFChPNrx7tcEgerUgWbeieCM9MfHInUDyK8DWYZ+YrywDJuTg==", "funding": [ { "type": "github", @@ -26439,6 +24861,7 @@ "url": "https://paypal.me/naturalintelligence" } ], + "license": "MIT", "dependencies": { "strnum": "^1.0.5" }, @@ -26516,6 +24939,7 @@ "url": "https://paypal.me/jimmywarting" } ], + "license": "MIT", "dependencies": { "node-domexception": "^1.0.0", "web-streams-polyfill": "^3.0.3" @@ -26524,12 +24948,6 @@ "node": "^12.20 || >= 14.13" } }, - "node_modules/fetch-retry": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/fetch-retry/-/fetch-retry-5.0.6.tgz", - "integrity": "sha512-3yurQZ2hD9VISAhJJP9bpYFNQrHHBXE2JxxjY5aLEcDi46RmAzJE2OC9FAde0yis5ElW0jTTzs0zfg/Cca4XqQ==", - "dev": true - }, "node_modules/figgy-pudding": { "version": "3.5.2", "resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.2.tgz", @@ -26560,51 +24978,6 @@ "node": "^10.12.0 || >=12.0.0" } }, - "node_modules/file-system-cache": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/file-system-cache/-/file-system-cache-2.3.0.tgz", - "integrity": "sha512-l4DMNdsIPsVnKrgEXbJwDJsA5mB8rGwHYERMgqQx/xAUtChPJMre1bXBzDEqqVbWv9AIbFezXMxeEkZDSrXUOQ==", - "dev": true, - "dependencies": { - "fs-extra": "11.1.1", - "ramda": "0.29.0" - } - }, - "node_modules/file-system-cache/node_modules/fs-extra": { - "version": "11.1.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.1.1.tgz", - "integrity": "sha512-MGIE4HOvQCeUCzmlHs0vXpih4ysz4wg9qiSAu6cd42lVwPbTM1TjV7RusoyQqMmk/95gdQZX72u+YW+c3eEpFQ==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=14.14" - } - }, - "node_modules/file-system-cache/node_modules/jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dev": true, - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/file-system-cache/node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "dev": true, - "engines": { - "node": ">= 10.0.0" - } - }, "node_modules/file-uri-to-path": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", @@ -26919,6 +25292,7 @@ "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", "dev": true, + "license": "BSD-3-Clause", "bin": { "flat": "cli.js" } @@ -26937,6 +25311,43 @@ "node": "^10.12.0 || >=12.0.0" } }, + "node_modules/flat-cache/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/flat-cache/node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/flatted": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", @@ -27021,7 +25432,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==", - "dev": true, "dependencies": { "cross-spawn": "^7.0.0", "signal-exit": "^4.0.1" @@ -27033,55 +25443,10 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/foreground-child/node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/foreground-child/node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/foreground-child/node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/foreground-child/node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/foreground-child/node_modules/signal-exit": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, "engines": { "node": ">=14" }, @@ -27089,21 +25454,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/foreground-child/node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, "node_modules/fork-ts-checker-webpack-plugin": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-8.0.0.tgz", @@ -27265,6 +25615,7 @@ "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-2.1.4.tgz", "integrity": "sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw==", "dev": true, + "license": "MIT", "engines": { "node": ">= 14.17" } @@ -27274,6 +25625,7 @@ "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", "dev": true, + "license": "MIT", "dependencies": { "fetch-blob": "^3.1.2" }, @@ -27293,6 +25645,7 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.0.tgz", "integrity": "sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==", + "dev": true, "engines": { "node": "*" }, @@ -27355,6 +25708,16 @@ "readable-stream": "^2.0.0" } }, + "node_modules/front-matter": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/front-matter/-/front-matter-4.0.2.tgz", + "integrity": "sha512-I8ZuJ/qG92NWX8i5x1Y8qyj3vizhXS31OxjKDu3LKP+7/qBgfIKValiZIEwoVoJKUHlhWtYrktkxV1XsX+pPlg==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-yaml": "^3.13.1" + } + }, "node_modules/fs-constants": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", @@ -27429,65 +25792,6 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, - "node_modules/fstream": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz", - "integrity": "sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.1.2", - "inherits": "~2.0.0", - "mkdirp": ">=0.5 0", - "rimraf": "2" - }, - "engines": { - "node": ">=0.6" - } - }, - "node_modules/fstream/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/fstream/node_modules/mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "dev": true, - "dependencies": { - "minimist": "^1.2.6" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, - "node_modules/fstream/node_modules/rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - } - }, "node_modules/ftp-response-parser": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/ftp-response-parser/-/ftp-response-parser-1.0.1.tgz", @@ -27562,68 +25866,21 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/gauge": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.4.tgz", - "integrity": "sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==", - "dev": true, - "dependencies": { - "aproba": "^1.0.3 || ^2.0.0", - "color-support": "^1.1.3", - "console-control-strings": "^1.1.0", - "has-unicode": "^2.0.1", - "signal-exit": "^3.0.7", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1", - "wide-align": "^1.1.5" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/gauge/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "node_modules/gauge/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/gauge/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/geckodriver": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/geckodriver/-/geckodriver-4.2.1.tgz", - "integrity": "sha512-4m/CRk0OI8MaANRuFIahvOxYTSjlNAO2p9JmE14zxueknq6cdtB5M9UGRQ8R9aMV0bLGNVHHDnDXmoXdOwJfWg==", + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/geckodriver/-/geckodriver-4.5.1.tgz", + "integrity": "sha512-lGCRqPMuzbRNDWJOQcUqhNqPvNsIFu6yzXF8J/6K3WCYFd2r5ckbeF7h1cxsnjA7YLSEiWzERCt6/gjZ3tW0ug==", "dev": true, "hasInstallScript": true, + "license": "MPL-2.0", "dependencies": { - "@wdio/logger": "^8.11.0", + "@wdio/logger": "^9.0.0", + "@zip.js/zip.js": "^2.7.48", "decamelize": "^6.0.0", - "http-proxy-agent": "^7.0.0", - "https-proxy-agent": "^7.0.1", - "node-fetch": "^3.3.1", - "tar-fs": "^3.0.4", - "unzipper": "^0.10.14", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.5", + "node-fetch": "^3.3.2", + "tar-fs": "^3.0.6", "which": "^4.0.0" }, "bin": { @@ -27633,16 +25890,46 @@ "node": "^16.13 || >=18 || >=20" } }, - "node_modules/geckodriver/node_modules/agent-base": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", - "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", + "node_modules/geckodriver/node_modules/@wdio/logger": { + "version": "9.1.3", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-9.1.3.tgz", + "integrity": "sha512-cumRMK/gE1uedBUw3WmWXOQ7HtB6DR8EyKQioUz2P0IJtRRpglMBdZV7Svr3b++WWawOuzZHMfbTkJQmaVt8Gw==", "dev": true, + "license": "MIT", "dependencies": { - "debug": "^4.3.4" + "chalk": "^5.1.2", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "strip-ansi": "^7.1.0" }, "engines": { - "node": ">= 14" + "node": ">=18.20.0" + } + }, + "node_modules/geckodriver/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/geckodriver/node_modules/chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, "node_modules/geckodriver/node_modules/data-uri-to-buffer": { @@ -27650,6 +25937,7 @@ "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", "dev": true, + "license": "MIT", "engines": { "node": ">= 12" } @@ -27659,6 +25947,7 @@ "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-6.0.0.tgz", "integrity": "sha512-Fv96DCsdOgB6mdGl67MT5JaTNKRzrzill5OH5s8bjYJXVlcXyPYGyPsUkWyGV5p1TXI5esYIYMMeDJL0hEIwaA==", "dev": true, + "license": "MIT", "engines": { "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, @@ -27666,38 +25955,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/geckodriver/node_modules/http-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", - "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", - "dev": true, - "dependencies": { - "agent-base": "^7.1.0", - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/geckodriver/node_modules/https-proxy-agent": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", - "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.0.2", - "debug": "4" - }, - "engines": { - "node": ">= 14" - } - }, "node_modules/geckodriver/node_modules/isexe": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", "dev": true, + "license": "ISC", "engines": { "node": ">=16" } @@ -27707,6 +25970,7 @@ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", "dev": true, + "license": "MIT", "dependencies": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", @@ -27731,11 +25995,28 @@ "once": "^1.3.1" } }, + "node_modules/geckodriver/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, "node_modules/geckodriver/node_modules/tar-fs": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.6.tgz", "integrity": "sha512-iokBDQQkUyeXhgPYaZxmczGPhnhXZ0CmrqI+MOb/WFGS9DW5wnfrLgtjUJBvz50vQ3qfRwJ62QVoCFu8mPVu5w==", "dev": true, + "license": "MIT", "dependencies": { "pump": "^3.0.0", "tar-stream": "^3.1.5" @@ -27750,6 +26031,7 @@ "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", "dev": true, + "license": "MIT", "dependencies": { "b4a": "^1.6.4", "fast-fifo": "^1.2.0", @@ -27761,6 +26043,7 @@ "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", "dev": true, + "license": "ISC", "dependencies": { "isexe": "^3.1.1" }, @@ -27818,15 +26101,6 @@ "node": ">=6" } }, - "node_modules/get-npm-tarball-url": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/get-npm-tarball-url/-/get-npm-tarball-url-2.1.0.tgz", - "integrity": "sha512-ro+DiMu5DXgRBabqXupW38h7WPZ9+Ad8UjwhvsmmN8w1sU7ab0nzAXvVZ4kqYg57OrqomRtJvepX5/xvFKNtjA==", - "dev": true, - "engines": { - "node": ">=12.17" - } - }, "node_modules/get-own-enumerable-property-symbols": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz", @@ -27838,6 +26112,7 @@ "resolved": "https://registry.npmjs.org/get-pkg-repo/-/get-pkg-repo-4.2.1.tgz", "integrity": "sha512-2+QbHjFRfGB74v/pYWjd5OhU3TDIC2Gv/YKUTk/tCvAz0pkn/Mz6P3uByuBimLOcPvN2jYdScl3xGFSrx0jEcA==", "dev": true, + "license": "MIT", "dependencies": { "@hutson/parse-repository-url": "^3.0.0", "hosted-git-info": "^4.0.0", @@ -27851,6 +26126,97 @@ "node": ">=6.9.0" } }, + "node_modules/get-pkg-repo/node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/get-pkg-repo/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/get-pkg-repo/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/get-pkg-repo/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/get-pkg-repo/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/get-pkg-repo/node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/get-pkg-repo/node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/get-port": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/get-port/-/get-port-5.1.1.tgz", @@ -27944,92 +26310,12 @@ "safe-buffer": "^5.1.1" } }, - "node_modules/giget": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/giget/-/giget-1.2.1.tgz", - "integrity": "sha512-4VG22mopWtIeHwogGSy1FViXVo0YT+m6BrqZfz0JJFwbSsePsCdOzdLIIli5BtMp7Xe8f/o2OmBpQX2NBOC24g==", - "dev": true, - "dependencies": { - "citty": "^0.1.5", - "consola": "^3.2.3", - "defu": "^6.1.3", - "node-fetch-native": "^1.6.1", - "nypm": "^0.3.3", - "ohash": "^1.1.3", - "pathe": "^1.1.1", - "tar": "^6.2.0" - }, - "bin": { - "giget": "dist/cli.mjs" - } - }, - "node_modules/giget/node_modules/chownr": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", - "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/giget/node_modules/consola": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/consola/-/consola-3.2.3.tgz", - "integrity": "sha512-I5qxpzLv+sJhTVEoLYNcTW+bThDCPsit0vLNKShZx6rLtpilNpmmeTPaeqJb9ZE9dV3DGaeby6Vuhrw38WjeyQ==", - "dev": true, - "engines": { - "node": "^14.18.0 || >=16.10.0" - } - }, - "node_modules/giget/node_modules/minipass": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", - "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/giget/node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true, - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/giget/node_modules/tar": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", - "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", - "dev": true, - "dependencies": { - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "minipass": "^5.0.0", - "minizlib": "^2.1.1", - "mkdirp": "^1.0.3", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/giget/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, "node_modules/git-raw-commits": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/git-raw-commits/-/git-raw-commits-3.0.0.tgz", "integrity": "sha512-b5OHmZ3vAgGrDn/X0kS+9qCfNKWe4K/jFnhwzVWWg0/k5eLa3060tZShrRg8Dja5kPc+YjS0Gc6y7cRr44Lpjw==", "dev": true, + "license": "MIT", "dependencies": { "dargs": "^7.0.0", "meow": "^8.1.2", @@ -28047,6 +26333,7 @@ "resolved": "https://registry.npmjs.org/git-remote-origin-url/-/git-remote-origin-url-2.0.0.tgz", "integrity": "sha512-eU+GGrZgccNJcsDH5LkXR3PB9M958hxc7sbA8DFJjrv9j4L2P/eZfKhM+QD6wyzpiv+b1BpK0XrYCxkovtjSLw==", "dev": true, + "license": "MIT", "dependencies": { "gitconfiglocal": "^1.0.0", "pify": "^2.3.0" @@ -28055,11 +26342,22 @@ "node": ">=4" } }, + "node_modules/git-remote-origin-url/node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/git-semver-tags": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/git-semver-tags/-/git-semver-tags-5.0.1.tgz", "integrity": "sha512-hIvOeZwRbQ+7YEUmCkHqo8FOLQZCEn18yevLHADlFPZY02KJGsu5FZt9YW/lybfK2uhWFI7Qg/07LekJiTv7iA==", "dev": true, + "license": "MIT", "dependencies": { "meow": "^8.1.2", "semver": "^7.0.0" @@ -28076,16 +26374,18 @@ "resolved": "https://registry.npmjs.org/git-up/-/git-up-7.0.0.tgz", "integrity": "sha512-ONdIrbBCFusq1Oy0sC71F5azx8bVkvtZtMJAsv+a6lz5YAmbNnLD6HAB4gptHZVLPR8S2/kVN6Gab7lryq5+lQ==", "dev": true, + "license": "MIT", "dependencies": { "is-ssh": "^1.4.0", "parse-url": "^8.1.0" } }, "node_modules/git-url-parse": { - "version": "13.1.0", - "resolved": "https://registry.npmjs.org/git-url-parse/-/git-url-parse-13.1.0.tgz", - "integrity": "sha512-5FvPJP/70WkIprlUZ33bm4UAaFdjcLkJLpWft1BeZKqwR0uhhNGoKwlUaPtVb4LxCSQ++erHapRak9kWGj+FCA==", + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/git-url-parse/-/git-url-parse-14.0.0.tgz", + "integrity": "sha512-NnLweV+2A4nCvn4U/m2AoYu0pPKlsmhK9cknG7IMwsjFY1S2jxM+mAhsDxyxfCIGfGaD+dozsyX4b6vkYc83yQ==", "dev": true, + "license": "MIT", "dependencies": { "git-up": "^7.0.0" } @@ -28095,6 +26395,7 @@ "resolved": "https://registry.npmjs.org/gitconfiglocal/-/gitconfiglocal-1.0.0.tgz", "integrity": "sha512-spLUXeTAVHxDtKsJc8FkFVgFtMdEN9qPGpL23VfSHx4fP4+Ds097IXLvymbnDH8FnmxX5Nr9bPw3A+AQ6mWEaQ==", "dev": true, + "license": "BSD", "dependencies": { "ini": "^1.3.2" } @@ -28104,14 +26405,9 @@ "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", "dev": true, + "license": "MIT", "optional": true }, - "node_modules/github-slugger": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-1.4.0.tgz", - "integrity": "sha512-w0dzqw/nt51xMVmlaV1+JRzN+oCa1KfcgGEWhxUG16wbdA+Xnt/yoFO8Z8x/V82ZcZ0wy6ln9QDup5avbhiDhQ==", - "dev": true - }, "node_modules/glob": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", @@ -28345,38 +26641,6 @@ "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==" }, - "node_modules/gunzip-maybe": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/gunzip-maybe/-/gunzip-maybe-1.4.2.tgz", - "integrity": "sha512-4haO1M4mLO91PW57BMsDFf75UmwoRX0GkdD+Faw+Lr+r/OZrOCS0pIBwOL1xCKQqnQzbNFGgK2V2CpBUPeFNTw==", - "dev": true, - "dependencies": { - "browserify-zlib": "^0.1.4", - "is-deflate": "^1.0.0", - "is-gzip": "^1.0.0", - "peek-stream": "^1.1.0", - "pumpify": "^1.3.3", - "through2": "^2.0.3" - }, - "bin": { - "gunzip-maybe": "bin.js" - } - }, - "node_modules/gunzip-maybe/node_modules/browserify-zlib": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.1.4.tgz", - "integrity": "sha512-19OEpq7vWgsH6WkvkBJQDFvJS1uPcbFOQ4v9CU839dO+ZZXUZO6XpE6hNCqvlIIj+4fZvRiJ6DsAQ382GwiyTQ==", - "dev": true, - "dependencies": { - "pako": "~0.2.0" - } - }, - "node_modules/gunzip-maybe/node_modules/pako": { - "version": "0.2.9", - "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", - "integrity": "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==", - "dev": true - }, "node_modules/handle-thing": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", @@ -28741,11 +27005,6 @@ "node": ">=10" } }, - "node_modules/hosted-git-info/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" - }, "node_modules/hpack.js": { "version": "2.1.6", "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", @@ -28835,9 +27094,9 @@ } }, "node_modules/html-webpack-plugin": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.6.0.tgz", - "integrity": "sha512-iwaY4wzbe48AfKLZ/Cc8k0L+FKG6oSNRaZ8x5A/T/IVDGyXcbHncM9TdDa93wn0FsSm82FhTKW7f3vS61thXAw==", + "version": "5.6.3", + "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.6.3.tgz", + "integrity": "sha512-QSf1yjtSAsmf7rYBV7XX86uua4W/vkhIt0xNXKbsi2foEeW7vjJQz4bhnpL3xH+l1ryl1680uNv968Z+X6jSYg==", "dev": true, "dependencies": { "@types/html-minifier-terser": "^6.0.0", @@ -29045,32 +27304,23 @@ } }, "node_modules/http-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", - "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", - "dev": true, + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "license": "MIT", "dependencies": { - "@tootallnate/once": "2", - "agent-base": "6", - "debug": "4" + "agent-base": "^7.1.0", + "debug": "^4.3.4" }, "engines": { - "node": ">= 6" - } - }, - "node_modules/http-proxy-agent/node_modules/@tootallnate/once": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", - "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", - "dev": true, - "engines": { - "node": ">= 10" + "node": ">= 14" } }, "node_modules/http-proxy-middleware": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz", - "integrity": "sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw==", + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.7.tgz", + "integrity": "sha512-fgVY8AV7qU7z/MmXJ/rxwbrtQH4jBQ9m7kp3llF0liB7glmFeVZFBepQb32T3y8n8k2+AEYuMPCpinYW+/CuRA==", + "license": "MIT", "dependencies": { "@types/http-proxy": "^1.17.8", "http-proxy": "^1.18.1", @@ -29126,15 +27376,16 @@ "dev": true }, "node_modules/https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "license": "MIT", "dependencies": { - "agent-base": "6", + "agent-base": "^7.1.2", "debug": "4" }, "engines": { - "node": ">= 6" + "node": ">= 14" } }, "node_modules/human-signals": { @@ -29145,15 +27396,6 @@ "node": ">=8.12.0" } }, - "node_modules/humanize-ms": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", - "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", - "dev": true, - "dependencies": { - "ms": "^2.0.0" - } - }, "node_modules/husky": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/husky/-/husky-7.0.0.tgz", @@ -29254,6 +27496,18 @@ "resolved": "https://registry.npmjs.org/image-ssim/-/image-ssim-0.2.0.tgz", "integrity": "sha512-W7+sO6/yhxy83L0G7xR8YAc5Z5QFtYEXXRV6EaE8tuYBZJnA3gVgp3q7X7muhLZVodeb9UfvjSbwt9VJwjIYAg==" }, + "node_modules/immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", + "license": "MIT" + }, + "node_modules/immutable": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.7.tgz", + "integrity": "sha512-1hqclzwYwjRDFLjcFxOM5AYkkG0rpFPpr1RLPMEuGczoS7YA8gLhy8SWXYRAA/XwfEHpfo3cw5JGioS32fnMRw==", + "license": "MIT" + }, "node_modules/import-fresh": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", @@ -29376,10 +27630,11 @@ "integrity": "sha512-1/bPE89IZhyf7dr5Pkz7b4UyVXy5pEt7PTEfye15UEn3AK8+2zwcDCfKk9Pwun4ltfhOSszOrReSsFcDKw/yoA==" }, "node_modules/import-meta-resolve": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-3.0.0.tgz", - "integrity": "sha512-4IwhLhNNA8yy445rPjD/lWh++7hMDOml2eHtd58eG7h+qK3EryMuuRbsHGPikCoAgIkkDnckKfWSk2iDla/ejg==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-3.1.1.tgz", + "integrity": "sha512-qeywsE/KC3w9Fd2ORrRDUw6nS/nLwZpXgfrOc2IILvZYnCaEMd+D56Vfg9k4G29gIeVi3XKql1RQatME8iYsiw==", "dev": true, + "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" @@ -29435,57 +27690,51 @@ "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" }, "node_modules/init-package-json": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/init-package-json/-/init-package-json-5.0.0.tgz", - "integrity": "sha512-kBhlSheBfYmq3e0L1ii+VKe3zBTLL5lDCDWR+f9dLmEGSB3MqLlMlsolubSsyI88Bg6EA+BIMlomAnQ1SwgQBw==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/init-package-json/-/init-package-json-6.0.3.tgz", + "integrity": "sha512-Zfeb5ol+H+eqJWHTaGca9BovufyGeIfr4zaaBorPmJBMrJ+KBnN+kQx2ZtXdsotUTgldHmHQV44xvUWOUA7E2w==", "dev": true, + "license": "ISC", "dependencies": { - "npm-package-arg": "^10.0.0", + "@npmcli/package-json": "^5.0.0", + "npm-package-arg": "^11.0.0", "promzard": "^1.0.0", - "read": "^2.0.0", - "read-package-json": "^6.0.0", + "read": "^3.0.1", "semver": "^7.3.5", "validate-npm-package-license": "^3.0.4", "validate-npm-package-name": "^5.0.0" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^16.14.0 || >=18.0.0" } }, "node_modules/init-package-json/node_modules/hosted-git-info": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-6.1.1.tgz", - "integrity": "sha512-r0EI+HBMcXadMrugk0GCQ+6BQV39PiWAZVfq7oIckeGiN7sjRGyQxPdft3nQekFTCQbYxLBH+/axZMeH8UX6+w==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz", + "integrity": "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==", "dev": true, + "license": "ISC", "dependencies": { - "lru-cache": "^7.5.1" + "lru-cache": "^10.0.1" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/init-package-json/node_modules/lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", - "dev": true, - "engines": { - "node": ">=12" + "node": "^16.14.0 || >=18.0.0" } }, "node_modules/init-package-json/node_modules/npm-package-arg": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-10.1.0.tgz", - "integrity": "sha512-uFyyCEmgBfZTtrKk/5xDfHp6+MdrqGotX/VoOyEEl3mBwiEE5FlBaePanazJSVMPT7vKepcjYBY2ztg9A3yPIA==", + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-11.0.3.tgz", + "integrity": "sha512-sHGJy8sOC1YraBywpzQlIKBE4pBbGbiF95U6Auspzyem956E0+FtDtsx1ZxlOJkQCZ1AFXAY/yuvtFYrOxF+Bw==", "dev": true, + "license": "ISC", "dependencies": { - "hosted-git-info": "^6.0.0", - "proc-log": "^3.0.0", + "hosted-git-info": "^7.0.0", + "proc-log": "^4.0.0", "semver": "^7.3.5", "validate-npm-package-name": "^5.0.0" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^16.14.0 || >=18.0.0" } }, "node_modules/init-package-json/node_modules/validate-npm-package-name": { @@ -29493,149 +27742,11 @@ "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-5.0.1.tgz", "integrity": "sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==", "dev": true, + "license": "ISC", "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/inquirer": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.1.0.tgz", - "integrity": "sha512-5fJMWEmikSYu0nv/flMc475MhGbB7TSPd/2IpFV4I4rMklboCH2rQjYY5kKiYGHqUF9gvaambupcJFFG9dvReg==", - "dependencies": { - "ansi-escapes": "^4.2.1", - "chalk": "^3.0.0", - "cli-cursor": "^3.1.0", - "cli-width": "^2.0.0", - "external-editor": "^3.0.3", - "figures": "^3.0.0", - "lodash": "^4.17.15", - "mute-stream": "0.0.8", - "run-async": "^2.4.0", - "rxjs": "^6.5.3", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0", - "through": "^2.3.6" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/inquirer/node_modules/chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/inquirer/node_modules/cli-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", - "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", - "dependencies": { - "restore-cursor": "^3.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/inquirer/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" - }, - "node_modules/inquirer/node_modules/figures": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", - "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", - "dependencies": { - "escape-string-regexp": "^1.0.5" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/inquirer/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/inquirer/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "engines": { - "node": ">=8" - } - }, - "node_modules/inquirer/node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "engines": { - "node": ">=6" - } - }, - "node_modules/inquirer/node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/inquirer/node_modules/restore-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", - "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", - "dependencies": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/inquirer/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/inquirer/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/internal-slot": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz", @@ -29658,19 +27769,17 @@ } }, "node_modules/intl-messageformat": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-4.4.0.tgz", - "integrity": "sha512-z+Bj2rS3LZSYU4+sNitdHrwnBhr0wO80ZJSW8EzKDBowwUe3Q/UsvgCGjrwa+HPzoGCLEb9HAjfJgo4j2Sac8w==", + "version": "10.7.7", + "resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-10.7.7.tgz", + "integrity": "sha512-F134jIoeYMro/3I0h08D0Yt4N9o9pjddU/4IIxMMURqbAtI2wu70X8hvG1V48W49zXHXv3RKSF/po+0fDfsGjA==", + "license": "BSD-3-Clause", "dependencies": { - "intl-messageformat-parser": "^1.8.1" + "@formatjs/ecma402-abstract": "2.2.4", + "@formatjs/fast-memoize": "2.2.3", + "@formatjs/icu-messageformat-parser": "2.9.4", + "tslib": "2" } }, - "node_modules/intl-messageformat-parser": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/intl-messageformat-parser/-/intl-messageformat-parser-1.8.1.tgz", - "integrity": "sha512-IMSCKVf0USrM/959vj3xac7s8f87sc+80Y/ipBzdKy4ifBv5Gsj2tZ41EAaURVg01QU71fYr77uA8Meh6kELbg==", - "deprecated": "We've written a new parser that's 6x faster and is backwards compatible. Please use @formatjs/icu-messageformat-parser" - }, "node_modules/invariant": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", @@ -29719,15 +27828,6 @@ "node": ">=8" } }, - "node_modules/is-absolute-url": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-3.0.3.tgz", - "integrity": "sha512-opmNIX7uFnS96NtPmhWQgQx6/NYFgsUXYMllcfzwWKUMwfo8kku1TvE6hkNcH+Q1ts5cMVrsY7j0bxXQDciu9Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/is-accessor-descriptor": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.1.tgz", @@ -29927,12 +28027,6 @@ "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-1.0.2.tgz", "integrity": "sha512-TRzl7mOCchnhchN+f3ICUCzYvL9ul7R+TYOsZ8xia++knyZAJfv/uA1FvQXsAnYIl1T3B2X5E/J7Wb1QXiIBXg==" }, - "node_modules/is-deflate": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-deflate/-/is-deflate-1.0.0.tgz", - "integrity": "sha512-YDoFpuZWu1VRXlsnlYMzKyVRITXj7Ej/V9gXQ2/pAe7X1J7M/RNOqaIYi6qUn+B7nGyB9pDXrv02dsB58d2ZAQ==", - "dev": true - }, "node_modules/is-descriptor": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.7.tgz", @@ -30026,15 +28120,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-gzip": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-gzip/-/is-gzip-1.0.0.tgz", - "integrity": "sha512-rcfALRIb1YewtnksfRIHGcIY93QnK8BIQ/2c9yDYcG/Y6+vRoJuTWBmmSEbyLLYtXm7q35pHOHbZFQBaLrhlWQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/is-hexadecimal": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-1.0.2.tgz", @@ -30083,7 +28168,8 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/is-map": { "version": "2.0.2", @@ -30094,22 +28180,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-nan": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/is-nan/-/is-nan-1.3.2.tgz", - "integrity": "sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.0", - "define-properties": "^1.1.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-negative-zero": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", @@ -30288,6 +28358,7 @@ "resolved": "https://registry.npmjs.org/is-ssh/-/is-ssh-1.4.0.tgz", "integrity": "sha512-x7+VxdxOdlV3CYpjvRLBv5Lo9OJerlYanjwFrPR9fuGPjCiNiCzFgAWpiLAohSbsnH4ZAys3SBh+hq5rJosxUQ==", "dev": true, + "license": "MIT", "dependencies": { "protocols": "^2.0.1" } @@ -30334,6 +28405,7 @@ "resolved": "https://registry.npmjs.org/is-text-path/-/is-text-path-1.0.1.tgz", "integrity": "sha512-xFuJpne9oFz5qDaodwmmG08e3CawH/2ZV8Qqza1Ko7Sk8POWbkRdwIoAWVhqvq0XeUzANEhKo2n0IXUGBm7A/w==", "dev": true, + "license": "MIT", "dependencies": { "text-extensions": "^1.0.0" }, @@ -30555,16 +28627,13 @@ } }, "node_modules/jackspeak": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz", - "integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==", - "dev": true, + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "license": "BlueOak-1.0.0", "dependencies": { "@isaacs/cliui": "^8.0.2" }, - "engines": { - "node": ">=14" - }, "funding": { "url": "https://github.com/sponsors/isaacs" }, @@ -30638,19 +28707,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-changed-files/node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, "node_modules/jest-changed-files/node_modules/execa": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", @@ -30744,39 +28800,6 @@ "node": ">=8" } }, - "node_modules/jest-changed-files/node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-changed-files/node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-changed-files/node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, "node_modules/jest-circus": { "version": "29.6.2", "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.6.2.tgz", @@ -30853,94 +28876,6 @@ } } }, - "node_modules/jest-cli/node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/jest-cli/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" - }, - "node_modules/jest-cli/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-cli/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-cli/node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/jest-cli/node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "engines": { - "node": ">=10" - } - }, - "node_modules/jest-cli/node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/jest-cli/node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "engines": { - "node": ">=12" - } - }, "node_modules/jest-config": { "version": "29.6.2", "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.6.2.tgz", @@ -31015,6 +28950,61 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/jest-dev-server": { + "version": "10.1.4", + "resolved": "https://registry.npmjs.org/jest-dev-server/-/jest-dev-server-10.1.4.tgz", + "integrity": "sha512-bGQ6sedNGtT6AFHhCVqGTXMPz7UyJi/ZrhNBgyqsP0XU9N8acCEIfqZEA22rOaZ+NdEVsaltk6tL7UT6aXfI7w==", + "license": "MIT", + "dependencies": { + "chalk": "^4.1.2", + "cwd": "^0.10.0", + "find-process": "^1.4.7", + "prompts": "^2.4.2", + "spawnd": "^10.1.4", + "tree-kill": "^1.2.2", + "wait-on": "^8.0.1" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/jest-dev-server/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-dev-server/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-dev-server/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/jest-diff": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", @@ -31826,18 +29816,6 @@ "node": ">= 14" } }, - "node_modules/jsdom/node_modules/http-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", - "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", - "dependencies": { - "agent-base": "^7.1.0", - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, "node_modules/jsdom/node_modules/https-proxy-agent": { "version": "7.0.5", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", @@ -32001,6 +29979,16 @@ "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", "dev": true }, + "node_modules/json-stringify-nice": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/json-stringify-nice/-/json-stringify-nice-1.1.4.tgz", + "integrity": "sha512-5Z5RFW63yxReJ7vANgW6eZFGWaQvnPE3WNmZoOJrSkGju2etKA2L5rrOa1sm877TVTFt57A80BH1bArcmlLfPw==", + "dev": true, + "license": "ISC", + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/json-stringify-safe": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", @@ -32062,13 +30050,15 @@ "dev": true, "engines": [ "node >= 0.2.0" - ] + ], + "license": "MIT" }, "node_modules/JSONStream": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", "dev": true, + "license": "(MIT OR Apache-2.0)", "dependencies": { "jsonparse": "^1.2.0", "through": ">=2.2.7 <3" @@ -32094,6 +30084,20 @@ "node": ">=4.0" } }, + "node_modules/just-diff": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/just-diff/-/just-diff-6.0.2.tgz", + "integrity": "sha512-S59eriX5u3/QhMNq3v/gm8Kd0w8OS6Tz2FS1NG4blv+z0MuQcBRJyFWjdovM0Rad4/P4aUPFtnkNjMjyMlMSYA==", + "dev": true, + "license": "MIT" + }, + "node_modules/just-diff-apply": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/just-diff-apply/-/just-diff-apply-5.5.0.tgz", + "integrity": "sha512-OYTthRfSh55WOItVqwpefPtNt2VdKsq5AnAK6apdtR6yCH8pr0CmSr710J0Mf+WdQy7K/OzMy7K2MgAfdQURDw==", + "dev": true, + "license": "MIT" + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -32160,6 +30164,7 @@ "resolved": "https://registry.npmjs.org/ky/-/ky-0.33.3.tgz", "integrity": "sha512-CasD9OCEQSFIam2U8efFK81Yeg8vNMTBUqtMOHlrcWQHqUX3HeCl9Dr31u4toV7emlH8Mymk5+9p0lL6mKb/Xw==", "dev": true, + "license": "MIT", "engines": { "node": ">=14.16" }, @@ -32181,12 +30186,13 @@ } }, "node_modules/launch-editor": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.6.0.tgz", - "integrity": "sha512-JpDCcQnyAAzZZaZ7vEiSqL690w7dAEyLao+KC96zBplnYbJS7TYNjvM3M7y3dGz+v7aIsJk3hllWuc0kWAjyRQ==", + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.9.1.tgz", + "integrity": "sha512-Gcnl4Bd+hRO9P9icCP/RVVT2o8SFlPXofuCxvA2SaZuH45whSvf5p8x5oih5ftLiVhEI4sp5xDY+R+b3zJBh5w==", + "license": "MIT", "dependencies": { "picocolors": "^1.0.0", - "shell-quote": "^1.7.3" + "shell-quote": "^1.8.1" } }, "node_modules/lazy-cache": { @@ -32197,25 +30203,12 @@ "node": ">=0.10.0" } }, - "node_modules/lazy-universal-dotenv": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/lazy-universal-dotenv/-/lazy-universal-dotenv-4.0.0.tgz", - "integrity": "sha512-aXpZJRnTkpK6gQ/z4nk+ZBLd/Qdp118cvPruLSIQzQNRhKwEcdXCOzXuF55VDqIiuAaY3UGZ10DJtvZzDcvsxg==", - "dev": true, - "dependencies": { - "app-root-dir": "^1.0.2", - "dotenv": "^16.0.0", - "dotenv-expand": "^10.0.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, "node_modules/lazystream": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz", "integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==", "dev": true, + "license": "MIT", "dependencies": { "readable-stream": "^2.0.5" }, @@ -32224,92 +30217,99 @@ } }, "node_modules/lerna": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/lerna/-/lerna-7.1.4.tgz", - "integrity": "sha512-/cabvmTTkmayyALIZx7OpHRex72i8xSOkiJchEkrKxAZHoLNaGSwqwKkj+x6WtmchhWl/gLlqwQXGRuxrJKiBw==", + "version": "8.1.9", + "resolved": "https://registry.npmjs.org/lerna/-/lerna-8.1.9.tgz", + "integrity": "sha512-ZRFlRUBB2obm+GkbTR7EbgTMuAdni6iwtTQTMy7LIrQ4UInG44LyfRepljtgUxh4HA0ltzsvWfPkd5J1DKGCeQ==", "dev": true, + "license": "MIT", "dependencies": { - "@lerna/child-process": "7.1.4", - "@lerna/create": "7.1.4", - "@npmcli/run-script": "6.0.2", - "@nx/devkit": ">=16.5.1 < 17", + "@lerna/create": "8.1.9", + "@npmcli/arborist": "7.5.4", + "@npmcli/package-json": "5.2.0", + "@npmcli/run-script": "8.1.0", + "@nx/devkit": ">=17.1.2 < 21", "@octokit/plugin-enterprise-rest": "6.0.1", "@octokit/rest": "19.0.11", + "aproba": "2.0.0", "byte-size": "8.1.1", "chalk": "4.1.0", "clone-deep": "4.0.1", - "cmd-shim": "6.0.1", + "cmd-shim": "6.0.3", + "color-support": "1.1.3", "columnify": "1.6.0", - "conventional-changelog-angular": "6.0.0", + "console-control-strings": "^1.1.0", + "conventional-changelog-angular": "7.0.0", "conventional-changelog-core": "5.0.1", "conventional-recommended-bump": "7.0.1", - "cosmiconfig": "^8.2.0", - "dedent": "0.7.0", - "envinfo": "7.8.1", + "cosmiconfig": "9.0.0", + "dedent": "1.5.3", + "envinfo": "7.13.0", "execa": "5.0.0", - "fs-extra": "^11.1.1", + "fs-extra": "^11.2.0", "get-port": "5.1.1", "get-stream": "6.0.0", - "git-url-parse": "13.1.0", - "glob-parent": "5.1.2", + "git-url-parse": "14.0.0", + "glob-parent": "6.0.2", "globby": "11.1.0", "graceful-fs": "4.2.11", "has-unicode": "2.0.1", "import-local": "3.1.0", "ini": "^1.3.8", - "init-package-json": "5.0.0", + "init-package-json": "6.0.3", "inquirer": "^8.2.4", "is-ci": "3.0.1", "is-stream": "2.0.0", "jest-diff": ">=29.4.3 < 30", "js-yaml": "4.1.0", - "libnpmaccess": "7.0.2", - "libnpmpublish": "7.3.0", + "libnpmaccess": "8.0.6", + "libnpmpublish": "9.0.9", "load-json-file": "6.2.0", "lodash": "^4.17.21", - "make-dir": "3.1.0", + "make-dir": "4.0.0", "minimatch": "3.0.5", "multimatch": "5.0.0", "node-fetch": "2.6.7", - "npm-package-arg": "8.1.1", - "npm-packlist": "5.1.1", - "npm-registry-fetch": "^14.0.5", - "npmlog": "^6.0.2", - "nx": ">=16.5.1 < 17", + "npm-package-arg": "11.0.2", + "npm-packlist": "8.0.2", + "npm-registry-fetch": "^17.1.0", + "nx": ">=17.1.2 < 21", "p-map": "4.0.0", "p-map-series": "2.1.0", "p-pipe": "3.1.0", "p-queue": "6.6.2", "p-reduce": "2.1.0", "p-waterfall": "2.1.1", - "pacote": "^15.2.0", + "pacote": "^18.0.6", "pify": "5.0.0", "read-cmd-shim": "4.0.0", - "read-package-json": "6.0.4", "resolve-from": "5.0.0", "rimraf": "^4.4.1", "semver": "^7.3.8", + "set-blocking": "^2.0.0", "signal-exit": "3.0.7", "slash": "3.0.0", - "ssri": "^9.0.1", + "ssri": "^10.0.6", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", "strong-log-transformer": "2.1.0", - "tar": "6.1.11", + "tar": "6.2.1", "temp-dir": "1.0.0", "typescript": ">=3 < 6", "upath": "2.0.1", - "uuid": "^9.0.0", + "uuid": "^10.0.0", "validate-npm-package-license": "3.0.4", - "validate-npm-package-name": "5.0.0", + "validate-npm-package-name": "5.0.1", + "wide-align": "1.1.5", "write-file-atomic": "5.0.1", "write-pkg": "4.0.0", - "yargs": "16.2.0", - "yargs-parser": "20.2.4" + "yargs": "17.7.2", + "yargs-parser": "21.1.1" }, "bin": { "lerna": "dist/cli.js" }, "engines": { - "node": "^14.17.0 || >=16.0.0" + "node": ">=18.0.0" } }, "node_modules/lerna/node_modules/@octokit/auth-token": { @@ -32468,11 +30468,19 @@ "@octokit/openapi-types": "^18.0.0" } }, + "node_modules/lerna/node_modules/aproba": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", + "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==", + "dev": true, + "license": "ISC" + }, "node_modules/lerna/node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true + "dev": true, + "license": "Python-2.0" }, "node_modules/lerna/node_modules/before-after-hook": { "version": "2.2.3", @@ -32518,15 +30526,16 @@ } }, "node_modules/lerna/node_modules/cosmiconfig": { - "version": "8.3.6", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", - "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", + "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", "dev": true, + "license": "MIT", "dependencies": { + "env-paths": "^2.2.1", "import-fresh": "^3.3.0", "js-yaml": "^4.1.0", - "parse-json": "^5.2.0", - "path-type": "^4.0.0" + "parse-json": "^5.2.0" }, "engines": { "node": ">=14" @@ -32543,18 +30552,19 @@ } } }, - "node_modules/lerna/node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "node_modules/lerna/node_modules/dedent": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.3.tgz", + "integrity": "sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ==", "dev": true, - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" + "license": "MIT", + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" }, - "engines": { - "node": ">= 8" + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } } }, "node_modules/lerna/node_modules/emoji-regex": { @@ -32563,18 +30573,6 @@ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true }, - "node_modules/lerna/node_modules/envinfo": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.8.1.tgz", - "integrity": "sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw==", - "dev": true, - "bin": { - "envinfo": "dist/cli.js" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/lerna/node_modules/execa": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/execa/-/execa-5.0.0.tgz", @@ -32640,34 +30638,35 @@ } }, "node_modules/lerna/node_modules/glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "version": "9.3.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-9.3.5.tgz", + "integrity": "sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q==", "dev": true, + "license": "ISC", "dependencies": { "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" + "minimatch": "^8.0.2", + "minipass": "^4.2.4", + "path-scurry": "^1.6.1" }, "engines": { - "node": ">=12" + "node": ">=16 || 14 >=14.17" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/lerna/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", "dev": true, + "license": "ISC", "dependencies": { - "is-glob": "^4.0.1" + "is-glob": "^4.0.3" }, "engines": { - "node": ">= 6" + "node": ">=10.13.0" } }, "node_modules/lerna/node_modules/glob/node_modules/brace-expansion": { @@ -32675,20 +30674,35 @@ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "dev": true, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" } }, "node_modules/lerna/node_modules/glob/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-8.0.4.tgz", + "integrity": "sha512-W0Wvr9HyFXZRGIDgCicunpQ299OKXs9RgZfaukz4qAW/pJhcpUfupc9c+OObPOFueNy8VSrZgEmDtk6Kh4WzDA==", "dev": true, + "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" }, "engines": { - "node": ">=10" + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/lerna/node_modules/glob/node_modules/minipass": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-4.2.8.tgz", + "integrity": "sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=8" } }, "node_modules/lerna/node_modules/has-flag": { @@ -32701,15 +30715,16 @@ } }, "node_modules/lerna/node_modules/hosted-git-info": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-3.0.8.tgz", - "integrity": "sha512-aXpmwoOhRBrw6X3j0h5RloK4x1OzsxMPyxqIHyNfSe2pypkVTZFpEiRoSipPEPlMrh0HW/XsjkJ5WgnCirpNUw==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz", + "integrity": "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==", "dev": true, + "license": "ISC", "dependencies": { - "lru-cache": "^6.0.0" + "lru-cache": "^10.0.1" }, "engines": { - "node": ">=10" + "node": "^16.14.0 || >=18.0.0" } }, "node_modules/lerna/node_modules/human-signals": { @@ -32722,15 +30737,16 @@ } }, "node_modules/lerna/node_modules/ignore-walk": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-5.0.1.tgz", - "integrity": "sha512-yemi4pMf51WKT7khInJqAvsIGzoqYXblnsz0ql8tM+yi1EKYTY1evX4NAbJrLL/Aanr2HyZeluqU+Oi7MGHokw==", + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-6.0.5.tgz", + "integrity": "sha512-VuuG0wCnjhnylG1ABXT3dAuIpTNDs/G8jlpmwXY03fXoXy/8ZK8/T+hMzt8L4WnrLCJgdybqgPagnF/f97cg3A==", "dev": true, + "license": "ISC", "dependencies": { - "minimatch": "^5.0.1" + "minimatch": "^9.0.0" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, "node_modules/lerna/node_modules/ignore-walk/node_modules/brace-expansion": { @@ -32738,20 +30754,25 @@ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "dev": true, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" } }, "node_modules/lerna/node_modules/ignore-walk/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "dev": true, + "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" }, "engines": { - "node": ">=10" + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/lerna/node_modules/inquirer": { @@ -32831,6 +30852,7 @@ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "dev": true, + "license": "MIT", "dependencies": { "argparse": "^2.0.1" }, @@ -32850,42 +30872,22 @@ "graceful-fs": "^4.1.6" } }, - "node_modules/lerna/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/lerna/node_modules/make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", "dev": true, + "license": "MIT", "dependencies": { - "semver": "^6.0.0" + "semver": "^7.5.3" }, "engines": { - "node": ">=8" + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/lerna/node_modules/make-dir/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/lerna/node_modules/mimic-fn": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", @@ -32907,6 +30909,16 @@ "node": "*" } }, + "node_modules/lerna/node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, "node_modules/lerna/node_modules/node-fetch": { "version": "2.6.7", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", @@ -32929,44 +30941,32 @@ } }, "node_modules/lerna/node_modules/npm-package-arg": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-8.1.1.tgz", - "integrity": "sha512-CsP95FhWQDwNqiYS+Q0mZ7FAEDytDZAkNxQqea6IaAFJTAY9Lhhqyl0irU/6PMc7BGfUmnsbHcqxJD7XuVM/rg==", + "version": "11.0.2", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-11.0.2.tgz", + "integrity": "sha512-IGN0IAwmhDJwy13Wc8k+4PEbTPhpJnMtfR53ZbOyjkvmEcLS4nCwp6mvMWjS5sUjeiW3mpx6cHmuhKEu9XmcQw==", "dev": true, + "license": "ISC", "dependencies": { - "hosted-git-info": "^3.0.6", - "semver": "^7.0.0", - "validate-npm-package-name": "^3.0.0" + "hosted-git-info": "^7.0.0", + "proc-log": "^4.0.0", + "semver": "^7.3.5", + "validate-npm-package-name": "^5.0.0" }, "engines": { - "node": ">=10" - } - }, - "node_modules/lerna/node_modules/npm-package-arg/node_modules/validate-npm-package-name": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-3.0.0.tgz", - "integrity": "sha512-M6w37eVCMMouJ9V/sdPGnC5H4uDr73/+xdq0FBLO3TFFX1+7wiUY6Es328NN+y43tmY+doUdN9g9J21vqB7iLw==", - "dev": true, - "dependencies": { - "builtins": "^1.0.3" + "node": "^16.14.0 || >=18.0.0" } }, "node_modules/lerna/node_modules/npm-packlist": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-5.1.1.tgz", - "integrity": "sha512-UfpSvQ5YKwctmodvPPkK6Fwk603aoVsf8AEbmVKAEECrfvL8SSe1A2YIwrJ6xmTHAITKPwwZsWo7WwEbNk0kxw==", + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-8.0.2.tgz", + "integrity": "sha512-shYrPFIS/JLP4oQmAwDyk5HcyysKW8/JLTEA32S0Z5TzvpaeeX2yMFfoK1fjEBnCBvVyIB/Jj/GBFdm0wsgzbA==", "dev": true, + "license": "ISC", "dependencies": { - "glob": "^8.0.1", - "ignore-walk": "^5.0.1", - "npm-bundled": "^1.1.2", - "npm-normalize-package-bin": "^1.0.1" - }, - "bin": { - "npm-packlist": "bin/index.js" + "ignore-walk": "^6.0.4" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, "node_modules/lerna/node_modules/npm-run-path": { @@ -33028,27 +31028,6 @@ "node": ">=8" } }, - "node_modules/lerna/node_modules/path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/lerna/node_modules/pify": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-5.0.0.tgz", - "integrity": "sha512-eW/gHNMlxdSP6dmG6uJip6FXN0EQBwm2clYYd8Wul42Cwu/DK8HEftzsapcNdYe2MfLiIwZqsDk2RDEsTE79hA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/lerna/node_modules/resolve-from": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", @@ -33076,6 +31055,7 @@ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-4.4.1.tgz", "integrity": "sha512-Gk8NlF062+T9CqNGn6h4tls3k6T1+/nXdOcSZVikNVtlRdYpA7wRJJMoXmuvOnLW844rPjdQ7JgXCYM6PPC/og==", "dev": true, + "license": "ISC", "dependencies": { "glob": "^9.2.0" }, @@ -33089,57 +31069,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/lerna/node_modules/rimraf/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/lerna/node_modules/rimraf/node_modules/glob": { - "version": "9.3.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-9.3.5.tgz", - "integrity": "sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "minimatch": "^8.0.2", - "minipass": "^4.2.4", - "path-scurry": "^1.6.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/lerna/node_modules/rimraf/node_modules/minimatch": { - "version": "8.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-8.0.4.tgz", - "integrity": "sha512-W0Wvr9HyFXZRGIDgCicunpQ299OKXs9RgZfaukz4qAW/pJhcpUfupc9c+OObPOFueNy8VSrZgEmDtk6Kh4WzDA==", - "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/lerna/node_modules/rimraf/node_modules/minipass": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-4.2.8.tgz", - "integrity": "sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/lerna/node_modules/rxjs": { "version": "7.8.1", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", @@ -33149,37 +31078,17 @@ "tslib": "^2.1.0" } }, - "node_modules/lerna/node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/lerna/node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/lerna/node_modules/ssri": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-9.0.1.tgz", - "integrity": "sha512-o57Wcn66jMQvfHG1FlYbWeZWW/dHZhJXjpIcTfXldXEk5nz5lStPo3mK0OJQfGR3RbZUlbISexbljkJzuEj/8Q==", + "version": "10.0.6", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-10.0.6.tgz", + "integrity": "sha512-MGrFH9Z4NP9Iyhqn16sDtBpRRNJ0Y2hNa6D65h736fVSaPCHr4DM4sWUNvVaSuC+0OBGhwsrydQwmgfg5LncqQ==", "dev": true, + "license": "ISC", "dependencies": { - "minipass": "^3.1.1" + "minipass": "^7.0.3" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, "node_modules/lerna/node_modules/string-width": { @@ -33233,40 +31142,28 @@ "yarn": "*" } }, - "node_modules/lerna/node_modules/validate-npm-package-name": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-5.0.0.tgz", - "integrity": "sha512-YuKoXDAhBYxY7SfOKxHBDoSyENFeW5VvIIQp2TGQuit8gpK6MnWaQelBKxso72DoxTZfZdcP3W90LqpSkgPzLQ==", - "dev": true, - "dependencies": { - "builtins": "^5.0.0" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/lerna/node_modules/validate-npm-package-name/node_modules/builtins": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/builtins/-/builtins-5.1.0.tgz", - "integrity": "sha512-SW9lzGTLvWTP1AY8xeAMZimqDrIaSdLQUcVr9DMef51niJ022Ri87SwRRKYm4A6iHfkPaiVUu/Duw2Wc4J7kKg==", + "node_modules/lerna/node_modules/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", "dev": true, - "dependencies": { - "semver": "^7.0.0" + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" } }, - "node_modules/lerna/node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "node_modules/lerna/node_modules/validate-npm-package-name": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-5.0.1.tgz", + "integrity": "sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==", "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, + "license": "ISC", "engines": { - "node": ">= 8" + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, "node_modules/lerna/node_modules/write-file-atomic": { @@ -33294,11 +31191,15 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/lerna/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true + "node_modules/lerna/node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } }, "node_modules/leven": { "version": "3.1.0", @@ -33340,52 +31241,46 @@ } }, "node_modules/libnpmaccess": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/libnpmaccess/-/libnpmaccess-7.0.2.tgz", - "integrity": "sha512-vHBVMw1JFMTgEk15zRsJuSAg7QtGGHpUSEfnbcRL1/gTBag9iEfJbyjpDmdJmwMhvpoLoNBtdAUCdGnaP32hhw==", + "version": "8.0.6", + "resolved": "https://registry.npmjs.org/libnpmaccess/-/libnpmaccess-8.0.6.tgz", + "integrity": "sha512-uM8DHDEfYG6G5gVivVl+yQd4pH3uRclHC59lzIbSvy7b5FEwR+mU49Zq1jEyRtRFv7+M99mUW9S0wL/4laT4lw==", "dev": true, + "license": "ISC", "dependencies": { - "npm-package-arg": "^10.1.0", - "npm-registry-fetch": "^14.0.3" + "npm-package-arg": "^11.0.2", + "npm-registry-fetch": "^17.0.1" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^16.14.0 || >=18.0.0" } }, "node_modules/libnpmaccess/node_modules/hosted-git-info": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-6.1.1.tgz", - "integrity": "sha512-r0EI+HBMcXadMrugk0GCQ+6BQV39PiWAZVfq7oIckeGiN7sjRGyQxPdft3nQekFTCQbYxLBH+/axZMeH8UX6+w==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz", + "integrity": "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==", "dev": true, + "license": "ISC", "dependencies": { - "lru-cache": "^7.5.1" + "lru-cache": "^10.0.1" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/libnpmaccess/node_modules/lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", - "dev": true, - "engines": { - "node": ">=12" + "node": "^16.14.0 || >=18.0.0" } }, "node_modules/libnpmaccess/node_modules/npm-package-arg": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-10.1.0.tgz", - "integrity": "sha512-uFyyCEmgBfZTtrKk/5xDfHp6+MdrqGotX/VoOyEEl3mBwiEE5FlBaePanazJSVMPT7vKepcjYBY2ztg9A3yPIA==", + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-11.0.3.tgz", + "integrity": "sha512-sHGJy8sOC1YraBywpzQlIKBE4pBbGbiF95U6Auspzyem956E0+FtDtsx1ZxlOJkQCZ1AFXAY/yuvtFYrOxF+Bw==", "dev": true, + "license": "ISC", "dependencies": { - "hosted-git-info": "^6.0.0", - "proc-log": "^3.0.0", + "hosted-git-info": "^7.0.0", + "proc-log": "^4.0.0", "semver": "^7.3.5", "validate-npm-package-name": "^5.0.0" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^16.14.0 || >=18.0.0" } }, "node_modules/libnpmaccess/node_modules/validate-npm-package-name": { @@ -33393,48 +31288,58 @@ "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-5.0.1.tgz", "integrity": "sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==", "dev": true, + "license": "ISC", "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, "node_modules/libnpmpublish": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/libnpmpublish/-/libnpmpublish-7.3.0.tgz", - "integrity": "sha512-fHUxw5VJhZCNSls0KLNEG0mCD2PN1i14gH5elGOgiVnU3VgTcRahagYP2LKI1m0tFCJ+XrAm0zVYyF5RCbXzcg==", + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/libnpmpublish/-/libnpmpublish-9.0.9.tgz", + "integrity": "sha512-26zzwoBNAvX9AWOPiqqF6FG4HrSCPsHFkQm7nT+xU1ggAujL/eae81RnCv4CJ2In9q9fh10B88sYSzKCUh/Ghg==", "dev": true, + "license": "ISC", "dependencies": { - "ci-info": "^3.6.1", - "normalize-package-data": "^5.0.0", - "npm-package-arg": "^10.1.0", - "npm-registry-fetch": "^14.0.3", - "proc-log": "^3.0.0", + "ci-info": "^4.0.0", + "normalize-package-data": "^6.0.1", + "npm-package-arg": "^11.0.2", + "npm-registry-fetch": "^17.0.1", + "proc-log": "^4.2.0", "semver": "^7.3.7", - "sigstore": "^1.4.0", - "ssri": "^10.0.1" + "sigstore": "^2.2.0", + "ssri": "^10.0.6" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^16.14.0 || >=18.0.0" } }, - "node_modules/libnpmpublish/node_modules/hosted-git-info": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-6.1.1.tgz", - "integrity": "sha512-r0EI+HBMcXadMrugk0GCQ+6BQV39PiWAZVfq7oIckeGiN7sjRGyQxPdft3nQekFTCQbYxLBH+/axZMeH8UX6+w==", + "node_modules/libnpmpublish/node_modules/ci-info": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.1.0.tgz", + "integrity": "sha512-HutrvTNsF48wnxkzERIXOe5/mlcfFcbfCmwcg6CJnizbSue78AbDt+1cgl26zwn61WFxhcPykPfZrbqjGmBb4A==", "dev": true, - "dependencies": { - "lru-cache": "^7.5.1" - }, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": ">=8" } }, - "node_modules/libnpmpublish/node_modules/lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "node_modules/libnpmpublish/node_modules/hosted-git-info": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz", + "integrity": "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==", "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^10.0.1" + }, "engines": { - "node": ">=12" + "node": "^16.14.0 || >=18.0.0" } }, "node_modules/libnpmpublish/node_modules/minipass": { @@ -33448,33 +31353,34 @@ } }, "node_modules/libnpmpublish/node_modules/normalize-package-data": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-5.0.0.tgz", - "integrity": "sha512-h9iPVIfrVZ9wVYQnxFgtw1ugSvGEMOlyPWWtm8BMJhnwyEL/FLbYbTY3V3PpjI/BUK67n9PEWDu6eHzu1fB15Q==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-6.0.2.tgz", + "integrity": "sha512-V6gygoYb/5EmNI+MEGrWkC+e6+Rr7mTmfHrxDbLzxQogBkgzo76rkok0Am6thgSF7Mv2nLOajAJj5vDJZEFn7g==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { - "hosted-git-info": "^6.0.0", - "is-core-module": "^2.8.1", + "hosted-git-info": "^7.0.0", "semver": "^7.3.5", "validate-npm-package-license": "^3.0.4" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^16.14.0 || >=18.0.0" } }, "node_modules/libnpmpublish/node_modules/npm-package-arg": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-10.1.0.tgz", - "integrity": "sha512-uFyyCEmgBfZTtrKk/5xDfHp6+MdrqGotX/VoOyEEl3mBwiEE5FlBaePanazJSVMPT7vKepcjYBY2ztg9A3yPIA==", + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-11.0.3.tgz", + "integrity": "sha512-sHGJy8sOC1YraBywpzQlIKBE4pBbGbiF95U6Auspzyem956E0+FtDtsx1ZxlOJkQCZ1AFXAY/yuvtFYrOxF+Bw==", "dev": true, + "license": "ISC", "dependencies": { - "hosted-git-info": "^6.0.0", - "proc-log": "^3.0.0", + "hosted-git-info": "^7.0.0", + "proc-log": "^4.0.0", "semver": "^7.3.5", "validate-npm-package-name": "^5.0.0" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^16.14.0 || >=18.0.0" } }, "node_modules/libnpmpublish/node_modules/ssri": { @@ -33482,6 +31388,7 @@ "resolved": "https://registry.npmjs.org/ssri/-/ssri-10.0.6.tgz", "integrity": "sha512-MGrFH9Z4NP9Iyhqn16sDtBpRRNJ0Y2hNa6D65h736fVSaPCHr4DM4sWUNvVaSuC+0OBGhwsrydQwmgfg5LncqQ==", "dev": true, + "license": "ISC", "dependencies": { "minipass": "^7.0.3" }, @@ -33494,39 +31401,51 @@ "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-5.0.1.tgz", "integrity": "sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==", "dev": true, + "license": "ISC", "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/lie": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz", + "integrity": "sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw==", + "license": "MIT", + "dependencies": { + "immediate": "~3.0.5" + } + }, "node_modules/lighthouse": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/lighthouse/-/lighthouse-10.4.0.tgz", - "integrity": "sha512-XQWHEWkJ8YxSPsxttBJORy5+hQrzbvGkYfeP3fJjyYKioWkF2MXfFqNK4ZuV4jL8pBu7Z91qnQP6In0bq1yXww==", + "version": "12.2.2", + "resolved": "https://registry.npmjs.org/lighthouse/-/lighthouse-12.2.2.tgz", + "integrity": "sha512-avoiiFeGN1gkWhp/W1schJoXOsTPxRKWV3+uW/rGHuov2g/HGB+4SN9J/av1GNSh13sEYgkHL3iJOp1+mBVKYQ==", + "license": "Apache-2.0", "dependencies": { - "@sentry/node": "^6.17.4", - "axe-core": "4.7.2", - "chrome-launcher": "^0.15.2", + "@paulirish/trace_engine": "0.0.32", + "@sentry/node": "^7.0.0", + "axe-core": "^4.10.2", + "chrome-launcher": "^1.1.2", "configstore": "^5.0.1", "csp_evaluator": "1.1.1", - "devtools-protocol": "0.0.1155343", + "devtools-protocol": "0.0.1312386", "enquirer": "^2.3.6", "http-link-header": "^1.1.1", - "intl-messageformat": "^4.4.0", + "intl-messageformat": "^10.5.3", "jpeg-js": "^0.4.4", - "js-library-detector": "^6.6.0", - "lighthouse-logger": "^1.4.1", - "lighthouse-stack-packs": "1.11.0", - "lodash": "^4.17.21", + "js-library-detector": "^6.7.0", + "lighthouse-logger": "^2.0.1", + "lighthouse-stack-packs": "1.12.2", + "lodash-es": "^4.17.21", "lookup-closest-locale": "6.2.0", "metaviewport-parser": "0.3.0", "open": "^8.4.0", "parse-cache-control": "1.0.1", - "ps-list": "^8.0.0", - "puppeteer-core": "^20.8.0", - "robots-parser": "^3.0.0", + "puppeteer-core": "^23.8.0", + "robots-parser": "^3.0.1", "semver": "^5.3.0", "speedline-core": "^1.4.3", - "third-party-web": "^0.23.3", + "third-party-web": "^0.26.1", + "tldts-icann": "^6.1.16", "ws": "^7.0.0", "yargs": "^17.3.1", "yargs-parser": "^21.0.0" @@ -33537,7 +31456,7 @@ "smokehouse": "cli/test/smokehouse/frontends/smokehouse-bin.js" }, "engines": { - "node": ">=16.16" + "node": ">=18.16" } }, "node_modules/lighthouse-logger": { @@ -33558,39 +31477,54 @@ } }, "node_modules/lighthouse-stack-packs": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/lighthouse-stack-packs/-/lighthouse-stack-packs-1.11.0.tgz", - "integrity": "sha512-sRr0z1S/I26VffRLq9KJsKtLk856YrJlNGmcJmbLX8dFn3MuzVPUbstuChEhqnSxZb8TZmVfthuXuwhG9vRoSw==" + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/lighthouse-stack-packs/-/lighthouse-stack-packs-1.12.2.tgz", + "integrity": "sha512-Ug8feS/A+92TMTCK6yHYLwaFMuelK/hAKRMdldYkMNwv+d9PtWxjXEg6rwKtsUXTADajhdrhXyuNCJ5/sfmPFw==", + "license": "Apache-2.0" }, - "node_modules/lighthouse/node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "node_modules/lighthouse/node_modules/chrome-launcher": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-1.1.2.tgz", + "integrity": "sha512-YclTJey34KUm5jB1aEJCq807bSievi7Nb/TU4Gu504fUYi3jw3KCIaH6L7nFWQhdEgH3V+wCh+kKD1P5cXnfxw==", + "license": "Apache-2.0", "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" + "@types/node": "*", + "escape-string-regexp": "^4.0.0", + "is-wsl": "^2.2.0", + "lighthouse-logger": "^2.0.1" + }, + "bin": { + "print-chrome-path": "bin/print-chrome-path.js" }, "engines": { - "node": ">=12" + "node": ">=12.13.0" } }, - "node_modules/lighthouse/node_modules/devtools-protocol": { - "version": "0.0.1155343", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1155343.tgz", - "integrity": "sha512-oD9vGBV2wTc7fAzAM6KC0chSgs234V8+qDEeK+mcbRj2UvcuA7lgBztGi/opj/iahcXD3BSj8Ymvib628yy9FA==" + "node_modules/lighthouse/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } }, - "node_modules/lighthouse/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + "node_modules/lighthouse/node_modules/devtools-protocol": { + "version": "0.0.1312386", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1312386.tgz", + "integrity": "sha512-DPnhUXvmvKT2dFA/j7B+riVLUt9Q6RKJlcppojL5CoRywJJKLDYnRlw0gTFKfgDPHP5E04UoB71SxoJlVZy8FA==", + "license": "BSD-3-Clause" }, - "node_modules/lighthouse/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "node_modules/lighthouse/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/lighthouse/node_modules/is-wsl": { @@ -33604,6 +31538,16 @@ "node": ">=8" } }, + "node_modules/lighthouse/node_modules/lighthouse-logger": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/lighthouse-logger/-/lighthouse-logger-2.0.1.tgz", + "integrity": "sha512-ioBrW3s2i97noEmnXxmUq7cjIcVRjT5HBpAYy8zE11CxU9HqlWHHeRxfeN1tn8F7OEMVPIC9x1f8t3Z7US9ehQ==", + "license": "Apache-2.0", + "dependencies": { + "debug": "^2.6.9", + "marky": "^1.2.2" + } + }, "node_modules/lighthouse/node_modules/open": { "version": "8.4.2", "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", @@ -33620,55 +31564,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/lighthouse/node_modules/puppeteer-core": { - "version": "20.9.0", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-20.9.0.tgz", - "integrity": "sha512-H9fYZQzMTRrkboEfPmf7m3CLDN6JvbxXA3qTtS+dFt27tR+CsFHzPsT6pzp6lYL6bJbAPaR0HaPO6uSi+F94Pg==", - "dependencies": { - "@puppeteer/browsers": "1.4.6", - "chromium-bidi": "0.4.16", - "cross-fetch": "4.0.0", - "debug": "4.3.4", - "devtools-protocol": "0.0.1147663", - "ws": "8.13.0" - }, - "engines": { - "node": ">=16.3.0" - }, - "peerDependencies": { - "typescript": ">= 4.7.4" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/lighthouse/node_modules/puppeteer-core/node_modules/devtools-protocol": { - "version": "0.0.1147663", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1147663.tgz", - "integrity": "sha512-hyWmRrexdhbZ1tcJUGpO95ivbRhWXz++F4Ko+n21AY5PNln2ovoJw+8ZMNDTtip+CNFQfrtLVh/w4009dXO/eQ==" - }, - "node_modules/lighthouse/node_modules/puppeteer-core/node_modules/ws": { - "version": "8.13.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", - "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, "node_modules/lighthouse/node_modules/semver": { "version": "5.7.2", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", @@ -33677,60 +31572,6 @@ "semver": "bin/semver" } }, - "node_modules/lighthouse/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/lighthouse/node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/lighthouse/node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "engines": { - "node": ">=10" - } - }, - "node_modules/lighthouse/node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/lighthouse/node_modules/yargs-parser": { "version": "21.1.1", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", @@ -33836,20 +31677,6 @@ "node": ">=8" } }, - "node_modules/lint-staged/node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, "node_modules/lint-staged/node_modules/execa": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/execa/-/execa-3.4.0.tgz", @@ -34014,27 +31841,6 @@ "node": ">=8" } }, - "node_modules/lint-staged/node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/lint-staged/node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/lint-staged/node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -34047,27 +31853,6 @@ "node": ">=8" } }, - "node_modules/lint-staged/node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/listenercount": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/listenercount/-/listenercount-1.0.1.tgz", - "integrity": "sha512-3mk/Zag0+IJxeDrxSgaDPy4zZ3w05PRZeJNnlWhzFz5OkX49J4krc+A8X2d2M69vGMBEX0uyl8M+W+8gH+kBqQ==", - "dev": true - }, "node_modules/listr": { "version": "0.14.3", "resolved": "https://registry.npmjs.org/listr/-/listr-0.14.3.tgz", @@ -34266,6 +32051,7 @@ "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-6.2.0.tgz", "integrity": "sha512-gUD/epcRms75Cw8RT1pUdHugZYM5ce64ucs2GEISABwkRsOQr0q2wm/MV2TKThycIe5e0ytRweW2RZxclogCdQ==", "dev": true, + "license": "MIT", "dependencies": { "graceful-fs": "^4.1.15", "parse-json": "^5.0.0", @@ -34298,24 +32084,45 @@ "node": ">=8.9.0" } }, + "node_modules/localforage": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/localforage/-/localforage-1.10.0.tgz", + "integrity": "sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==", + "license": "Apache-2.0", + "dependencies": { + "lie": "3.1.1" + } + }, "node_modules/locate-app": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/locate-app/-/locate-app-2.1.0.tgz", - "integrity": "sha512-rcVo/iLUxrd9d0lrmregK/Z5Y5NCpSwf9KlMbPpOHmKmdxdQY1Fj8NDQ5QymJTryCsBLqwmniFv2f3JKbk9Bvg==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/locate-app/-/locate-app-2.5.0.tgz", + "integrity": "sha512-xIqbzPMBYArJRmPGUZD9CzV9wOqmVtQnaAn3wrj3s6WYW0bQvPI7x+sPYUGmDTYMHefVK//zc6HEYZ1qnxIK+Q==", "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://buymeacoffee.com/hejny" + }, + { + "type": "github", + "url": "https://github.com/hejny/locate-app/blob/main/README.md#%EF%B8%8F-contributing" + } + ], + "license": "Apache-2.0", "dependencies": { - "n12": "0.4.0", - "type-fest": "2.13.0", - "userhome": "1.0.0" + "@promptbook/utils": "0.69.5", + "type-fest": "4.26.0", + "userhome": "1.0.1" } }, "node_modules/locate-app/node_modules/type-fest": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.13.0.tgz", - "integrity": "sha512-lPfAm42MxE4/456+QyIaaVBAwgpJb6xZ8PRu09utnhPdWwcyj9vgy6Sq0Z5yNbJ21EdxB5dRU/Qg8bsyAMtlcw==", + "version": "4.26.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.26.0.tgz", + "integrity": "sha512-OduNjVJsFbifKb57UqZ2EMP1i4u64Xwow3NYXUtBbD4vIwJdQd4+xl8YDou1dlm4DVrtwT/7Ky8z8WyCULVfxw==", "dev": true, + "license": "(MIT OR CC0-1.0)", "engines": { - "node": ">=12.20" + "node": ">=16" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -34347,6 +32154,12 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, + "node_modules/lodash-es": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", + "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", + "license": "MIT" + }, "node_modules/lodash.clonedeep": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", @@ -34383,7 +32196,8 @@ "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.ismatch/-/lodash.ismatch-4.4.0.tgz", "integrity": "sha512-fPMfXjGQEV9Xsq/8MTSgUf255gawYRbjwMyDbcvDhXgV7enSZA0hynz6vMPnpAb5iONEzBHBPsT+0zes5Z301g==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/lodash.memoize": { "version": "4.1.2", @@ -34661,10 +32475,11 @@ } }, "node_modules/loglevel": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.8.1.tgz", - "integrity": "sha512-tCRIJM51SHjAayKwC+QAg8hT8vg6z7GSgLJKGvzuPb1Wc+hLzqtuVLxp6/HzSPOozuK+8ErAhy7U/sVzw8Dgfg==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.9.2.tgz", + "integrity": "sha512-HgMmCqIJSAKqo68l0rS2AanEWfkxaZ5wNiEFb5ggm08lDs9Xl2KxBlX3PTcaD2chBM1gXAYf491/M2Rv8Jwayg==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.6.0" }, @@ -34677,7 +32492,8 @@ "version": "0.8.4", "resolved": "https://registry.npmjs.org/loglevel-plugin-prefix/-/loglevel-plugin-prefix-0.8.4.tgz", "integrity": "sha512-WpG9CcFAOjz/FtNht+QJeGpvVl/cdR6P0z6OcXSkr8wFJOsV2GRj2j10JLfjuA4aYkcKCNIEqRGCyTife9R8/g==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/longest-streak": { "version": "2.0.2", @@ -34700,6 +32516,12 @@ "loose-envify": "cli.js" } }, + "node_modules/loupe": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.2.tgz", + "integrity": "sha512-23I4pFZHmAemUnz8WZXbYRSKYj801VDaNv9ETuMh7IrMc7VuVVSo+Z9iLE3ni30+U48iDWfi30d3twAXBYmnCg==", + "dev": true + }, "node_modules/lower-case": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", @@ -34716,19 +32538,11 @@ "node": ">=8" } }, - "node_modules/lru_map": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/lru_map/-/lru_map-0.3.3.tgz", - "integrity": "sha512-Pn9cox5CsMYngeDbmChANltQl+5pi6XmTrraMSzhPmMBbmgcxmqWry0U3PGapCU1yB4/LqCcom7qhHZiF/jGfQ==" - }, "node_modules/lru-cache": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", - "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", - "dependencies": { - "pseudomap": "^1.0.2", - "yallist": "^2.1.2" - } + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "license": "ISC" }, "node_modules/lunr": { "version": "2.3.9", @@ -34755,15 +32569,12 @@ } }, "node_modules/magic-string": { - "version": "0.30.7", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.7.tgz", - "integrity": "sha512-8vBuFF/I/+OSLRmdf2wwFCJCz+nSn0m6DPvGH1fS/KiQoSaR+sETbov0eIk9KhEKy8CYqIkIAnbohxT/4H0kuA==", + "version": "0.30.15", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.15.tgz", + "integrity": "sha512-zXeaYRgZ6ldS1RJJUrMrYgNJ4fdwnyI6tVqoiIhyCyv5IVTK9BU8Ic2l253GGETQHxI4HNUwhJ3fjDhKqEoaAw==", "dev": true, "dependencies": { - "@jridgewell/sourcemap-codec": "^1.4.15" - }, - "engines": { - "node": ">=12" + "@jridgewell/sourcemap-codec": "^1.5.0" } }, "node_modules/make-dir": { @@ -34792,29 +32603,27 @@ "dev": true }, "node_modules/make-fetch-happen": { - "version": "11.1.1", - "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-11.1.1.tgz", - "integrity": "sha512-rLWS7GCSTcEujjVBs2YqG7Y4643u8ucvCJeSRqiLYhesrDuzeuFIk37xREzAsfQaqzl8b9rNCE4m6J8tvX4Q8w==", + "version": "13.0.1", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-13.0.1.tgz", + "integrity": "sha512-cKTUFc/rbKUd/9meOvgrpJ2WrNzymt6jfRDdwg5UCnVzv9dTpEj9JS5m3wtziXVCjluIXyL8pcaukYqezIzZQA==", "dev": true, + "license": "ISC", "dependencies": { - "agentkeepalive": "^4.2.1", - "cacache": "^17.0.0", + "@npmcli/agent": "^2.0.0", + "cacache": "^18.0.0", "http-cache-semantics": "^4.1.1", - "http-proxy-agent": "^5.0.0", - "https-proxy-agent": "^5.0.0", "is-lambda": "^1.0.1", - "lru-cache": "^7.7.1", - "minipass": "^5.0.0", + "minipass": "^7.0.2", "minipass-fetch": "^3.0.0", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", "negotiator": "^0.6.3", + "proc-log": "^4.2.0", "promise-retry": "^2.0.1", - "socks-proxy-agent": "^7.0.0", "ssri": "^10.0.0" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^16.14.0 || >=18.0.0" } }, "node_modules/make-fetch-happen/node_modules/brace-expansion": { @@ -34822,22 +32631,24 @@ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "dev": true, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" } }, "node_modules/make-fetch-happen/node_modules/cacache": { - "version": "17.1.4", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-17.1.4.tgz", - "integrity": "sha512-/aJwG2l3ZMJ1xNAnqbMpA40of9dj/pIH3QfiuQSqjfPJF747VR0J/bHn+/KdNnHKc6XQcWt/AfRSBft82W1d2A==", + "version": "18.0.4", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-18.0.4.tgz", + "integrity": "sha512-B+L5iIa9mgcjLbliir2th36yEwPftrzteHYujzsx3dFP/31GCHcIeS8f5MGd80odLOjaOvSpU3EEAmRQptkxLQ==", "dev": true, + "license": "ISC", "dependencies": { "@npmcli/fs": "^3.1.0", "fs-minipass": "^3.0.0", "glob": "^10.2.2", - "lru-cache": "^7.7.1", + "lru-cache": "^10.0.1", "minipass": "^7.0.3", - "minipass-collect": "^1.0.2", + "minipass-collect": "^2.0.1", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", "p-map": "^4.0.0", @@ -34846,17 +32657,7 @@ "unique-filename": "^3.0.0" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/make-fetch-happen/node_modules/cacache/node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=16 || 14 >=14.17" + "node": "^16.14.0 || >=18.0.0" } }, "node_modules/make-fetch-happen/node_modules/fs-minipass": { @@ -34864,6 +32665,7 @@ "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-3.0.3.tgz", "integrity": "sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==", "dev": true, + "license": "ISC", "dependencies": { "minipass": "^7.0.3" }, @@ -34871,16 +32673,6 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/make-fetch-happen/node_modules/fs-minipass/node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, "node_modules/make-fetch-happen/node_modules/glob": { "version": "10.4.5", "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", @@ -34902,41 +32694,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/make-fetch-happen/node_modules/glob/node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/make-fetch-happen/node_modules/jackspeak": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", - "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" - } - }, - "node_modules/make-fetch-happen/node_modules/lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", - "dev": true, - "engines": { - "node": ">=12" - } - }, "node_modules/make-fetch-happen/node_modules/minimatch": { "version": "9.0.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", @@ -34954,12 +32711,13 @@ } }, "node_modules/make-fetch-happen/node_modules/minipass": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", - "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", "dev": true, + "license": "ISC", "engines": { - "node": ">=8" + "node": ">=16 || 14 >=14.17" } }, "node_modules/make-fetch-happen/node_modules/ssri": { @@ -34967,6 +32725,7 @@ "resolved": "https://registry.npmjs.org/ssri/-/ssri-10.0.6.tgz", "integrity": "sha512-MGrFH9Z4NP9Iyhqn16sDtBpRRNJ0Y2hNa6D65h736fVSaPCHr4DM4sWUNvVaSuC+0OBGhwsrydQwmgfg5LncqQ==", "dev": true, + "license": "ISC", "dependencies": { "minipass": "^7.0.3" }, @@ -34974,21 +32733,12 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/make-fetch-happen/node_modules/ssri/node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, "node_modules/make-fetch-happen/node_modules/unique-filename": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-3.0.0.tgz", "integrity": "sha512-afXhuC55wkAmZ0P18QsVE6kp8JaxrEokN2HGIoIVv2ijHQd419H0+6EigAFcIzXeMIkcIkNBpB3L/DXB3cTS/g==", "dev": true, + "license": "ISC", "dependencies": { "unique-slug": "^4.0.0" }, @@ -35001,6 +32751,7 @@ "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-4.0.0.tgz", "integrity": "sha512-WrcA6AyEfqDX5bWige/4NQfPZMtASNVxdmWR76WESYQVAACSgWcR6e9i0mofqqBxYFtL4oAxPIptY73/0YE1DQ==", "dev": true, + "license": "ISC", "dependencies": { "imurmurhash": "^0.1.4" }, @@ -35081,10 +32832,11 @@ } }, "node_modules/markdown-builder/node_modules/cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "version": "6.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.6.tgz", + "integrity": "sha512-VqCUuhcd1iB+dsv8gxPttb5iZh/D0iubSP21g36KXdEuf6I5JiioesUVjpCdHV9MZRUfVFlvwtIUyPfxo5trtw==", "dev": true, + "license": "MIT", "dependencies": { "nice-try": "^1.0.4", "path-key": "^2.0.1", @@ -35349,18 +33101,6 @@ "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-1.1.3.tgz", "integrity": "sha512-1RUZVgQlpJSPWYbFSpmudq5nHY1doEIv89gBtF0s4gW1GF2XorxcA/70M5vq7rLv0a6mhOUccRsqkwhwLCIQ2Q==" }, - "node_modules/markdown-to-jsx": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/markdown-to-jsx/-/markdown-to-jsx-7.4.1.tgz", - "integrity": "sha512-GbrbkTnHp9u6+HqbPRFJbObi369AgJNXi/sGqq5HRsoZW063xR1XDCaConqq+whfEIAlzB1YPnOgsPc7B7bc/A==", - "dev": true, - "engines": { - "node": ">= 10" - }, - "peerDependencies": { - "react": ">= 0.14.0" - } - }, "node_modules/markdownlint": { "version": "0.25.1", "resolved": "https://registry.npmjs.org/markdownlint/-/markdownlint-0.25.1.tgz", @@ -35534,58 +33274,6 @@ "unist-util-visit": "^1.1.0" } }, - "node_modules/mdast-util-definitions": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-definitions/-/mdast-util-definitions-4.0.0.tgz", - "integrity": "sha512-k8AJ6aNnUkB7IE+5azR9h81O5EQ/cTDXtWdMq9Kk5KcEW/8ritU5CeLg/9HhOC++nALHBlaogJ5jz0Ybk3kPMQ==", - "dev": true, - "dependencies": { - "unist-util-visit": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-definitions/node_modules/unist-util-is": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-4.1.0.tgz", - "integrity": "sha512-ZOQSsnce92GrxSqlnEEseX0gi7GH9zTJZ0p9dtu87WRb/37mMPO2Ilx1s/t9vBHrFhbgweUwb+t7cIn5dxPhZg==", - "dev": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-definitions/node_modules/unist-util-visit": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-2.0.3.tgz", - "integrity": "sha512-iJ4/RczbJMkD0712mGktuGpm/U4By4FfDonL7N/9tATGIF4imikjOuagyMY53tnZq3NP6BcmlrHhEKAfGWjh7Q==", - "dev": true, - "dependencies": { - "@types/unist": "^2.0.0", - "unist-util-is": "^4.0.0", - "unist-util-visit-parents": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-definitions/node_modules/unist-util-visit-parents": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-3.1.1.tgz", - "integrity": "sha512-1KROIZWo6bcMrZEwiH2UrXDyalAa0uqzWCxCJj6lPOvTve2WkfgCytoDTPaMnodXh1WrXOq0haVYHj99ynJlsg==", - "dev": true, - "dependencies": { - "@types/unist": "^2.0.0", - "unist-util-is": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, "node_modules/mdast-util-inject": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/mdast-util-inject/-/mdast-util-inject-1.1.0.tgz", @@ -35671,6 +33359,7 @@ "resolved": "https://registry.npmjs.org/meow/-/meow-8.1.2.tgz", "integrity": "sha512-r85E3NdZ+mpYk1C6RjPFEMSE+s1iZMuHtsHAqY0DT3jZczl0diWUZ8g6oU7h0M9cD2EL+PzaYghhCLzR0ZNn5Q==", "dev": true, + "license": "MIT", "dependencies": { "@types/minimist": "^1.2.0", "camelcase-keys": "^6.2.2", @@ -35696,6 +33385,7 @@ "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz", "integrity": "sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "hosted-git-info": "^4.0.1", "is-core-module": "^2.5.0", @@ -35711,6 +33401,7 @@ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.18.1.tgz", "integrity": "sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==", "dev": true, + "license": "(MIT OR CC0-1.0)", "engines": { "node": ">=10" }, @@ -35804,7 +33495,8 @@ "node_modules/merge-descriptors": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" + "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==", + "license": "MIT" }, "node_modules/merge-stream": { "version": "2.0.0", @@ -35958,6 +33650,43 @@ "node": ">=18" } }, + "node_modules/metro-cache/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/metro-cache/node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/metro-config": { "version": "0.80.5", "resolved": "https://registry.npmjs.org/metro-config/-/metro-config-0.80.5.tgz", @@ -36181,19 +33910,6 @@ "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==" }, - "node_modules/metro/node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/metro/node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -36202,10 +33918,26 @@ "ms": "2.0.0" } }, - "node_modules/metro/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + "node_modules/metro/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } }, "node_modules/metro/node_modules/hermes-estree": { "version": "0.18.2", @@ -36220,12 +33952,20 @@ "hermes-estree": "0.18.2" } }, - "node_modules/metro/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "engines": { - "node": ">=8" + "node_modules/metro/node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/metro/node_modules/source-map": { @@ -36236,68 +33976,6 @@ "node": ">=0.10.0" } }, - "node_modules/metro/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/metro/node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/metro/node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "engines": { - "node": ">=10" - } - }, - "node_modules/metro/node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/metro/node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "engines": { - "node": ">=12" - } - }, "node_modules/micromatch": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", @@ -36386,11 +34064,13 @@ } }, "node_modules/mini-css-extract-plugin": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.5.1.tgz", - "integrity": "sha512-CRC6E2yedNjfOA3nQjpqAkpnranxhxmilhBPYtldnXcPT/QZb3aJFzvt0pp8W1jhuLR/E0zDa+QEHuC/HhhaLQ==", + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.9.2.tgz", + "integrity": "sha512-GJuACcS//jtq4kCtd5ii/M0SZf7OZRH+BxdqXZHaJfb8TJiVl+NgQRPwiYt2EuqeSkNydn/7vP+bcE27C5mb9w==", + "license": "MIT", "dependencies": { - "schema-utils": "^4.0.0" + "schema-utils": "^4.0.0", + "tapable": "^2.2.1" }, "engines": { "node": ">= 12.13.0" @@ -36423,6 +34103,7 @@ "version": "5.1.0", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3" }, @@ -36433,12 +34114,14 @@ "node_modules/mini-css-extract-plugin/node_modules/json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" }, "node_modules/mini-css-extract-plugin/node_modules/schema-utils": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.2.0.tgz", "integrity": "sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw==", + "license": "MIT", "dependencies": { "@types/json-schema": "^7.0.9", "ajv": "^8.9.0", @@ -36453,6 +34136,15 @@ "url": "https://opencollective.com/webpack" } }, + "node_modules/mini-css-extract-plugin/node_modules/tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/minimalistic-assert": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", @@ -36517,24 +34209,36 @@ } }, "node_modules/minipass-collect": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz", - "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-2.0.1.tgz", + "integrity": "sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw==", "dev": true, + "license": "ISC", "dependencies": { - "minipass": "^3.0.0" + "minipass": "^7.0.3" }, "engines": { - "node": ">= 8" + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minipass-collect/node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" } }, "node_modules/minipass-fetch": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-3.0.3.tgz", - "integrity": "sha512-n5ITsTkDqYkYJZjcRWzZt9qnZKCT7nKCosJhHoj7S7zD+BP4jVbWs+odsniw5TA3E0sLomhTKOKjF86wf11PuQ==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-3.0.5.tgz", + "integrity": "sha512-2N8elDQAtSnFV0Dk7gt15KHsS0Fyz6CbYZ360h0WTYV1Ty46li3rAXVOQj1THMNLdmrD9Vt5pBPtWtVkpwGBqg==", "dev": true, + "license": "MIT", "dependencies": { - "minipass": "^5.0.0", + "minipass": "^7.0.3", "minipass-sized": "^1.0.3", "minizlib": "^2.1.2" }, @@ -36546,12 +34250,13 @@ } }, "node_modules/minipass-fetch/node_modules/minipass": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", - "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", "dev": true, + "license": "ISC", "engines": { - "node": ">=8" + "node": ">=16 || 14 >=14.17" } }, "node_modules/minipass-flush": { @@ -36559,6 +34264,7 @@ "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", "dev": true, + "license": "ISC", "dependencies": { "minipass": "^3.0.0" }, @@ -36566,21 +34272,12 @@ "node": ">= 8" } }, - "node_modules/minipass-json-stream": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minipass-json-stream/-/minipass-json-stream-1.0.1.tgz", - "integrity": "sha512-ODqY18UZt/I8k+b7rl2AENgbWE8IDYam+undIJONvigAz8KR5GWblsFTEfQs0WODsjbSXWlm+JHEv8Gr6Tfdbg==", - "dev": true, - "dependencies": { - "jsonparse": "^1.3.1", - "minipass": "^3.0.0" - } - }, "node_modules/minipass-pipeline": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", "dev": true, + "license": "ISC", "dependencies": { "minipass": "^3.0.0" }, @@ -36593,6 +34290,7 @@ "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", "dev": true, + "license": "ISC", "dependencies": { "minipass": "^3.0.0" }, @@ -36600,12 +34298,6 @@ "node": ">=8" } }, - "node_modules/minipass/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, "node_modules/minizlib": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", @@ -36619,12 +34311,6 @@ "node": ">= 8" } }, - "node_modules/minizlib/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, "node_modules/mississippi": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz", @@ -36658,9 +34344,10 @@ } }, "node_modules/mitt": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.0.tgz", - "integrity": "sha512-7dX2/10ITVyqh4aOSVI9gdape+t9l2/8QxHrFmUXu4EEUpdlxl6RudZUPZoc+zuY2hk1j7XxVroIVIan/pD/SQ==" + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", + "license": "MIT" }, "node_modules/mixin-deep": { "version": "1.3.2", @@ -36737,7 +34424,8 @@ "node_modules/mkdirp-classic": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", - "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==" + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "dev": true }, "node_modules/mock-match-media": { "version": "0.4.2", @@ -36753,6 +34441,7 @@ "resolved": "https://registry.npmjs.org/modify-values/-/modify-values-1.0.1.tgz", "integrity": "sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -36761,6 +34450,7 @@ "version": "2.29.4", "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==", + "license": "MIT", "engines": { "node": "*" } @@ -37049,6 +34739,7 @@ "resolved": "https://registry.npmjs.org/multimatch/-/multimatch-5.0.0.tgz", "integrity": "sha512-ypMKuglUrZUD99Tk2bUQ+xNQj43lPEfAeX2o9cTteAmShXy2VHDJpuwu1o0xqoKCt9jLVAvwyFKdLTPXKAfJyA==", "dev": true, + "license": "MIT", "dependencies": { "@types/minimatch": "^3.0.3", "array-differ": "^3.0.0", @@ -37068,6 +34759,7 @@ "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -37077,6 +34769,7 @@ "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -37151,12 +34844,6 @@ "rimraf": "bin.js" } }, - "node_modules/n12": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/n12/-/n12-0.4.0.tgz", - "integrity": "sha512-p/hj4zQ8d3pbbFLQuN1K9honUxiDDhueOWyFLw/XgBv+wZCE44bcLH4CIcsolOceJQduh4Jf7m/LfaTxyGmGtQ==", - "dev": true - }, "node_modules/nan": { "version": "2.18.0", "resolved": "https://registry.npmjs.org/nan/-/nan-2.18.0.tgz", @@ -37165,15 +34852,16 @@ "optional": true }, "node_modules/nanoid": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", - "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "version": "3.3.8", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", + "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", "funding": [ { "type": "github", "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "bin": { "nanoid": "bin/nanoid.cjs" }, @@ -37208,6 +34896,7 @@ "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz", "integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==", "dev": true, + "license": "MIT", "optional": true }, "node_modules/natural-compare": { @@ -37241,6 +34930,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz", "integrity": "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==", + "license": "MIT", "engines": { "node": ">= 0.4.0" } @@ -37284,10 +34974,11 @@ } }, "node_modules/node-abi": { - "version": "3.47.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.47.0.tgz", - "integrity": "sha512-2s6B2CWZM//kPgwnuI0KrYwNjfdByE25zvAaEpq9IH4zcNsarH8Ihu/UuX6XMPEogDAxkuUFeZn60pXNHAqn3A==", + "version": "3.71.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.71.0.tgz", + "integrity": "sha512-SZ40vRiy/+wRTf21hxkkEjPJZpARzUMVcJoQse2EF8qkUWbbO2z7vd5oA/H6bVH6SZQ5STGcu0KRDS7biNRfxw==", "dev": true, + "license": "MIT", "optional": true, "dependencies": { "semver": "^7.3.5" @@ -37302,10 +34993,12 @@ "integrity": "sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==" }, "node_modules/node-addon-api": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.2.1.tgz", - "integrity": "sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==", - "dev": true + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-6.1.0.tgz", + "integrity": "sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==", + "dev": true, + "license": "MIT", + "optional": true }, "node_modules/node-dir": { "version": "0.1.17", @@ -37333,6 +35026,7 @@ "url": "https://paypal.me/jimmywarting" } ], + "license": "MIT", "engines": { "node": ">=10.5.0" } @@ -37357,12 +35051,6 @@ } } }, - "node_modules/node-fetch-native": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.6.2.tgz", - "integrity": "sha512-69mtXOFZ6hSkYiXAVB5SqaRvrbITC/NPyqv7yuu/qw0nmgPyYbIMYYNIDhNtwPrzk0ptrimrLz/hhjvm4w5Z+w==", - "dev": true - }, "node_modules/node-forge": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", @@ -37372,74 +35060,111 @@ } }, "node_modules/node-gyp": { - "version": "9.4.0", - "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-9.4.0.tgz", - "integrity": "sha512-dMXsYP6gc9rRbejLXmTbVRYjAHw7ppswsKyMxuxJxxOHzluIO1rGp9TOQgjFJ+2MCqcOcQTOPB/8Xwhr+7s4Eg==", + "version": "10.3.1", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-10.3.1.tgz", + "integrity": "sha512-Pp3nFHBThHzVtNY7U6JfPjvT/DTE8+o/4xKsLQtBoU+j2HLsGlhcfzflAoUreaJbNmYnX+LlLi0qjV8kpyO6xQ==", "dev": true, + "license": "MIT", "dependencies": { "env-paths": "^2.2.0", "exponential-backoff": "^3.1.1", - "glob": "^7.1.4", + "glob": "^10.3.10", "graceful-fs": "^4.2.6", - "make-fetch-happen": "^11.0.3", - "nopt": "^6.0.0", - "npmlog": "^6.0.0", - "rimraf": "^3.0.2", + "make-fetch-happen": "^13.0.0", + "nopt": "^7.0.0", + "proc-log": "^4.1.0", "semver": "^7.3.5", - "tar": "^6.1.2", - "which": "^2.0.2" + "tar": "^6.2.1", + "which": "^4.0.0" }, "bin": { "node-gyp": "bin/node-gyp.js" }, "engines": { - "node": "^12.13 || ^14.13 || >=16" + "node": "^16.14.0 || >=18.0.0" } }, - "node_modules/node-gyp-build": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.6.0.tgz", - "integrity": "sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ==", + "node_modules/node-gyp/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "dev": true, - "bin": { - "node-gyp-build": "bin.js", - "node-gyp-build-optional": "optional.js", - "node-gyp-build-test": "build-test.js" + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" } }, "node_modules/node-gyp/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", "dev": true, + "license": "ISC", "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/node-gyp/node_modules/isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "dev": true, + "license": "ISC", "engines": { - "node": "*" + "node": ">=16" + } + }, + "node_modules/node-gyp/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/node-gyp/node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, "node_modules/node-gyp/node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", + "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", "dev": true, + "license": "ISC", "dependencies": { - "isexe": "^2.0.0" + "isexe": "^3.1.1" }, "bin": { - "node-which": "bin/node-which" + "node-which": "bin/which.js" }, "engines": { - "node": ">= 8" + "node": "^16.13.0 || >=18.0.0" } }, "node_modules/node-int64": { @@ -37488,7 +35213,8 @@ "version": "1.1.12", "resolved": "https://registry.npmjs.org/node-machine-id/-/node-machine-id-1.1.12.tgz", "integrity": "sha512-QNABxbrPa3qEIfrE6GOJ7BYIuignnJw7iQ2YPbc3Nla1HzRJjXzZOiikfF8m7eAMfichLt3M4VgLOetqgDmgGQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/node-releases": { "version": "2.0.18", @@ -37517,18 +35243,19 @@ } }, "node_modules/nopt": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-6.0.0.tgz", - "integrity": "sha512-ZwLpbTgdhuZUnZzjd7nb1ZV+4DoiC6/sfiVKok72ym/4Tlf+DFdlHYmT2JPmcNNWV6Pi3SDf1kT+A4r9RTuT9g==", + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.1.tgz", + "integrity": "sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==", "dev": true, + "license": "ISC", "dependencies": { - "abbrev": "^1.0.0" + "abbrev": "^2.0.0" }, "bin": { "nopt": "bin/nopt.js" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, "node_modules/normalize-package-data": { @@ -37596,10 +35323,11 @@ } }, "node_modules/npm-install-checks": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-6.1.1.tgz", - "integrity": "sha512-dH3GmQL4vsPtld59cOn8uY0iOqRmqKvV+DLGwNXV/Q7MDgD2QfOADWd/mFXcIE5LVhYYGjA3baz6W9JneqnuCw==", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-6.3.0.tgz", + "integrity": "sha512-W29RiK/xtpCGqn6f3ixfRYGk+zRyr+Ew9F2E20BfXxT5/euLdA/Nm7fO7OeTGuAmTs30cpgInyJ0cYe708YTZw==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "semver": "^7.1.1" }, @@ -37877,39 +35605,32 @@ } }, "node_modules/npm-pick-manifest": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-8.0.2.tgz", - "integrity": "sha512-1dKY+86/AIiq1tkKVD3l0WI+Gd3vkknVGAggsFeBkTvbhMQ1OND/LKkYv4JtXPKUJ8bOTCyLiqEg2P6QNdK+Gg==", + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-9.1.0.tgz", + "integrity": "sha512-nkc+3pIIhqHVQr085X9d2JzPzLyjzQS96zbruppqC9aZRm/x8xx6xhI98gHtsfELP2bE+loHq8ZaHFHhe+NauA==", "dev": true, + "license": "ISC", "dependencies": { "npm-install-checks": "^6.0.0", "npm-normalize-package-bin": "^3.0.0", - "npm-package-arg": "^10.0.0", + "npm-package-arg": "^11.0.0", "semver": "^7.3.5" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^16.14.0 || >=18.0.0" } }, "node_modules/npm-pick-manifest/node_modules/hosted-git-info": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-6.1.1.tgz", - "integrity": "sha512-r0EI+HBMcXadMrugk0GCQ+6BQV39PiWAZVfq7oIckeGiN7sjRGyQxPdft3nQekFTCQbYxLBH+/axZMeH8UX6+w==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz", + "integrity": "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==", "dev": true, + "license": "ISC", "dependencies": { - "lru-cache": "^7.5.1" + "lru-cache": "^10.0.1" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/npm-pick-manifest/node_modules/lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", - "dev": true, - "engines": { - "node": ">=12" + "node": "^16.14.0 || >=18.0.0" } }, "node_modules/npm-pick-manifest/node_modules/npm-normalize-package-bin": { @@ -37917,23 +35638,25 @@ "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-3.0.1.tgz", "integrity": "sha512-dMxCf+zZ+3zeQZXKxmyuCKlIDPGuv8EF940xbkC4kQVDTtqoh6rJFO+JTKSA6/Rwi0getWmtuy4Itup0AMcaDQ==", "dev": true, + "license": "ISC", "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, "node_modules/npm-pick-manifest/node_modules/npm-package-arg": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-10.1.0.tgz", - "integrity": "sha512-uFyyCEmgBfZTtrKk/5xDfHp6+MdrqGotX/VoOyEEl3mBwiEE5FlBaePanazJSVMPT7vKepcjYBY2ztg9A3yPIA==", + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-11.0.3.tgz", + "integrity": "sha512-sHGJy8sOC1YraBywpzQlIKBE4pBbGbiF95U6Auspzyem956E0+FtDtsx1ZxlOJkQCZ1AFXAY/yuvtFYrOxF+Bw==", "dev": true, + "license": "ISC", "dependencies": { - "hosted-git-info": "^6.0.0", - "proc-log": "^3.0.0", + "hosted-git-info": "^7.0.0", + "proc-log": "^4.0.0", "semver": "^7.3.5", "validate-npm-package-name": "^5.0.0" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^16.14.0 || >=18.0.0" } }, "node_modules/npm-pick-manifest/node_modules/validate-npm-package-name": { @@ -37941,71 +35664,68 @@ "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-5.0.1.tgz", "integrity": "sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==", "dev": true, + "license": "ISC", "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, "node_modules/npm-registry-fetch": { - "version": "14.0.5", - "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-14.0.5.tgz", - "integrity": "sha512-kIDMIo4aBm6xg7jOttupWZamsZRkAqMqwqqbVXnUqstY5+tapvv6bkH/qMR76jdgV+YljEUCyWx3hRYMrJiAgA==", + "version": "17.1.0", + "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-17.1.0.tgz", + "integrity": "sha512-5+bKQRH0J1xG1uZ1zMNvxW0VEyoNWgJpY9UDuluPFLKDfJ9u2JmmjmTJV1srBGQOROfdBMiVvnH2Zvpbm+xkVA==", "dev": true, + "license": "ISC", "dependencies": { - "make-fetch-happen": "^11.0.0", - "minipass": "^5.0.0", + "@npmcli/redact": "^2.0.0", + "jsonparse": "^1.3.1", + "make-fetch-happen": "^13.0.0", + "minipass": "^7.0.2", "minipass-fetch": "^3.0.0", - "minipass-json-stream": "^1.0.1", "minizlib": "^2.1.2", - "npm-package-arg": "^10.0.0", - "proc-log": "^3.0.0" + "npm-package-arg": "^11.0.0", + "proc-log": "^4.0.0" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^16.14.0 || >=18.0.0" } }, "node_modules/npm-registry-fetch/node_modules/hosted-git-info": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-6.1.1.tgz", - "integrity": "sha512-r0EI+HBMcXadMrugk0GCQ+6BQV39PiWAZVfq7oIckeGiN7sjRGyQxPdft3nQekFTCQbYxLBH+/axZMeH8UX6+w==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz", + "integrity": "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==", "dev": true, + "license": "ISC", "dependencies": { - "lru-cache": "^7.5.1" + "lru-cache": "^10.0.1" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/npm-registry-fetch/node_modules/lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", - "dev": true, - "engines": { - "node": ">=12" + "node": "^16.14.0 || >=18.0.0" } }, "node_modules/npm-registry-fetch/node_modules/minipass": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", - "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", "dev": true, + "license": "ISC", "engines": { - "node": ">=8" + "node": ">=16 || 14 >=14.17" } }, "node_modules/npm-registry-fetch/node_modules/npm-package-arg": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-10.1.0.tgz", - "integrity": "sha512-uFyyCEmgBfZTtrKk/5xDfHp6+MdrqGotX/VoOyEEl3mBwiEE5FlBaePanazJSVMPT7vKepcjYBY2ztg9A3yPIA==", + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-11.0.3.tgz", + "integrity": "sha512-sHGJy8sOC1YraBywpzQlIKBE4pBbGbiF95U6Auspzyem956E0+FtDtsx1ZxlOJkQCZ1AFXAY/yuvtFYrOxF+Bw==", "dev": true, + "license": "ISC", "dependencies": { - "hosted-git-info": "^6.0.0", - "proc-log": "^3.0.0", + "hosted-git-info": "^7.0.0", + "proc-log": "^4.0.0", "semver": "^7.3.5", "validate-npm-package-name": "^5.0.0" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^16.14.0 || >=18.0.0" } }, "node_modules/npm-registry-fetch/node_modules/validate-npm-package-name": { @@ -38013,6 +35733,7 @@ "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-5.0.1.tgz", "integrity": "sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==", "dev": true, + "license": "ISC", "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } @@ -38069,10 +35790,11 @@ } }, "node_modules/npm-run-all/node_modules/cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "version": "6.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.6.tgz", + "integrity": "sha512-VqCUuhcd1iB+dsv8gxPttb5iZh/D0iubSP21g36KXdEuf6I5JiioesUVjpCdHV9MZRUfVFlvwtIUyPfxo5trtw==", "dev": true, + "license": "MIT", "dependencies": { "nice-try": "^1.0.4", "path-key": "^2.0.1", @@ -38165,21 +35887,6 @@ "node": ">=4" } }, - "node_modules/npmlog": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.2.tgz", - "integrity": "sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==", - "dev": true, - "dependencies": { - "are-we-there-yet": "^3.0.0", - "console-control-strings": "^1.1.0", - "gauge": "^4.0.3", - "set-blocking": "^2.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, "node_modules/nth-check": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", @@ -38211,66 +35918,66 @@ "integrity": "sha512-cTGB9ptp9dY9A5VbMSe7fQBcl/tt22Vcqdq8+eN93rblOuE0aCFu4aZ2vMwct/2t+lFnosm8RkQW1I0Omb1UtQ==" }, "node_modules/nx": { - "version": "16.6.0", - "resolved": "https://registry.npmjs.org/nx/-/nx-16.6.0.tgz", - "integrity": "sha512-4UaS9nRakpZs45VOossA7hzSQY2dsr035EoPRGOc81yoMFW6Sqn1Rgq4hiLbHZOY8MnWNsLMkgolNMz1jC8YUQ==", + "version": "20.2.1", + "resolved": "https://registry.npmjs.org/nx/-/nx-20.2.1.tgz", + "integrity": "sha512-zUw1DT9BW2ajbDZgcUFKfysGqrbJwsMRjPxT6GthuqcLtDc2iJi3+/UBTLsITSeiw4Za4qPVxjaK4+yma9Ju5w==", "dev": true, "hasInstallScript": true, + "license": "MIT", "dependencies": { - "@nrwl/tao": "16.6.0", - "@parcel/watcher": "2.0.4", + "@napi-rs/wasm-runtime": "0.2.4", "@yarnpkg/lockfile": "^1.1.0", - "@yarnpkg/parsers": "3.0.0-rc.46", - "@zkochan/js-yaml": "0.0.6", - "axios": "^1.0.0", + "@yarnpkg/parsers": "3.0.2", + "@zkochan/js-yaml": "0.0.7", + "axios": "^1.7.4", "chalk": "^4.1.0", "cli-cursor": "3.1.0", "cli-spinners": "2.6.1", - "cliui": "^7.0.2", - "dotenv": "~10.0.0", + "cliui": "^8.0.1", + "dotenv": "~16.4.5", + "dotenv-expand": "~11.0.6", "enquirer": "~2.3.6", - "fast-glob": "3.2.7", "figures": "3.2.0", "flat": "^5.0.2", - "fs-extra": "^11.1.0", - "glob": "7.1.4", + "front-matter": "^4.0.2", "ignore": "^5.0.4", - "js-yaml": "4.1.0", + "jest-diff": "^29.4.1", "jsonc-parser": "3.2.0", - "lines-and-columns": "~2.0.3", - "minimatch": "3.0.5", + "lines-and-columns": "2.0.3", + "minimatch": "9.0.3", "node-machine-id": "1.1.12", "npm-run-path": "^4.0.1", "open": "^8.4.0", - "semver": "7.5.3", + "ora": "5.3.0", + "semver": "^7.5.3", "string-width": "^4.2.3", - "strong-log-transformer": "^2.1.0", "tar-stream": "~2.2.0", "tmp": "~0.2.1", "tsconfig-paths": "^4.1.2", "tslib": "^2.3.0", - "v8-compile-cache": "2.3.0", + "yaml": "^2.6.0", "yargs": "^17.6.2", "yargs-parser": "21.1.1" }, "bin": { - "nx": "bin/nx.js" + "nx": "bin/nx.js", + "nx-cloud": "bin/nx-cloud.js" }, "optionalDependencies": { - "@nx/nx-darwin-arm64": "16.6.0", - "@nx/nx-darwin-x64": "16.6.0", - "@nx/nx-freebsd-x64": "16.6.0", - "@nx/nx-linux-arm-gnueabihf": "16.6.0", - "@nx/nx-linux-arm64-gnu": "16.6.0", - "@nx/nx-linux-arm64-musl": "16.6.0", - "@nx/nx-linux-x64-gnu": "16.6.0", - "@nx/nx-linux-x64-musl": "16.6.0", - "@nx/nx-win32-arm64-msvc": "16.6.0", - "@nx/nx-win32-x64-msvc": "16.6.0" + "@nx/nx-darwin-arm64": "20.2.1", + "@nx/nx-darwin-x64": "20.2.1", + "@nx/nx-freebsd-x64": "20.2.1", + "@nx/nx-linux-arm-gnueabihf": "20.2.1", + "@nx/nx-linux-arm64-gnu": "20.2.1", + "@nx/nx-linux-arm64-musl": "20.2.1", + "@nx/nx-linux-x64-gnu": "20.2.1", + "@nx/nx-linux-x64-musl": "20.2.1", + "@nx/nx-win32-arm64-msvc": "20.2.1", + "@nx/nx-win32-x64-msvc": "20.2.1" }, "peerDependencies": { - "@swc-node/register": "^1.4.2", - "@swc/core": "^1.2.173" + "@swc-node/register": "^1.8.0", + "@swc/core": "^1.3.85" }, "peerDependenciesMeta": { "@swc-node/register": { @@ -38281,17 +35988,34 @@ } } }, - "node_modules/nx/node_modules/argparse": { + "node_modules/nx/node_modules/axios": { + "version": "1.7.9", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.9.tgz", + "integrity": "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==", + "dev": true, + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/nx/node_modules/brace-expansion": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } }, "node_modules/nx/node_modules/cli-cursor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", "dev": true, + "license": "MIT", "dependencies": { "restore-cursor": "^3.1.0" }, @@ -38300,36 +36024,49 @@ } }, "node_modules/nx/node_modules/cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", "dev": true, + "license": "ISC", "dependencies": { "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", + "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" } }, - "node_modules/nx/node_modules/dotenv": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-10.0.0.tgz", - "integrity": "sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==", + "node_modules/nx/node_modules/dotenv-expand": { + "version": "11.0.7", + "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-11.0.7.tgz", + "integrity": "sha512-zIHwmZPRshsCdpMDyVsqGmgyP0yT8GAgXUnkdAoJisxvf33k7yO6OuoKmcTGuXPWSsm8Oh88nZicRLA9Y0rUeA==", "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "dotenv": "^16.4.5" + }, "engines": { - "node": ">=10" + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" } }, "node_modules/nx/node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/nx/node_modules/figures": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", "dev": true, + "license": "MIT", "dependencies": { "escape-string-regexp": "^1.0.5" }, @@ -38340,42 +36077,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/nx/node_modules/fs-extra": { - "version": "11.2.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", - "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=14.14" - } - }, - "node_modules/nx/node_modules/glob": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", - "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - } - }, "node_modules/nx/node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -38385,6 +36092,7 @@ "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", "dev": true, + "license": "MIT", "dependencies": { "is-docker": "^2.0.0" }, @@ -38392,76 +36100,47 @@ "node": ">=8" } }, - "node_modules/nx/node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, "node_modules/nx/node_modules/jsonc-parser": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==", - "dev": true - }, - "node_modules/nx/node_modules/jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", "dev": true, - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } + "license": "MIT" }, "node_modules/nx/node_modules/lines-and-columns": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-2.0.4.tgz", - "integrity": "sha512-wM1+Z03eypVAVUCE7QdSqpVIvelbOakn1M0bPDoA4SGWPx3sNDVUiMo3L6To6WWGClB7VyXnhQ4Sn7gxiJbE6A==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-2.0.3.tgz", + "integrity": "sha512-cNOjgCnLB+FnvWWtyRTzmB3POJ+cXxTA81LoW7u8JdmhfXzriropYwpjShnz1QLLWsQwY7nIxoDmcPTwphDK9w==", "dev": true, + "license": "MIT", "engines": { "node": "^12.20.0 || ^14.13.1 || >=16.0.0" } }, - "node_modules/nx/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/nx/node_modules/mimic-fn": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } }, "node_modules/nx/node_modules/minimatch": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.5.tgz", - "integrity": "sha512-tUpxzX0VAzJHjLu0xUfFv1gwVp9ba3IOuRAVH2EGuRW8a5emA2FlACLqiT/lDVtS1W+TGNwqz3sWaNyLgDJWuw==", + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", "dev": true, + "license": "ISC", "dependencies": { - "brace-expansion": "^1.1.7" + "brace-expansion": "^2.0.1" }, "engines": { - "node": "*" + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/nx/node_modules/npm-run-path": { @@ -38469,6 +36148,7 @@ "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", "dev": true, + "license": "MIT", "dependencies": { "path-key": "^3.0.0" }, @@ -38481,6 +36161,7 @@ "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", "dev": true, + "license": "MIT", "dependencies": { "mimic-fn": "^2.1.0" }, @@ -38496,6 +36177,7 @@ "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", "dev": true, + "license": "MIT", "dependencies": { "define-lazy-prop": "^2.0.0", "is-docker": "^2.1.1", @@ -38508,11 +36190,35 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/nx/node_modules/ora": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.3.0.tgz", + "integrity": "sha512-zAKMgGXUim0Jyd6CXK9lraBnD3H5yPGBPPOkC23a2BG6hsm4Zu6OQSjQuEtV0BHDf4aKHcUFvJiGRrFuW3MG8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "bl": "^4.0.3", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "log-symbols": "^4.0.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/nx/node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -38522,6 +36228,7 @@ "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", "dev": true, + "license": "MIT", "dependencies": { "onetime": "^5.1.0", "signal-exit": "^3.0.2" @@ -38530,26 +36237,12 @@ "node": ">=8" } }, - "node_modules/nx/node_modules/semver": { - "version": "7.5.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.3.tgz", - "integrity": "sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/nx/node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, + "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -38564,6 +36257,7 @@ "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", "dev": true, + "license": "MIT", "engines": { "node": ">=4" } @@ -38573,6 +36267,7 @@ "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz", "integrity": "sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==", "dev": true, + "license": "MIT", "engines": { "node": ">=14.14" } @@ -38582,6 +36277,7 @@ "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz", "integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==", "dev": true, + "license": "MIT", "dependencies": { "json5": "^2.2.2", "minimist": "^1.2.6", @@ -38591,20 +36287,12 @@ "node": ">=6" } }, - "node_modules/nx/node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "dev": true, - "engines": { - "node": ">= 10.0.0" - } - }, "node_modules/nx/node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -38617,37 +36305,17 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/nx/node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/nx/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/nx/node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "node_modules/nx/node_modules/yaml": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.6.1.tgz", + "integrity": "sha512-7r0XPzioN/Q9kXBro/XPnA6kznR73DHq+GXh5ON7ZozRO6aMjbmiBuKste2wslTFkC5d1dw0GooOCepZXJ2SAg==", "dev": true, - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" + "license": "ISC", + "bin": { + "yaml": "bin.mjs" }, "engines": { - "node": ">=12" + "node": ">= 14" } }, "node_modules/nx/node_modules/yargs-parser": { @@ -38655,235 +36323,11 @@ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", "dev": true, + "license": "ISC", "engines": { "node": ">=12" } }, - "node_modules/nx/node_modules/yargs/node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/nypm": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/nypm/-/nypm-0.3.6.tgz", - "integrity": "sha512-2CATJh3pd6CyNfU5VZM7qSwFu0ieyabkEdnogE30Obn1czrmOYiZ8DOZLe1yBdLKWoyD3Mcy2maUs+0MR3yVjQ==", - "dev": true, - "dependencies": { - "citty": "^0.1.5", - "execa": "^8.0.1", - "pathe": "^1.1.2", - "ufo": "^1.3.2" - }, - "bin": { - "nypm": "dist/cli.mjs" - }, - "engines": { - "node": "^14.16.0 || >=16.10.0" - } - }, - "node_modules/nypm/node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/nypm/node_modules/execa": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", - "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", - "dev": true, - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^8.0.1", - "human-signals": "^5.0.0", - "is-stream": "^3.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^5.1.0", - "onetime": "^6.0.0", - "signal-exit": "^4.1.0", - "strip-final-newline": "^3.0.0" - }, - "engines": { - "node": ">=16.17" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/nypm/node_modules/get-stream": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", - "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", - "dev": true, - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/nypm/node_modules/human-signals": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", - "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", - "dev": true, - "engines": { - "node": ">=16.17.0" - } - }, - "node_modules/nypm/node_modules/is-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", - "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", - "dev": true, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/nypm/node_modules/mimic-fn": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", - "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/nypm/node_modules/npm-run-path": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", - "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", - "dev": true, - "dependencies": { - "path-key": "^4.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/nypm/node_modules/npm-run-path/node_modules/path-key": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", - "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/nypm/node_modules/onetime": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", - "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", - "dev": true, - "dependencies": { - "mimic-fn": "^4.0.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/nypm/node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/nypm/node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/nypm/node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/nypm/node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/nypm/node_modules/strip-final-newline": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", - "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/nypm/node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, "node_modules/ob1": { "version": "0.80.5", "resolved": "https://registry.npmjs.org/ob1/-/ob1-0.80.5.tgz", @@ -39090,12 +36534,6 @@ "integrity": "sha512-fZ4qZdQ2nxJvtcasX7Ghl+WlWS/d9IgnBIwFZXVNNZUmzpno91SX5bc5vuxiuKoCtK78XxGGNuSCrDC7xYB3OQ==", "dev": true }, - "node_modules/ohash": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/ohash/-/ohash-1.1.3.tgz", - "integrity": "sha512-zuHHiGTYTA1sYJ/wZN+t5HKZaH23i4yI1HMwbuXm24Nid7Dv0KcuRlKoNKS9UNfAVSBlnGLcuQrnOKWOZoEGaw==", - "dev": true - }, "node_modules/on-finished": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", @@ -39444,6 +36882,7 @@ "resolved": "https://registry.npmjs.org/p-map-series/-/p-map-series-2.1.0.tgz", "integrity": "sha512-RpYIIK1zXSNEOdwxcfe7FdvGcs7+y5n8rifMhMNWvaxRNMPINJHF5GDeuVxWqnfrcHPSCnp7Oo5yNXHId9Av2Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -39465,6 +36904,7 @@ "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-6.6.2.tgz", "integrity": "sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==", "dev": true, + "license": "MIT", "dependencies": { "eventemitter3": "^4.0.4", "p-timeout": "^3.2.0" @@ -39486,11 +36926,12 @@ } }, "node_modules/p-retry": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.1.tgz", - "integrity": "sha512-e2xXGNhZOZ0lfgR9kL34iGlU8N/KO0xZnQxVEwdeOvpqNDQfdnxIYizvWtK8RglUa3bGqI8g0R/BdfzLMxRkiA==", + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", + "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", + "license": "MIT", "dependencies": { - "@types/retry": "^0.12.0", + "@types/retry": "0.12.0", "retry": "^0.13.1" }, "engines": { @@ -39501,6 +36942,7 @@ "version": "0.13.1", "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "license": "MIT", "engines": { "node": ">= 4" } @@ -39510,6 +36952,7 @@ "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz", "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==", "dev": true, + "license": "MIT", "dependencies": { "p-finally": "^1.0.0" }, @@ -39541,80 +36984,31 @@ } }, "node_modules/pac-proxy-agent": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.0.1.tgz", - "integrity": "sha512-ASV8yU4LLKBAjqIPMbrgtaKIvxQri/yh2OpI+S6hVa9JRkUI3Y3NPFbfngDtY7oFtSMD3w31Xns89mDa3Feo5A==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.1.0.tgz", + "integrity": "sha512-Z5FnLVVZSnX7WjBg0mhDtydeRZ1xMcATZThjySQUHqr+0ksP8kqaw23fNKkaaN/Z8gwLUs/W7xdl0I75eP2Xyw==", + "license": "MIT", "dependencies": { "@tootallnate/quickjs-emscripten": "^0.23.0", - "agent-base": "^7.0.2", + "agent-base": "^7.1.2", "debug": "^4.3.4", "get-uri": "^6.0.1", "http-proxy-agent": "^7.0.0", - "https-proxy-agent": "^7.0.2", - "pac-resolver": "^7.0.0", - "socks-proxy-agent": "^8.0.2" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/pac-proxy-agent/node_modules/agent-base": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", - "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", - "dependencies": { - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/pac-proxy-agent/node_modules/http-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", - "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", - "dependencies": { - "agent-base": "^7.1.0", - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/pac-proxy-agent/node_modules/https-proxy-agent": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", - "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", - "license": "MIT", - "dependencies": { - "agent-base": "^7.0.2", - "debug": "4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/pac-proxy-agent/node_modules/socks-proxy-agent": { - "version": "8.0.4", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.4.tgz", - "integrity": "sha512-GNAq/eg8Udq2x0eNiFkr9gRg5bA7PXEWagQdeRX4cPSG+X/8V38v637gim9bjFptMk1QWsCTr0ttrJEiXbNnRw==", - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.1", - "debug": "^4.3.4", - "socks": "^2.8.3" + "https-proxy-agent": "^7.0.6", + "pac-resolver": "^7.0.1", + "socks-proxy-agent": "^8.0.5" }, "engines": { "node": ">= 14" } }, "node_modules/pac-resolver": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.0.tgz", - "integrity": "sha512-Fd9lT9vJbHYRACT8OhCbZBbxr6KRSawSovFpy8nDGshaK99S/EBhVIHp9+crhxrsZOuvLpgL1n23iyPg6Rl2hg==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz", + "integrity": "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==", + "license": "MIT", "dependencies": { "degenerator": "^5.0.0", - "ip": "^1.1.8", "netmask": "^2.0.2" }, "engines": { @@ -39646,39 +37040,38 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz", "integrity": "sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==", - "dev": true, "license": "BlueOak-1.0.0" }, "node_modules/pacote": { - "version": "15.2.0", - "resolved": "https://registry.npmjs.org/pacote/-/pacote-15.2.0.tgz", - "integrity": "sha512-rJVZeIwHTUta23sIZgEIM62WYwbmGbThdbnkt81ravBplQv+HjyroqnLRNH2+sLJHcGZmLRmhPwACqhfTcOmnA==", + "version": "18.0.6", + "resolved": "https://registry.npmjs.org/pacote/-/pacote-18.0.6.tgz", + "integrity": "sha512-+eK3G27SMwsB8kLIuj4h1FUhHtwiEUo21Tw8wNjmvdlpOEr613edv+8FUsTj/4F/VN5ywGE19X18N7CC2EJk6A==", "dev": true, + "license": "ISC", "dependencies": { - "@npmcli/git": "^4.0.0", + "@npmcli/git": "^5.0.0", "@npmcli/installed-package-contents": "^2.0.1", - "@npmcli/promise-spawn": "^6.0.1", - "@npmcli/run-script": "^6.0.0", - "cacache": "^17.0.0", + "@npmcli/package-json": "^5.1.0", + "@npmcli/promise-spawn": "^7.0.0", + "@npmcli/run-script": "^8.0.0", + "cacache": "^18.0.0", "fs-minipass": "^3.0.0", - "minipass": "^5.0.0", - "npm-package-arg": "^10.0.0", - "npm-packlist": "^7.0.0", - "npm-pick-manifest": "^8.0.0", - "npm-registry-fetch": "^14.0.0", - "proc-log": "^3.0.0", + "minipass": "^7.0.2", + "npm-package-arg": "^11.0.0", + "npm-packlist": "^8.0.0", + "npm-pick-manifest": "^9.0.0", + "npm-registry-fetch": "^17.0.0", + "proc-log": "^4.0.0", "promise-retry": "^2.0.1", - "read-package-json": "^6.0.0", - "read-package-json-fast": "^3.0.0", - "sigstore": "^1.3.0", + "sigstore": "^2.2.0", "ssri": "^10.0.0", "tar": "^6.1.11" }, "bin": { - "pacote": "lib/bin.js" + "pacote": "bin/index.js" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^16.14.0 || >=18.0.0" } }, "node_modules/pacote/node_modules/brace-expansion": { @@ -39686,22 +37079,24 @@ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "dev": true, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" } }, "node_modules/pacote/node_modules/cacache": { - "version": "17.1.4", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-17.1.4.tgz", - "integrity": "sha512-/aJwG2l3ZMJ1xNAnqbMpA40of9dj/pIH3QfiuQSqjfPJF747VR0J/bHn+/KdNnHKc6XQcWt/AfRSBft82W1d2A==", + "version": "18.0.4", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-18.0.4.tgz", + "integrity": "sha512-B+L5iIa9mgcjLbliir2th36yEwPftrzteHYujzsx3dFP/31GCHcIeS8f5MGd80odLOjaOvSpU3EEAmRQptkxLQ==", "dev": true, + "license": "ISC", "dependencies": { "@npmcli/fs": "^3.1.0", "fs-minipass": "^3.0.0", "glob": "^10.2.2", - "lru-cache": "^7.7.1", + "lru-cache": "^10.0.1", "minipass": "^7.0.3", - "minipass-collect": "^1.0.2", + "minipass-collect": "^2.0.1", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", "p-map": "^4.0.0", @@ -39710,17 +37105,7 @@ "unique-filename": "^3.0.0" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/pacote/node_modules/cacache/node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=16 || 14 >=14.17" + "node": "^16.14.0 || >=18.0.0" } }, "node_modules/pacote/node_modules/fs-minipass": { @@ -39728,6 +37113,7 @@ "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-3.0.3.tgz", "integrity": "sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==", "dev": true, + "license": "ISC", "dependencies": { "minipass": "^7.0.3" }, @@ -39735,16 +37121,6 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/pacote/node_modules/fs-minipass/node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, "node_modules/pacote/node_modules/glob": { "version": "10.4.5", "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", @@ -39766,26 +37142,17 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/pacote/node_modules/glob/node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, "node_modules/pacote/node_modules/hosted-git-info": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-6.1.1.tgz", - "integrity": "sha512-r0EI+HBMcXadMrugk0GCQ+6BQV39PiWAZVfq7oIckeGiN7sjRGyQxPdft3nQekFTCQbYxLBH+/axZMeH8UX6+w==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz", + "integrity": "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==", "dev": true, + "license": "ISC", "dependencies": { - "lru-cache": "^7.5.1" + "lru-cache": "^10.0.1" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^16.14.0 || >=18.0.0" } }, "node_modules/pacote/node_modules/ignore-walk": { @@ -39793,6 +37160,7 @@ "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-6.0.5.tgz", "integrity": "sha512-VuuG0wCnjhnylG1ABXT3dAuIpTNDs/G8jlpmwXY03fXoXy/8ZK8/T+hMzt8L4WnrLCJgdybqgPagnF/f97cg3A==", "dev": true, + "license": "ISC", "dependencies": { "minimatch": "^9.0.0" }, @@ -39800,31 +37168,6 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/pacote/node_modules/jackspeak": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", - "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" - } - }, - "node_modules/pacote/node_modules/lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", - "dev": true, - "engines": { - "node": ">=12" - } - }, "node_modules/pacote/node_modules/minimatch": { "version": "9.0.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", @@ -39842,36 +37185,39 @@ } }, "node_modules/pacote/node_modules/minipass": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", - "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", "dev": true, + "license": "ISC", "engines": { - "node": ">=8" + "node": ">=16 || 14 >=14.17" } }, "node_modules/pacote/node_modules/npm-package-arg": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-10.1.0.tgz", - "integrity": "sha512-uFyyCEmgBfZTtrKk/5xDfHp6+MdrqGotX/VoOyEEl3mBwiEE5FlBaePanazJSVMPT7vKepcjYBY2ztg9A3yPIA==", + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-11.0.3.tgz", + "integrity": "sha512-sHGJy8sOC1YraBywpzQlIKBE4pBbGbiF95U6Auspzyem956E0+FtDtsx1ZxlOJkQCZ1AFXAY/yuvtFYrOxF+Bw==", "dev": true, + "license": "ISC", "dependencies": { - "hosted-git-info": "^6.0.0", - "proc-log": "^3.0.0", + "hosted-git-info": "^7.0.0", + "proc-log": "^4.0.0", "semver": "^7.3.5", "validate-npm-package-name": "^5.0.0" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^16.14.0 || >=18.0.0" } }, "node_modules/pacote/node_modules/npm-packlist": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-7.0.4.tgz", - "integrity": "sha512-d6RGEuRrNS5/N84iglPivjaJPxhDbZmlbTwTDX2IbcRHG5bZCdtysYMhwiPvcF4GisXHGn7xsxv+GQ7T/02M5Q==", + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-8.0.2.tgz", + "integrity": "sha512-shYrPFIS/JLP4oQmAwDyk5HcyysKW8/JLTEA32S0Z5TzvpaeeX2yMFfoK1fjEBnCBvVyIB/Jj/GBFdm0wsgzbA==", "dev": true, + "license": "ISC", "dependencies": { - "ignore-walk": "^6.0.0" + "ignore-walk": "^6.0.4" }, "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" @@ -39882,6 +37228,7 @@ "resolved": "https://registry.npmjs.org/ssri/-/ssri-10.0.6.tgz", "integrity": "sha512-MGrFH9Z4NP9Iyhqn16sDtBpRRNJ0Y2hNa6D65h736fVSaPCHr4DM4sWUNvVaSuC+0OBGhwsrydQwmgfg5LncqQ==", "dev": true, + "license": "ISC", "dependencies": { "minipass": "^7.0.3" }, @@ -39889,21 +37236,12 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/pacote/node_modules/ssri/node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, "node_modules/pacote/node_modules/unique-filename": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-3.0.0.tgz", "integrity": "sha512-afXhuC55wkAmZ0P18QsVE6kp8JaxrEokN2HGIoIVv2ijHQd419H0+6EigAFcIzXeMIkcIkNBpB3L/DXB3cTS/g==", "dev": true, + "license": "ISC", "dependencies": { "unique-slug": "^4.0.0" }, @@ -39916,6 +37254,7 @@ "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-4.0.0.tgz", "integrity": "sha512-WrcA6AyEfqDX5bWige/4NQfPZMtASNVxdmWR76WESYQVAACSgWcR6e9i0mofqqBxYFtL4oAxPIptY73/0YE1DQ==", "dev": true, + "license": "ISC", "dependencies": { "imurmurhash": "^0.1.4" }, @@ -39928,6 +37267,7 @@ "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-5.0.1.tgz", "integrity": "sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==", "dev": true, + "license": "ISC", "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } @@ -39987,6 +37327,31 @@ "resolved": "https://registry.npmjs.org/parse-cache-control/-/parse-cache-control-1.0.1.tgz", "integrity": "sha512-60zvsJReQPX5/QP0Kzfd/VrpjScIQ7SHBW6bFCYfEP+fp0Eppr1SHhIO5nd1PjZtvclzSzES9D/p5nFJurwfWg==" }, + "node_modules/parse-conflict-json": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/parse-conflict-json/-/parse-conflict-json-3.0.1.tgz", + "integrity": "sha512-01TvEktc68vwbJOtWZluyWeVGWjP+bZwXtPDMQVbBKzbJ/vZBif0L69KH1+cHv1SZ6e0FKLvjyHe8mqsIqYOmw==", + "dev": true, + "license": "ISC", + "dependencies": { + "json-parse-even-better-errors": "^3.0.0", + "just-diff": "^6.0.0", + "just-diff-apply": "^5.2.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/parse-conflict-json/node_modules/json-parse-even-better-errors": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.2.tgz", + "integrity": "sha512-fi0NG4bPjCHunUJffmLd0gxssIgkNmArMvis4iNah6Owg1MCJjWhEcDLmsK6iGkJq3tHwbDkTlce70/tmXN4cQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, "node_modules/parse-entities": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-1.2.2.tgz", @@ -40039,6 +37404,7 @@ "resolved": "https://registry.npmjs.org/parse-path/-/parse-path-7.0.0.tgz", "integrity": "sha512-Euf9GG8WT9CdqwuWJGdf3RkUcTBArppHABkO7Lm8IzRQp0e2r/kkFnmhu4TSK30Wcu5rVAZLmfPKSBBi9tWFog==", "dev": true, + "license": "MIT", "dependencies": { "protocols": "^2.0.0" } @@ -40048,6 +37414,7 @@ "resolved": "https://registry.npmjs.org/parse-url/-/parse-url-8.1.0.tgz", "integrity": "sha512-xDvOoLU5XRrcOZvnI6b8zA6n9O9ejNk/GExuz1yBuWUGn9KA97GI6HTs6u02wKara1CeVmZhH+0TZFdWScR89w==", "dev": true, + "license": "MIT", "dependencies": { "parse-path": "^7.0.0" } @@ -40152,20 +37519,6 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/patch-package/node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, "node_modules/patch-package/node_modules/fs-extra": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", @@ -40250,15 +37603,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/patch-package/node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/patch-package/node_modules/rimraf": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", @@ -40271,27 +37615,6 @@ "rimraf": "bin.js" } }, - "node_modules/patch-package/node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/patch-package/node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/patch-package/node_modules/slash": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", @@ -40322,21 +37645,6 @@ "node": ">= 10.0.0" } }, - "node_modules/patch-package/node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, "node_modules/patch-package/node_modules/yaml": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.6.0.tgz", @@ -40411,7 +37719,6 @@ "version": "1.11.1", "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", - "dev": true, "license": "BlueOak-1.0.0", "dependencies": { "lru-cache": "^10.2.0", @@ -40424,18 +37731,10 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/path-scurry/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true, - "license": "ISC" - }, "node_modules/path-scurry/node_modules/minipass": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "dev": true, "license": "ISC", "engines": { "node": ">=16 || 14 >=14.17" @@ -40444,7 +37743,8 @@ "node_modules/path-to-regexp": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" + "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==", + "license": "MIT" }, "node_modules/path-type": { "version": "3.0.0", @@ -40467,11 +37767,14 @@ "node": ">=4" } }, - "node_modules/pathe": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", - "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", - "dev": true + "node_modules/pathval": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz", + "integrity": "sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==", + "dev": true, + "engines": { + "node": ">= 14.16" + } }, "node_modules/pbkdf2": { "version": "3.1.2", @@ -40489,17 +37792,6 @@ "node": ">=0.12" } }, - "node_modules/peek-stream": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/peek-stream/-/peek-stream-1.1.3.tgz", - "integrity": "sha512-FhJ+YbOSBb9/rIl2ZeE/QHEsWn7PqNYt8ARAY3kIgNGOk13g9FGyIY6JIl/xB/3TFRVoTv5as0l11weORrTekA==", - "dev": true, - "dependencies": { - "buffer-from": "^1.0.0", - "duplexify": "^3.5.0", - "through2": "^2.0.3" - } - }, "node_modules/pegjs": { "version": "0.10.0", "resolved": "https://registry.npmjs.org/pegjs/-/pegjs-0.10.0.tgz", @@ -40554,11 +37846,16 @@ } }, "node_modules/pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-5.0.0.tgz", + "integrity": "sha512-eW/gHNMlxdSP6dmG6uJip6FXN0EQBwm2clYYd8Wul42Cwu/DK8HEftzsapcNdYe2MfLiIwZqsDk2RDEsTE79hA==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/pinkie": { @@ -40606,12 +37903,13 @@ "dev": true }, "node_modules/playwright": { - "version": "1.48.1", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.48.1.tgz", - "integrity": "sha512-j8CiHW/V6HxmbntOfyB4+T/uk08tBy6ph0MpBXwuoofkSnLmlfdYNNkFTYD6ofzzlSqLA1fwH4vwvVFvJgLN0w==", + "version": "1.49.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.49.1.tgz", + "integrity": "sha512-VYL8zLoNTBxVOrJBbDuRgDWa3i+mfQgDTrL8Ah9QXZ7ax4Dsj0MSq5bYgytRnDVVe+njoKnfsYkH3HzqVj5UZA==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "playwright-core": "1.48.1" + "playwright-core": "1.49.1" }, "bin": { "playwright": "cli.js" @@ -40624,10 +37922,11 @@ } }, "node_modules/playwright-core": { - "version": "1.48.1", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.48.1.tgz", - "integrity": "sha512-Yw/t4VAFX/bBr1OzwCuOMZkY1Cnb4z/doAFSwf4huqAGWmf9eMNjmK7NiOljCdLmxeRYcGPPmcDgU0zOlzP0YA==", + "version": "1.49.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.49.1.tgz", + "integrity": "sha512-BzmpVcs4kE2CH15rWfzpjzVGhWERJfmnXmniSyKeRZUs9Ws65m+RGIi7mjJK/euCegfn3i7jvqWeWyHe9y3Vgg==", "dev": true, + "license": "Apache-2.0", "bin": { "playwright-core": "cli.js" }, @@ -40836,27 +38135,6 @@ "postcss": "^8.2.15" } }, - "node_modules/postcss-import": { - "version": "16.1.0", - "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-16.1.0.tgz", - "integrity": "sha512-7hsAZ4xGXl4MW+OKEWCnF6T5jqBw80/EE9aXg1r2yyn1RsVEU8EtKXbijEODa+rg7iih4bKf7vlvTGYR4CnPNg==", - "dependencies": { - "postcss-value-parser": "^4.0.0", - "read-cache": "^1.0.0", - "resolve": "^1.1.7" - }, - "engines": { - "node": ">=18.0.0" - }, - "peerDependencies": { - "postcss": "^8.0.0" - } - }, - "node_modules/postcss-import/node_modules/postcss-value-parser": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" - }, "node_modules/postcss-loader": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-6.2.1.tgz", @@ -41420,10 +38698,11 @@ } }, "node_modules/prebuild-install": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.1.tgz", - "integrity": "sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.2.tgz", + "integrity": "sha512-UnNke3IQb6sgarcZIDU3gbMeTp/9SSU1DAIkil7PrqG1vZlBtY5msYccSKSHDqa3hNg436IXK+SNImReuA1wEQ==", "dev": true, + "license": "MIT", "optional": true, "dependencies": { "detect-libc": "^2.0.0", @@ -41531,20 +38810,12 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==" }, - "node_modules/pretty-hrtime": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", - "integrity": "sha512-66hKPCr+72mlfiSjlEB1+45IjXSqvVAIy6mocupoww4tBFE9R9IhwwUGoI4G++Tc9Aq+2rxOt0RFU6gPcrte0A==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/proc-log": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-3.0.0.tgz", - "integrity": "sha512-++Vn7NS4Xf9NacaU9Xq3URUuqZETPsf8L4j5/ckhaRYsfPeRyzGw+iDjFhV/Jr3uNmTvvddEJFWh5R1gRgUH8A==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-4.2.0.tgz", + "integrity": "sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA==", "dev": true, + "license": "ISC", "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } @@ -41563,6 +38834,16 @@ "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" }, + "node_modules/proggy": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/proggy/-/proggy-2.0.0.tgz", + "integrity": "sha512-69agxLtnI8xBs9gUGqEnK26UfiexpHy+KUpBQWabiytQjnn5wFY8rklAi7GRfABIuPNnQ/ik48+LGLkYYJcy4A==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, "node_modules/progress": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", @@ -41579,6 +38860,26 @@ "asap": "~2.0.6" } }, + "node_modules/promise-all-reject-late": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/promise-all-reject-late/-/promise-all-reject-late-1.0.1.tgz", + "integrity": "sha512-vuf0Lf0lOxyQREH7GDIOUMLS7kz+gs8i6B+Yi8dC68a2sychGrHTJYghMBD6k7eUcH0H5P73EckCA48xijWqXw==", + "dev": true, + "license": "ISC", + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/promise-call-limit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/promise-call-limit/-/promise-call-limit-3.0.2.tgz", + "integrity": "sha512-mRPQO2T1QQVw11E7+UdCJu7S61eJVWknzml9sC1heAdj1jxl0fWMBypIt9ZOcLFf8FkG995ZD7RnVk7HH72fZw==", + "dev": true, + "license": "ISC", + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/promise-inflight": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", @@ -41590,6 +38891,7 @@ "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", "dev": true, + "license": "MIT", "dependencies": { "err-code": "^2.0.2", "retry": "^0.12.0" @@ -41611,12 +38913,13 @@ } }, "node_modules/promzard": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/promzard/-/promzard-1.0.0.tgz", - "integrity": "sha512-KQVDEubSUHGSt5xLakaToDFrSoZhStB8dXLzk2xvwR67gJktrHFvpR63oZgHyK19WKbHFLXJqCPXdVR3aBP8Ig==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/promzard/-/promzard-1.0.2.tgz", + "integrity": "sha512-2FPputGL+mP3jJ3UZg/Dl9YOkovB7DX0oOr+ck5QbZ5MtORtds8k/BZdn+02peDLI8/YWbmzx34k5fA+fHvCVQ==", "dev": true, + "license": "ISC", "dependencies": { - "read": "^2.0.0" + "read": "^3.0.1" }, "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" @@ -41645,7 +38948,8 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/protocols/-/protocols-2.0.1.tgz", "integrity": "sha512-/XJ368cyBJ7fzLMwLKv1e4vLxOju2MNAIokcr7meSaNcVbWz/CPcW22cP04mwxOErdA5mwjA8Q6w/cdAQxVn7Q==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/proxy-addr": { "version": "2.0.7", @@ -41660,54 +38964,20 @@ } }, "node_modules/proxy-agent": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.3.0.tgz", - "integrity": "sha512-0LdR757eTj/JfuU7TL2YCuAZnxWXu3tkJbg4Oq3geW/qFNT/32T0sp2HnZ9O0lMR4q3vwAt0+xCA8SR0WAD0og==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.3.1.tgz", + "integrity": "sha512-Rb5RVBy1iyqOtNl15Cw/llpeLH8bsb37gM1FUfKQ+Wck6xHlbAhWGUFiTRHtkjqGTA5pSHz6+0hrPW/oECihPQ==", + "dev": true, + "license": "MIT", "dependencies": { "agent-base": "^7.0.2", "debug": "^4.3.4", "http-proxy-agent": "^7.0.0", - "https-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.2", "lru-cache": "^7.14.1", - "pac-proxy-agent": "^7.0.0", + "pac-proxy-agent": "^7.0.1", "proxy-from-env": "^1.1.0", - "socks-proxy-agent": "^8.0.1" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/proxy-agent/node_modules/agent-base": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", - "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", - "dependencies": { - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/proxy-agent/node_modules/http-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", - "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", - "dependencies": { - "agent-base": "^7.1.0", - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/proxy-agent/node_modules/https-proxy-agent": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", - "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", - "license": "MIT", - "dependencies": { - "agent-base": "^7.0.2", - "debug": "4" + "socks-proxy-agent": "^8.0.2" }, "engines": { "node": ">= 14" @@ -41717,24 +38987,12 @@ "version": "7.18.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true, + "license": "ISC", "engines": { "node": ">=12" } }, - "node_modules/proxy-agent/node_modules/socks-proxy-agent": { - "version": "8.0.4", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.4.tgz", - "integrity": "sha512-GNAq/eg8Udq2x0eNiFkr9gRg5bA7PXEWagQdeRX4cPSG+X/8V38v637gim9bjFptMk1QWsCTr0ttrJEiXbNnRw==", - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.1", - "debug": "^4.3.4", - "socks": "^2.8.3" - }, - "engines": { - "node": ">= 14" - } - }, "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", @@ -41746,22 +39004,6 @@ "integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==", "dev": true }, - "node_modules/ps-list": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/ps-list/-/ps-list-8.1.1.tgz", - "integrity": "sha512-OPS9kEJYVmiO48u/B9qneqhkMvgCxT+Tm28VCEJpheTpl8cJ0ffZRRNgS5mrQRTrX5yRTpaJ+hRDeefXYmmorQ==", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/pseudomap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", - "integrity": "sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==" - }, "node_modules/public-encrypt": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", @@ -41812,15 +39054,15 @@ } }, "node_modules/puppeteer-core": { - "version": "23.1.0", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-23.1.0.tgz", - "integrity": "sha512-SvAsu+xnLN2FMXE/59bp3s3WXp8ewqUGzVV4AQtml/2xmsciZnU/bXcCW+eETHPWQ6Agg2vTI7QzWXPpEARK2g==", + "version": "23.10.1", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-23.10.1.tgz", + "integrity": "sha512-ey6NwixHYEUnhCA/uYi7uQQ4a0CZw4k+MatbHXGl5GEzaiRQziYUxc2HGpdQZ/gnh4KQWAKkocyIg1/dIm5d0g==", "license": "Apache-2.0", "dependencies": { - "@puppeteer/browsers": "2.3.1", - "chromium-bidi": "0.6.4", - "debug": "^4.3.6", - "devtools-protocol": "0.0.1312386", + "@puppeteer/browsers": "2.5.0", + "chromium-bidi": "0.8.0", + "debug": "^4.3.7", + "devtools-protocol": "0.0.1367902", "typed-query-selector": "^2.12.0", "ws": "^8.18.0" }, @@ -41829,12 +39071,12 @@ } }, "node_modules/puppeteer-core/node_modules/@puppeteer/browsers": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.3.1.tgz", - "integrity": "sha512-uK7o3hHkK+naEobMSJ+2ySYyXtQkBxIH8Gn4MK9ciePjNV+Pf+PgY/W7iPzn2MTjl3stcYB5AlcTmPYw7AXDwA==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.5.0.tgz", + "integrity": "sha512-6TQAc/5uRILE6deixJ1CR8rXyTbzXIXNgO1D0Woi9Bqicz2FV5iKP3BHYEg6o4UATCMcbQQ0jbmeaOkn/HQk2w==", "license": "Apache-2.0", "dependencies": { - "debug": "^4.3.6", + "debug": "^4.3.7", "extract-zip": "^2.0.1", "progress": "^2.0.3", "proxy-agent": "^6.4.0", @@ -41850,22 +39092,10 @@ "node": ">=18" } }, - "node_modules/puppeteer-core/node_modules/agent-base": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", - "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", - "license": "MIT", - "dependencies": { - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, "node_modules/puppeteer-core/node_modules/chromium-bidi": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.6.4.tgz", - "integrity": "sha512-8zoq6ogmhQQkAKZVKO2ObFTl4uOkqoX1PlKQX3hZQ5E9cbUotcAb7h4pTNVAGGv8Z36PF3CtdOriEp/Rz82JqQ==", + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.8.0.tgz", + "integrity": "sha512-uJydbGdTw0DEUjhoogGveneJVWX/9YuqkWePzMmkBYwtdAqo5d3J/ovNKFr+/2hWXYmYCr6it8mSSTIj6SS6Ug==", "license": "Apache-2.0", "dependencies": { "mitt": "3.0.1", @@ -41876,24 +39106,10 @@ "devtools-protocol": "*" } }, - "node_modules/puppeteer-core/node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/puppeteer-core/node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -41907,12 +39123,6 @@ } } }, - "node_modules/puppeteer-core/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "license": "MIT" - }, "node_modules/puppeteer-core/node_modules/extract-zip": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", @@ -41933,41 +39143,6 @@ "@types/yauzl": "^2.9.1" } }, - "node_modules/puppeteer-core/node_modules/http-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", - "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.0", - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/puppeteer-core/node_modules/https-proxy-agent": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", - "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", - "license": "MIT", - "dependencies": { - "agent-base": "^7.0.2", - "debug": "4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/puppeteer-core/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/puppeteer-core/node_modules/lru-cache": { "version": "7.18.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", @@ -41977,12 +39152,6 @@ "node": ">=12" } }, - "node_modules/puppeteer-core/node_modules/mitt": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", - "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", - "license": "MIT" - }, "node_modules/puppeteer-core/node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -41990,19 +39159,19 @@ "license": "MIT" }, "node_modules/puppeteer-core/node_modules/proxy-agent": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.4.0.tgz", - "integrity": "sha512-u0piLU+nCOHMgGjRbimiXmA9kM/L9EHh3zL81xCdp7m+Y2pHIsnmbdDoEDoAz5geaonNR6q6+yOPQs6n4T6sBQ==", + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.5.0.tgz", + "integrity": "sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==", "license": "MIT", "dependencies": { - "agent-base": "^7.0.2", + "agent-base": "^7.1.2", "debug": "^4.3.4", "http-proxy-agent": "^7.0.1", - "https-proxy-agent": "^7.0.3", + "https-proxy-agent": "^7.0.6", "lru-cache": "^7.14.1", - "pac-proxy-agent": "^7.0.1", + "pac-proxy-agent": "^7.1.0", "proxy-from-env": "^1.1.0", - "socks-proxy-agent": "^8.0.2" + "socks-proxy-agent": "^8.0.5" }, "engines": { "node": ">= 14" @@ -42030,34 +39199,6 @@ "node": ">=10" } }, - "node_modules/puppeteer-core/node_modules/socks-proxy-agent": { - "version": "8.0.4", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.4.tgz", - "integrity": "sha512-GNAq/eg8Udq2x0eNiFkr9gRg5bA7PXEWagQdeRX4cPSG+X/8V38v637gim9bjFptMk1QWsCTr0ttrJEiXbNnRw==", - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.1", - "debug": "^4.3.4", - "socks": "^2.8.3" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/puppeteer-core/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/puppeteer-core/node_modules/tar-fs": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.6.tgz", @@ -42083,23 +39224,6 @@ "streamx": "^2.15.0" } }, - "node_modules/puppeteer-core/node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, "node_modules/puppeteer-core/node_modules/ws": { "version": "8.18.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", @@ -42121,42 +39245,6 @@ } } }, - "node_modules/puppeteer-core/node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "node_modules/puppeteer-core/node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "license": "MIT", - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/puppeteer-core/node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, "node_modules/pure-rand": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.0.2.tgz", @@ -42173,11 +39261,13 @@ ] }, "node_modules/qs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "dev": true, + "license": "BSD-3-Clause", "dependencies": { - "side-channel": "^1.0.4" + "side-channel": "^1.0.6" }, "engines": { "node": ">=0.6" @@ -42267,16 +39357,6 @@ "node": ">=8" } }, - "node_modules/ramda": { - "version": "0.29.0", - "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.29.0.tgz", - "integrity": "sha512-BBea6L67bYLtdbOqfp8f58fPMqEwx0doL+pAi8TZyp2YWz8R9G8z9x75CZI8W+ftqhFHCpEX2cRnUUXK130iKA==", - "dev": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/ramda" - } - }, "node_modules/randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -42307,6 +39387,7 @@ "version": "2.5.1", "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "license": "MIT", "dependencies": { "bytes": "3.1.2", "http-errors": "2.0.0", @@ -42321,6 +39402,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -42329,6 +39411,7 @@ "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3" }, @@ -42398,6 +39481,7 @@ "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", "dev": true, + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", "optional": true, "dependencies": { "deep-extend": "^0.6.0", @@ -42414,6 +39498,7 @@ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", "dev": true, + "license": "MIT", "optional": true, "engines": { "node": ">=0.10.0" @@ -42475,9 +39560,9 @@ } }, "node_modules/react-docgen": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/react-docgen/-/react-docgen-7.0.3.tgz", - "integrity": "sha512-i8aF1nyKInZnANZ4uZrH49qn1paRgBZ7wZiCNBMnenlPzEv0mRl+ShpTVEI6wZNl8sSc79xZkivtgLKQArcanQ==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/react-docgen/-/react-docgen-7.1.0.tgz", + "integrity": "sha512-APPU8HB2uZnpl6Vt/+0AFoVYgSRtfiP6FLrZgPPTDmqSb2R4qZRbgd0A3VzIFxDt5e+Fozjx79WjLWnF69DK8g==", "dev": true, "dependencies": { "@babel/core": "^7.18.9", @@ -42505,12 +39590,6 @@ "typescript": ">= 4.3.x" } }, - "node_modules/react-docgen/node_modules/@types/doctrine": { - "version": "0.0.9", - "resolved": "https://registry.npmjs.org/@types/doctrine/-/doctrine-0.0.9.tgz", - "integrity": "sha512-eOIHzCUSH7SMfonMG1LsC2f8vxBFtho6NGBznK41R84YzPuvSBzrhEps33IsQiOW9+VL6NQ9DbjQJznk/S4uRA==", - "dev": true - }, "node_modules/react-docgen/node_modules/doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", @@ -42550,27 +39629,6 @@ "react": "^18.3.1" } }, - "node_modules/react-element-to-jsx-string": { - "version": "15.0.0", - "resolved": "https://registry.npmjs.org/react-element-to-jsx-string/-/react-element-to-jsx-string-15.0.0.tgz", - "integrity": "sha512-UDg4lXB6BzlobN60P8fHWVPX3Kyw8ORrTeBtClmIlGdkOOE+GYQSFvmEU5iLLpwp/6v42DINwNcwOhOLfQ//FQ==", - "dev": true, - "dependencies": { - "@base2/pretty-print-object": "1.0.1", - "is-plain-object": "5.0.0", - "react-is": "18.1.0" - }, - "peerDependencies": { - "react": "^0.14.8 || ^15.0.1 || ^16.0.0 || ^17.0.1 || ^18.0.0", - "react-dom": "^0.14.8 || ^15.0.1 || ^16.0.0 || ^17.0.1 || ^18.0.0" - } - }, - "node_modules/react-element-to-jsx-string/node_modules/react-is": { - "version": "18.1.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.1.0.tgz", - "integrity": "sha512-Fl7FuabXsJnV5Q1qIOQwx/sagGF18kogb4gpfcG4gjLBWO0WDiiz1ko/ExayuxE7InyQkBLkxRFG5oxY6Uu3Kg==", - "dev": true - }, "node_modules/react-freeze": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/react-freeze/-/react-freeze-1.0.3.tgz", @@ -42899,19 +39957,6 @@ "@types/yargs-parser": "*" } }, - "node_modules/react-native/node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/react-native/node_modules/deprecated-react-native-prop-types": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/deprecated-react-native-prop-types/-/deprecated-react-native-prop-types-5.0.0.tgz", @@ -42925,19 +39970,6 @@ "node": ">=18" } }, - "node_modules/react-native/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" - }, - "node_modules/react-native/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "engines": { - "node": ">=8" - } - }, "node_modules/react-native/node_modules/mkdirp": { "version": "0.5.6", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", @@ -42976,35 +40008,6 @@ "loose-envify": "^1.1.0" } }, - "node_modules/react-native/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/react-native/node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, "node_modules/react-native/node_modules/ws": { "version": "6.2.3", "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.3.tgz", @@ -43014,39 +40017,6 @@ "async-limiter": "~1.0.0" } }, - "node_modules/react-native/node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "engines": { - "node": ">=10" - } - }, - "node_modules/react-native/node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/react-native/node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "engines": { - "node": ">=12" - } - }, "node_modules/react-refresh": { "version": "0.14.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.0.tgz", @@ -43081,9 +40051,9 @@ } }, "node_modules/react-remove-scroll-bar": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.4.tgz", - "integrity": "sha512-63C4YQBUt0m6ALadE9XV56hV8BgJWDmmTPY758iIJjfQKt2nYwoUrPk0LXRXcB/yIj82T1/Ixfdpdk68LwIB0A==", + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.6.tgz", + "integrity": "sha512-DtSYaao4mBmX+HDo5YWYdBWQwYIQQshUV/dVxFxK+KM26Wjwp1gZ6rv6OC3oujI6Bfu6Xyg3TwK533AQutsn/g==", "dependencies": { "react-style-singleton": "^2.2.1", "tslib": "^2.0.0" @@ -43326,45 +40296,24 @@ "dev": true }, "node_modules/read": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/read/-/read-2.1.0.tgz", - "integrity": "sha512-bvxi1QLJHcaywCAEsAk4DG3nVoqiY2Csps3qzWalhj5hFqRn1d/OixkFXtLO1PrgHUcAP0FNaSY/5GYNfENFFQ==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/read/-/read-3.0.1.tgz", + "integrity": "sha512-SLBrDU/Srs/9EoWhU5GdbAoxG1GzpQHo/6qiGItaoLJ1thmYpcNIM1qISEUvyHBzfGlWIyd6p2DNi1oV1VmAuw==", "dev": true, + "license": "ISC", "dependencies": { - "mute-stream": "~1.0.0" + "mute-stream": "^1.0.0" }, "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/read-cache": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", - "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", - "dependencies": { - "pify": "^2.3.0" - } - }, "node_modules/read-cmd-shim": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/read-cmd-shim/-/read-cmd-shim-4.0.0.tgz", "integrity": "sha512-yILWifhaSEEytfXI76kB9xEEiG1AiozaCJZ83A87ytjRiN+jVibXjedjCRNjoZviinhG+4UkalO3mWTd8u5O0Q==", "dev": true, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/read-package-json": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/read-package-json/-/read-package-json-6.0.4.tgz", - "integrity": "sha512-AEtWXYfopBj2z5N5PbkAOeNHRPUg5q+Nen7QLxV8M2zJq1ym6/lCz3fYNTCXe19puu2d06jfHhrP7v/S2PtMMw==", - "dev": true, - "dependencies": { - "glob": "^10.2.2", - "json-parse-even-better-errors": "^3.0.0", - "normalize-package-data": "^5.0.0", - "npm-normalize-package-bin": "^3.0.0" - }, + "license": "ISC", "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } @@ -43374,6 +40323,7 @@ "resolved": "https://registry.npmjs.org/read-package-json-fast/-/read-package-json-fast-3.0.2.tgz", "integrity": "sha512-0J+Msgym3vrLOUB3hzQCuZHII0xkNGCtz/HJH9xZshwv9DbDwkw1KaE3gx/e2J5rpEY5rtOy6cyhKOPrkP7FZw==", "dev": true, + "license": "ISC", "dependencies": { "json-parse-even-better-errors": "^3.0.0", "npm-normalize-package-bin": "^3.0.0" @@ -43387,6 +40337,7 @@ "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.2.tgz", "integrity": "sha512-fi0NG4bPjCHunUJffmLd0gxssIgkNmArMvis4iNah6Owg1MCJjWhEcDLmsK6iGkJq3tHwbDkTlce70/tmXN4cQ==", "dev": true, + "license": "MIT", "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } @@ -43396,132 +40347,7 @@ "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-3.0.1.tgz", "integrity": "sha512-dMxCf+zZ+3zeQZXKxmyuCKlIDPGuv8EF940xbkC4kQVDTtqoh6rJFO+JTKSA6/Rwi0getWmtuy4Itup0AMcaDQ==", "dev": true, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/read-package-json/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/read-package-json/node_modules/glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", - "dev": true, "license": "ISC", - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/read-package-json/node_modules/hosted-git-info": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-6.1.1.tgz", - "integrity": "sha512-r0EI+HBMcXadMrugk0GCQ+6BQV39PiWAZVfq7oIckeGiN7sjRGyQxPdft3nQekFTCQbYxLBH+/axZMeH8UX6+w==", - "dev": true, - "dependencies": { - "lru-cache": "^7.5.1" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/read-package-json/node_modules/jackspeak": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", - "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" - } - }, - "node_modules/read-package-json/node_modules/json-parse-even-better-errors": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.2.tgz", - "integrity": "sha512-fi0NG4bPjCHunUJffmLd0gxssIgkNmArMvis4iNah6Owg1MCJjWhEcDLmsK6iGkJq3tHwbDkTlce70/tmXN4cQ==", - "dev": true, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/read-package-json/node_modules/lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", - "dev": true, - "engines": { - "node": ">=12" - } - }, - "node_modules/read-package-json/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/read-package-json/node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/read-package-json/node_modules/normalize-package-data": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-5.0.0.tgz", - "integrity": "sha512-h9iPVIfrVZ9wVYQnxFgtw1ugSvGEMOlyPWWtm8BMJhnwyEL/FLbYbTY3V3PpjI/BUK67n9PEWDu6eHzu1fB15Q==", - "dev": true, - "dependencies": { - "hosted-git-info": "^6.0.0", - "is-core-module": "^2.8.1", - "semver": "^7.3.5", - "validate-npm-package-license": "^3.0.4" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/read-package-json/node_modules/npm-normalize-package-bin": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-3.0.1.tgz", - "integrity": "sha512-dMxCf+zZ+3zeQZXKxmyuCKlIDPGuv8EF940xbkC4kQVDTtqoh6rJFO+JTKSA6/Rwi0getWmtuy4Itup0AMcaDQ==", - "dev": true, "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } @@ -43633,6 +40459,7 @@ "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-1.0.0.tgz", "integrity": "sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==", "dev": true, + "license": "ISC", "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } @@ -43894,62 +40721,6 @@ "unified": "^7.0.0" } }, - "node_modules/remark-external-links": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/remark-external-links/-/remark-external-links-8.0.0.tgz", - "integrity": "sha512-5vPSX0kHoSsqtdftSHhIYofVINC8qmp0nctkeU9YoJwV3YfiBRiI6cbFRJ0oI/1F9xS+bopXG0m2KS8VFscuKA==", - "dev": true, - "dependencies": { - "extend": "^3.0.0", - "is-absolute-url": "^3.0.0", - "mdast-util-definitions": "^4.0.0", - "space-separated-tokens": "^1.0.0", - "unist-util-visit": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/remark-external-links/node_modules/unist-util-is": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-4.1.0.tgz", - "integrity": "sha512-ZOQSsnce92GrxSqlnEEseX0gi7GH9zTJZ0p9dtu87WRb/37mMPO2Ilx1s/t9vBHrFhbgweUwb+t7cIn5dxPhZg==", - "dev": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/remark-external-links/node_modules/unist-util-visit": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-2.0.3.tgz", - "integrity": "sha512-iJ4/RczbJMkD0712mGktuGpm/U4By4FfDonL7N/9tATGIF4imikjOuagyMY53tnZq3NP6BcmlrHhEKAfGWjh7Q==", - "dev": true, - "dependencies": { - "@types/unist": "^2.0.0", - "unist-util-is": "^4.0.0", - "unist-util-visit-parents": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/remark-external-links/node_modules/unist-util-visit-parents": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-3.1.1.tgz", - "integrity": "sha512-1KROIZWo6bcMrZEwiH2UrXDyalAa0uqzWCxCJj6lPOvTve2WkfgCytoDTPaMnodXh1WrXOq0haVYHj99ynJlsg==", - "dev": true, - "dependencies": { - "@types/unist": "^2.0.0", - "unist-util-is": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, "node_modules/remark-parse": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-6.0.3.tgz", @@ -43972,60 +40743,6 @@ "xtend": "^4.0.1" } }, - "node_modules/remark-slug": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/remark-slug/-/remark-slug-6.1.0.tgz", - "integrity": "sha512-oGCxDF9deA8phWvxFuyr3oSJsdyUAxMFbA0mZ7Y1Sas+emILtO+e5WutF9564gDsEN4IXaQXm5pFo6MLH+YmwQ==", - "dev": true, - "dependencies": { - "github-slugger": "^1.0.0", - "mdast-util-to-string": "^1.0.0", - "unist-util-visit": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/remark-slug/node_modules/unist-util-is": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-4.1.0.tgz", - "integrity": "sha512-ZOQSsnce92GrxSqlnEEseX0gi7GH9zTJZ0p9dtu87WRb/37mMPO2Ilx1s/t9vBHrFhbgweUwb+t7cIn5dxPhZg==", - "dev": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/remark-slug/node_modules/unist-util-visit": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-2.0.3.tgz", - "integrity": "sha512-iJ4/RczbJMkD0712mGktuGpm/U4By4FfDonL7N/9tATGIF4imikjOuagyMY53tnZq3NP6BcmlrHhEKAfGWjh7Q==", - "dev": true, - "dependencies": { - "@types/unist": "^2.0.0", - "unist-util-is": "^4.0.0", - "unist-util-visit-parents": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/remark-slug/node_modules/unist-util-visit-parents": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-3.1.1.tgz", - "integrity": "sha512-1KROIZWo6bcMrZEwiH2UrXDyalAa0uqzWCxCJj6lPOvTve2WkfgCytoDTPaMnodXh1WrXOq0haVYHj99ynJlsg==", - "dev": true, - "dependencies": { - "@types/unist": "^2.0.0", - "unist-util-is": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, "node_modules/remark-stringify": { "version": "6.0.4", "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-6.0.4.tgz", @@ -44396,6 +41113,7 @@ "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", "dev": true, + "license": "MIT", "engines": { "node": ">= 4" } @@ -44416,38 +41134,73 @@ "dev": true }, "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.10.tgz", + "integrity": "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==", + "license": "ISC", "dependencies": { - "glob": "^7.1.3" + "glob": "^10.3.7" }, "bin": { - "rimraf": "bin.js" + "rimraf": "dist/esm/bin.mjs" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/rimraf/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, "node_modules/rimraf/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "license": "ISC", "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" }, "engines": { - "node": "*" + "node": ">=16 || 14 >=14.17" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/rimraf/node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, "node_modules/ripemd160": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", @@ -44466,20 +41219,26 @@ "node": ">=10.0.0" } }, + "node_modules/route-recognizer": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/route-recognizer/-/route-recognizer-0.3.4.tgz", + "integrity": "sha512-2+MhsfPhvauN1O8KaXpXAOfR/fwe8dnUXVM+xw7yt40lJRfPVQxV6yryZm0cgRvAj5fMF/mdRZbL2ptwbs5i2g==", + "license": "MIT" + }, "node_modules/rrweb-cssom": { "version": "0.7.1", "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.7.1.tgz", "integrity": "sha512-TrEMa7JGdVm0UThDJSx7ddw5nVm3UJS9o9CCIZ72B1vSyEZoziDqBYP3XIoi/12lKrJR8rE3jeFHMok2F/Mnsg==" }, "node_modules/rtlcss": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/rtlcss/-/rtlcss-4.0.0.tgz", - "integrity": "sha512-j6oypPP+mgFwDXL1JkLCtm6U/DQntMUqlv5SOhpgHhdIE+PmBcjrtAHIpXfbIup47kD5Sgja9JDsDF1NNOsBwQ==", - "dev": true, + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/rtlcss/-/rtlcss-4.3.0.tgz", + "integrity": "sha512-FI+pHEn7Wc4NqKXMXFM+VAYKEj/mRIcW4h24YVwVtyjI+EqGrLc2Hx/Ny0lrZ21cBWU2goLy36eqMcNj3AQJig==", + "license": "MIT", "dependencies": { "escalade": "^3.1.1", "picocolors": "^1.0.0", - "postcss": "^8.4.6", + "postcss": "^8.4.21", "strip-json-comments": "^3.1.1" }, "bin": { @@ -44489,96 +41248,10 @@ "node": ">=12.0.0" } }, - "node_modules/rtlcss-webpack-plugin": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/rtlcss-webpack-plugin/-/rtlcss-webpack-plugin-4.0.7.tgz", - "integrity": "sha512-ouSbJtgcLBBQIsMgarxsDnfgRqm/AS4BKls/mz/Xb6HSl+PdEzefTR+Wz5uWQx4odoX0g261Z7yb3QBz0MTm0g==", - "dependencies": { - "babel-runtime": "~6.25.0", - "rtlcss": "^3.5.0" - } - }, - "node_modules/rtlcss-webpack-plugin/node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/rtlcss-webpack-plugin/node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/rtlcss-webpack-plugin/node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/rtlcss-webpack-plugin/node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "engines": { - "node": ">=8" - } - }, - "node_modules/rtlcss-webpack-plugin/node_modules/rtlcss": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/rtlcss/-/rtlcss-3.5.0.tgz", - "integrity": "sha512-wzgMaMFHQTnyi9YOwsx9LjOxYXJPzS8sYnFaKm6R5ysvTkwzHiB0vxnbHwchHQT65PTdBjDG21/kQBWI7q9O7A==", - "dependencies": { - "find-up": "^5.0.0", - "picocolors": "^1.0.0", - "postcss": "^8.3.11", - "strip-json-comments": "^3.1.1" - }, - "bin": { - "rtlcss": "bin/rtlcss.js" - } - }, - "node_modules/rtlcss-webpack-plugin/node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/rtlcss/node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, "engines": { "node": ">=8" }, @@ -44600,19 +41273,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/run-applescript/node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, "node_modules/run-applescript/node_modules/execa": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", @@ -44706,43 +41366,11 @@ "node": ">=8" } }, - "node_modules/run-applescript/node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/run-applescript/node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "engines": { - "node": ">=8" - } - }, - "node_modules/run-applescript/node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, "node_modules/run-async": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", + "dev": true, "engines": { "node": ">=0.12.0" } @@ -44821,6 +41449,7 @@ "version": "6.6.7", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "dev": true, "dependencies": { "tslib": "^1.9.0" }, @@ -44831,7 +41460,8 @@ "node_modules/rxjs/node_modules/tslib": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true }, "node_modules/sade": { "version": "1.8.1", @@ -44846,10 +41476,11 @@ } }, "node_modules/safaridriver": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/safaridriver/-/safaridriver-0.1.0.tgz", - "integrity": "sha512-azzzIP3gR1TB9bVPv7QO4Zjw0rR1BWEU/s2aFdUMN48gxDjxEB13grAEuXDmkKPgE74cObymDxmAmZnL3clj4w==", - "dev": true + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/safaridriver/-/safaridriver-0.1.2.tgz", + "integrity": "sha512-4R309+gWflJktzPXBQCobbWEHlzC4aK3a+Ov3tz2Ib2aBxiwd11phkdIBH1l0EO22x24CJMUQkpKFumRriCSRg==", + "dev": true, + "license": "MIT" }, "node_modules/safe-array-concat": { "version": "1.0.0", @@ -44924,42 +41555,46 @@ } }, "node_modules/sass": { - "version": "1.35.2", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.35.2.tgz", - "integrity": "sha512-jhO5KAR+AMxCEwIH3v+4zbB2WB0z67V1X0jbapfVwQQdjHZUGUyukpnoM6+iCMfsIUC016w9OPKQ5jrNOS9uXw==", + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.54.0.tgz", + "integrity": "sha512-C4zp79GCXZfK0yoHZg+GxF818/aclhp9F48XBu/+bm9vXEVAYov9iU3FBVRMq3Hx3OA4jfKL+p2K9180mEh0xQ==", + "license": "MIT", "dependencies": { - "chokidar": ">=3.0.0 <4.0.0" + "chokidar": ">=3.0.0 <4.0.0", + "immutable": "^4.0.0", + "source-map-js": ">=0.6.2 <2.0.0" }, "bin": { "sass": "sass.js" }, "engines": { - "node": ">=8.9.0" + "node": ">=12.0.0" } }, "node_modules/sass-loader": { - "version": "12.1.0", - "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-12.1.0.tgz", - "integrity": "sha512-FVJZ9kxVRYNZTIe2xhw93n3xJNYZADr+q69/s98l9nTCrWASo+DR2Ot0s5xTKQDDEosUkatsGeHxcH4QBp5bSg==", + "version": "16.0.3", + "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-16.0.3.tgz", + "integrity": "sha512-gosNorT1RCkuCMyihv6FBRR7BMV06oKRAs+l4UMp1mlcVg9rWN6KMmUj3igjQwmYys4mDP3etEYJgiHRbgHCHA==", + "license": "MIT", "dependencies": { - "klona": "^2.0.4", "neo-async": "^2.6.2" }, "engines": { - "node": ">= 12.13.0" + "node": ">= 18.12.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/webpack" }, "peerDependencies": { - "fibers": ">= 3.1.0", - "node-sass": "^4.0.0 || ^5.0.0 || ^6.0.0", + "@rspack/core": "0.x || 1.x", + "node-sass": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0", "sass": "^1.3.0", + "sass-embedded": "*", "webpack": "^5.0.0" }, "peerDependenciesMeta": { - "fibers": { + "@rspack/core": { "optional": true }, "node-sass": { @@ -44967,6 +41602,12 @@ }, "sass": { "optional": true + }, + "sass-embedded": { + "optional": true + }, + "webpack": { + "optional": true } } }, @@ -45036,10 +41677,12 @@ "integrity": "sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==" }, "node_modules/selfsigned": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.1.1.tgz", - "integrity": "sha512-GSL3aowiF7wa/WtSFwnUrludWFoNhftq8bUkH9pkzjpN2XSPOAYEgg6e0sS9s0rZwgJzJiQRPU18A6clnoW5wQ==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.4.1.tgz", + "integrity": "sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q==", + "license": "MIT", "dependencies": { + "@types/node-forge": "^1.3.0", "node-forge": "^1" }, "engines": { @@ -45077,15 +41720,11 @@ "node": ">=10" } }, - "node_modules/semver/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" - }, "node_modules/send": { "version": "0.18.0", "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "license": "MIT", "dependencies": { "debug": "2.6.9", "depd": "2.0.0", @@ -45109,6 +41748,7 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", "dependencies": { "ms": "2.0.0" } @@ -45116,12 +41756,14 @@ "node_modules/send/node_modules/debug/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" }, "node_modules/send/node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -45130,6 +41772,7 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", "bin": { "mime": "cli.js" }, @@ -45140,12 +41783,14 @@ "node_modules/send/node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" }, "node_modules/send/node_modules/on-finished": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", "dependencies": { "ee-first": "1.1.1" }, @@ -45157,6 +41802,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -45264,6 +41910,7 @@ "version": "1.15.0", "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "license": "MIT", "dependencies": { "encodeurl": "~1.0.2", "escape-html": "~1.0.3", @@ -45375,6 +42022,7 @@ "integrity": "sha512-KyLTWwgcR9Oe4d9HwCwNM2l7+J0dUQwn/yf7S0EnTtb0eVS4RxO0eUSvxPtzT4F3SY+C4K6fqdv/DO27sJ/v/w==", "dev": true, "hasInstallScript": true, + "license": "Apache-2.0", "optional": true, "dependencies": { "color": "^4.2.3", @@ -45393,13 +42041,6 @@ "url": "https://opencollective.com/libvips" } }, - "node_modules/sharp/node_modules/node-addon-api": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-6.1.0.tgz", - "integrity": "sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==", - "dev": true, - "optional": true - }, "node_modules/sharp/node_modules/pump": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz", @@ -45417,6 +42058,7 @@ "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.6.tgz", "integrity": "sha512-iokBDQQkUyeXhgPYaZxmczGPhnhXZ0CmrqI+MOb/WFGS9DW5wnfrLgtjUJBvz50vQ3qfRwJ62QVoCFu8mPVu5w==", "dev": true, + "license": "MIT", "optional": true, "dependencies": { "pump": "^3.0.0", @@ -45432,6 +42074,7 @@ "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", "dev": true, + "license": "MIT", "optional": true, "dependencies": { "b4a": "^1.6.4", @@ -45443,6 +42086,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", + "dev": true, "dependencies": { "shebang-regex": "^1.0.0" }, @@ -45454,6 +42098,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", + "dev": true, "engines": { "node": ">=0.10.0" } @@ -45668,21 +42313,21 @@ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" }, "node_modules/sigstore": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/sigstore/-/sigstore-1.8.0.tgz", - "integrity": "sha512-ogU8qtQ3VFBawRJ8wjsBEX/vIFeHuGs1fm4jZtjWQwjo8pfAt7T/rh+udlAN4+QUe0IzA8qRSc/YZ7dHP6kh+w==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/sigstore/-/sigstore-2.3.1.tgz", + "integrity": "sha512-8G+/XDU8wNsJOQS5ysDVO0Etg9/2uA5gR9l4ZwijjlwxBcrU6RPfwi2+jJmbP+Ap1Hlp/nVAaEO4Fj22/SL2gQ==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@sigstore/bundle": "^1.0.0", - "@sigstore/protobuf-specs": "^0.2.0", - "@sigstore/tuf": "^1.0.3", - "make-fetch-happen": "^11.0.1" - }, - "bin": { - "sigstore": "bin/sigstore.js" + "@sigstore/bundle": "^2.3.2", + "@sigstore/core": "^1.0.0", + "@sigstore/protobuf-specs": "^0.3.2", + "@sigstore/sign": "^2.3.2", + "@sigstore/tuf": "^2.3.4", + "@sigstore/verify": "^1.2.1" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^16.14.0 || >=18.0.0" } }, "node_modules/simple-concat": { @@ -45704,6 +42349,7 @@ "url": "https://feross.org/support" } ], + "license": "MIT", "optional": true }, "node_modules/simple-get": { @@ -45725,6 +42371,7 @@ "url": "https://feross.org/support" } ], + "license": "MIT", "optional": true, "dependencies": { "decompress-response": "^6.0.0", @@ -46060,17 +42707,17 @@ } }, "node_modules/socks-proxy-agent": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-7.0.0.tgz", - "integrity": "sha512-Fgl0YPZ902wEsAyiQ+idGd1A7rSFx/ayC1CQVMw5P+EQx2V0SgpGtf6OKFhVjPflPUl9YMmEOnmfjCdMUsygww==", - "dev": true, + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", + "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", + "license": "MIT", "dependencies": { - "agent-base": "^6.0.2", - "debug": "^4.3.3", - "socks": "^2.6.2" + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "socks": "^2.8.3" }, "engines": { - "node": ">= 10" + "node": ">= 14" } }, "node_modules/sort-keys": { @@ -46162,15 +42809,22 @@ "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", "deprecated": "See https://github.com/lydell/source-map-url#deprecated" }, - "node_modules/space-separated-tokens": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-1.1.5.tgz", - "integrity": "sha512-q/JSVd1Lptzhf5bkYm4ob4iWPjx0KiRe3sRFBNrVqbJkFaBm5vbbowy1mymoPNLRa52+oadOhJ+K49wsSeSjTA==", + "node_modules/spacetrim": { + "version": "0.11.59", + "resolved": "https://registry.npmjs.org/spacetrim/-/spacetrim-0.11.59.tgz", + "integrity": "sha512-lLYsktklSRKprreOm7NXReW8YiX2VBjbgmXYEziOoGf/qsJqAEACaDvoTtUOycwjpaSh+bT8eu0KrJn7UNxiCg==", "dev": true, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } + "funding": [ + { + "type": "individual", + "url": "https://buymeacoffee.com/hejny" + }, + { + "type": "github", + "url": "https://github.com/hejny/spacetrim/blob/main/README.md#%EF%B8%8F-contributing" + } + ], + "license": "Apache-2.0" }, "node_modules/spawn-command": { "version": "0.0.2-1", @@ -46178,6 +42832,31 @@ "integrity": "sha512-n98l9E2RMSJ9ON1AKisHzz7V42VDiBQGY6PB1BwRglz99wpVsSuGzQ+jOi6lFXBGVTCrRpltvjm+/XA+tpeJrg==", "dev": true }, + "node_modules/spawnd": { + "version": "10.1.4", + "resolved": "https://registry.npmjs.org/spawnd/-/spawnd-10.1.4.tgz", + "integrity": "sha512-drqHc0mKJmtMsiGMOCwzlc5eZ0RPtRvT7tQAluW2A0qUc0G7TQ8KLcn3E6K5qzkLkH2UkS3nYQiVGULvvsD9dw==", + "license": "MIT", + "dependencies": { + "signal-exit": "^4.1.0", + "tree-kill": "^1.2.2" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/spawnd/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/spdx-correct": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.0.0.tgz", @@ -46274,6 +42953,7 @@ "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==", "dev": true, + "license": "MIT", "dependencies": { "through": "2" }, @@ -46306,6 +42986,7 @@ "resolved": "https://registry.npmjs.org/split2/-/split2-3.2.2.tgz", "integrity": "sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==", "dev": true, + "license": "ISC", "dependencies": { "readable-stream": "^3.0.0" } @@ -46315,6 +42996,7 @@ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", "dev": true, + "license": "MIT", "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -46440,27 +43122,30 @@ "node": ">= 0.4" } }, - "node_modules/store2": { - "version": "2.14.2", - "resolved": "https://registry.npmjs.org/store2/-/store2-2.14.2.tgz", - "integrity": "sha512-siT1RiqlfQnGqgT/YzXVUNsom9S0H1OX+dpdGN1xkyYATo4I6sep5NmsRD/40s3IIOvlCq6akxkqG82urIZW1w==", - "dev": true - }, "node_modules/storybook": { - "version": "7.6.15", - "resolved": "https://registry.npmjs.org/storybook/-/storybook-7.6.15.tgz", - "integrity": "sha512-Ybezq9JRk5CBhzjgzZ/oT7mnU45UwhyVSGKW+PUKZGGUG9VH2hCrTEES9f/zEF82kj/5COVPyqR/5vlXuuS39A==", + "version": "8.4.7", + "resolved": "https://registry.npmjs.org/storybook/-/storybook-8.4.7.tgz", + "integrity": "sha512-RP/nMJxiWyFc8EVMH5gp20ID032Wvk+Yr3lmKidoegto5Iy+2dVQnUoElZb2zpbVXNHWakGuAkfI0dY1Hfp/vw==", "dev": true, "dependencies": { - "@storybook/cli": "7.6.15" + "@storybook/core": "8.4.7" }, "bin": { - "sb": "index.js", - "storybook": "index.js" + "getstorybook": "bin/index.cjs", + "sb": "bin/index.cjs", + "storybook": "bin/index.cjs" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "prettier": "^2 || ^3" + }, + "peerDependenciesMeta": { + "prettier": { + "optional": true + } } }, "node_modules/storybook-source-link": { @@ -46608,7 +43293,6 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -46621,14 +43305,12 @@ "node_modules/string-width-cjs/node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, "node_modules/string-width-cjs/node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, "engines": { "node": ">=8" } @@ -46772,7 +43454,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "dependencies": { "ansi-regex": "^5.0.1" }, @@ -46849,6 +43530,7 @@ "resolved": "https://registry.npmjs.org/strong-log-transformer/-/strong-log-transformer-2.1.0.tgz", "integrity": "sha512-B3Hgul+z0L9a236FAUC9iZsL+nVHgoCJnqCbN588DjYxvGXaXaaFbfmQ/JhvKjZwsOukuR72XbHv71Qkug0HxA==", "dev": true, + "license": "Apache-2.0", "dependencies": { "duplexer": "^0.1.1", "minimist": "^1.2.0", @@ -47415,19 +44097,6 @@ "node": ">=0.10.0" } }, - "node_modules/swc-loader": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/swc-loader/-/swc-loader-0.2.6.tgz", - "integrity": "sha512-9Zi9UP2YmDpgmQVbyOPJClY0dwf58JDyDMQ7uRc4krmc72twNI2fvlBWHLqVekBpPc7h5NJkGVT1zNDxFrqhvg==", - "dev": true, - "dependencies": { - "@swc/counter": "^0.1.3" - }, - "peerDependencies": { - "@swc/core": "^1.2.147", - "webpack": ">=2" - } - }, "node_modules/symbol-observable": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz", @@ -47442,12 +44111,6 @@ "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==" }, - "node_modules/synchronous-promise": { - "version": "2.0.17", - "resolved": "https://registry.npmjs.org/synchronous-promise/-/synchronous-promise-2.0.17.tgz", - "integrity": "sha512-AsS729u2RHUfEra9xJrE39peJcc2stq2+poBXX8bcM08Y6g9j/i/PUzwNQqkaJde7Ntg1TO7bSREbR5sdosQ+g==", - "dev": true - }, "node_modules/synckit": { "version": "0.8.5", "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.5.tgz", @@ -47546,20 +44209,21 @@ } }, "node_modules/tar": { - "version": "6.1.11", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.11.tgz", - "integrity": "sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", "dev": true, + "license": "ISC", "dependencies": { "chownr": "^2.0.0", "fs-minipass": "^2.0.0", - "minipass": "^3.0.0", + "minipass": "^5.0.0", "minizlib": "^2.1.1", "mkdirp": "^1.0.3", "yallist": "^4.0.0" }, "engines": { - "node": ">= 10" + "node": ">=10" } }, "node_modules/tar-fs": { @@ -47567,6 +44231,7 @@ "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", "dev": true, + "optional": true, "dependencies": { "chownr": "^1.1.1", "mkdirp-classic": "^0.5.2", @@ -47580,6 +44245,7 @@ "integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==", "dev": true, "license": "MIT", + "optional": true, "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" @@ -47620,15 +44286,27 @@ "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", "dev": true, + "license": "ISC", "engines": { "node": ">=10" } }, + "node_modules/tar/node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=8" + } + }, "node_modules/tar/node_modules/mkdirp": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", "dev": true, + "license": "MIT", "bin": { "mkdirp": "bin/cmd.js" }, @@ -47636,12 +44314,6 @@ "node": ">=10" } }, - "node_modules/tar/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, "node_modules/teen_process": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/teen_process/-/teen_process-2.0.2.tgz", @@ -47693,15 +44365,6 @@ "node": ">= 8" } }, - "node_modules/telejson": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/telejson/-/telejson-7.2.0.tgz", - "integrity": "sha512-1QTEcJkJEhc8OnStBx/ILRu5J2p0GjvWsBx56bmZRqnrkdBMUe+nX92jxV+p3dB4CP6PZCdJMQJwCggkNBMzkQ==", - "dev": true, - "dependencies": { - "memoizerific": "^1.11.3" - } - }, "node_modules/temp": { "version": "0.8.4", "resolved": "https://registry.npmjs.org/temp/-/temp-0.8.4.tgz", @@ -47718,6 +44381,7 @@ "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-1.0.0.tgz", "integrity": "sha512-xZFXEGbG7SNC3itwBzI3RYjq/cEhBkx2hJuKGIUOcEULmkQExXiHat2z/qkISYsuR+IKumhEfKKbV5qXmhICFQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=4" } @@ -47752,89 +44416,6 @@ "rimraf": "bin.js" } }, - "node_modules/tempy": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/tempy/-/tempy-1.0.1.tgz", - "integrity": "sha512-biM9brNqxSc04Ee71hzFbryD11nX7VPhQQY32AdDmjFvodsRFz/3ufeoTZ6uYkRFfGo188tENcASNs3vTdsM0w==", - "dev": true, - "dependencies": { - "del": "^6.0.0", - "is-stream": "^2.0.0", - "temp-dir": "^2.0.0", - "type-fest": "^0.16.0", - "unique-string": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/tempy/node_modules/del": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/del/-/del-6.1.1.tgz", - "integrity": "sha512-ua8BhapfP0JUJKC/zV9yHHDW/rDoDxP4Zhn3AkA6/xT6gY7jYXJiaeyBZznYVujhZZET+UgcbZiQ7sN3WqcImg==", - "dev": true, - "dependencies": { - "globby": "^11.0.1", - "graceful-fs": "^4.2.4", - "is-glob": "^4.0.1", - "is-path-cwd": "^2.2.0", - "is-path-inside": "^3.0.2", - "p-map": "^4.0.0", - "rimraf": "^3.0.2", - "slash": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/tempy/node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/tempy/node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/tempy/node_modules/temp-dir": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-2.0.0.tgz", - "integrity": "sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/tempy/node_modules/type-fest": { - "version": "0.16.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.16.0.tgz", - "integrity": "sha512-eaBzG6MxNzEn9kiwvtre90cXaNLkmadMWa1zQMs3XORCXNbsH/OewwbxC5ia9dCxIxnTAsSxXJaa/p5y8DlvJg==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/terminal-link": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.0.0.tgz", @@ -48026,6 +44607,7 @@ "resolved": "https://registry.npmjs.org/text-extensions/-/text-extensions-1.9.0.tgz", "integrity": "sha512-wiBrwC1EhBelW12Zy26JeOUkQ5mRu+5o8rpsJk5+2t+Y5vE7e842qtZDQ2g1NpX/29HdyFeJ4nSIhI47ENSxlQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10" } @@ -48042,9 +44624,10 @@ "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==" }, "node_modules/third-party-web": { - "version": "0.23.4", - "resolved": "https://registry.npmjs.org/third-party-web/-/third-party-web-0.23.4.tgz", - "integrity": "sha512-kwYnSZRhEvv0SBW2fp8SBBKRglMoBjV8xz6C31m0ewqOtknB5UL+Ihg+M81hyFY5ldkZuGWPb+e4GVDkzf/gYg==" + "version": "0.26.2", + "resolved": "https://registry.npmjs.org/third-party-web/-/third-party-web-0.26.2.tgz", + "integrity": "sha512-taJ0Us0lKoYBqcbccMuDElSUPOxmBfwlHe1OkHQ3KFf+RwovvBHdXhbFk9XJVQE2vHzxbTwvwg5GFsT9hbDokQ==", + "license": "MIT" }, "node_modules/throat": { "version": "5.0.0", @@ -48093,6 +44676,24 @@ "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", "dev": true }, + "node_modules/tinyrainbow": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-1.2.0.tgz", + "integrity": "sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==", + "dev": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-3.0.2.tgz", + "integrity": "sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==", + "dev": true, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/titleize": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/titleize/-/titleize-3.0.0.tgz", @@ -48116,9 +44717,19 @@ } }, "node_modules/tldts-core": { - "version": "6.1.50", - "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.50.tgz", - "integrity": "sha512-na2EcZqmdA2iV9zHV7OHQDxxdciEpxrjbkp+aHmZgnZKHzoElLajP59np5/4+sare9fQBfixgvXKx8ev1d7ytw==" + "version": "6.1.66", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.66.tgz", + "integrity": "sha512-s07jJruSwndD2X8bVjwioPfqpIc1pDTzszPe9pL1Skbh4bjytL85KNQ3tolqLbCvpQHawIsGfFi9dgerWjqW4g==", + "license": "MIT" + }, + "node_modules/tldts-icann": { + "version": "6.1.66", + "resolved": "https://registry.npmjs.org/tldts-icann/-/tldts-icann-6.1.66.tgz", + "integrity": "sha512-f4AgNXjymBX3/EXYrnvjyBhXVQ+NWyPzXjqRb17vr0b6SprZKVNnsWNFJAPI6JkPHCm7dHhFDgyneHQEq5uJRA==", + "license": "MIT", + "dependencies": { + "tldts-core": "^6.1.66" + } }, "node_modules/tmp": { "version": "0.0.33", @@ -48194,12 +44805,6 @@ "node": ">=0.10.0" } }, - "node_modules/tocbot": { - "version": "4.25.0", - "resolved": "https://registry.npmjs.org/tocbot/-/tocbot-4.25.0.tgz", - "integrity": "sha512-kE5wyCQJ40hqUaRVkyQ4z5+4juzYsv/eK+aqD97N62YH0TxFhzJvo22RUQQZdO3YnXAk42ZOfOpjVdy+Z0YokA==", - "dev": true - }, "node_modules/toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", @@ -48240,6 +44845,16 @@ "tree-kill": "cli.js" } }, + "node_modules/treeverse": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/treeverse/-/treeverse-3.0.0.tgz", + "integrity": "sha512-gcANaAnd2QDZFmHFEOF4k7uc1J/6a6z3DJMd/QwEyxLoKGiptJRwid582r7QIsFlFMIZ3SnxfS52S4hm2DHkuQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, "node_modules/trim": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/trim/-/trim-0.0.1.tgz", @@ -48401,17 +45016,18 @@ "dev": true }, "node_modules/tuf-js": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/tuf-js/-/tuf-js-1.1.7.tgz", - "integrity": "sha512-i3P9Kgw3ytjELUfpuKVDNBJvk4u5bXL6gskv572mcevPbSKCV3zt3djhmlEQ65yERjIbOSncy7U4cQJaB1CBCg==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tuf-js/-/tuf-js-2.2.1.tgz", + "integrity": "sha512-GwIJau9XaA8nLVbUXsN3IlFi7WmQ48gBUrl3FTkkL/XLu/POhBzfmX9hd33FNMX1qAsfl6ozO1iMmW9NC8YniA==", "dev": true, + "license": "MIT", "dependencies": { - "@tufjs/models": "1.0.4", + "@tufjs/models": "2.0.1", "debug": "^4.3.4", - "make-fetch-happen": "^11.1.1" + "make-fetch-happen": "^13.0.1" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^16.14.0 || >=18.0.0" } }, "node_modules/tunnel": { @@ -48427,6 +45043,7 @@ "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", "dev": true, + "license": "Apache-2.0", "optional": true, "dependencies": { "safe-buffer": "^5.0.1" @@ -48633,9 +45250,9 @@ } }, "node_modules/typescript": { - "version": "5.5.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.3.tgz", - "integrity": "sha512-/hreyEujaB0w76zKo6717l3L0o/qEUtRgdvUBvlkhoWeOVMjMuHNHk0BRBzikzuGDqNmPQbg5ifMEqsHLiIUcQ==", + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.2.tgz", + "integrity": "sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==", "dev": true, "license": "Apache-2.0", "bin": { @@ -48651,12 +45268,6 @@ "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==" }, - "node_modules/ufo": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.4.0.tgz", - "integrity": "sha512-Hhy+BhRBleFjpJ2vchUNN40qgkh0366FWJGqVLYBHev0vpHTrXSA0ryT+74UiW6KWsldNurQMKGqCm1M2zBciQ==", - "dev": true - }, "node_modules/uglify-js": { "version": "3.13.7", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.13.7.tgz", @@ -48729,9 +45340,10 @@ } }, "node_modules/undici-types": { - "version": "5.26.5", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", + "license": "MIT" }, "node_modules/unherit": { "version": "1.1.1", @@ -48930,23 +45542,23 @@ } }, "node_modules/unplugin": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-1.7.1.tgz", - "integrity": "sha512-JqzORDAPxxs8ErLV4x+LL7bk5pk3YlcWqpSNsIkAZj972KzFZLClc/ekppahKkOczGkwIG6ElFgdOgOlK4tXZw==", + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-1.16.0.tgz", + "integrity": "sha512-5liCNPuJW8dqh3+DM6uNM2EI3MLLpCKp/KY+9pB5M2S2SR2qvvDHhKgBOaTWEbZTAws3CXfB0rKTIolWKL05VQ==", "dev": true, "dependencies": { - "acorn": "^8.11.3", - "chokidar": "^3.5.3", - "webpack-sources": "^3.2.3", - "webpack-virtual-modules": "^0.6.1" + "acorn": "^8.14.0", + "webpack-virtual-modules": "^0.6.2" + }, + "engines": { + "node": ">=14.0.0" } }, "node_modules/unplugin/node_modules/acorn": { - "version": "8.13.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.13.0.tgz", - "integrity": "sha512-8zSiw54Oxrdym50NlZ9sUusyO1Z1ZchgRLWRaK6c86XJFClyCgFKetdowBg5bKxyp/u+CDBJG4Mpp0m3HLZl9w==", + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", + "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", "dev": true, - "license": "MIT", "bin": { "acorn": "bin/acorn" }, @@ -48954,13 +45566,6 @@ "node": ">=0.4.0" } }, - "node_modules/unplugin/node_modules/webpack-virtual-modules": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz", - "integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==", - "dev": true, - "license": "MIT" - }, "node_modules/unset-value": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", @@ -49017,30 +45622,6 @@ "node": ">=8" } }, - "node_modules/unzipper": { - "version": "0.10.14", - "resolved": "https://registry.npmjs.org/unzipper/-/unzipper-0.10.14.tgz", - "integrity": "sha512-ti4wZj+0bQTiX2KmKWuwj7lhV+2n//uXEotUmGuQqrbVZSEGFMbI68+c6JCQ8aAmUWYvtHEz2A8K6wXvueR/6g==", - "dev": true, - "dependencies": { - "big-integer": "^1.6.17", - "binary": "~0.3.0", - "bluebird": "~3.4.1", - "buffer-indexof-polyfill": "~1.0.0", - "duplexer2": "~0.1.4", - "fstream": "^1.0.12", - "graceful-fs": "^4.2.2", - "listenercount": "~1.0.1", - "readable-stream": "~2.3.6", - "setimmediate": "~1.0.4" - } - }, - "node_modules/unzipper/node_modules/bluebird": { - "version": "3.4.7", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.4.7.tgz", - "integrity": "sha512-iD3898SR7sWVRHbiQv+sHUtHnMvC1o3nW5rAcqnq3uOn07DSAppZYUkIGslDz6gXC7HfunPe7YVBgoEJASPcHA==", - "dev": true - }, "node_modules/upath": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", @@ -49195,26 +45776,11 @@ "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==", "dev": true }, - "node_modules/url/node_modules/qs": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", - "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "side-channel": "^1.0.6" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/urlpattern-polyfill": { "version": "10.0.0", "resolved": "https://registry.npmjs.org/urlpattern-polyfill/-/urlpattern-polyfill-10.0.0.tgz", - "integrity": "sha512-H/A06tKD7sS1O1X2SshBVeA5FLycRpjqiBeqGKmBwBDBy28EnRjORxTNe269KSSr5un5qyWi1iL61wLxpd+ZOg==" + "integrity": "sha512-H/A06tKD7sS1O1X2SshBVeA5FLycRpjqiBeqGKmBwBDBy28EnRjORxTNe269KSSr5un5qyWi1iL61wLxpd+ZOg==", + "license": "MIT" }, "node_modules/use": { "version": "3.1.1", @@ -49262,19 +45828,6 @@ "react": "^16.8.0" } }, - "node_modules/use-resize-observer": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/use-resize-observer/-/use-resize-observer-9.1.0.tgz", - "integrity": "sha512-R25VqO9Wb3asSD4eqtcxk8sJalvIOYBqS8MNZlpDSQ4l4xMQxC/J7Id9HoTqPq8FwULIn0PVW+OAqF2dyYbjow==", - "dev": true, - "dependencies": { - "@juggle/resize-observer": "^3.3.1" - }, - "peerDependencies": { - "react": "16.8.0 - 18", - "react-dom": "16.8.0 - 18" - } - }, "node_modules/use-sidecar": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.2.tgz", @@ -49308,18 +45861,20 @@ } }, "node_modules/use-sync-external-store": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", - "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.2.tgz", + "integrity": "sha512-PElTlVMwpblvbNqQ82d2n6RjStvdSoNe9FG28kNfz3WiXilJm4DdNkEzRhCZuIDwY8U08WVihhGR5iRqAwfDiw==", + "license": "MIT", "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0" } }, "node_modules/userhome": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/userhome/-/userhome-1.0.0.tgz", - "integrity": "sha512-ayFKY3H+Pwfy4W98yPdtH1VqH4psDeyW8lYYFzfecR9d6hqLpqhecktvYR3SEEXt7vG0S1JEpciI3g94pMErig==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/userhome/-/userhome-1.0.1.tgz", + "integrity": "sha512-5cnLm4gseXjAclKowC4IjByaGsjtAoV6PrOQOljplNB54ReUYJP8HdAFq2muHinSDAh09PPX/uXDPfdxRHvuSA==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.8.0" } @@ -49511,11 +46066,51 @@ "node": ">=18" } }, + "node_modules/wait-on": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/wait-on/-/wait-on-8.0.1.tgz", + "integrity": "sha512-1wWQOyR2LVVtaqrcIL2+OM+x7bkpmzVROa0Nf6FryXkS+er5Sa1kzFGjzZRqLnHa3n1rACFLeTwUqE1ETL9Mig==", + "license": "MIT", + "dependencies": { + "axios": "^1.7.7", + "joi": "^17.13.3", + "lodash": "^4.17.21", + "minimist": "^1.2.8", + "rxjs": "^7.8.1" + }, + "bin": { + "wait-on": "bin/wait-on" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/wait-on/node_modules/axios": { + "version": "1.7.9", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.9.tgz", + "integrity": "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/wait-on/node_modules/rxjs": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, "node_modules/wait-port": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/wait-port/-/wait-port-1.1.0.tgz", "integrity": "sha512-3e04qkoN3LxTMLakdqeWth8nih8usyg+sf1Bgdf9wwUkp05iuK1eSY/QpLvscT/+F/gA89+LpUmmgBtesbqI2Q==", "dev": true, + "license": "MIT", "dependencies": { "chalk": "^4.1.2", "commander": "^9.3.0", @@ -49533,6 +46128,7 @@ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -49549,6 +46145,7 @@ "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", "dev": true, + "license": "MIT", "engines": { "node": "^12.20.0 || >=14" } @@ -49558,6 +46155,7 @@ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -49567,6 +46165,7 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, + "license": "MIT", "dependencies": { "has-flag": "^4.0.0" }, @@ -49574,6 +46173,13 @@ "node": ">=8" } }, + "node_modules/walk-up-path": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/walk-up-path/-/walk-up-path-3.0.1.tgz", + "integrity": "sha512-9YlCL/ynK3CTlrSRrDxZvUauLzAswPCrsaCgilqFevUYpeEW0/3ScEjaa3kbW/T0ghhkEr7mv+fpjqn1Y1YuTA==", + "dev": true, + "license": "ISC" + }, "node_modules/walker": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", @@ -49831,10 +46437,11 @@ } }, "node_modules/web-streams-polyfill": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz", - "integrity": "sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", "dev": true, + "license": "MIT", "engines": { "node": ">= 8" } @@ -49844,6 +46451,7 @@ "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-8.16.20.tgz", "integrity": "sha512-3Dynj9pfTqmbDadqmMmD/sQgGFwho92zQPGgpAqLUMebE/qEkraoIfRWdbi2tw1ityiThOJVPTXfwsY/bpvknw==", "dev": true, + "license": "MIT", "dependencies": { "@types/node": "^20.1.0", "@types/ws": "^8.5.3", @@ -49866,6 +46474,7 @@ "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-5.6.0.tgz", "integrity": "sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==", "dev": true, + "license": "MIT", "engines": { "node": ">=14.16" }, @@ -49878,6 +46487,7 @@ "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-5.0.1.tgz", "integrity": "sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==", "dev": true, + "license": "MIT", "dependencies": { "defer-to-connect": "^2.0.1" }, @@ -49885,21 +46495,12 @@ "node": ">=14.16" } }, - "node_modules/webdriver/node_modules/@types/node": { - "version": "20.16.15", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.16.15.tgz", - "integrity": "sha512-DV58qQz9dBMqVVn+qnKwGa51QzCD4YM/tQM16qLKxdf5tqz5W4QwtrMzjSTbabN1cFTSuyxVYBy+QWHjWW8X/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~6.19.2" - } - }, "node_modules/webdriver/node_modules/cacheable-lookup": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-7.0.0.tgz", "integrity": "sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w==", "dev": true, + "license": "MIT", "engines": { "node": ">=14.16" } @@ -49909,6 +46510,7 @@ "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-10.2.14.tgz", "integrity": "sha512-zkDT5WAF4hSSoUgyfg5tFIxz8XQK+25W/TLVojJTMKBaxevLBBtLxgqguAuVQB8PVW79FVjHcU+GJ9tVbDZ9mQ==", "dev": true, + "license": "MIT", "dependencies": { "@types/http-cache-semantics": "^4.0.2", "get-stream": "^6.0.1", @@ -49927,6 +46529,7 @@ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", "dev": true, + "license": "MIT", "engines": { "node": ">=10" }, @@ -49939,6 +46542,7 @@ "resolved": "https://registry.npmjs.org/got/-/got-12.6.1.tgz", "integrity": "sha512-mThBblvlAF1d4O5oqyvN+ZxLAYwIJK7bpMxgYqPD9okW0C3qm5FFn7k811QrcuEBwaogR3ngOFoCfs6mRv7teQ==", "dev": true, + "license": "MIT", "dependencies": { "@sindresorhus/is": "^5.2.0", "@szmarczak/http-timer": "^5.0.1", @@ -49964,6 +46568,7 @@ "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-2.2.1.tgz", "integrity": "sha512-V5nVw1PAOgfI3Lmeaj2Exmeg7fenjhRUgz1lPSezy1CuhPYbgQtbQj4jZfEAEMlaL+vupsvhjqCyjzob0yxsmQ==", "dev": true, + "license": "MIT", "dependencies": { "quick-lru": "^5.1.1", "resolve-alpn": "^1.2.0" @@ -49977,6 +46582,7 @@ "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-3.0.0.tgz", "integrity": "sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==", "dev": true, + "license": "MIT", "engines": { "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, @@ -49989,6 +46595,7 @@ "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-4.0.0.tgz", "integrity": "sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg==", "dev": true, + "license": "MIT", "engines": { "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, @@ -50001,6 +46608,7 @@ "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.0.1.tgz", "integrity": "sha512-IO9QvjUMWxPQQhs60oOu10CRkWCiZzSUkzbXGGV9pviYl1fXYcvkzQ5jV9z8Y6un8ARoVRl4EtC6v6jNqbaJ/w==", "dev": true, + "license": "MIT", "engines": { "node": ">=14.16" }, @@ -50013,6 +46621,7 @@ "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-3.0.0.tgz", "integrity": "sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==", "dev": true, + "license": "MIT", "engines": { "node": ">=12.20" } @@ -50022,6 +46631,7 @@ "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", "dev": true, + "license": "MIT", "engines": { "node": ">=10" }, @@ -50034,6 +46644,7 @@ "resolved": "https://registry.npmjs.org/responselike/-/responselike-3.0.0.tgz", "integrity": "sha512-40yHxbNcl2+rzXvZuVkrYohathsSJlMTXKryG5y8uciHv1+xDLHQpgjG64JUO9nrEq2jGLH6IZ8BcZyw3wrweg==", "dev": true, + "license": "MIT", "dependencies": { "lowercase-keys": "^3.0.0" }, @@ -50044,13 +46655,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/webdriver/node_modules/undici-types": { - "version": "6.19.8", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", - "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", - "dev": true, - "license": "MIT" - }, "node_modules/webdriver/node_modules/ws": { "version": "8.18.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", @@ -50078,6 +46682,7 @@ "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-8.16.20.tgz", "integrity": "sha512-2xSJDrMxwPF1kucB/r7Wc8yF689GGi7iSKrog7vkkoIiRY25vd3U129iN2mTYgNDyM6SM0kw+GP5W1s73khpYw==", "dev": true, + "license": "MIT", "dependencies": { "@types/node": "^20.1.0", "@wdio/config": "8.16.20", @@ -50116,14 +46721,34 @@ } } }, - "node_modules/webdriverio/node_modules/@types/node": { - "version": "20.16.15", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.16.15.tgz", - "integrity": "sha512-DV58qQz9dBMqVVn+qnKwGa51QzCD4YM/tQM16qLKxdf5tqz5W4QwtrMzjSTbabN1cFTSuyxVYBy+QWHjWW8X/g==", + "node_modules/webdriverio/node_modules/@puppeteer/browsers": { + "version": "1.4.6", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.4.6.tgz", + "integrity": "sha512-x4BEjr2SjOPowNeiguzjozQbsc6h437ovD/wu+JpaenxVLm3jkgzHY2xOslMTp50HoTvQreMjiexiGQw1sqZlQ==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "undici-types": "~6.19.2" + "debug": "4.3.4", + "extract-zip": "2.0.1", + "progress": "2.0.3", + "proxy-agent": "6.3.0", + "tar-fs": "3.0.4", + "unbzip2-stream": "1.4.3", + "yargs": "17.7.1" + }, + "bin": { + "browsers": "lib/cjs/main-cli.js" + }, + "engines": { + "node": ">=16.3.0" + }, + "peerDependencies": { + "typescript": ">= 4.7.4" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, "node_modules/webdriverio/node_modules/aria-query": { @@ -50145,11 +46770,65 @@ "balanced-match": "^1.0.0" } }, + "node_modules/webdriverio/node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/webdriverio/node_modules/devtools-protocol": { "version": "0.0.1203626", "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1203626.tgz", "integrity": "sha512-nEzHZteIUZfGCZtTiS1fRpC8UZmsfD1SiyPvaUNvS13dvKf666OAm8YTi0+Ca3n1nLEyu49Cy4+dPWpaHFJk9g==", - "dev": true + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/webdriverio/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/webdriverio/node_modules/extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + }, + "bin": { + "extract-zip": "cli.js" + }, + "engines": { + "node": ">= 10.17.0" + }, + "optionalDependencies": { + "@types/yauzl": "^2.9.1" + } + }, + "node_modules/webdriverio/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } }, "node_modules/webdriverio/node_modules/is-plain-obj": { "version": "4.1.0", @@ -50163,6 +46842,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/webdriverio/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, "node_modules/webdriverio/node_modules/minimatch": { "version": "9.0.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", @@ -50179,11 +46868,43 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/webdriverio/node_modules/proxy-agent": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.3.0.tgz", + "integrity": "sha512-0LdR757eTj/JfuU7TL2YCuAZnxWXu3tkJbg4Oq3geW/qFNT/32T0sp2HnZ9O0lMR4q3vwAt0+xCA8SR0WAD0og==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.0.2", + "debug": "^4.3.4", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.0", + "lru-cache": "^7.14.1", + "pac-proxy-agent": "^7.0.0", + "proxy-from-env": "^1.1.0", + "socks-proxy-agent": "^8.0.1" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/webdriverio/node_modules/pump": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz", + "integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==", + "dev": true, + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, "node_modules/webdriverio/node_modules/puppeteer-core": { "version": "20.9.0", "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-20.9.0.tgz", "integrity": "sha512-H9fYZQzMTRrkboEfPmf7m3CLDN6JvbxXA3qTtS+dFt27tR+CsFHzPsT6pzp6lYL6bJbAPaR0HaPO6uSi+F94Pg==", "dev": true, + "license": "Apache-2.0", "dependencies": { "@puppeteer/browsers": "1.4.6", "chromium-bidi": "0.4.16", @@ -50208,7 +46929,8 @@ "version": "0.0.1147663", "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1147663.tgz", "integrity": "sha512-hyWmRrexdhbZ1tcJUGpO95ivbRhWXz++F4Ko+n21AY5PNln2ovoJw+8ZMNDTtip+CNFQfrtLVh/w4009dXO/eQ==", - "dev": true + "dev": true, + "license": "BSD-3-Clause" }, "node_modules/webdriverio/node_modules/serialize-error": { "version": "11.0.3", @@ -50225,6 +46947,45 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/webdriverio/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/webdriverio/node_modules/tar-fs": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.4.tgz", + "integrity": "sha512-5AFQU8b9qLfZCX9zp2duONhPmZv0hGYiBPJsyUdqMjzq/mqVpy/rEUSeHk1+YitmxugaptgBh5oDGU3VsAJq4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^3.1.5" + } + }, + "node_modules/webdriverio/node_modules/tar-stream": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", + "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "b4a": "^1.6.4", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" + } + }, "node_modules/webdriverio/node_modules/type-fest": { "version": "2.19.0", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", @@ -50237,18 +46998,30 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/webdriverio/node_modules/undici-types": { - "version": "6.19.8", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", - "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", + "node_modules/webdriverio/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, - "license": "MIT" + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } }, "node_modules/webdriverio/node_modules/ws": { "version": "8.13.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", "dev": true, + "license": "MIT", "engines": { "node": ">=10.0.0" }, @@ -50265,23 +47038,63 @@ } } }, + "node_modules/webdriverio/node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/webdriverio/node_modules/yargs": { + "version": "17.7.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.1.tgz", + "integrity": "sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/webdriverio/node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, "node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" }, "node_modules/webpack": { - "version": "5.95.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.95.0.tgz", - "integrity": "sha512-2t3XstrKULz41MNMBF+cJ97TyHdyQ8HCt//pqErqDvNjU9YQBnZxIHa11VXsi7F3mb5/aO2tuDxdeTPdU7xu9Q==", - "dependencies": { - "@types/estree": "^1.0.5", - "@webassemblyjs/ast": "^1.12.1", - "@webassemblyjs/wasm-edit": "^1.12.1", - "@webassemblyjs/wasm-parser": "^1.12.1", - "acorn": "^8.7.1", - "acorn-import-attributes": "^1.9.5", - "browserslist": "^4.21.10", + "version": "5.97.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.97.0.tgz", + "integrity": "sha512-CWT8v7ShSfj7tGs4TLRtaOLmOCPWhoKEvp+eA7FVx8Xrjb3XfT0aXdxDItnRZmE8sHcH+a8ayDrJCOjXKxVFfQ==", + "license": "MIT", + "dependencies": { + "@types/eslint-scope": "^3.7.7", + "@types/estree": "^1.0.6", + "@webassemblyjs/ast": "^1.14.1", + "@webassemblyjs/wasm-edit": "^1.14.1", + "@webassemblyjs/wasm-parser": "^1.14.1", + "acorn": "^8.14.0", + "browserslist": "^4.24.0", "chrome-trace-event": "^1.0.2", "enhanced-resolve": "^5.17.1", "es-module-lexer": "^1.2.1", @@ -50459,60 +47272,6 @@ "node": ">=14" } }, - "node_modules/webpack-cli/node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/webpack-cli/node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "engines": { - "node": ">=8" - } - }, - "node_modules/webpack-cli/node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/webpack-cli/node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "engines": { - "node": ">=8" - } - }, - "node_modules/webpack-cli/node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, "node_modules/webpack-dev-middleware": { "version": "6.1.3", "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-6.1.3.tgz", @@ -50546,7 +47305,6 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "dev": true, - "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -50583,9 +47341,9 @@ "dev": true }, "node_modules/webpack-dev-middleware/node_modules/schema-utils": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.2.0.tgz", - "integrity": "sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.0.tgz", + "integrity": "sha512-Gf9qqc58SpCA/xdziiHz35F4GNIWYWZrEshUc/G/r5BnLph6xpKuLeoJoQuj5WfBIx/eQLf+hmVPYHaxJu7V2g==", "dev": true, "dependencies": { "@types/json-schema": "^7.0.9", @@ -50594,7 +47352,7 @@ "ajv-keywords": "^5.1.0" }, "engines": { - "node": ">= 12.13.0" + "node": ">= 10.13.0" }, "funding": { "type": "opencollective", @@ -50602,9 +47360,10 @@ } }, "node_modules/webpack-dev-server": { - "version": "4.15.1", - "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.15.1.tgz", - "integrity": "sha512-5hbAst3h3C3L8w6W4P96L5vaV0PxSmJhxZvWKYIdgxOQm8pNZ5dEOmmSLBVpP85ReeyRt6AS1QJNyo/oFFPeVA==", + "version": "4.15.2", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.15.2.tgz", + "integrity": "sha512-0XavAZbNJ5sDrCbkpWL8mia0o5WPOd2YGtxrEiZkBK9FjLppIUK2TgxK6qGD2P3hUXTJNNPVibrerKcx5WkR1g==", + "license": "MIT", "dependencies": { "@types/bonjour": "^3.5.9", "@types/connect-history-api-fallback": "^1.3.5", @@ -50634,7 +47393,7 @@ "serve-index": "^1.9.1", "sockjs": "^0.3.24", "spdy": "^4.0.2", - "webpack-dev-middleware": "^5.3.1", + "webpack-dev-middleware": "^5.3.4", "ws": "^8.13.0" }, "bin": { @@ -50679,6 +47438,7 @@ "version": "5.1.0", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3" }, @@ -50689,12 +47449,35 @@ "node_modules/webpack-dev-server/node_modules/colorette": { "version": "2.0.20", "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", - "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==" + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "license": "MIT" + }, + "node_modules/webpack-dev-server/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } }, "node_modules/webpack-dev-server/node_modules/ipaddr.js": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.2.0.tgz", "integrity": "sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==", + "license": "MIT", "engines": { "node": ">= 10" } @@ -50703,6 +47486,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "license": "MIT", "dependencies": { "is-docker": "^2.0.0" }, @@ -50713,12 +47497,14 @@ "node_modules/webpack-dev-server/node_modules/json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" }, "node_modules/webpack-dev-server/node_modules/open": { "version": "8.4.2", "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", + "license": "MIT", "dependencies": { "define-lazy-prop": "^2.0.0", "is-docker": "^2.1.1", @@ -50731,10 +47517,27 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/webpack-dev-server/node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/webpack-dev-server/node_modules/schema-utils": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.2.0.tgz", "integrity": "sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw==", + "license": "MIT", "dependencies": { "@types/json-schema": "^7.0.9", "ajv": "^8.9.0", @@ -50753,6 +47556,7 @@ "version": "5.3.4", "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.4.tgz", "integrity": "sha512-BVdTqhhs+0IfoeAf7EoH5WE+exCmqGerHfDM0IL096Px60Tq2Mn9MAbnaGUe6HiMa41KMCYF19gyzZmBcq/o4Q==", + "license": "MIT", "dependencies": { "colorette": "^2.0.10", "memfs": "^3.4.3", @@ -50824,131 +47628,150 @@ } }, "node_modules/webpack-virtual-modules": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.5.0.tgz", - "integrity": "sha512-kyDivFZ7ZM0BVOUteVbDFhlRt7Ah/CSPwJdi8hBpkK7QLumUqdLtVfm/PX/hkcnrvr0i77fO5+TjZ94Pe+C9iw==", + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz", + "integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==", "dev": true }, + "node_modules/webpack/node_modules/@types/estree": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "license": "MIT" + }, "node_modules/webpack/node_modules/@webassemblyjs/ast": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.12.1.tgz", - "integrity": "sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", + "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", + "license": "MIT", "dependencies": { - "@webassemblyjs/helper-numbers": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6" + "@webassemblyjs/helper-numbers": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2" } }, "node_modules/webpack/node_modules/@webassemblyjs/helper-api-error": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz", - "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==" + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", + "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", + "license": "MIT" }, "node_modules/webpack/node_modules/@webassemblyjs/helper-buffer": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.12.1.tgz", - "integrity": "sha512-nzJwQw99DNDKr9BVCOZcLuJJUlqkJh+kVzVl6Fmq/tI5ZtEyWT1KZMyOXltXLZJmDtvLCDgwsyrkohEtopTXCw==" + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", + "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", + "license": "MIT" }, "node_modules/webpack/node_modules/@webassemblyjs/helper-wasm-bytecode": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz", - "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==" + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", + "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", + "license": "MIT" }, "node_modules/webpack/node_modules/@webassemblyjs/helper-wasm-section": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.12.1.tgz", - "integrity": "sha512-Jif4vfB6FJlUlSbgEMHUyk1j234GTNG9dBJ4XJdOySoj518Xj0oGsNi59cUQF4RRMS9ouBUxDDdyBVfPTypa5g==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", + "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", + "license": "MIT", "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-buffer": "1.12.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/wasm-gen": "1.12.1" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/wasm-gen": "1.14.1" } }, "node_modules/webpack/node_modules/@webassemblyjs/ieee754": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz", - "integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", + "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", + "license": "MIT", "dependencies": { "@xtuc/ieee754": "^1.2.0" } }, "node_modules/webpack/node_modules/@webassemblyjs/leb128": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz", - "integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", + "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", + "license": "Apache-2.0", "dependencies": { "@xtuc/long": "4.2.2" } }, "node_modules/webpack/node_modules/@webassemblyjs/utf8": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz", - "integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==" + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", + "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", + "license": "MIT" }, "node_modules/webpack/node_modules/@webassemblyjs/wasm-edit": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.12.1.tgz", - "integrity": "sha512-1DuwbVvADvS5mGnXbE+c9NfA8QRcZ6iKquqjjmR10k6o+zzsRVesil54DKexiowcFCPdr/Q0qaMgB01+SQ1u6g==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", + "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", + "license": "MIT", "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-buffer": "1.12.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/helper-wasm-section": "1.12.1", - "@webassemblyjs/wasm-gen": "1.12.1", - "@webassemblyjs/wasm-opt": "1.12.1", - "@webassemblyjs/wasm-parser": "1.12.1", - "@webassemblyjs/wast-printer": "1.12.1" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/helper-wasm-section": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-opt": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1", + "@webassemblyjs/wast-printer": "1.14.1" } }, "node_modules/webpack/node_modules/@webassemblyjs/wasm-gen": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.12.1.tgz", - "integrity": "sha512-TDq4Ojh9fcohAw6OIMXqiIcTq5KUXTGRkVxbSo1hQnSy6lAM5GSdfwWeSxpAo0YzgsgF182E/U0mDNhuA0tW7w==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", + "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", + "license": "MIT", "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/ieee754": "1.11.6", - "@webassemblyjs/leb128": "1.11.6", - "@webassemblyjs/utf8": "1.11.6" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" } }, "node_modules/webpack/node_modules/@webassemblyjs/wasm-opt": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.12.1.tgz", - "integrity": "sha512-Jg99j/2gG2iaz3hijw857AVYekZe2SAskcqlWIZXjji5WStnOpVoat3gQfT/Q5tb2djnCjBtMocY/Su1GfxPBg==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", + "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", + "license": "MIT", "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-buffer": "1.12.1", - "@webassemblyjs/wasm-gen": "1.12.1", - "@webassemblyjs/wasm-parser": "1.12.1" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1" } }, "node_modules/webpack/node_modules/@webassemblyjs/wasm-parser": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.12.1.tgz", - "integrity": "sha512-xikIi7c2FHXysxXe3COrVUPSheuBtpcfhbpFj4gmu7KRLYOzANztwUU0IbsqvMqzuNK2+glRGWCEqZo1WCLyAQ==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", + "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", + "license": "MIT", "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-api-error": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/ieee754": "1.11.6", - "@webassemblyjs/leb128": "1.11.6", - "@webassemblyjs/utf8": "1.11.6" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-api-error": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" } }, "node_modules/webpack/node_modules/@webassemblyjs/wast-printer": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.12.1.tgz", - "integrity": "sha512-+X4WAlOisVWQMikjbcvY2e0rwPsKQ9F688lksZhBcPycBBuii3O7m8FACbDMWDojpAqvjIncrG8J0XHKyQfVeA==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", + "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", + "license": "MIT", "dependencies": { - "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/ast": "1.14.1", "@xtuc/long": "4.2.2" } }, "node_modules/webpack/node_modules/acorn": { - "version": "8.13.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.13.0.tgz", - "integrity": "sha512-8zSiw54Oxrdym50NlZ9sUusyO1Z1ZchgRLWRaK6c86XJFClyCgFKetdowBg5bKxyp/u+CDBJG4Mpp0m3HLZl9w==", + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", + "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", "license": "MIT", "bin": { "acorn": "bin/acorn" @@ -51209,10 +48032,11 @@ } }, "node_modules/windows-release/node_modules/cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "version": "6.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.6.tgz", + "integrity": "sha512-VqCUuhcd1iB+dsv8gxPttb5iZh/D0iubSP21g36KXdEuf6I5JiioesUVjpCdHV9MZRUfVFlvwtIUyPfxo5trtw==", "dev": true, + "license": "MIT", "dependencies": { "nice-try": "^1.0.4", "path-key": "^2.0.1", @@ -51390,7 +48214,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -51406,14 +48229,12 @@ "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, "node_modules/wrap-ansi-cjs/node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, "engines": { "node": ">=8" } @@ -51422,7 +48243,6 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -51664,9 +48484,10 @@ "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==" }, "node_modules/yallist": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==" + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC" }, "node_modules/yaml": { "version": "1.10.2", @@ -51677,21 +48498,21 @@ } }, "node_modules/yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "dev": true, + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "license": "MIT", "dependencies": { - "cliui": "^7.0.2", + "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", - "string-width": "^4.2.0", + "string-width": "^4.2.3", "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" + "yargs-parser": "^21.1.1" }, "engines": { - "node": ">=10" + "node": ">=12" } }, "node_modules/yargs-parser": { @@ -51703,27 +48524,30 @@ } }, "node_modules/yargs/node_modules/cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dev": true, + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "license": "ISC", "dependencies": { "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", + "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" } }, "node_modules/yargs/node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true + "license": "MIT" }, "node_modules/yargs/node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -51732,7 +48556,7 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, + "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -51746,7 +48570,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -51763,11 +48587,20 @@ "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, + "license": "ISC", "engines": { "node": ">=10" } }, + "node_modules/yargs/node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, "node_modules/yauzl": { "version": "2.10.0", "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", @@ -51813,11 +48646,24 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/yoctocolors-cjs": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.2.tgz", + "integrity": "sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/zip-stream": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-5.0.1.tgz", - "integrity": "sha512-UfZ0oa0C8LI58wJ+moL46BDIMgCQbnsb+2PoiJYtonhBsMh2bq1eRBVkvjfVsqbEHd9/EgKPUuL9saSSsec8OA==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-5.0.2.tgz", + "integrity": "sha512-LfOdrUvPB8ZoXtvOBz6DlNClfvi//b5d56mSWyJi7XbH/HfhOHfUhOqxhT/rUiR7yiktlunqRo+jY6y/cWC/5g==", "dev": true, + "license": "MIT", "dependencies": { "archiver-utils": "^4.0.1", "compress-commons": "^5.0.1", @@ -51832,6 +48678,7 @@ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", "dev": true, + "license": "MIT", "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -51850,14 +48697,26 @@ "url": "https://github.com/sponsors/colinhacks" } }, + "node_modules/zod-validation-error": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-3.4.0.tgz", + "integrity": "sha512-ZOPR9SVY6Pb2qqO5XHt+MkkTRxGXb4EVtnjc9JpXUOtUB1T9Ru7mZOT361AN3MsetVe7R0a1KZshJDZdgp9miQ==", + "dev": true, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "zod": "^3.18.0" + } + }, "packages/a11y": { "name": "@wordpress/a11y", - "version": "4.11.0", + "version": "4.16.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/dom-ready": "*", - "@wordpress/i18n": "*" + "@wordpress/dom-ready": "file:../dom-ready", + "@wordpress/i18n": "file:../i18n" }, "engines": { "node": ">=18.12.0", @@ -51866,14 +48725,14 @@ }, "packages/annotations": { "name": "@wordpress/annotations", - "version": "3.11.0", + "version": "3.16.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/data": "*", - "@wordpress/hooks": "*", - "@wordpress/i18n": "*", - "@wordpress/rich-text": "*", + "@wordpress/data": "file:../data", + "@wordpress/hooks": "file:../hooks", + "@wordpress/i18n": "file:../i18n", + "@wordpress/rich-text": "file:../rich-text", "uuid": "^9.0.1" }, "engines": { @@ -51894,12 +48753,12 @@ }, "packages/api-fetch": { "name": "@wordpress/api-fetch", - "version": "7.11.0", + "version": "7.16.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/i18n": "*", - "@wordpress/url": "*" + "@wordpress/i18n": "file:../i18n", + "@wordpress/url": "file:../url" }, "engines": { "node": ">=18.12.0", @@ -51908,7 +48767,7 @@ }, "packages/autop": { "name": "@wordpress/autop", - "version": "4.11.0", + "version": "4.16.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7" @@ -51920,7 +48779,7 @@ }, "packages/babel-plugin-import-jsx-pragma": { "name": "@wordpress/babel-plugin-import-jsx-pragma", - "version": "5.11.0", + "version": "5.16.0", "license": "GPL-2.0-or-later", "engines": { "node": ">=18.12.0", @@ -51932,7 +48791,7 @@ }, "packages/babel-plugin-makepot": { "name": "@wordpress/babel-plugin-makepot", - "version": "6.11.0", + "version": "6.16.0", "license": "GPL-2.0-or-later", "dependencies": { "deepmerge": "^4.3.0", @@ -51949,7 +48808,7 @@ }, "packages/babel-preset-default": { "name": "@wordpress/babel-preset-default", - "version": "8.11.0", + "version": "8.16.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/core": "7.25.7", @@ -51958,8 +48817,8 @@ "@babel/preset-env": "7.25.7", "@babel/preset-typescript": "7.25.7", "@babel/runtime": "7.25.7", - "@wordpress/browserslist-config": "*", - "@wordpress/warning": "*", + "@wordpress/browserslist-config": "file:../browserslist-config", + "@wordpress/warning": "file:../warning", "browserslist": "^4.21.10", "core-js": "^3.31.0", "react": "^18.3.0" @@ -53080,7 +49939,7 @@ }, "packages/base-styles": { "name": "@wordpress/base-styles", - "version": "5.11.0", + "version": "5.16.0", "license": "GPL-2.0-or-later", "engines": { "node": ">=18.12.0", @@ -53089,7 +49948,7 @@ }, "packages/blob": { "name": "@wordpress/blob", - "version": "4.11.0", + "version": "4.16.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7" @@ -53101,28 +49960,28 @@ }, "packages/block-directory": { "name": "@wordpress/block-directory", - "version": "5.11.0", + "version": "5.16.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/a11y": "*", - "@wordpress/api-fetch": "*", - "@wordpress/block-editor": "*", - "@wordpress/blocks": "*", - "@wordpress/components": "*", - "@wordpress/compose": "*", - "@wordpress/core-data": "*", - "@wordpress/data": "*", - "@wordpress/editor": "*", - "@wordpress/element": "*", - "@wordpress/hooks": "*", - "@wordpress/html-entities": "*", - "@wordpress/i18n": "*", - "@wordpress/icons": "*", - "@wordpress/notices": "*", - "@wordpress/plugins": "*", - "@wordpress/private-apis": "*", - "@wordpress/url": "*", + "@wordpress/a11y": "file:../a11y", + "@wordpress/api-fetch": "file:../api-fetch", + "@wordpress/block-editor": "file:../block-editor", + "@wordpress/blocks": "file:../blocks", + "@wordpress/components": "file:../components", + "@wordpress/compose": "file:../compose", + "@wordpress/core-data": "file:../core-data", + "@wordpress/data": "file:../data", + "@wordpress/editor": "file:../editor", + "@wordpress/element": "file:../element", + "@wordpress/hooks": "file:../hooks", + "@wordpress/html-entities": "file:../html-entities", + "@wordpress/i18n": "file:../i18n", + "@wordpress/icons": "file:../icons", + "@wordpress/notices": "file:../notices", + "@wordpress/plugins": "file:../plugins", + "@wordpress/private-apis": "file:../private-apis", + "@wordpress/url": "file:../url", "change-case": "^4.1.2", "clsx": "^2.1.1" }, @@ -53137,44 +49996,45 @@ }, "packages/block-editor": { "name": "@wordpress/block-editor", - "version": "14.6.0", + "version": "14.11.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7", "@emotion/react": "^11.7.1", "@emotion/styled": "^11.6.0", "@react-spring/web": "^9.4.5", - "@wordpress/a11y": "*", - "@wordpress/api-fetch": "*", - "@wordpress/blob": "*", - "@wordpress/block-serialization-default-parser": "*", - "@wordpress/blocks": "*", - "@wordpress/commands": "*", - "@wordpress/components": "*", - "@wordpress/compose": "*", - "@wordpress/data": "*", - "@wordpress/date": "*", - "@wordpress/deprecated": "*", - "@wordpress/dom": "*", - "@wordpress/element": "*", - "@wordpress/escape-html": "*", - "@wordpress/hooks": "*", - "@wordpress/html-entities": "*", - "@wordpress/i18n": "*", - "@wordpress/icons": "*", - "@wordpress/is-shallow-equal": "*", - "@wordpress/keyboard-shortcuts": "*", - "@wordpress/keycodes": "*", - "@wordpress/notices": "*", - "@wordpress/preferences": "*", - "@wordpress/priority-queue": "*", - "@wordpress/private-apis": "*", - "@wordpress/rich-text": "*", - "@wordpress/style-engine": "*", - "@wordpress/token-list": "*", - "@wordpress/url": "*", - "@wordpress/warning": "*", - "@wordpress/wordcount": "*", + "@wordpress/a11y": "file:../a11y", + "@wordpress/api-fetch": "file:../api-fetch", + "@wordpress/blob": "file:../blob", + "@wordpress/block-serialization-default-parser": "file:../block-serialization-default-parser", + "@wordpress/blocks": "file:../blocks", + "@wordpress/commands": "file:../commands", + "@wordpress/components": "file:../components", + "@wordpress/compose": "file:../compose", + "@wordpress/data": "file:../data", + "@wordpress/date": "file:../date", + "@wordpress/deprecated": "file:../deprecated", + "@wordpress/dom": "file:../dom", + "@wordpress/element": "file:../element", + "@wordpress/escape-html": "file:../escape-html", + "@wordpress/hooks": "file:../hooks", + "@wordpress/html-entities": "file:../html-entities", + "@wordpress/i18n": "file:../i18n", + "@wordpress/icons": "file:../icons", + "@wordpress/is-shallow-equal": "file:../is-shallow-equal", + "@wordpress/keyboard-shortcuts": "file:../keyboard-shortcuts", + "@wordpress/keycodes": "file:../keycodes", + "@wordpress/notices": "file:../notices", + "@wordpress/preferences": "file:../preferences", + "@wordpress/priority-queue": "file:../priority-queue", + "@wordpress/private-apis": "file:../private-apis", + "@wordpress/rich-text": "file:../rich-text", + "@wordpress/style-engine": "file:../style-engine", + "@wordpress/token-list": "file:../token-list", + "@wordpress/upload-media": "file:../upload-media", + "@wordpress/url": "file:../url", + "@wordpress/warning": "file:../warning", + "@wordpress/wordcount": "file:../wordcount", "change-case": "^4.1.2", "clsx": "^2.1.1", "colord": "^2.7.0", @@ -53237,43 +50097,43 @@ }, "packages/block-library": { "name": "@wordpress/block-library", - "version": "9.11.0", + "version": "9.16.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/a11y": "*", - "@wordpress/api-fetch": "*", - "@wordpress/autop": "*", - "@wordpress/blob": "*", - "@wordpress/block-editor": "*", - "@wordpress/blocks": "*", - "@wordpress/components": "*", - "@wordpress/compose": "*", - "@wordpress/core-data": "*", - "@wordpress/data": "*", - "@wordpress/date": "*", - "@wordpress/deprecated": "*", - "@wordpress/dom": "*", - "@wordpress/element": "*", - "@wordpress/escape-html": "*", - "@wordpress/hooks": "*", - "@wordpress/html-entities": "*", - "@wordpress/i18n": "*", - "@wordpress/icons": "*", - "@wordpress/interactivity": "*", - "@wordpress/interactivity-router": "*", - "@wordpress/keyboard-shortcuts": "*", - "@wordpress/keycodes": "*", - "@wordpress/notices": "*", - "@wordpress/patterns": "*", - "@wordpress/primitives": "*", - "@wordpress/private-apis": "*", - "@wordpress/reusable-blocks": "*", - "@wordpress/rich-text": "*", - "@wordpress/server-side-render": "*", - "@wordpress/url": "*", - "@wordpress/viewport": "*", - "@wordpress/wordcount": "*", + "@wordpress/a11y": "file:../a11y", + "@wordpress/api-fetch": "file:../api-fetch", + "@wordpress/autop": "file:../autop", + "@wordpress/blob": "file:../blob", + "@wordpress/block-editor": "file:../block-editor", + "@wordpress/blocks": "file:../blocks", + "@wordpress/components": "file:../components", + "@wordpress/compose": "file:../compose", + "@wordpress/core-data": "file:../core-data", + "@wordpress/data": "file:../data", + "@wordpress/date": "file:../date", + "@wordpress/deprecated": "file:../deprecated", + "@wordpress/dom": "file:../dom", + "@wordpress/element": "file:../element", + "@wordpress/escape-html": "file:../escape-html", + "@wordpress/hooks": "file:../hooks", + "@wordpress/html-entities": "file:../html-entities", + "@wordpress/i18n": "file:../i18n", + "@wordpress/icons": "file:../icons", + "@wordpress/interactivity": "file:../interactivity", + "@wordpress/interactivity-router": "file:../interactivity-router", + "@wordpress/keyboard-shortcuts": "file:../keyboard-shortcuts", + "@wordpress/keycodes": "file:../keycodes", + "@wordpress/notices": "file:../notices", + "@wordpress/patterns": "file:../patterns", + "@wordpress/primitives": "file:../primitives", + "@wordpress/private-apis": "file:../private-apis", + "@wordpress/reusable-blocks": "file:../reusable-blocks", + "@wordpress/rich-text": "file:../rich-text", + "@wordpress/server-side-render": "file:../server-side-render", + "@wordpress/url": "file:../url", + "@wordpress/viewport": "file:../viewport", + "@wordpress/wordcount": "file:../wordcount", "change-case": "^4.1.2", "clsx": "^2.1.1", "colord": "^2.7.0", @@ -53303,7 +50163,7 @@ }, "packages/block-serialization-default-parser": { "name": "@wordpress/block-serialization-default-parser", - "version": "5.11.0", + "version": "5.16.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7" @@ -53315,7 +50175,7 @@ }, "packages/block-serialization-spec-parser": { "name": "@wordpress/block-serialization-spec-parser", - "version": "5.11.0", + "version": "5.16.0", "license": "GPL-2.0-or-later", "dependencies": { "pegjs": "^0.10.0", @@ -53328,25 +50188,25 @@ }, "packages/blocks": { "name": "@wordpress/blocks", - "version": "14.0.0", + "version": "14.5.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/autop": "*", - "@wordpress/blob": "*", - "@wordpress/block-serialization-default-parser": "*", - "@wordpress/data": "*", - "@wordpress/deprecated": "*", - "@wordpress/dom": "*", - "@wordpress/element": "*", - "@wordpress/hooks": "*", - "@wordpress/html-entities": "*", - "@wordpress/i18n": "*", - "@wordpress/is-shallow-equal": "*", - "@wordpress/private-apis": "*", - "@wordpress/rich-text": "*", - "@wordpress/shortcode": "*", - "@wordpress/warning": "*", + "@wordpress/autop": "file:../autop", + "@wordpress/blob": "file:../blob", + "@wordpress/block-serialization-default-parser": "file:../block-serialization-default-parser", + "@wordpress/data": "file:../data", + "@wordpress/deprecated": "file:../deprecated", + "@wordpress/dom": "file:../dom", + "@wordpress/element": "file:../element", + "@wordpress/hooks": "file:../hooks", + "@wordpress/html-entities": "file:../html-entities", + "@wordpress/i18n": "file:../i18n", + "@wordpress/is-shallow-equal": "file:../is-shallow-equal", + "@wordpress/private-apis": "file:../private-apis", + "@wordpress/rich-text": "file:../rich-text", + "@wordpress/shortcode": "file:../shortcode", + "@wordpress/warning": "file:../warning", "change-case": "^4.1.2", "colord": "^2.7.0", "fast-deep-equal": "^3.1.3", @@ -53382,7 +50242,7 @@ }, "packages/browserslist-config": { "name": "@wordpress/browserslist-config", - "version": "6.11.0", + "version": "6.16.0", "license": "GPL-2.0-or-later", "engines": { "node": ">=18.12.0", @@ -53391,17 +50251,17 @@ }, "packages/commands": { "name": "@wordpress/commands", - "version": "1.11.0", + "version": "1.16.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/components": "*", - "@wordpress/data": "*", - "@wordpress/element": "*", - "@wordpress/i18n": "*", - "@wordpress/icons": "*", - "@wordpress/keyboard-shortcuts": "*", - "@wordpress/private-apis": "*", + "@wordpress/components": "file:../components", + "@wordpress/data": "file:../data", + "@wordpress/element": "file:../element", + "@wordpress/i18n": "file:../i18n", + "@wordpress/icons": "file:../icons", + "@wordpress/keyboard-shortcuts": "file:../keyboard-shortcuts", + "@wordpress/private-apis": "file:../private-apis", "clsx": "^2.1.1", "cmdk": "^1.0.0" }, @@ -53630,10 +50490,10 @@ }, "packages/components": { "name": "@wordpress/components", - "version": "28.11.0", + "version": "29.2.0", "license": "GPL-2.0-or-later", "dependencies": { - "@ariakit/react": "^0.4.10", + "@ariakit/react": "^0.4.15", "@babel/runtime": "7.25.7", "@emotion/cache": "^11.7.1", "@emotion/css": "^11.7.1", @@ -53645,23 +50505,23 @@ "@types/gradient-parser": "0.1.3", "@types/highlight-words-core": "1.2.1", "@use-gesture/react": "^10.3.1", - "@wordpress/a11y": "*", - "@wordpress/compose": "*", - "@wordpress/date": "*", - "@wordpress/deprecated": "*", - "@wordpress/dom": "*", - "@wordpress/element": "*", - "@wordpress/escape-html": "*", - "@wordpress/hooks": "*", - "@wordpress/html-entities": "*", - "@wordpress/i18n": "*", - "@wordpress/icons": "*", - "@wordpress/is-shallow-equal": "*", - "@wordpress/keycodes": "*", - "@wordpress/primitives": "*", - "@wordpress/private-apis": "*", - "@wordpress/rich-text": "*", - "@wordpress/warning": "*", + "@wordpress/a11y": "file:../a11y", + "@wordpress/compose": "file:../compose", + "@wordpress/date": "file:../date", + "@wordpress/deprecated": "file:../deprecated", + "@wordpress/dom": "file:../dom", + "@wordpress/element": "file:../element", + "@wordpress/escape-html": "file:../escape-html", + "@wordpress/hooks": "file:../hooks", + "@wordpress/html-entities": "file:../html-entities", + "@wordpress/i18n": "file:../i18n", + "@wordpress/icons": "file:../icons", + "@wordpress/is-shallow-equal": "file:../is-shallow-equal", + "@wordpress/keycodes": "file:../keycodes", + "@wordpress/primitives": "file:../primitives", + "@wordpress/private-apis": "file:../private-apis", + "@wordpress/rich-text": "file:../rich-text", + "@wordpress/warning": "file:../warning", "change-case": "^4.1.2", "clsx": "^2.1.1", "colord": "^2.7.0", @@ -53688,36 +50548,6 @@ "react-dom": "^18.0.0" } }, - "packages/components/node_modules/@ariakit/react": { - "version": "0.4.10", - "resolved": "https://registry.npmjs.org/@ariakit/react/-/react-0.4.10.tgz", - "integrity": "sha512-c1+6sNLj57aAXrBZMCVGG+OXeFrPAG0TV1jT7oPJcN/KLRs3aCuO3CCJVep/eKepFzzK01kNRGYX3wPT1TXPNw==", - "dependencies": { - "@ariakit/react-core": "0.4.10" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/ariakit" - }, - "peerDependencies": { - "react": "^17.0.0 || ^18.0.0 || ^19.0.0", - "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" - } - }, - "packages/components/node_modules/@ariakit/react-core": { - "version": "0.4.10", - "resolved": "https://registry.npmjs.org/@ariakit/react-core/-/react-core-0.4.10.tgz", - "integrity": "sha512-r6DZmtHBmSoOj848+RpBwdZy/55YxPhMhfH14JIO2OLn1F6iSFkQwR7AAGpIrlYycWJFSF7KrQu50O+SSfFJdQ==", - "dependencies": { - "@ariakit/core": "0.4.9", - "@floating-ui/dom": "^1.0.0", - "use-sync-external-store": "^1.2.0" - }, - "peerDependencies": { - "react": "^17.0.0 || ^18.0.0 || ^19.0.0", - "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" - } - }, "packages/components/node_modules/@floating-ui/react-dom": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.1.tgz", @@ -53751,18 +50581,18 @@ }, "packages/compose": { "name": "@wordpress/compose", - "version": "7.11.0", + "version": "7.16.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7", "@types/mousetrap": "^1.6.8", - "@wordpress/deprecated": "*", - "@wordpress/dom": "*", - "@wordpress/element": "*", - "@wordpress/is-shallow-equal": "*", - "@wordpress/keycodes": "*", - "@wordpress/priority-queue": "*", - "@wordpress/undo-manager": "*", + "@wordpress/deprecated": "file:../deprecated", + "@wordpress/dom": "file:../dom", + "@wordpress/element": "file:../element", + "@wordpress/is-shallow-equal": "file:../is-shallow-equal", + "@wordpress/keycodes": "file:../keycodes", + "@wordpress/priority-queue": "file:../priority-queue", + "@wordpress/undo-manager": "file:../undo-manager", "change-case": "^4.1.2", "clipboard": "^2.0.11", "mousetrap": "^1.6.5", @@ -53788,23 +50618,23 @@ }, "packages/core-commands": { "name": "@wordpress/core-commands", - "version": "1.11.0", + "version": "1.16.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/block-editor": "*", - "@wordpress/commands": "*", - "@wordpress/compose": "*", - "@wordpress/core-data": "*", - "@wordpress/data": "*", - "@wordpress/element": "*", - "@wordpress/html-entities": "*", - "@wordpress/i18n": "*", - "@wordpress/icons": "*", - "@wordpress/notices": "*", - "@wordpress/private-apis": "*", - "@wordpress/router": "*", - "@wordpress/url": "*" + "@wordpress/block-editor": "file:../block-editor", + "@wordpress/commands": "file:../commands", + "@wordpress/compose": "file:../compose", + "@wordpress/core-data": "file:../core-data", + "@wordpress/data": "file:../data", + "@wordpress/element": "file:../element", + "@wordpress/html-entities": "file:../html-entities", + "@wordpress/i18n": "file:../i18n", + "@wordpress/icons": "file:../icons", + "@wordpress/notices": "file:../notices", + "@wordpress/private-apis": "file:../private-apis", + "@wordpress/router": "file:../router", + "@wordpress/url": "file:../url" }, "engines": { "node": ">=18.12.0", @@ -53817,26 +50647,26 @@ }, "packages/core-data": { "name": "@wordpress/core-data", - "version": "7.11.0", + "version": "7.16.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/api-fetch": "*", - "@wordpress/block-editor": "*", - "@wordpress/blocks": "*", - "@wordpress/compose": "*", - "@wordpress/data": "*", - "@wordpress/deprecated": "*", - "@wordpress/element": "*", - "@wordpress/html-entities": "*", - "@wordpress/i18n": "*", - "@wordpress/is-shallow-equal": "*", - "@wordpress/private-apis": "*", - "@wordpress/rich-text": "*", - "@wordpress/sync": "*", - "@wordpress/undo-manager": "*", - "@wordpress/url": "*", - "@wordpress/warning": "*", + "@wordpress/api-fetch": "file:../api-fetch", + "@wordpress/block-editor": "file:../block-editor", + "@wordpress/blocks": "file:../blocks", + "@wordpress/compose": "file:../compose", + "@wordpress/data": "file:../data", + "@wordpress/deprecated": "file:../deprecated", + "@wordpress/element": "file:../element", + "@wordpress/html-entities": "file:../html-entities", + "@wordpress/i18n": "file:../i18n", + "@wordpress/is-shallow-equal": "file:../is-shallow-equal", + "@wordpress/private-apis": "file:../private-apis", + "@wordpress/rich-text": "file:../rich-text", + "@wordpress/sync": "file:../sync", + "@wordpress/undo-manager": "file:../undo-manager", + "@wordpress/url": "file:../url", + "@wordpress/warning": "file:../warning", "change-case": "^4.1.2", "equivalent-key-map": "^0.2.2", "fast-deep-equal": "^3.1.3", @@ -53862,21 +50692,21 @@ }, "packages/create-block": { "name": "@wordpress/create-block", - "version": "4.54.0", + "version": "4.59.0", "license": "GPL-2.0-or-later", "dependencies": { - "@wordpress/lazy-import": "*", + "@inquirer/prompts": "^7.2.0", + "@wordpress/lazy-import": "file:../lazy-import", "chalk": "^4.0.0", "change-case": "^4.1.2", "check-node-version": "^4.1.0", "commander": "^9.2.0", "execa": "^4.0.2", "fast-glob": "^3.2.7", - "inquirer": "^7.1.0", "make-dir": "^3.0.0", "mustache": "^4.0.0", "npm-package-arg": "^8.1.5", - "rimraf": "^3.0.2", + "rimraf": "^5.0.10", "write-pkg": "^4.0.0" }, "bin": { @@ -53889,7 +50719,7 @@ }, "packages/create-block-interactive-template": { "name": "@wordpress/create-block-interactive-template", - "version": "2.11.0", + "version": "2.16.0", "license": "GPL-2.0-or-later", "engines": { "node": ">=18.12.0", @@ -53898,7 +50728,7 @@ }, "packages/create-block-tutorial-template": { "name": "@wordpress/create-block-tutorial-template", - "version": "4.11.0", + "version": "4.16.0", "license": "GPL-2.0-or-later", "engines": { "node": ">=18.12.0", @@ -53907,31 +50737,30 @@ }, "packages/customize-widgets": { "name": "@wordpress/customize-widgets", - "version": "5.11.0", + "version": "5.16.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/block-editor": "*", - "@wordpress/block-library": "*", - "@wordpress/blocks": "*", - "@wordpress/components": "*", - "@wordpress/compose": "*", - "@wordpress/core-data": "*", - "@wordpress/data": "*", - "@wordpress/dom": "*", - "@wordpress/editor": "*", - "@wordpress/element": "*", - "@wordpress/hooks": "*", - "@wordpress/i18n": "*", - "@wordpress/icons": "*", - "@wordpress/interface": "*", - "@wordpress/is-shallow-equal": "*", - "@wordpress/keyboard-shortcuts": "*", - "@wordpress/keycodes": "*", - "@wordpress/media-utils": "*", - "@wordpress/preferences": "*", - "@wordpress/private-apis": "*", - "@wordpress/widgets": "*", + "@wordpress/block-editor": "file:../block-editor", + "@wordpress/block-library": "file:../block-library", + "@wordpress/blocks": "file:../blocks", + "@wordpress/components": "file:../components", + "@wordpress/compose": "file:../compose", + "@wordpress/core-data": "file:../core-data", + "@wordpress/data": "file:../data", + "@wordpress/dom": "file:../dom", + "@wordpress/element": "file:../element", + "@wordpress/hooks": "file:../hooks", + "@wordpress/i18n": "file:../i18n", + "@wordpress/icons": "file:../icons", + "@wordpress/interface": "file:../interface", + "@wordpress/is-shallow-equal": "file:../is-shallow-equal", + "@wordpress/keyboard-shortcuts": "file:../keyboard-shortcuts", + "@wordpress/keycodes": "file:../keycodes", + "@wordpress/media-utils": "file:../media-utils", + "@wordpress/preferences": "file:../preferences", + "@wordpress/private-apis": "file:../private-apis", + "@wordpress/widgets": "file:../widgets", "clsx": "^2.1.1", "fast-deep-equal": "^3.1.3" }, @@ -53946,17 +50775,17 @@ }, "packages/data": { "name": "@wordpress/data", - "version": "10.11.0", + "version": "10.16.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/compose": "*", - "@wordpress/deprecated": "*", - "@wordpress/element": "*", - "@wordpress/is-shallow-equal": "*", - "@wordpress/priority-queue": "*", - "@wordpress/private-apis": "*", - "@wordpress/redux-routine": "*", + "@wordpress/compose": "file:../compose", + "@wordpress/deprecated": "file:../deprecated", + "@wordpress/element": "file:../element", + "@wordpress/is-shallow-equal": "file:../is-shallow-equal", + "@wordpress/priority-queue": "file:../priority-queue", + "@wordpress/private-apis": "file:../private-apis", + "@wordpress/redux-routine": "file:../redux-routine", "deepmerge": "^4.3.0", "equivalent-key-map": "^0.2.2", "is-plain-object": "^5.0.0", @@ -53975,13 +50804,13 @@ }, "packages/data-controls": { "name": "@wordpress/data-controls", - "version": "4.11.0", + "version": "4.16.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/api-fetch": "*", - "@wordpress/data": "*", - "@wordpress/deprecated": "*" + "@wordpress/api-fetch": "file:../api-fetch", + "@wordpress/data": "file:../data", + "@wordpress/deprecated": "file:../deprecated" }, "engines": { "node": ">=18.12.0", @@ -53993,20 +50822,20 @@ }, "packages/dataviews": { "name": "@wordpress/dataviews", - "version": "4.7.0", + "version": "4.12.0", "license": "GPL-2.0-or-later", "dependencies": { - "@ariakit/react": "^0.4.10", + "@ariakit/react": "^0.4.15", "@babel/runtime": "7.25.7", - "@wordpress/components": "*", - "@wordpress/compose": "*", - "@wordpress/data": "*", - "@wordpress/element": "*", - "@wordpress/i18n": "*", - "@wordpress/icons": "*", - "@wordpress/primitives": "*", - "@wordpress/private-apis": "*", - "@wordpress/warning": "*", + "@wordpress/components": "file:../components", + "@wordpress/compose": "file:../compose", + "@wordpress/data": "file:../data", + "@wordpress/element": "file:../element", + "@wordpress/i18n": "file:../i18n", + "@wordpress/icons": "file:../icons", + "@wordpress/primitives": "file:../primitives", + "@wordpress/private-apis": "file:../private-apis", + "@wordpress/warning": "file:../warning", "clsx": "^2.1.1", "remove-accents": "^0.5.0" }, @@ -54018,43 +50847,13 @@ "react": "^18.0.0" } }, - "packages/dataviews/node_modules/@ariakit/react": { - "version": "0.4.10", - "resolved": "https://registry.npmjs.org/@ariakit/react/-/react-0.4.10.tgz", - "integrity": "sha512-c1+6sNLj57aAXrBZMCVGG+OXeFrPAG0TV1jT7oPJcN/KLRs3aCuO3CCJVep/eKepFzzK01kNRGYX3wPT1TXPNw==", - "dependencies": { - "@ariakit/react-core": "0.4.10" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/ariakit" - }, - "peerDependencies": { - "react": "^17.0.0 || ^18.0.0 || ^19.0.0", - "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" - } - }, - "packages/dataviews/node_modules/@ariakit/react-core": { - "version": "0.4.10", - "resolved": "https://registry.npmjs.org/@ariakit/react-core/-/react-core-0.4.10.tgz", - "integrity": "sha512-r6DZmtHBmSoOj848+RpBwdZy/55YxPhMhfH14JIO2OLn1F6iSFkQwR7AAGpIrlYycWJFSF7KrQu50O+SSfFJdQ==", - "dependencies": { - "@ariakit/core": "0.4.9", - "@floating-ui/dom": "^1.0.0", - "use-sync-external-store": "^1.2.0" - }, - "peerDependencies": { - "react": "^17.0.0 || ^18.0.0 || ^19.0.0", - "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" - } - }, "packages/date": { "name": "@wordpress/date", - "version": "5.11.0", + "version": "5.16.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/deprecated": "*", + "@wordpress/deprecated": "file:../deprecated", "moment": "^2.29.4", "moment-timezone": "^0.5.40" }, @@ -54065,7 +50864,7 @@ }, "packages/dependency-extraction-webpack-plugin": { "name": "@wordpress/dependency-extraction-webpack-plugin", - "version": "6.11.0", + "version": "6.16.0", "license": "GPL-2.0-or-later", "dependencies": { "json2php": "^0.0.7" @@ -54080,11 +50879,11 @@ }, "packages/deprecated": { "name": "@wordpress/deprecated", - "version": "4.11.0", + "version": "4.16.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/hooks": "*" + "@wordpress/hooks": "file:../hooks" }, "engines": { "node": ">=18.12.0", @@ -54093,7 +50892,7 @@ }, "packages/docgen": { "name": "@wordpress/docgen", - "version": "2.11.0", + "version": "2.16.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/core": "7.25.7", @@ -54114,11 +50913,11 @@ }, "packages/dom": { "name": "@wordpress/dom", - "version": "4.11.0", + "version": "4.16.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/deprecated": "*" + "@wordpress/deprecated": "file:../deprecated" }, "engines": { "node": ">=18.12.0", @@ -54127,7 +50926,7 @@ }, "packages/dom-ready": { "name": "@wordpress/dom-ready", - "version": "4.11.0", + "version": "4.16.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7" @@ -54139,13 +50938,13 @@ }, "packages/e2e-test-utils": { "name": "@wordpress/e2e-test-utils", - "version": "11.11.0", + "version": "11.16.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/api-fetch": "*", - "@wordpress/keycodes": "*", - "@wordpress/url": "*", + "@wordpress/api-fetch": "file:../api-fetch", + "@wordpress/keycodes": "file:../keycodes", + "@wordpress/url": "file:../url", "change-case": "^4.1.2", "form-data": "^4.0.0", "node-fetch": "2.7.0" @@ -54156,18 +50955,18 @@ }, "peerDependencies": { "jest": ">=29", - "puppeteer-core": ">=11" + "puppeteer-core": ">=23" } }, "packages/e2e-test-utils-playwright": { "name": "@wordpress/e2e-test-utils-playwright", - "version": "1.11.0", + "version": "1.16.0", "license": "GPL-2.0-or-later", "dependencies": { "change-case": "^4.1.2", "form-data": "^4.0.0", "get-port": "^5.1.1", - "lighthouse": "^10.4.0", + "lighthouse": "^12.2.2", "mime": "^3.0.0", "web-vitals": "^4.2.1" }, @@ -54187,16 +50986,16 @@ }, "packages/e2e-tests": { "name": "@wordpress/e2e-tests", - "version": "8.11.0", + "version": "8.16.0", "license": "GPL-2.0-or-later", "dependencies": { - "@wordpress/e2e-test-utils": "*", - "@wordpress/interactivity": "*", - "@wordpress/interactivity-router": "*", - "@wordpress/jest-console": "*", - "@wordpress/jest-puppeteer-axe": "*", - "@wordpress/scripts": "*", - "@wordpress/url": "*", + "@wordpress/e2e-test-utils": "file:../e2e-test-utils", + "@wordpress/interactivity": "file:../interactivity", + "@wordpress/interactivity-router": "file:../interactivity-router", + "@wordpress/jest-console": "file:../jest-console", + "@wordpress/jest-puppeteer-axe": "file:../jest-puppeteer-axe", + "@wordpress/scripts": "file:../scripts", + "@wordpress/url": "file:../url", "chalk": "^4.0.0", "expect-puppeteer": "^4.4.0", "filenamify": "^4.2.0", @@ -54210,7 +51009,7 @@ }, "peerDependencies": { "jest": ">=29", - "puppeteer-core": ">=11", + "puppeteer-core": ">=23", "react": "^18.0.0", "react-dom": "^18.0.0" } @@ -54225,39 +51024,39 @@ }, "packages/edit-post": { "name": "@wordpress/edit-post", - "version": "8.11.0", + "version": "8.16.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/a11y": "*", - "@wordpress/api-fetch": "*", - "@wordpress/block-editor": "*", - "@wordpress/block-library": "*", - "@wordpress/blocks": "*", - "@wordpress/commands": "*", - "@wordpress/components": "*", - "@wordpress/compose": "*", - "@wordpress/core-commands": "*", - "@wordpress/core-data": "*", - "@wordpress/data": "*", - "@wordpress/deprecated": "*", - "@wordpress/dom": "*", - "@wordpress/editor": "*", - "@wordpress/element": "*", - "@wordpress/hooks": "*", - "@wordpress/html-entities": "*", - "@wordpress/i18n": "*", - "@wordpress/icons": "*", - "@wordpress/keyboard-shortcuts": "*", - "@wordpress/keycodes": "*", - "@wordpress/notices": "*", - "@wordpress/plugins": "*", - "@wordpress/preferences": "*", - "@wordpress/private-apis": "*", - "@wordpress/url": "*", - "@wordpress/viewport": "*", - "@wordpress/warning": "*", - "@wordpress/widgets": "*", + "@wordpress/a11y": "file:../a11y", + "@wordpress/api-fetch": "file:../api-fetch", + "@wordpress/block-editor": "file:../block-editor", + "@wordpress/block-library": "file:../block-library", + "@wordpress/blocks": "file:../blocks", + "@wordpress/commands": "file:../commands", + "@wordpress/components": "file:../components", + "@wordpress/compose": "file:../compose", + "@wordpress/core-commands": "file:../core-commands", + "@wordpress/core-data": "file:../core-data", + "@wordpress/data": "file:../data", + "@wordpress/deprecated": "file:../deprecated", + "@wordpress/dom": "file:../dom", + "@wordpress/editor": "file:../editor", + "@wordpress/element": "file:../element", + "@wordpress/hooks": "file:../hooks", + "@wordpress/html-entities": "file:../html-entities", + "@wordpress/i18n": "file:../i18n", + "@wordpress/icons": "file:../icons", + "@wordpress/keyboard-shortcuts": "file:../keyboard-shortcuts", + "@wordpress/keycodes": "file:../keycodes", + "@wordpress/notices": "file:../notices", + "@wordpress/plugins": "file:../plugins", + "@wordpress/preferences": "file:../preferences", + "@wordpress/private-apis": "file:../private-apis", + "@wordpress/url": "file:../url", + "@wordpress/viewport": "file:../viewport", + "@wordpress/warning": "file:../warning", + "@wordpress/widgets": "file:../widgets", "clsx": "^2.1.1", "memize": "^2.1.0" }, @@ -54272,50 +51071,51 @@ }, "packages/edit-site": { "name": "@wordpress/edit-site", - "version": "6.11.0", + "version": "6.16.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7", "@react-spring/web": "^9.4.5", - "@wordpress/a11y": "*", - "@wordpress/api-fetch": "*", - "@wordpress/blob": "*", - "@wordpress/block-editor": "*", - "@wordpress/block-library": "*", - "@wordpress/blocks": "*", - "@wordpress/commands": "*", - "@wordpress/components": "*", - "@wordpress/compose": "*", - "@wordpress/core-commands": "*", - "@wordpress/core-data": "*", - "@wordpress/data": "*", - "@wordpress/dataviews": "*", - "@wordpress/date": "*", - "@wordpress/deprecated": "*", - "@wordpress/dom": "*", - "@wordpress/editor": "*", - "@wordpress/element": "*", - "@wordpress/escape-html": "*", - "@wordpress/fields": "*", - "@wordpress/hooks": "*", - "@wordpress/html-entities": "*", - "@wordpress/i18n": "*", - "@wordpress/icons": "*", - "@wordpress/keyboard-shortcuts": "*", - "@wordpress/keycodes": "*", - "@wordpress/notices": "*", - "@wordpress/patterns": "*", - "@wordpress/plugins": "*", - "@wordpress/preferences": "*", - "@wordpress/primitives": "*", - "@wordpress/private-apis": "*", - "@wordpress/reusable-blocks": "*", - "@wordpress/router": "*", - "@wordpress/style-engine": "*", - "@wordpress/url": "*", - "@wordpress/viewport": "*", - "@wordpress/widgets": "*", - "@wordpress/wordcount": "*", + "@wordpress/a11y": "file:../a11y", + "@wordpress/api-fetch": "file:../api-fetch", + "@wordpress/blob": "file:../blob", + "@wordpress/block-editor": "file:../block-editor", + "@wordpress/block-library": "file:../block-library", + "@wordpress/blocks": "file:../blocks", + "@wordpress/commands": "file:../commands", + "@wordpress/components": "file:../components", + "@wordpress/compose": "file:../compose", + "@wordpress/core-commands": "file:../core-commands", + "@wordpress/core-data": "file:../core-data", + "@wordpress/data": "file:../data", + "@wordpress/dataviews": "file:../dataviews", + "@wordpress/date": "file:../date", + "@wordpress/deprecated": "file:../deprecated", + "@wordpress/dom": "file:../dom", + "@wordpress/editor": "file:../editor", + "@wordpress/element": "file:../element", + "@wordpress/escape-html": "file:../escape-html", + "@wordpress/fields": "file:../fields", + "@wordpress/hooks": "file:../hooks", + "@wordpress/html-entities": "file:../html-entities", + "@wordpress/i18n": "file:../i18n", + "@wordpress/icons": "file:../icons", + "@wordpress/keyboard-shortcuts": "file:../keyboard-shortcuts", + "@wordpress/keycodes": "file:../keycodes", + "@wordpress/media-utils": "file:../media-utils", + "@wordpress/notices": "file:../notices", + "@wordpress/patterns": "file:../patterns", + "@wordpress/plugins": "file:../plugins", + "@wordpress/preferences": "file:../preferences", + "@wordpress/primitives": "file:../primitives", + "@wordpress/private-apis": "file:../private-apis", + "@wordpress/reusable-blocks": "file:../reusable-blocks", + "@wordpress/router": "file:../router", + "@wordpress/style-engine": "file:../style-engine", + "@wordpress/url": "file:../url", + "@wordpress/viewport": "file:../viewport", + "@wordpress/widgets": "file:../widgets", + "@wordpress/wordcount": "file:../wordcount", "change-case": "^4.1.2", "clsx": "^2.1.1", "colord": "^2.9.2", @@ -54334,37 +51134,36 @@ }, "packages/edit-widgets": { "name": "@wordpress/edit-widgets", - "version": "6.11.0", + "version": "6.16.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/api-fetch": "*", - "@wordpress/block-editor": "*", - "@wordpress/block-library": "*", - "@wordpress/blocks": "*", - "@wordpress/components": "*", - "@wordpress/compose": "*", - "@wordpress/core-data": "*", - "@wordpress/data": "*", - "@wordpress/deprecated": "*", - "@wordpress/dom": "*", - "@wordpress/editor": "*", - "@wordpress/element": "*", - "@wordpress/hooks": "*", - "@wordpress/i18n": "*", - "@wordpress/icons": "*", - "@wordpress/interface": "*", - "@wordpress/keyboard-shortcuts": "*", - "@wordpress/keycodes": "*", - "@wordpress/media-utils": "*", - "@wordpress/notices": "*", - "@wordpress/patterns": "*", - "@wordpress/plugins": "*", - "@wordpress/preferences": "*", - "@wordpress/private-apis": "*", - "@wordpress/reusable-blocks": "*", - "@wordpress/url": "*", - "@wordpress/widgets": "*", + "@wordpress/api-fetch": "file:../api-fetch", + "@wordpress/block-editor": "file:../block-editor", + "@wordpress/block-library": "file:../block-library", + "@wordpress/blocks": "file:../blocks", + "@wordpress/components": "file:../components", + "@wordpress/compose": "file:../compose", + "@wordpress/core-data": "file:../core-data", + "@wordpress/data": "file:../data", + "@wordpress/deprecated": "file:../deprecated", + "@wordpress/dom": "file:../dom", + "@wordpress/element": "file:../element", + "@wordpress/hooks": "file:../hooks", + "@wordpress/i18n": "file:../i18n", + "@wordpress/icons": "file:../icons", + "@wordpress/interface": "file:../interface", + "@wordpress/keyboard-shortcuts": "file:../keyboard-shortcuts", + "@wordpress/keycodes": "file:../keycodes", + "@wordpress/media-utils": "file:../media-utils", + "@wordpress/notices": "file:../notices", + "@wordpress/patterns": "file:../patterns", + "@wordpress/plugins": "file:../plugins", + "@wordpress/preferences": "file:../preferences", + "@wordpress/private-apis": "file:../private-apis", + "@wordpress/reusable-blocks": "file:../reusable-blocks", + "@wordpress/url": "file:../url", + "@wordpress/widgets": "file:../widgets", "clsx": "^2.1.1" }, "engines": { @@ -54378,45 +51177,45 @@ }, "packages/editor": { "name": "@wordpress/editor", - "version": "14.11.0", + "version": "14.16.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/a11y": "*", - "@wordpress/api-fetch": "*", - "@wordpress/blob": "*", - "@wordpress/block-editor": "*", - "@wordpress/blocks": "*", - "@wordpress/commands": "*", - "@wordpress/components": "*", - "@wordpress/compose": "*", - "@wordpress/core-data": "*", - "@wordpress/data": "*", - "@wordpress/dataviews": "*", - "@wordpress/date": "*", - "@wordpress/deprecated": "*", - "@wordpress/dom": "*", - "@wordpress/element": "*", - "@wordpress/fields": "*", - "@wordpress/hooks": "*", - "@wordpress/html-entities": "*", - "@wordpress/i18n": "*", - "@wordpress/icons": "*", - "@wordpress/interface": "*", - "@wordpress/keyboard-shortcuts": "*", - "@wordpress/keycodes": "*", - "@wordpress/media-utils": "*", - "@wordpress/notices": "*", - "@wordpress/patterns": "*", - "@wordpress/plugins": "*", - "@wordpress/preferences": "*", - "@wordpress/private-apis": "*", - "@wordpress/reusable-blocks": "*", - "@wordpress/rich-text": "*", - "@wordpress/server-side-render": "*", - "@wordpress/url": "*", - "@wordpress/warning": "*", - "@wordpress/wordcount": "*", + "@wordpress/a11y": "file:../a11y", + "@wordpress/api-fetch": "file:../api-fetch", + "@wordpress/blob": "file:../blob", + "@wordpress/block-editor": "file:../block-editor", + "@wordpress/blocks": "file:../blocks", + "@wordpress/commands": "file:../commands", + "@wordpress/components": "file:../components", + "@wordpress/compose": "file:../compose", + "@wordpress/core-data": "file:../core-data", + "@wordpress/data": "file:../data", + "@wordpress/dataviews": "file:../dataviews", + "@wordpress/date": "file:../date", + "@wordpress/deprecated": "file:../deprecated", + "@wordpress/dom": "file:../dom", + "@wordpress/element": "file:../element", + "@wordpress/fields": "file:../fields", + "@wordpress/hooks": "file:../hooks", + "@wordpress/html-entities": "file:../html-entities", + "@wordpress/i18n": "file:../i18n", + "@wordpress/icons": "file:../icons", + "@wordpress/interface": "file:../interface", + "@wordpress/keyboard-shortcuts": "file:../keyboard-shortcuts", + "@wordpress/keycodes": "file:../keycodes", + "@wordpress/media-utils": "file:../media-utils", + "@wordpress/notices": "file:../notices", + "@wordpress/patterns": "file:../patterns", + "@wordpress/plugins": "file:../plugins", + "@wordpress/preferences": "file:../preferences", + "@wordpress/private-apis": "file:../private-apis", + "@wordpress/reusable-blocks": "file:../reusable-blocks", + "@wordpress/rich-text": "file:../rich-text", + "@wordpress/server-side-render": "file:../server-side-render", + "@wordpress/url": "file:../url", + "@wordpress/warning": "file:../warning", + "@wordpress/wordcount": "file:../wordcount", "change-case": "^4.1.2", "client-zip": "^2.4.5", "clsx": "^2.1.1", @@ -54440,13 +51239,13 @@ }, "packages/element": { "name": "@wordpress/element", - "version": "6.11.0", + "version": "6.16.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7", "@types/react": "^18.2.79", "@types/react-dom": "^18.2.25", - "@wordpress/escape-html": "*", + "@wordpress/escape-html": "file:../escape-html", "change-case": "^4.1.2", "is-plain-object": "^5.0.0", "react": "^18.3.0", @@ -54459,18 +51258,18 @@ }, "packages/env": { "name": "@wordpress/env", - "version": "10.11.0", + "version": "10.16.0", "license": "GPL-2.0-or-later", "dependencies": { + "@inquirer/prompts": "^7.2.0", "chalk": "^4.0.0", "copy-dir": "^1.3.0", "docker-compose": "^0.24.3", "extract-zip": "^1.6.7", "got": "^11.8.5", - "inquirer": "^7.1.0", "js-yaml": "^3.13.1", "ora": "^4.0.2", - "rimraf": "^3.0.2", + "rimraf": "^5.0.10", "simple-git": "^3.5.0", "terminal-link": "^2.0.0", "yargs": "^17.3.0" @@ -54483,19 +51282,6 @@ "npm": ">=8.19.2" } }, - "packages/env/node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, "packages/env/node_modules/docker-compose": { "version": "0.24.2", "resolved": "https://registry.npmjs.org/docker-compose/-/docker-compose-0.24.2.tgz", @@ -54507,53 +51293,6 @@ "node": ">= 6.0.0" } }, - "packages/env/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" - }, - "packages/env/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "engines": { - "node": ">=8" - } - }, - "packages/env/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "packages/env/node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "packages/env/node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==" - }, "packages/env/node_modules/yaml": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.6.0.tgz", @@ -54566,31 +51305,9 @@ "node": ">= 14" } }, - "packages/env/node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "packages/env/node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==" - }, "packages/escape-html": { "name": "@wordpress/escape-html", - "version": "3.11.0", + "version": "3.16.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7" @@ -54602,18 +51319,18 @@ }, "packages/eslint-plugin": { "name": "@wordpress/eslint-plugin", - "version": "21.4.0", + "version": "22.2.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/eslint-parser": "7.25.7", "@typescript-eslint/eslint-plugin": "^6.4.1", "@typescript-eslint/parser": "^6.4.1", - "@wordpress/babel-preset-default": "*", - "@wordpress/prettier-config": "*", + "@wordpress/babel-preset-default": "file:../babel-preset-default", + "@wordpress/prettier-config": "file:../prettier-config", "cosmiconfig": "^7.0.0", "eslint-config-prettier": "^8.3.0", "eslint-plugin-import": "^2.25.2", - "eslint-plugin-jest": "^27.2.3", + "eslint-plugin-jest": "^27.4.3", "eslint-plugin-jsdoc": "^46.4.6", "eslint-plugin-jsx-a11y": "^6.5.1", "eslint-plugin-playwright": "^0.15.3", @@ -54631,7 +51348,7 @@ "@babel/core": ">=7", "eslint": ">=8", "prettier": ">=3", - "typescript": ">=4" + "typescript": ">=5" }, "peerDependenciesMeta": { "prettier": { @@ -54671,32 +51388,33 @@ }, "packages/fields": { "name": "@wordpress/fields", - "version": "0.3.0", + "version": "0.8.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/api-fetch": "*", - "@wordpress/blob": "*", - "@wordpress/blocks": "*", - "@wordpress/components": "*", - "@wordpress/compose": "*", - "@wordpress/core-data": "*", - "@wordpress/data": "*", - "@wordpress/dataviews": "*", - "@wordpress/date": "*", - "@wordpress/element": "*", - "@wordpress/hooks": "*", - "@wordpress/html-entities": "*", - "@wordpress/i18n": "*", - "@wordpress/icons": "*", - "@wordpress/media-utils": "*", - "@wordpress/notices": "*", - "@wordpress/patterns": "*", - "@wordpress/primitives": "*", - "@wordpress/private-apis": "*", - "@wordpress/router": "*", - "@wordpress/url": "*", - "@wordpress/warning": "*", + "@wordpress/api-fetch": "file:../api-fetch", + "@wordpress/blob": "file:../blob", + "@wordpress/block-editor": "file:../block-editor", + "@wordpress/blocks": "file:../blocks", + "@wordpress/components": "file:../components", + "@wordpress/compose": "file:../compose", + "@wordpress/core-data": "file:../core-data", + "@wordpress/data": "file:../data", + "@wordpress/dataviews": "file:../dataviews", + "@wordpress/date": "file:../date", + "@wordpress/element": "file:../element", + "@wordpress/hooks": "file:../hooks", + "@wordpress/html-entities": "file:../html-entities", + "@wordpress/i18n": "file:../i18n", + "@wordpress/icons": "file:../icons", + "@wordpress/media-utils": "file:../media-utils", + "@wordpress/notices": "file:../notices", + "@wordpress/patterns": "file:../patterns", + "@wordpress/primitives": "file:../primitives", + "@wordpress/private-apis": "file:../private-apis", + "@wordpress/router": "file:../router", + "@wordpress/url": "file:../url", + "@wordpress/warning": "file:../warning", "change-case": "4.1.2", "client-zip": "^2.4.5", "clsx": "2.1.1", @@ -54712,22 +51430,22 @@ }, "packages/format-library": { "name": "@wordpress/format-library", - "version": "5.11.0", + "version": "5.16.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/a11y": "*", - "@wordpress/block-editor": "*", - "@wordpress/components": "*", - "@wordpress/compose": "*", - "@wordpress/data": "*", - "@wordpress/element": "*", - "@wordpress/html-entities": "*", - "@wordpress/i18n": "*", - "@wordpress/icons": "*", - "@wordpress/private-apis": "*", - "@wordpress/rich-text": "*", - "@wordpress/url": "*" + "@wordpress/a11y": "file:../a11y", + "@wordpress/block-editor": "file:../block-editor", + "@wordpress/components": "file:../components", + "@wordpress/compose": "file:../compose", + "@wordpress/data": "file:../data", + "@wordpress/element": "file:../element", + "@wordpress/html-entities": "file:../html-entities", + "@wordpress/i18n": "file:../i18n", + "@wordpress/icons": "file:../icons", + "@wordpress/private-apis": "file:../private-apis", + "@wordpress/rich-text": "file:../rich-text", + "@wordpress/url": "file:../url" }, "engines": { "node": ">=18.12.0", @@ -54740,7 +51458,7 @@ }, "packages/hooks": { "name": "@wordpress/hooks", - "version": "4.11.0", + "version": "4.16.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7" @@ -54752,7 +51470,7 @@ }, "packages/html-entities": { "name": "@wordpress/html-entities", - "version": "4.11.0", + "version": "4.16.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7" @@ -54764,11 +51482,11 @@ }, "packages/i18n": { "name": "@wordpress/i18n", - "version": "5.11.0", + "version": "5.16.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/hooks": "*", + "@wordpress/hooks": "file:../hooks", "gettext-parser": "^1.3.1", "memize": "^2.1.0", "sprintf-js": "^1.1.1", @@ -54784,12 +51502,12 @@ }, "packages/icons": { "name": "@wordpress/icons", - "version": "10.11.0", + "version": "10.16.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/element": "*", - "@wordpress/primitives": "*" + "@wordpress/element": "file:../element", + "@wordpress/primitives": "file:../primitives" }, "engines": { "node": ">=18.12.0", @@ -54798,7 +51516,7 @@ }, "packages/interactivity": { "name": "@wordpress/interactivity", - "version": "6.11.0", + "version": "6.16.0", "license": "GPL-2.0-or-later", "dependencies": { "@preact/signals": "^1.3.0", @@ -54811,11 +51529,11 @@ }, "packages/interactivity-router": { "name": "@wordpress/interactivity-router", - "version": "2.11.0", + "version": "2.16.0", "license": "GPL-2.0-or-later", "dependencies": { - "@wordpress/a11y": "*", - "@wordpress/interactivity": "*" + "@wordpress/a11y": "file:../a11y", + "@wordpress/interactivity": "file:../interactivity" }, "engines": { "node": ">=18.12.0", @@ -54824,21 +51542,21 @@ }, "packages/interface": { "name": "@wordpress/interface", - "version": "8.0.0", + "version": "9.1.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/a11y": "*", - "@wordpress/components": "*", - "@wordpress/compose": "*", - "@wordpress/data": "*", - "@wordpress/deprecated": "*", - "@wordpress/element": "*", - "@wordpress/i18n": "*", - "@wordpress/icons": "*", - "@wordpress/plugins": "*", - "@wordpress/preferences": "*", - "@wordpress/viewport": "*", + "@wordpress/a11y": "file:../a11y", + "@wordpress/components": "file:../components", + "@wordpress/compose": "file:../compose", + "@wordpress/data": "file:../data", + "@wordpress/deprecated": "file:../deprecated", + "@wordpress/element": "file:../element", + "@wordpress/i18n": "file:../i18n", + "@wordpress/icons": "file:../icons", + "@wordpress/plugins": "file:../plugins", + "@wordpress/preferences": "file:../preferences", + "@wordpress/viewport": "file:../viewport", "clsx": "^2.1.1" }, "engines": { @@ -54852,7 +51570,7 @@ }, "packages/is-shallow-equal": { "name": "@wordpress/is-shallow-equal", - "version": "5.11.0", + "version": "5.16.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7" @@ -54864,7 +51582,7 @@ }, "packages/jest-console": { "name": "@wordpress/jest-console", - "version": "8.11.0", + "version": "8.16.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7", @@ -54880,10 +51598,10 @@ }, "packages/jest-preset-default": { "name": "@wordpress/jest-preset-default", - "version": "12.11.0", + "version": "12.16.0", "license": "GPL-2.0-or-later", "dependencies": { - "@wordpress/jest-console": "*", + "@wordpress/jest-console": "file:../jest-console", "babel-jest": "29.7.0" }, "engines": { @@ -54897,7 +51615,7 @@ }, "packages/jest-puppeteer-axe": { "name": "@wordpress/jest-puppeteer-axe", - "version": "7.11.0", + "version": "7.16.0", "license": "GPL-2.0-or-later", "dependencies": { "@axe-core/puppeteer": "^4.0.0", @@ -54919,13 +51637,13 @@ }, "packages/keyboard-shortcuts": { "name": "@wordpress/keyboard-shortcuts", - "version": "5.11.0", + "version": "5.16.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/data": "*", - "@wordpress/element": "*", - "@wordpress/keycodes": "*" + "@wordpress/data": "file:../data", + "@wordpress/element": "file:../element", + "@wordpress/keycodes": "file:../keycodes" }, "engines": { "node": ">=18.12.0", @@ -54937,11 +51655,11 @@ }, "packages/keycodes": { "name": "@wordpress/keycodes", - "version": "4.11.0", + "version": "4.16.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/i18n": "*" + "@wordpress/i18n": "file:../i18n" }, "engines": { "node": ">=18.12.0", @@ -54950,7 +51668,7 @@ }, "packages/lazy-import": { "name": "@wordpress/lazy-import", - "version": "2.11.0", + "version": "2.16.0", "license": "GPL-2.0-or-later", "dependencies": { "execa": "^4.0.2", @@ -54964,16 +51682,16 @@ }, "packages/list-reusable-blocks": { "name": "@wordpress/list-reusable-blocks", - "version": "5.11.0", + "version": "5.16.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/api-fetch": "*", - "@wordpress/blob": "*", - "@wordpress/components": "*", - "@wordpress/compose": "*", - "@wordpress/element": "*", - "@wordpress/i18n": "*", + "@wordpress/api-fetch": "file:../api-fetch", + "@wordpress/blob": "file:../blob", + "@wordpress/components": "file:../components", + "@wordpress/compose": "file:../compose", + "@wordpress/element": "file:../element", + "@wordpress/i18n": "file:../i18n", "change-case": "^4.1.2" }, "engines": { @@ -54987,15 +51705,15 @@ }, "packages/media-utils": { "name": "@wordpress/media-utils", - "version": "5.11.0", + "version": "5.16.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/api-fetch": "*", - "@wordpress/blob": "*", - "@wordpress/element": "*", - "@wordpress/i18n": "*", - "@wordpress/private-apis": "*" + "@wordpress/api-fetch": "file:../api-fetch", + "@wordpress/blob": "file:../blob", + "@wordpress/element": "file:../element", + "@wordpress/i18n": "file:../i18n", + "@wordpress/private-apis": "file:../private-apis" }, "engines": { "node": ">=18.12.0", @@ -55004,12 +51722,12 @@ }, "packages/notices": { "name": "@wordpress/notices", - "version": "5.11.0", + "version": "5.16.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/a11y": "*", - "@wordpress/data": "*" + "@wordpress/a11y": "file:../a11y", + "@wordpress/data": "file:../data" }, "engines": { "node": ">=18.12.0", @@ -55021,7 +51739,7 @@ }, "packages/npm-package-json-lint-config": { "name": "@wordpress/npm-package-json-lint-config", - "version": "5.11.0", + "version": "5.16.0", "license": "GPL-2.0-or-later", "engines": { "node": ">=18.12.0", @@ -55033,17 +51751,17 @@ }, "packages/nux": { "name": "@wordpress/nux", - "version": "9.11.0", + "version": "9.16.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/components": "*", - "@wordpress/compose": "*", - "@wordpress/data": "*", - "@wordpress/deprecated": "*", - "@wordpress/element": "*", - "@wordpress/i18n": "*", - "@wordpress/icons": "*" + "@wordpress/components": "file:../components", + "@wordpress/compose": "file:../compose", + "@wordpress/data": "file:../data", + "@wordpress/deprecated": "file:../deprecated", + "@wordpress/element": "file:../element", + "@wordpress/i18n": "file:../i18n", + "@wordpress/icons": "file:../icons" }, "engines": { "node": ">=18.12.0", @@ -55056,24 +51774,24 @@ }, "packages/patterns": { "name": "@wordpress/patterns", - "version": "2.11.0", + "version": "2.16.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/a11y": "*", - "@wordpress/block-editor": "*", - "@wordpress/blocks": "*", - "@wordpress/components": "*", - "@wordpress/compose": "*", - "@wordpress/core-data": "*", - "@wordpress/data": "*", - "@wordpress/element": "*", - "@wordpress/html-entities": "*", - "@wordpress/i18n": "*", - "@wordpress/icons": "*", - "@wordpress/notices": "*", - "@wordpress/private-apis": "*", - "@wordpress/url": "*" + "@wordpress/a11y": "file:../a11y", + "@wordpress/block-editor": "file:../block-editor", + "@wordpress/blocks": "file:../blocks", + "@wordpress/components": "file:../components", + "@wordpress/compose": "file:../compose", + "@wordpress/core-data": "file:../core-data", + "@wordpress/data": "file:../data", + "@wordpress/element": "file:../element", + "@wordpress/html-entities": "file:../html-entities", + "@wordpress/i18n": "file:../i18n", + "@wordpress/icons": "file:../icons", + "@wordpress/notices": "file:../notices", + "@wordpress/private-apis": "file:../private-apis", + "@wordpress/url": "file:../url" }, "engines": { "node": ">=18.12.0", @@ -55086,17 +51804,17 @@ }, "packages/plugins": { "name": "@wordpress/plugins", - "version": "7.11.0", + "version": "7.16.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/components": "*", - "@wordpress/compose": "*", - "@wordpress/deprecated": "*", - "@wordpress/element": "*", - "@wordpress/hooks": "*", - "@wordpress/icons": "*", - "@wordpress/is-shallow-equal": "*", + "@wordpress/components": "file:../components", + "@wordpress/compose": "file:../compose", + "@wordpress/deprecated": "file:../deprecated", + "@wordpress/element": "file:../element", + "@wordpress/hooks": "file:../hooks", + "@wordpress/icons": "file:../icons", + "@wordpress/is-shallow-equal": "file:../is-shallow-equal", "memize": "^2.0.1" }, "engines": { @@ -55110,11 +51828,11 @@ }, "packages/postcss-plugins-preset": { "name": "@wordpress/postcss-plugins-preset", - "version": "5.11.0", + "version": "5.16.0", "license": "GPL-2.0-or-later", "dependencies": { - "@wordpress/base-styles": "*", - "autoprefixer": "^10.2.5" + "@wordpress/base-styles": "file:../base-styles", + "autoprefixer": "^10.4.20" }, "engines": { "node": ">=18.12.0", @@ -55124,9 +51842,62 @@ "postcss": "^8.0.0" } }, + "packages/postcss-plugins-preset/node_modules/autoprefixer": { + "version": "10.4.20", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.20.tgz", + "integrity": "sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "browserslist": "^4.23.3", + "caniuse-lite": "^1.0.30001646", + "fraction.js": "^4.3.7", + "normalize-range": "^0.1.2", + "picocolors": "^1.0.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "packages/postcss-plugins-preset/node_modules/fraction.js": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", + "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", + "engines": { + "node": "*" + }, + "funding": { + "type": "patreon", + "url": "https://github.com/sponsors/rawify" + } + }, + "packages/postcss-plugins-preset/node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" + }, "packages/postcss-themes": { "name": "@wordpress/postcss-themes", - "version": "6.11.0", + "version": "6.16.0", "license": "GPL-2.0-or-later", "engines": { "node": ">=18.12.0", @@ -55138,19 +51909,19 @@ }, "packages/preferences": { "name": "@wordpress/preferences", - "version": "4.11.0", + "version": "4.16.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/a11y": "*", - "@wordpress/components": "*", - "@wordpress/compose": "*", - "@wordpress/data": "*", - "@wordpress/deprecated": "*", - "@wordpress/element": "*", - "@wordpress/i18n": "*", - "@wordpress/icons": "*", - "@wordpress/private-apis": "*", + "@wordpress/a11y": "file:../a11y", + "@wordpress/components": "file:../components", + "@wordpress/compose": "file:../compose", + "@wordpress/data": "file:../data", + "@wordpress/deprecated": "file:../deprecated", + "@wordpress/element": "file:../element", + "@wordpress/i18n": "file:../i18n", + "@wordpress/icons": "file:../icons", + "@wordpress/private-apis": "file:../private-apis", "clsx": "^2.1.1" }, "engines": { @@ -55164,11 +51935,11 @@ }, "packages/preferences-persistence": { "name": "@wordpress/preferences-persistence", - "version": "2.11.0", + "version": "2.16.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/api-fetch": "*" + "@wordpress/api-fetch": "file:../api-fetch" }, "engines": { "node": ">=18.12.0", @@ -55177,7 +51948,7 @@ }, "packages/prettier-config": { "name": "@wordpress/prettier-config", - "version": "4.11.0", + "version": "4.16.0", "license": "GPL-2.0-or-later", "engines": { "node": ">=18.12.0", @@ -55189,11 +51960,11 @@ }, "packages/primitives": { "name": "@wordpress/primitives", - "version": "4.11.0", + "version": "4.16.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/element": "*", + "@wordpress/element": "file:../element", "clsx": "^2.1.1" }, "engines": { @@ -55206,7 +51977,7 @@ }, "packages/priority-queue": { "name": "@wordpress/priority-queue", - "version": "3.11.0", + "version": "3.16.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7", @@ -55219,7 +51990,7 @@ }, "packages/private-apis": { "name": "@wordpress/private-apis", - "version": "1.11.0", + "version": "1.16.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7" @@ -55231,7 +52002,7 @@ }, "packages/project-management-automation": { "name": "@wordpress/project-management-automation", - "version": "2.11.0", + "version": "2.16.0", "license": "GPL-2.0-or-later", "dependencies": { "@actions/core": "1.9.1", @@ -55259,12 +52030,12 @@ }, "packages/react-i18n": { "name": "@wordpress/react-i18n", - "version": "4.11.0", + "version": "4.16.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/element": "*", - "@wordpress/i18n": "*", + "@wordpress/element": "file:../element", + "@wordpress/i18n": "file:../i18n", "utility-types": "^3.10.0" }, "engines": { @@ -55277,8 +52048,8 @@ "version": "1.121.0", "license": "GPL-2.0-or-later", "dependencies": { - "@wordpress/element": "*", - "@wordpress/keycodes": "*" + "@wordpress/element": "file:../element", + "@wordpress/keycodes": "file:../keycodes" }, "engines": { "node": ">=18.12.0", @@ -55294,7 +52065,7 @@ "version": "1.121.0", "license": "GPL-2.0-or-later", "dependencies": { - "@wordpress/react-native-aztec": "*" + "@wordpress/react-native-aztec": "file:../react-native-aztec" }, "engines": { "node": ">=18.12.0", @@ -55319,18 +52090,18 @@ "@react-navigation/native": "6.0.14", "@react-navigation/routers": "5.4.9", "@react-navigation/stack": "6.3.5", - "@wordpress/api-fetch": "*", - "@wordpress/block-editor": "*", - "@wordpress/block-library": "*", - "@wordpress/blocks": "*", - "@wordpress/components": "*", - "@wordpress/data": "*", - "@wordpress/edit-post": "*", - "@wordpress/element": "*", - "@wordpress/hooks": "*", - "@wordpress/i18n": "*", - "@wordpress/react-native-aztec": "*", - "@wordpress/react-native-bridge": "*", + "@wordpress/api-fetch": "file:../api-fetch", + "@wordpress/block-editor": "file:../block-editor", + "@wordpress/block-library": "file:../block-library", + "@wordpress/blocks": "file:../blocks", + "@wordpress/components": "file:../components", + "@wordpress/data": "file:../data", + "@wordpress/edit-post": "file:../edit-post", + "@wordpress/element": "file:../element", + "@wordpress/hooks": "file:../hooks", + "@wordpress/i18n": "file:../i18n", + "@wordpress/react-native-aztec": "file:../react-native-aztec", + "@wordpress/react-native-bridge": "file:../react-native-bridge", "core-js": "^3.31.0", "fast-average-color": "^9.1.1", "gettext-parser": "^1.3.1", @@ -55415,7 +52186,7 @@ }, "packages/readable-js-assets-webpack-plugin": { "name": "@wordpress/readable-js-assets-webpack-plugin", - "version": "3.11.0", + "version": "3.16.0", "license": "GPL-2.0-or-later", "engines": { "node": ">=18.12.0", @@ -55427,7 +52198,7 @@ }, "packages/redux-routine": { "name": "@wordpress/redux-routine", - "version": "5.11.0", + "version": "5.16.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7", @@ -55470,21 +52241,21 @@ }, "packages/reusable-blocks": { "name": "@wordpress/reusable-blocks", - "version": "5.11.0", + "version": "5.16.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/block-editor": "*", - "@wordpress/blocks": "*", - "@wordpress/components": "*", - "@wordpress/core-data": "*", - "@wordpress/data": "*", - "@wordpress/element": "*", - "@wordpress/i18n": "*", - "@wordpress/icons": "*", - "@wordpress/notices": "*", - "@wordpress/private-apis": "*", - "@wordpress/url": "*" + "@wordpress/block-editor": "file:../block-editor", + "@wordpress/blocks": "file:../blocks", + "@wordpress/components": "file:../components", + "@wordpress/core-data": "file:../core-data", + "@wordpress/data": "file:../data", + "@wordpress/element": "file:../element", + "@wordpress/i18n": "file:../i18n", + "@wordpress/icons": "file:../icons", + "@wordpress/notices": "file:../notices", + "@wordpress/private-apis": "file:../private-apis", + "@wordpress/url": "file:../url" }, "engines": { "node": ">=18.12.0", @@ -55497,18 +52268,18 @@ }, "packages/rich-text": { "name": "@wordpress/rich-text", - "version": "7.11.0", + "version": "7.16.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/a11y": "*", - "@wordpress/compose": "*", - "@wordpress/data": "*", - "@wordpress/deprecated": "*", - "@wordpress/element": "*", - "@wordpress/escape-html": "*", - "@wordpress/i18n": "*", - "@wordpress/keycodes": "*", + "@wordpress/a11y": "file:../a11y", + "@wordpress/compose": "file:../compose", + "@wordpress/data": "file:../data", + "@wordpress/deprecated": "file:../deprecated", + "@wordpress/element": "file:../element", + "@wordpress/escape-html": "file:../escape-html", + "@wordpress/i18n": "file:../i18n", + "@wordpress/keycodes": "file:../keycodes", "memize": "^2.1.0" }, "engines": { @@ -55521,14 +52292,16 @@ }, "packages/router": { "name": "@wordpress/router", - "version": "1.11.0", + "version": "1.16.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/element": "*", - "@wordpress/private-apis": "*", - "@wordpress/url": "*", - "history": "^5.3.0" + "@wordpress/compose": "file:../compose", + "@wordpress/element": "file:../element", + "@wordpress/private-apis": "file:../private-apis", + "@wordpress/url": "file:../url", + "history": "^5.3.0", + "route-recognizer": "^0.3.4" }, "engines": { "node": ">=18.12.0", @@ -55540,22 +52313,22 @@ }, "packages/scripts": { "name": "@wordpress/scripts", - "version": "30.4.0", + "version": "30.9.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/core": "7.25.7", "@pmmmwh/react-refresh-webpack-plugin": "^0.5.11", "@svgr/webpack": "^8.0.1", - "@wordpress/babel-preset-default": "*", - "@wordpress/browserslist-config": "*", - "@wordpress/dependency-extraction-webpack-plugin": "*", - "@wordpress/e2e-test-utils-playwright": "*", - "@wordpress/eslint-plugin": "*", - "@wordpress/jest-preset-default": "*", - "@wordpress/npm-package-json-lint-config": "*", - "@wordpress/postcss-plugins-preset": "*", - "@wordpress/prettier-config": "*", - "@wordpress/stylelint-config": "*", + "@wordpress/babel-preset-default": "file:../babel-preset-default", + "@wordpress/browserslist-config": "file:../browserslist-config", + "@wordpress/dependency-extraction-webpack-plugin": "file:../dependency-extraction-webpack-plugin", + "@wordpress/e2e-test-utils-playwright": "file:../e2e-test-utils-playwright", + "@wordpress/eslint-plugin": "file:../eslint-plugin", + "@wordpress/jest-preset-default": "file:../jest-preset-default", + "@wordpress/npm-package-json-lint-config": "file:../npm-package-json-lint-config", + "@wordpress/postcss-plugins-preset": "file:../postcss-plugins-preset", + "@wordpress/prettier-config": "file:../prettier-config", + "@wordpress/stylelint-config": "file:../stylelint-config", "adm-zip": "^0.5.9", "babel-jest": "29.7.0", "babel-loader": "9.2.1", @@ -55564,7 +52337,7 @@ "check-node-version": "^4.1.0", "clean-webpack-plugin": "^3.0.0", "copy-webpack-plugin": "^10.2.0", - "cross-spawn": "^5.1.0", + "cross-spawn": "^7.0.6", "css-loader": "^6.2.0", "cssnano": "^6.0.1", "cwd": "^0.10.0", @@ -55574,33 +52347,32 @@ "fast-glob": "^3.2.7", "filenamify": "^4.2.0", "jest": "^29.6.2", - "jest-dev-server": "^9.0.1", + "jest-dev-server": "^10.1.4", "jest-environment-jsdom": "^29.6.2", "jest-environment-node": "^29.6.2", "json2php": "^0.0.9", "markdownlint-cli": "^0.31.1", "merge-deep": "^3.0.3", - "mini-css-extract-plugin": "^2.5.1", + "mini-css-extract-plugin": "^2.9.2", "minimist": "^1.2.0", "npm-package-json-lint": "^6.4.0", "npm-packlist": "^3.0.0", "postcss": "^8.4.5", - "postcss-import": "^16.1.0", "postcss-loader": "^6.2.1", "prettier": "npm:wp-prettier@3.0.3", - "puppeteer-core": "^23.1.0", + "puppeteer-core": "^23.10.1", "react-refresh": "^0.14.0", "read-pkg-up": "^7.0.1", "resolve-bin": "^0.4.0", - "rtlcss-webpack-plugin": "^4.0.7", - "sass": "^1.35.2", - "sass-loader": "^12.1.0", + "rtlcss": "^4.3.0", + "sass": "^1.54.0", + "sass-loader": "^16.0.3", "schema-utils": "^4.2.0", "source-map-loader": "^3.0.0", "stylelint": "^16.8.2", "terser-webpack-plugin": "^5.3.10", "url-loader": "^4.1.1", - "webpack": "^5.95.0", + "webpack": "^5.97.0", "webpack-bundle-analyzer": "^4.9.1", "webpack-cli": "^5.1.4", "webpack-dev-server": "^4.15.1" @@ -55613,7 +52385,7 @@ "npm": ">=8.19.2" }, "peerDependencies": { - "@playwright/test": "^1.48.1", + "@playwright/test": "^1.49.1", "react": "^18.0.0", "react-dom": "^18.0.0" } @@ -55634,154 +52406,16 @@ "url": "https://github.com/sponsors/epoberezkin" } }, - "packages/scripts/node_modules/axios": { - "version": "1.7.7", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz", - "integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==", - "license": "MIT", - "dependencies": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.0", - "proxy-from-env": "^1.1.0" - } - }, - "packages/scripts/node_modules/babel-loader": { - "version": "8.4.1", - "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.4.1.tgz", - "integrity": "sha512-nXzRChX+Z1GoE6yWavBQg6jDslyFF3SDjl2paADuoQtQW10JqShJt62R6eJQ5m/pjJFDT8xgKIWSP85OY8eXeA==", + "packages/scripts/node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", "license": "MIT", "dependencies": { - "find-cache-dir": "^3.3.1", - "loader-utils": "^2.0.4", - "make-dir": "^3.1.0", - "schema-utils": "^2.6.5" - }, - "engines": { - "node": ">= 8.9" + "fast-deep-equal": "^3.1.3" }, "peerDependencies": { - "@babel/core": "^7.0.0", - "webpack": ">=2" - } - }, - "packages/scripts/node_modules/babel-loader/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "packages/scripts/node_modules/babel-loader/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "license": "MIT" - }, - "packages/scripts/node_modules/babel-loader/node_modules/schema-utils": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz", - "integrity": "sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==", - "license": "MIT", - "dependencies": { - "@types/json-schema": "^7.0.5", - "ajv": "^6.12.4", - "ajv-keywords": "^3.5.2" - }, - "engines": { - "node": ">= 8.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "packages/scripts/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "packages/scripts/node_modules/find-cache-dir": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", - "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", - "dependencies": { - "commondir": "^1.0.1", - "make-dir": "^3.0.2", - "pkg-dir": "^4.1.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/avajs/find-cache-dir?sponsor=1" - } - }, - "packages/scripts/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "packages/scripts/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "packages/scripts/node_modules/jest-dev-server": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/jest-dev-server/-/jest-dev-server-9.0.1.tgz", - "integrity": "sha512-eqpJKSvVl4M0ojHZUPNbka8yEzLNbIMiINXDsuMF3lYfIdRO2iPqy+ASR4wBQ6nUyR3OT24oKPWhpsfLhgAVyg==", - "dependencies": { - "chalk": "^4.1.2", - "cwd": "^0.10.0", - "find-process": "^1.4.7", - "prompts": "^2.4.2", - "spawnd": "^9.0.1", - "tree-kill": "^1.2.2", - "wait-on": "^7.0.1" - }, - "engines": { - "node": ">=16" - } - }, - "packages/scripts/node_modules/joi": { - "version": "17.11.0", - "resolved": "https://registry.npmjs.org/joi/-/joi-17.11.0.tgz", - "integrity": "sha512-NgB+lZLNoqISVy1rZocE9PZI36bL/77ie924Ri43yEvi9GUUMPeyVIr8KdFTMUlby1p0PBYMk9spIxEUQYqrJQ==", - "dependencies": { - "@hapi/hoek": "^9.0.0", - "@hapi/topo": "^5.0.0", - "@sideway/address": "^4.1.3", - "@sideway/formula": "^3.0.1", - "@sideway/pinpoint": "^2.0.0" + "ajv": "^8.8.2" } }, "packages/scripts/node_modules/json-schema-traverse": { @@ -55796,91 +52430,6 @@ "integrity": "sha512-fQMYwvPsQt8hxRnCGyg1r2JVi6yL8Um0DIIawiKiMK9yhAAkcRNj5UsBWoaFvFzPpcWbgw9L6wzj+UMYA702Mw==", "license": "BSD" }, - "packages/scripts/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "packages/scripts/node_modules/make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dependencies": { - "semver": "^6.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "packages/scripts/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "packages/scripts/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "packages/scripts/node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "engines": { - "node": ">=6" - } - }, - "packages/scripts/node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "engines": { - "node": ">=8" - } - }, - "packages/scripts/node_modules/pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dependencies": { - "find-up": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "packages/scripts/node_modules/rxjs": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", - "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", - "dependencies": { - "tslib": "^2.1.0" - } - }, "packages/scripts/node_modules/schema-utils": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.2.0.tgz", @@ -55900,93 +52449,21 @@ "url": "https://opencollective.com/webpack" } }, - "packages/scripts/node_modules/schema-utils/node_modules/ajv-keywords": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", - "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3" - }, - "peerDependencies": { - "ajv": "^8.8.2" - } - }, - "packages/scripts/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "bin": { - "semver": "bin/semver.js" - } - }, - "packages/scripts/node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "packages/scripts/node_modules/spawnd": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/spawnd/-/spawnd-9.0.1.tgz", - "integrity": "sha512-vaMk8E9CpbjTYToBxLXowDeArGf1+yI7A6PU6Nr57b2g8BVY8nRi5vTBj3bMF8UkCrMdTMyf/Lh+lrcrW2z7pw==", - "dependencies": { - "signal-exit": "^4.1.0", - "tree-kill": "^1.2.2" - }, - "engines": { - "node": ">=16" - } - }, - "packages/scripts/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "packages/scripts/node_modules/wait-on": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/wait-on/-/wait-on-7.2.0.tgz", - "integrity": "sha512-wCQcHkRazgjG5XoAq9jbTMLpNIjoSlZslrJ2+N9MxDsGEv1HnFoVjOCexL0ESva7Y9cu350j+DWADdk54s4AFQ==", - "dependencies": { - "axios": "^1.6.1", - "joi": "^17.11.0", - "lodash": "^4.17.21", - "minimist": "^1.2.8", - "rxjs": "^7.8.1" - }, - "bin": { - "wait-on": "bin/wait-on" - }, - "engines": { - "node": ">=12.0.0" - } - }, "packages/server-side-render": { "name": "@wordpress/server-side-render", - "version": "5.11.0", + "version": "5.16.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/api-fetch": "*", - "@wordpress/blocks": "*", - "@wordpress/components": "*", - "@wordpress/compose": "*", - "@wordpress/data": "*", - "@wordpress/deprecated": "*", - "@wordpress/element": "*", - "@wordpress/i18n": "*", - "@wordpress/url": "*", + "@wordpress/api-fetch": "file:../api-fetch", + "@wordpress/blocks": "file:../blocks", + "@wordpress/components": "file:../components", + "@wordpress/compose": "file:../compose", + "@wordpress/data": "file:../data", + "@wordpress/deprecated": "file:../deprecated", + "@wordpress/element": "file:../element", + "@wordpress/i18n": "file:../i18n", + "@wordpress/url": "file:../url", "fast-deep-equal": "^3.1.3" }, "engines": { @@ -56000,7 +52477,7 @@ }, "packages/shortcode": { "name": "@wordpress/shortcode", - "version": "4.11.0", + "version": "4.16.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7", @@ -56013,7 +52490,7 @@ }, "packages/style-engine": { "name": "@wordpress/style-engine", - "version": "2.11.0", + "version": "2.16.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7", @@ -56026,7 +52503,7 @@ }, "packages/stylelint-config": { "name": "@wordpress/stylelint-config", - "version": "23.3.0", + "version": "23.8.0", "license": "MIT", "dependencies": { "@stylistic/stylelint-plugin": "^3.0.1", @@ -56137,12 +52614,12 @@ }, "packages/sync": { "name": "@wordpress/sync", - "version": "1.11.0", + "version": "1.16.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7", "@types/simple-peer": "^9.11.5", - "@wordpress/url": "*", + "@wordpress/url": "file:../url", "import-locals": "^2.0.0", "lib0": "^0.2.42", "simple-peer": "^9.11.0", @@ -56158,7 +52635,7 @@ }, "packages/token-list": { "name": "@wordpress/token-list", - "version": "3.11.0", + "version": "3.16.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7" @@ -56170,20 +52647,47 @@ }, "packages/undo-manager": { "name": "@wordpress/undo-manager", - "version": "1.11.0", + "version": "1.16.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/is-shallow-equal": "*" + "@wordpress/is-shallow-equal": "file:../is-shallow-equal" }, "engines": { "node": ">=18.12.0", "npm": ">=8.19.2" } }, + "packages/upload-media": { + "name": "@wordpress/upload-media", + "version": "0.1.0", + "license": "GPL-2.0-or-later", + "dependencies": { + "@babel/runtime": "7.25.7", + "@shopify/web-worker": "^6.4.0", + "@wordpress/api-fetch": "file:../api-fetch", + "@wordpress/blob": "file:../blob", + "@wordpress/compose": "file:../compose", + "@wordpress/data": "file:../data", + "@wordpress/element": "file:../element", + "@wordpress/i18n": "file:../i18n", + "@wordpress/preferences": "file:../preferences", + "@wordpress/private-apis": "file:../private-apis", + "@wordpress/url": "file:../url", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=18.12.0", + "npm": ">=8.19.2" + }, + "peerDependencies": { + "react": "^18.0.0", + "react-dom": "^18.0.0" + } + }, "packages/url": { "name": "@wordpress/url", - "version": "4.11.0", + "version": "4.16.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7", @@ -56196,13 +52700,13 @@ }, "packages/viewport": { "name": "@wordpress/viewport", - "version": "6.11.0", + "version": "6.16.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/compose": "*", - "@wordpress/data": "*", - "@wordpress/element": "*" + "@wordpress/compose": "file:../compose", + "@wordpress/data": "file:../data", + "@wordpress/element": "file:../element" }, "engines": { "node": ">=18.12.0", @@ -56226,7 +52730,7 @@ }, "packages/warning": { "name": "@wordpress/warning", - "version": "3.11.0", + "version": "3.16.0", "license": "GPL-2.0-or-later", "engines": { "node": ">=18.12.0", @@ -56235,21 +52739,21 @@ }, "packages/widgets": { "name": "@wordpress/widgets", - "version": "4.11.0", + "version": "4.16.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/api-fetch": "*", - "@wordpress/block-editor": "*", - "@wordpress/blocks": "*", - "@wordpress/components": "*", - "@wordpress/compose": "*", - "@wordpress/core-data": "*", - "@wordpress/data": "*", - "@wordpress/element": "*", - "@wordpress/i18n": "*", - "@wordpress/icons": "*", - "@wordpress/notices": "*", + "@wordpress/api-fetch": "file:../api-fetch", + "@wordpress/block-editor": "file:../block-editor", + "@wordpress/blocks": "file:../blocks", + "@wordpress/components": "file:../components", + "@wordpress/compose": "file:../compose", + "@wordpress/core-data": "file:../core-data", + "@wordpress/data": "file:../data", + "@wordpress/element": "file:../element", + "@wordpress/i18n": "file:../i18n", + "@wordpress/icons": "file:../icons", + "@wordpress/notices": "file:../notices", "clsx": "^2.1.1" }, "engines": { @@ -56263,7 +52767,7 @@ }, "packages/wordcount": { "name": "@wordpress/wordcount", - "version": "4.11.0", + "version": "4.16.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7" diff --git a/package.json b/package.json index a2059f7d53a65d..1dbf7d44df4055 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gutenberg", - "version": "19.7.0-rc.1", + "version": "20.1.0-rc.1", "private": true, "description": "A new WordPress editor experience.", "author": "The WordPress Contributors", @@ -25,33 +25,38 @@ "@actions/core": "1.9.1", "@actions/github": "5.0.0", "@apidevtools/json-schema-ref-parser": "11.6.4", - "@ariakit/test": "^0.4.2", + "@ariakit/test": "^0.4.7", "@babel/core": "7.25.7", "@babel/plugin-syntax-jsx": "7.25.7", "@babel/runtime-corejs3": "7.25.7", "@babel/traverse": "7.25.7", "@emotion/babel-plugin": "11.11.0", + "@emotion/is-prop-valid": "1.2.2", "@emotion/jest": "11.7.1", "@emotion/native": "11.0.0", - "@geometricpanda/storybook-addon-badges": "2.0.1", + "@geometricpanda/storybook-addon-badges": "2.0.5", + "@inquirer/prompts": "7.2.0", "@octokit/rest": "16.26.0", "@octokit/types": "6.34.0", "@octokit/webhooks-types": "5.8.0", - "@playwright/test": "1.48.1", + "@playwright/test": "1.49.1", "@pmmmwh/react-refresh-webpack-plugin": "0.5.11", "@react-native/babel-preset": "0.73.10", "@react-native/metro-babel-transformer": "0.73.10", "@react-native/metro-config": "0.73.4", - "@storybook/addon-a11y": "7.6.15", - "@storybook/addon-actions": "7.6.15", - "@storybook/addon-controls": "7.6.15", - "@storybook/addon-docs": "7.6.15", - "@storybook/addon-toolbars": "7.6.15", - "@storybook/addon-viewport": "7.6.15", - "@storybook/react": "7.6.15", - "@storybook/react-webpack5": "7.6.15", - "@storybook/source-loader": "7.6.15", - "@storybook/theming": "7.6.15", + "@storybook/addon-a11y": "8.4.7", + "@storybook/addon-actions": "8.4.7", + "@storybook/addon-controls": "8.4.7", + "@storybook/addon-docs": "8.4.7", + "@storybook/addon-toolbars": "8.4.7", + "@storybook/addon-viewport": "8.4.7", + "@storybook/addon-webpack5-compiler-babel": "3.0.3", + "@storybook/react": "8.4.7", + "@storybook/react-webpack5": "8.4.7", + "@storybook/source-loader": "8.4.7", + "@storybook/test": "8.4.7", + "@storybook/theming": "8.4.7", + "@storybook/types": "8.4.7", "@testing-library/jest-dom": "5.16.5", "@testing-library/react": "14.3.0", "@testing-library/react-native": "12.4.3", @@ -60,6 +65,7 @@ "@types/estree": "1.0.5", "@types/istanbul-lib-report": "3.0.0", "@types/mime": "2.0.3", + "@types/node": "20.17.10", "@types/npm-package-arg": "6.1.1", "@types/prettier": "2.4.4", "@types/qs": "6.9.7", @@ -87,19 +93,21 @@ "commander": "9.2.0", "concurrently": "3.5.0", "copy-webpack-plugin": "10.2.0", - "core-js-builder": "3.38.1", - "cross-env": "3.2.4", + "core-js-builder": "3.39.0", + "cross-env": "7.0.3", "css-loader": "6.2.0", "cssnano": "6.0.1", "deep-freeze": "0.0.1", "equivalent-key-map": "0.2.2", + "esbuild": "0.18.20", "escape-html": "1.0.3", "eslint-import-resolver-node": "0.3.4", "eslint-plugin-eslint-comments": "3.1.2", "eslint-plugin-import": "2.25.2", - "eslint-plugin-jest": "27.2.3", + "eslint-plugin-jest": "27.4.3", "eslint-plugin-jest-dom": "5.0.2", "eslint-plugin-prettier": "5.0.0", + "eslint-plugin-react-compiler": "19.0.0-beta-0dec889-20241115", "eslint-plugin-ssr-friendly": "1.0.6", "eslint-plugin-storybook": "0.6.13", "eslint-plugin-testing-library": "6.0.2", @@ -108,7 +116,6 @@ "filenamify": "4.2.0", "glob": "7.1.2", "husky": "7.0.0", - "inquirer": "7.1.0", "jest": "29.6.2", "jest-environment-jsdom": "^29.6.2", "jest-jasmine2": "29.6.2", @@ -116,7 +123,7 @@ "jest-message-util": "29.6.2", "jest-watch-typeahead": "2.2.2", "json2md": "2.0.1", - "lerna": "7.1.4", + "lerna": "8.1.9", "lint-staged": "10.0.2", "make-dir": "3.0.0", "mkdirp": "3.0.1", @@ -128,12 +135,11 @@ "npm-run-all": "4.1.5", "patch-package": "8.0.0", "postcss": "8.4.38", - "postcss-import": "16.1.0", "postcss-loader": "6.2.1", "postcss-local-keyframes": "^0.0.2", "prettier": "npm:wp-prettier@3.0.3", "progress": "2.0.3", - "puppeteer-core": "23.1.0", + "puppeteer-core": "23.10.1", "raw-loader": "4.0.2", "react": "18.3.1", "react-docgen-typescript": "2.2.2", @@ -146,25 +152,25 @@ "reassure": "0.7.1", "redux": "5.0.1", "resize-observer-polyfill": "1.5.1", - "rimraf": "3.0.2", - "rtlcss": "4.0.0", - "sass": "1.35.2", - "sass-loader": "12.1.0", + "rimraf": "5.0.10", + "rtlcss": "4.3.0", + "sass": "1.54.0", + "sass-loader": "16.0.3", "semver": "7.5.4", "simple-git": "3.24.0", "snapshot-diff": "0.10.0", "source-map-loader": "3.0.0", "sprintf-js": "1.1.1", - "storybook": "7.6.15", + "storybook": "8.4.7", "storybook-source-link": "2.0.9", "strip-json-comments": "5.0.0", "style-loader": "3.2.1", "terser": "5.32.0", "terser-webpack-plugin": "5.3.10", - "typescript": "5.5.3", + "typescript": "5.7.2", "uuid": "9.0.1", "webdriverio": "8.16.20", - "webpack": "5.95.0", + "webpack": "5.97.0", "webpack-bundle-analyzer": "4.9.1", "worker-farm": "1.7.0" }, @@ -175,11 +181,13 @@ "build": "npm run build:packages && wp-scripts build", "build:analyze-bundles": "npm run build -- --webpack-bundle-analyzer", "build:package-types": "node ./bin/packages/validate-typescript-version.js && ( tsc --build || ( echo 'tsc failed. Try cleaning up first: `npm run clean:package-types`'; exit 1 ) ) && node ./bin/packages/check-build-type-declaration-files.js", + "build:profile-types": "rimraf ./ts-traces && npm run clean:package-types && node ./bin/packages/validate-typescript-version.js && ( tsc --build --extendedDiagnostics --generateTrace ./ts-traces || ( echo 'tsc failed.'; exit 1 ) ) && node ./bin/packages/check-build-type-declaration-files.js && npx --yes @typescript/analyze-trace ts-traces > ts-traces/analysis.txt && echo $'\n\nDone! Build traces saved to ts-traces/ directory.\nTrace analysis saved to ts-traces/analysis.txt.'", "prebuild:packages": "npm run clean:packages && npm run --if-present --workspaces build", "build:packages": "npm run --silent build:package-types && node ./bin/packages/build.js", + "postbuild:packages": " npm run --if-present --workspaces build:wp", "build:plugin-zip": "bash ./bin/build-plugin-zip.sh", - "clean:package-types": "tsc --build --clean && rimraf \"./packages/*/build-types\"", - "clean:packages": "rimraf \"./packages/*/@(build|build-module|build-style)\"", + "clean:package-types": "tsc --build --clean && rimraf --glob \"./packages/*/build-types\"", + "clean:packages": "rimraf --glob \"./packages/*/{build,build-module,build-wp,build-style}\"", "component-usage-stats": "node ./node_modules/react-scanner/bin/react-scanner -c ./react-scanner.config.js", "dev": "cross-env NODE_ENV=development npm run build:packages && concurrently \"wp-scripts start\" \"npm run dev:packages\"", "dev:packages": "cross-env NODE_ENV=development concurrently \"node ./bin/packages/watch.js\" \"tsc --build --watch\"", @@ -191,7 +199,7 @@ "docs:gen": "node ./docs/tool/index.js", "docs:theme-ref": "node ./bin/api-docs/gen-theme-reference.mjs", "env": "wp-env", - "fixtures:clean": "rimraf \"test/integration/fixtures/blocks/*.+(json|serialized.html)\"", + "fixtures:clean": "rimraf --glob \"test/integration/fixtures/blocks/*.{json,serialized.html}\"", "fixtures:generate": "cross-env GENERATE_MISSING_FIXTURES=y npm run test:unit test/integration/full-content/ && npm run format test/integration/fixtures/blocks/*.json", "fixtures:regenerate": "npm-run-all fixtures:clean fixtures:generate", "format": "wp-scripts format", diff --git a/packages/README.md b/packages/README.md index f73aca35786f5a..79f07af29382c0 100644 --- a/packages/README.md +++ b/packages/README.md @@ -25,19 +25,25 @@ When creating a new package, you need to provide at least the following. Package "bugs": { "url": "https://github.com/WordPress/gutenberg/issues" }, + "engines": { + "node": ">=18.12.0", + "npm": ">=8.19.2" + }, "main": "build/index.js", "module": "build-module/index.js", "react-native": "src/index", + // Include this line to include the package as a WordPress script. + "wpScript": true, + // Include this line to include the package as a WordPress script module. + "wpScriptModuleExports": "./build-module/index.js", + "types": "build-types", + "sideEffects": false, "dependencies": { "@babel/runtime": "7.25.7" }, "publishConfig": { "access": "public" - }, - // Include this line to include the package as a WordPress script. - "wpScript": true, - // Include this line to include the package as a WordPress script module. - "wpScriptModuleExports": "./build-module/index.js" + } } ``` @@ -84,7 +90,7 @@ When creating a new package, you need to provide at least the following. Package Initial release. ``` -To ensure your package is recognized, you should also _manually_ add your new package to the root `package.json` file and then run `npm install` to update the dependencies. +To ensure your package is recognized in npm workspaces, you should run `npm install` to update the package lock file. ## Managing Dependencies @@ -255,13 +261,17 @@ For consumers to use the published type declarations, we'll set the `types` fiel ```json { "main": "build/index.js", - "main-module": "build-module/index.js", + "module": "build-module/index.js", "types": "build-types" } ``` Ensure that the `build-types` directory will be included in the published package, for example if a `files` field is declared. +## Supported Node.js and npm versions + +WordPress packages adhere the [Node.js Release Schedule](https://nodejs.org/en/about/previous-releases/). Consequently, the minimum required versions of Node.js and npm are specified using the `engines` field in `package.json` for all packages. This ensures that production applications run only on Active LTS or Maintenance LTS releases on Node.js. LTS release status is "long-term support", which typically guarantees that critical bugs will be fixed for a total of 30 months. + ## Optimizing for bundlers In order for bundlers to tree-shake packages effectively, they often need to know whether a package includes side effects in its code. This is done through the `sideEffects` field in the package's `package.json`. diff --git a/packages/a11y/CHANGELOG.md b/packages/a11y/CHANGELOG.md index 5e90577e9155ab..036d332c5a0b0e 100644 --- a/packages/a11y/CHANGELOG.md +++ b/packages/a11y/CHANGELOG.md @@ -2,6 +2,16 @@ ## Unreleased +## 4.16.0 (2025-01-15) + +## 4.15.0 (2025-01-02) + +## 4.14.0 (2024-12-11) + +## 4.13.0 (2024-11-27) + +## 4.12.0 (2024-11-16) + ## 4.11.0 (2024-10-30) ## 4.10.0 (2024-10-16) diff --git a/packages/a11y/package.json b/packages/a11y/package.json index 008bd9088e0778..8bc6e23a461263 100644 --- a/packages/a11y/package.json +++ b/packages/a11y/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/a11y", - "version": "4.11.0", + "version": "4.16.0", "description": "Accessibility (a11y) utilities for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -32,8 +32,8 @@ "types": "build-types", "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/dom-ready": "*", - "@wordpress/i18n": "*" + "@wordpress/dom-ready": "file:../dom-ready", + "@wordpress/i18n": "file:../i18n" }, "publishConfig": { "access": "public" diff --git a/packages/a11y/tsconfig.json b/packages/a11y/tsconfig.json index 093c2775f96d66..13229eadde8f21 100644 --- a/packages/a11y/tsconfig.json +++ b/packages/a11y/tsconfig.json @@ -1,10 +1,5 @@ { "$schema": "https://json.schemastore.org/tsconfig.json", "extends": "../../tsconfig.base.json", - "compilerOptions": { - "rootDir": "src", - "declarationDir": "build-types" - }, - "references": [ { "path": "../dom-ready" }, { "path": "../i18n" } ], - "include": [ "src/**/*" ] + "references": [ { "path": "../dom-ready" }, { "path": "../i18n" } ] } diff --git a/packages/annotations/CHANGELOG.md b/packages/annotations/CHANGELOG.md index 0618f4962f5eb5..6b3bc3564d58d6 100644 --- a/packages/annotations/CHANGELOG.md +++ b/packages/annotations/CHANGELOG.md @@ -2,6 +2,16 @@ ## Unreleased +## 3.16.0 (2025-01-15) + +## 3.15.0 (2025-01-02) + +## 3.14.0 (2024-12-11) + +## 3.13.0 (2024-11-27) + +## 3.12.0 (2024-11-16) + ## 3.11.0 (2024-10-30) ## 3.10.0 (2024-10-16) diff --git a/packages/annotations/package.json b/packages/annotations/package.json index b1d6d210807a87..a426bfa3d6ef7a 100644 --- a/packages/annotations/package.json +++ b/packages/annotations/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/annotations", - "version": "3.11.0", + "version": "3.16.0", "description": "Annotate content in the Gutenberg editor.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -28,10 +28,10 @@ "wpScript": true, "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/data": "*", - "@wordpress/hooks": "*", - "@wordpress/i18n": "*", - "@wordpress/rich-text": "*", + "@wordpress/data": "file:../data", + "@wordpress/hooks": "file:../hooks", + "@wordpress/i18n": "file:../i18n", + "@wordpress/rich-text": "file:../rich-text", "uuid": "^9.0.1" }, "peerDependencies": { diff --git a/packages/api-fetch/CHANGELOG.md b/packages/api-fetch/CHANGELOG.md index 8d3faea535faf3..47cfb75849ce99 100644 --- a/packages/api-fetch/CHANGELOG.md +++ b/packages/api-fetch/CHANGELOG.md @@ -2,6 +2,16 @@ ## Unreleased +## 7.16.0 (2025-01-15) + +## 7.15.0 (2025-01-02) + +## 7.14.0 (2024-12-11) + +## 7.13.0 (2024-11-27) + +## 7.12.0 (2024-11-16) + ## 7.11.0 (2024-10-30) ## 7.10.0 (2024-10-16) diff --git a/packages/api-fetch/package.json b/packages/api-fetch/package.json index 7d5b8dfd588897..44e5968e0b1b49 100644 --- a/packages/api-fetch/package.json +++ b/packages/api-fetch/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/api-fetch", - "version": "7.11.0", + "version": "7.16.0", "description": "Utility to make WordPress REST API requests.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -30,8 +30,8 @@ "types": "build-types", "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/i18n": "*", - "@wordpress/url": "*" + "@wordpress/i18n": "file:../i18n", + "@wordpress/url": "file:../url" }, "publishConfig": { "access": "public" diff --git a/packages/api-fetch/tsconfig.json b/packages/api-fetch/tsconfig.json index f9d517286a102f..635fe4a8c0d353 100644 --- a/packages/api-fetch/tsconfig.json +++ b/packages/api-fetch/tsconfig.json @@ -1,11 +1,6 @@ { "$schema": "https://json.schemastore.org/tsconfig.json", "extends": "../../tsconfig.base.json", - "compilerOptions": { - "rootDir": "src", - "declarationDir": "build-types" - }, "references": [ { "path": "../i18n" }, { "path": "../url" } ], - "include": [ "src/**/*" ], - "exclude": [ "**/test/**/*" ] + "exclude": [ "**/test" ] } diff --git a/packages/autop/CHANGELOG.md b/packages/autop/CHANGELOG.md index 9534b183d6ebb1..9b73fe7ecaaead 100644 --- a/packages/autop/CHANGELOG.md +++ b/packages/autop/CHANGELOG.md @@ -2,6 +2,16 @@ ## Unreleased +## 4.16.0 (2025-01-15) + +## 4.15.0 (2025-01-02) + +## 4.14.0 (2024-12-11) + +## 4.13.0 (2024-11-27) + +## 4.12.0 (2024-11-16) + ## 4.11.0 (2024-10-30) ## 4.10.0 (2024-10-16) diff --git a/packages/autop/package.json b/packages/autop/package.json index 336dda06edfe2c..b334be020f6f97 100644 --- a/packages/autop/package.json +++ b/packages/autop/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/autop", - "version": "4.11.0", + "version": "4.16.0", "description": "WordPress's automatic paragraph functions `autop` and `removep`.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/autop/tsconfig.json b/packages/autop/tsconfig.json index a09ec7466c435b..f68a855bab79cc 100644 --- a/packages/autop/tsconfig.json +++ b/packages/autop/tsconfig.json @@ -1,10 +1,5 @@ { "$schema": "https://json.schemastore.org/tsconfig.json", "extends": "../../tsconfig.base.json", - "compilerOptions": { - "rootDir": "src", - "declarationDir": "build-types" - }, - "references": [ { "path": "../dom-ready" } ], - "include": [ "src/**/*" ] + "references": [ { "path": "../dom-ready" } ] } diff --git a/packages/babel-plugin-import-jsx-pragma/CHANGELOG.md b/packages/babel-plugin-import-jsx-pragma/CHANGELOG.md index 87bc412fd90eb3..1c4e5fa416687a 100644 --- a/packages/babel-plugin-import-jsx-pragma/CHANGELOG.md +++ b/packages/babel-plugin-import-jsx-pragma/CHANGELOG.md @@ -2,6 +2,16 @@ ## Unreleased +## 5.16.0 (2025-01-15) + +## 5.15.0 (2025-01-02) + +## 5.14.0 (2024-12-11) + +## 5.13.0 (2024-11-27) + +## 5.12.0 (2024-11-16) + ## 5.11.0 (2024-10-30) ## 5.10.0 (2024-10-16) diff --git a/packages/babel-plugin-import-jsx-pragma/package.json b/packages/babel-plugin-import-jsx-pragma/package.json index d9b565b4e27ec0..f27d527c84c697 100644 --- a/packages/babel-plugin-import-jsx-pragma/package.json +++ b/packages/babel-plugin-import-jsx-pragma/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/babel-plugin-import-jsx-pragma", - "version": "5.11.0", + "version": "5.16.0", "description": "Babel transform plugin for automatically injecting an import to be used as the pragma for the React JSX Transform plugin.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/babel-plugin-makepot/CHANGELOG.md b/packages/babel-plugin-makepot/CHANGELOG.md index f643a9304c68ab..73b357e78768dd 100644 --- a/packages/babel-plugin-makepot/CHANGELOG.md +++ b/packages/babel-plugin-makepot/CHANGELOG.md @@ -2,6 +2,16 @@ ## Unreleased +## 6.16.0 (2025-01-15) + +## 6.15.0 (2025-01-02) + +## 6.14.0 (2024-12-11) + +## 6.13.0 (2024-11-27) + +## 6.12.0 (2024-11-16) + ## 6.11.0 (2024-10-30) ## 6.10.0 (2024-10-16) diff --git a/packages/babel-plugin-makepot/index.js b/packages/babel-plugin-makepot/index.js index 0ae8a763d03359..3f24f733cec0d6 100644 --- a/packages/babel-plugin-makepot/index.js +++ b/packages/babel-plugin-makepot/index.js @@ -85,7 +85,7 @@ const REGEXP_TRANSLATOR_COMMENT = /^\s*translators:\s*([\s\S]+)/im; /** * Given an argument node (or recursed node), attempts to return a string - * represenation of that node's value. + * representation of that node's value. * * @param {Object} node AST node. * @@ -265,7 +265,7 @@ module.exports = () => { ); } - // Attempt to exract nplurals from header. + // Attempt to extract nplurals from header. const pluralsMatch = ( baseData.headers[ 'plural-forms' ] || '' ).match( /nplurals\s*=\s*(\d+);/ ); diff --git a/packages/babel-plugin-makepot/package.json b/packages/babel-plugin-makepot/package.json index cf04d3dc72e409..56dc01c4fa9ac6 100644 --- a/packages/babel-plugin-makepot/package.json +++ b/packages/babel-plugin-makepot/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/babel-plugin-makepot", - "version": "6.11.0", + "version": "6.16.0", "description": "WordPress Babel internationalization (i18n) plugin.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/babel-preset-default/CHANGELOG.md b/packages/babel-preset-default/CHANGELOG.md index d6eef13b90d456..08633f5903d957 100644 --- a/packages/babel-preset-default/CHANGELOG.md +++ b/packages/babel-preset-default/CHANGELOG.md @@ -2,6 +2,16 @@ ## Unreleased +## 8.16.0 (2025-01-15) + +## 8.15.0 (2025-01-02) + +## 8.14.0 (2024-12-11) + +## 8.13.0 (2024-11-27) + +## 8.12.0 (2024-11-16) + ## 8.11.0 (2024-10-30) ## 8.10.0 (2024-10-16) diff --git a/packages/babel-preset-default/package.json b/packages/babel-preset-default/package.json index 2f727d1886425d..db0aad976eea5a 100644 --- a/packages/babel-preset-default/package.json +++ b/packages/babel-preset-default/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/babel-preset-default", - "version": "8.11.0", + "version": "8.16.0", "description": "Default Babel preset for WordPress development.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -38,8 +38,8 @@ "@babel/preset-env": "7.25.7", "@babel/preset-typescript": "7.25.7", "@babel/runtime": "7.25.7", - "@wordpress/browserslist-config": "*", - "@wordpress/warning": "*", + "@wordpress/browserslist-config": "file:../browserslist-config", + "@wordpress/warning": "file:../warning", "browserslist": "^4.21.10", "core-js": "^3.31.0", "react": "^18.3.0" diff --git a/packages/babel-preset-default/polyfill-exclusions.js b/packages/babel-preset-default/polyfill-exclusions.js index 507396c930b99c..ca8c045d124146 100644 --- a/packages/babel-preset-default/polyfill-exclusions.js +++ b/packages/babel-preset-default/polyfill-exclusions.js @@ -7,4 +7,25 @@ module.exports = [ // This is an IE-only feature which we don't use, and don't want to polyfill. // @see https://github.com/WordPress/gutenberg/pull/49234 'web.immediate', + // Remove Set feature polyfills. + // + // The Babel/core-js integration has a severe limitation, in that any Set + // objects (e.g. `new Set()`) are assumed to need all instance methods, and + // get them all polyfilled. There is no validation as to whether those + // methods are actually in use. + // + // This limitation causes a number of packages to unnecessarily get a + // dependency on `wp-polyfill`, which in most cases gets loaded as part of + // the critical path and can thus have an impact on performance. + // + // There is no good solution to this, and the one we've opted for here is + // to disable polyfilling these features entirely. Developers will need to + // take care not to use them in scenarios where the code may be running in + // older browsers without native support for them. + // + // These need to be specified as both `es.` and `esnext.` due to the way + // internal dependencies are set up in Babel / core-js. + // + // @see https://github.com/WordPress/gutenberg/pull/67230 + /^es(next)?\.set\./, ]; diff --git a/packages/base-styles/CHANGELOG.md b/packages/base-styles/CHANGELOG.md index 61ff1100ac6453..51a6d9500efe90 100644 --- a/packages/base-styles/CHANGELOG.md +++ b/packages/base-styles/CHANGELOG.md @@ -2,6 +2,16 @@ ## Unreleased +## 5.16.0 (2025-01-15) + +## 5.15.0 (2025-01-02) + +## 5.14.0 (2024-12-11) + +## 5.13.0 (2024-11-27) + +## 5.12.0 (2024-11-16) + ## 5.11.0 (2024-10-30) ## 5.10.0 (2024-10-16) diff --git a/packages/base-styles/_animations.scss b/packages/base-styles/_animations.scss index ae5de9a803008c..728f702ba16303 100644 --- a/packages/base-styles/_animations.scss +++ b/packages/base-styles/_animations.scss @@ -14,10 +14,10 @@ } } - - animation: __wp-base-styles-fade-in $speed $easing $delay; - animation-fill-mode: forwards; - @include reduce-motion("animation"); + @media not (prefers-reduced-motion) { + animation: __wp-base-styles-fade-in $speed $easing $delay; + animation-fill-mode: forwards; + } } @mixin animation__fade-out($speed: 0.08s, $delay: 0s, $easing: linear) { @@ -30,10 +30,10 @@ } } - - animation: __wp-base-styles-fade-out $speed $easing $delay; - animation-fill-mode: forwards; - @include reduce-motion("animation"); + @media not (prefers-reduced-motion) { + animation: __wp-base-styles-fade-out $speed $easing $delay; + animation-fill-mode: forwards; + } } // Deprecated @@ -41,8 +41,3 @@ @warn "The `edit-post__fade-in-animation` mixin is deprecated. Use `animation__fade-in` instead."; @include animation__fade-in($speed, $delay); } - -@mixin editor-canvas-resize-animation($additional-transition-rules...) { - transition: all 400ms cubic-bezier(0.46, 0.03, 0.52, 0.96), $additional-transition-rules; - @include reduce-motion("transition"); -} diff --git a/packages/base-styles/_mixins.scss b/packages/base-styles/_mixins.scss index 65f98bf6f15bfc..9f089b8d9e8322 100644 --- a/packages/base-styles/_mixins.scss +++ b/packages/base-styles/_mixins.scss @@ -18,25 +18,25 @@ @mixin heading-small() { @include _text-heading(); font-size: $font-size-x-small; - line-height: $line-height-x-small; + line-height: $font-line-height-x-small; } @mixin heading-medium() { @include _text-heading(); font-size: $font-size-medium; - line-height: $line-height-small; + line-height: $font-line-height-small; } @mixin heading-large() { @include _text-heading(); font-size: $font-size-large; - line-height: $line-height-small; + line-height: $font-line-height-small; } @mixin heading-x-large() { @include _text-heading(); font-size: $font-size-x-large; - line-height: $line-height-medium; + line-height: $font-line-height-medium; } @mixin heading-2x-large() { @@ -48,25 +48,25 @@ @mixin body-small() { @include _text-body(); font-size: $font-size-small; - line-height: $line-height-x-small; + line-height: $font-line-height-x-small; } @mixin body-medium() { @include _text-body(); font-size: $font-size-medium; - line-height: $line-height-small; + line-height: $font-line-height-small; } @mixin body-large() { @include _text-body(); font-size: $font-size-large; - line-height: $line-height-medium; + line-height: $font-line-height-medium; } @mixin body-x-large() { @include _text-body(); font-size: $font-size-x-large; - line-height: $line-height-x-large; + line-height: $font-line-height-x-large; } /** @@ -141,10 +141,13 @@ // Tabs, Inputs, Square buttons. @mixin input-style__neutral() { box-shadow: 0 0 0 transparent; - transition: box-shadow 0.1s linear; + + @media not (prefers-reduced-motion) { + transition: box-shadow 0.1s linear; + } + border-radius: $radius-small; border: $border-width solid $gray-600; - @include reduce-motion("transition"); } diff --git a/packages/base-styles/_variables.scss b/packages/base-styles/_variables.scss index ec0bdf91f2489d..562a568084c812 100644 --- a/packages/base-styles/_variables.scss +++ b/packages/base-styles/_variables.scss @@ -145,7 +145,7 @@ $radio-input-size: 16px; $radio-input-size-sm: 24px; // Width & height for small viewports. // Deprecated, please avoid using these. -$block-padding: 14px; // Used to define space between block footprint and surrouding borders. +$block-padding: 14px; // Used to define space between block footprint and surrounding borders. $radius-block-ui: $radius-small; $shadow-popover: $elevation-x-small; $shadow-modal: $elevation-large; diff --git a/packages/base-styles/_z-index.scss b/packages/base-styles/_z-index.scss index e4d6ce4ce1b1c9..6f7e3f2f13c309 100644 --- a/packages/base-styles/_z-index.scss +++ b/packages/base-styles/_z-index.scss @@ -25,7 +25,6 @@ $z-layers: ( ".components-popover__close": 5, ".block-editor-block-list__insertion-point": 6, ".block-editor-warning": 5, - ".block-library-gallery-item__inline-menu": 20, ".block-editor-url-input__suggestions": 30, ".edit-post-layout__footer": 30, ".interface-interface-skeleton__header": 30, @@ -123,7 +122,7 @@ $z-layers: ( // Should be above the popover (dropdown) ".reusable-blocks-menu-items__convert-modal": 1000001, ".patterns-menu-items__convert-modal": 1000001, - ".editor-create-template-part-modal": 1000001, + ".fields-create-template-part-modal": 1000001, ".block-editor-block-lock-modal": 1000001, ".block-editor-template-part__selection-modal": 1000001, ".block-editor-block-rename-modal": 1000001, @@ -132,6 +131,7 @@ $z-layers: ( ".editor-action-modal": 1000001, ".editor-post-template__swap-template-modal": 1000001, ".edit-site-template-panel__replace-template-modal": 1000001, + ".fields-controls__template-modal": 1000001, // Note: The ConfirmDialog component's z-index is being set to 1000001 in packages/components/src/confirm-dialog/styles.ts // because it uses emotion and not sass. We need it to render on top its parent popover. @@ -165,9 +165,9 @@ $z-layers: ( ".components-resizable-box__corner-handle": 2, // Make sure block manager sticky category titles appear above the options - ".editor-block-manager__category-title": 1, + ".block-editor-block-manager__category-title": 1, // And block manager sticky disabled block count is higher still - ".editor-block-manager__disabled-blocks-count": 2, + ".block-editor-block-manager__disabled-blocks-count": 2, // Needs to appear below other color circular picker related UI elements. ".components-circular-option-picker__option-wrapper::before": -1, diff --git a/packages/base-styles/package.json b/packages/base-styles/package.json index 7246dc83eaf4c6..2ff26eb2f475d2 100644 --- a/packages/base-styles/package.json +++ b/packages/base-styles/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/base-styles", - "version": "5.11.0", + "version": "5.16.0", "description": "Base SCSS utilities and variables for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/blob/CHANGELOG.md b/packages/blob/CHANGELOG.md index 660c60776c7bd1..126b56cd813503 100644 --- a/packages/blob/CHANGELOG.md +++ b/packages/blob/CHANGELOG.md @@ -2,6 +2,16 @@ ## Unreleased +## 4.16.0 (2025-01-15) + +## 4.15.0 (2025-01-02) + +## 4.14.0 (2024-12-11) + +## 4.13.0 (2024-11-27) + +## 4.12.0 (2024-11-16) + ## 4.11.0 (2024-10-30) ## 4.10.0 (2024-10-16) diff --git a/packages/blob/package.json b/packages/blob/package.json index 0dc01ac7198f59..d2dd0c206892f7 100644 --- a/packages/blob/package.json +++ b/packages/blob/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/blob", - "version": "4.11.0", + "version": "4.16.0", "description": "Blob utilities for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/blob/tsconfig.json b/packages/blob/tsconfig.json index 6e33d8ff82d47e..7ff060ab6ce105 100644 --- a/packages/blob/tsconfig.json +++ b/packages/blob/tsconfig.json @@ -1,9 +1,4 @@ { "$schema": "https://json.schemastore.org/tsconfig.json", - "extends": "../../tsconfig.base.json", - "compilerOptions": { - "rootDir": "src", - "declarationDir": "build-types" - }, - "include": [ "src/**/*" ] + "extends": "../../tsconfig.base.json" } diff --git a/packages/block-directory/CHANGELOG.md b/packages/block-directory/CHANGELOG.md index 53636f34dad586..80b139c9a966d0 100644 --- a/packages/block-directory/CHANGELOG.md +++ b/packages/block-directory/CHANGELOG.md @@ -2,6 +2,16 @@ ## Unreleased +## 5.16.0 (2025-01-15) + +## 5.15.0 (2025-01-02) + +## 5.14.0 (2024-12-11) + +## 5.13.0 (2024-11-27) + +## 5.12.0 (2024-11-16) + ## 5.11.0 (2024-10-30) ## 5.10.0 (2024-10-16) diff --git a/packages/block-directory/package.json b/packages/block-directory/package.json index 18a40824aa4754..e1f4cc0cefa988 100644 --- a/packages/block-directory/package.json +++ b/packages/block-directory/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/block-directory", - "version": "5.11.0", + "version": "5.16.0", "description": "Extend editor with block directory features to search, download and install blocks.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -28,24 +28,24 @@ "wpScript": true, "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/a11y": "*", - "@wordpress/api-fetch": "*", - "@wordpress/block-editor": "*", - "@wordpress/blocks": "*", - "@wordpress/components": "*", - "@wordpress/compose": "*", - "@wordpress/core-data": "*", - "@wordpress/data": "*", - "@wordpress/editor": "*", - "@wordpress/element": "*", - "@wordpress/hooks": "*", - "@wordpress/html-entities": "*", - "@wordpress/i18n": "*", - "@wordpress/icons": "*", - "@wordpress/notices": "*", - "@wordpress/plugins": "*", - "@wordpress/private-apis": "*", - "@wordpress/url": "*", + "@wordpress/a11y": "file:../a11y", + "@wordpress/api-fetch": "file:../api-fetch", + "@wordpress/block-editor": "file:../block-editor", + "@wordpress/blocks": "file:../blocks", + "@wordpress/components": "file:../components", + "@wordpress/compose": "file:../compose", + "@wordpress/core-data": "file:../core-data", + "@wordpress/data": "file:../data", + "@wordpress/editor": "file:../editor", + "@wordpress/element": "file:../element", + "@wordpress/hooks": "file:../hooks", + "@wordpress/html-entities": "file:../html-entities", + "@wordpress/i18n": "file:../i18n", + "@wordpress/icons": "file:../icons", + "@wordpress/notices": "file:../notices", + "@wordpress/plugins": "file:../plugins", + "@wordpress/private-apis": "file:../private-apis", + "@wordpress/url": "file:../url", "change-case": "^4.1.2", "clsx": "^2.1.1" }, diff --git a/packages/block-directory/src/components/downloadable-block-list-item/style.scss b/packages/block-directory/src/components/downloadable-block-list-item/style.scss index e13e46ef8d8786..e30722c88ee791 100644 --- a/packages/block-directory/src/components/downloadable-block-list-item/style.scss +++ b/packages/block-directory/src/components/downloadable-block-list-item/style.scss @@ -15,7 +15,10 @@ background: none; border: 0; text-align: left; - transition: box-shadow 0.1s linear; + + @media not (prefers-reduced-motion) { + transition: box-shadow 0.1s linear; + } // The item contains absolutely positioned items. // Set `position: relative` on the parent to prevent overflow issues diff --git a/packages/block-directory/src/store/resolvers.js b/packages/block-directory/src/store/resolvers.js index cd13ba5ccd7134..b697b1dd630297 100644 --- a/packages/block-directory/src/store/resolvers.js +++ b/packages/block-directory/src/store/resolvers.js @@ -35,5 +35,7 @@ export const getDownloadableBlocks = ); dispatch( receiveDownloadableBlocks( blocks, filterValue ) ); - } catch {} + } catch { + dispatch( receiveDownloadableBlocks( [], filterValue ) ); + } }; diff --git a/packages/block-editor/CHANGELOG.md b/packages/block-editor/CHANGELOG.md index 6755c4ec83f215..96ecfe5d0e629c 100644 --- a/packages/block-editor/CHANGELOG.md +++ b/packages/block-editor/CHANGELOG.md @@ -2,6 +2,16 @@ ## Unreleased +## 14.11.0 (2025-01-15) + +## 14.10.0 (2025-01-02) + +## 14.9.0 (2024-12-11) + +## 14.8.0 (2024-11-27) + +## 14.7.0 (2024-11-16) + ## 14.6.0 (2024-10-30) ## 14.5.0 (2024-10-16) @@ -93,7 +103,7 @@ ### Enhancements -- Embed the `ObserveTyping` behavior within the `BlockList` component making to simplify instanciations of third-party block editors. +- Embed the `ObserveTyping` behavior within the `BlockList` component making to simplify instantiations of third-party block editors. ## 12.8.0 (2023-08-16) diff --git a/packages/block-editor/README.md b/packages/block-editor/README.md index a0f75683914408..8fe2c5f1179dcd 100644 --- a/packages/block-editor/README.md +++ b/packages/block-editor/README.md @@ -659,6 +659,14 @@ _Related_ - +### LinkControl + +Renders a link control. A link control is a controlled input which maintains a value associated with a link (HTML anchor element) and relevant settings for how that link is expected to behave. + +_Parameters_ + +- _props_ `WPLinkControlProps`: Component props. + ### MediaPlaceholder _Related_ @@ -705,10 +713,50 @@ Undocumented declaration. ### PlainText +Render an auto-growing textarea allow users to fill any textual content. + _Related_ - +_Usage_ + +```jsx +import { registerBlockType } from '@wordpress/blocks'; +import { PlainText } from '@wordpress/block-editor'; + +registerBlockType( 'my-plugin/example-block', { + // ... + + attributes: { + content: { + type: 'string', + }, + }, + + edit( { className, attributes, setAttributes } ) { + return ( + setAttributes( { content } ) } + /> + ); + }, +} ); +``` + +_Parameters_ + +- _props_ `Object`: Component props. +- _props.value_ `string`: String value of the textarea. +- _props.onChange_ `Function`: Function called when the text value changes. +- _props.ref_ `[Object]`: The component forwards the `ref` property to the `TextareaAutosize` component. + +_Returns_ + +- `Element`: Plain text component + ### privateApis Private @wordpress/block-editor APIs. diff --git a/packages/block-editor/package.json b/packages/block-editor/package.json index b4672bc57690eb..0a5691666dc72d 100644 --- a/packages/block-editor/package.json +++ b/packages/block-editor/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/block-editor", - "version": "14.6.0", + "version": "14.11.0", "description": "Generic block editor.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -37,37 +37,38 @@ "@emotion/react": "^11.7.1", "@emotion/styled": "^11.6.0", "@react-spring/web": "^9.4.5", - "@wordpress/a11y": "*", - "@wordpress/api-fetch": "*", - "@wordpress/blob": "*", - "@wordpress/block-serialization-default-parser": "*", - "@wordpress/blocks": "*", - "@wordpress/commands": "*", - "@wordpress/components": "*", - "@wordpress/compose": "*", - "@wordpress/data": "*", - "@wordpress/date": "*", - "@wordpress/deprecated": "*", - "@wordpress/dom": "*", - "@wordpress/element": "*", - "@wordpress/escape-html": "*", - "@wordpress/hooks": "*", - "@wordpress/html-entities": "*", - "@wordpress/i18n": "*", - "@wordpress/icons": "*", - "@wordpress/is-shallow-equal": "*", - "@wordpress/keyboard-shortcuts": "*", - "@wordpress/keycodes": "*", - "@wordpress/notices": "*", - "@wordpress/preferences": "*", - "@wordpress/priority-queue": "*", - "@wordpress/private-apis": "*", - "@wordpress/rich-text": "*", - "@wordpress/style-engine": "*", - "@wordpress/token-list": "*", - "@wordpress/url": "*", - "@wordpress/warning": "*", - "@wordpress/wordcount": "*", + "@wordpress/a11y": "file:../a11y", + "@wordpress/api-fetch": "file:../api-fetch", + "@wordpress/blob": "file:../blob", + "@wordpress/block-serialization-default-parser": "file:../block-serialization-default-parser", + "@wordpress/blocks": "file:../blocks", + "@wordpress/commands": "file:../commands", + "@wordpress/components": "file:../components", + "@wordpress/compose": "file:../compose", + "@wordpress/data": "file:../data", + "@wordpress/date": "file:../date", + "@wordpress/deprecated": "file:../deprecated", + "@wordpress/dom": "file:../dom", + "@wordpress/element": "file:../element", + "@wordpress/escape-html": "file:../escape-html", + "@wordpress/hooks": "file:../hooks", + "@wordpress/html-entities": "file:../html-entities", + "@wordpress/i18n": "file:../i18n", + "@wordpress/icons": "file:../icons", + "@wordpress/is-shallow-equal": "file:../is-shallow-equal", + "@wordpress/keyboard-shortcuts": "file:../keyboard-shortcuts", + "@wordpress/keycodes": "file:../keycodes", + "@wordpress/notices": "file:../notices", + "@wordpress/preferences": "file:../preferences", + "@wordpress/priority-queue": "file:../priority-queue", + "@wordpress/private-apis": "file:../private-apis", + "@wordpress/rich-text": "file:../rich-text", + "@wordpress/style-engine": "file:../style-engine", + "@wordpress/token-list": "file:../token-list", + "@wordpress/upload-media": "file:../upload-media", + "@wordpress/url": "file:../url", + "@wordpress/warning": "file:../warning", + "@wordpress/wordcount": "file:../wordcount", "change-case": "^4.1.2", "clsx": "^2.1.1", "colord": "^2.7.0", diff --git a/packages/block-editor/src/autocompleters/block.js b/packages/block-editor/src/autocompleters/block.js index 5fc107c4d3d69e..eedd5e102db2ca 100644 --- a/packages/block-editor/src/autocompleters/block.js +++ b/packages/block-editor/src/autocompleters/block.js @@ -23,12 +23,10 @@ import { orderInserterBlockItems } from '../utils/order-inserter-block-items'; const noop = () => {}; const SHOWN_BLOCK_TYPES = 9; -/** @typedef {import('@wordpress/components').WPCompleter} WPCompleter */ - /** * Creates a blocks repeater for replacing the current block with a selected block type. * - * @return {WPCompleter} A blocks completer. + * @return {Object} A blocks completer. */ function createBlockCompleter() { return { @@ -157,6 +155,6 @@ function createBlockCompleter() { /** * Creates a blocks repeater for replacing the current block with a selected block type. * - * @return {WPCompleter} A blocks completer. + * @return {Object} A blocks completer. */ export default createBlockCompleter(); diff --git a/packages/block-editor/src/autocompleters/link.js b/packages/block-editor/src/autocompleters/link.js index fb64cb151294d6..9088b65b4abc86 100644 --- a/packages/block-editor/src/autocompleters/link.js +++ b/packages/block-editor/src/autocompleters/link.js @@ -10,12 +10,10 @@ import { decodeEntities } from '@wordpress/html-entities'; const SHOWN_SUGGESTIONS = 10; -/** @typedef {import('@wordpress/components').WPCompleter} WPCompleter */ - /** * Creates a suggestion list for links to posts or pages. * - * @return {WPCompleter} A links completer. + * @return {Object} A links completer. */ function createLinkCompleter() { return { @@ -60,6 +58,6 @@ function createLinkCompleter() { /** * Creates a suggestion list for links to posts or pages.. * - * @return {WPCompleter} A link completer. + * @return {Object} A link completer. */ export default createLinkCompleter(); diff --git a/packages/block-editor/src/components/alignment-control/stories/aliginment-toolbar.story.js b/packages/block-editor/src/components/alignment-control/stories/aliginment-toolbar.story.js new file mode 100644 index 00000000000000..9d029c30b48466 --- /dev/null +++ b/packages/block-editor/src/components/alignment-control/stories/aliginment-toolbar.story.js @@ -0,0 +1,47 @@ +/** + * WordPress dependencies + */ +import { useState } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import { AlignmentToolbar } from '..'; + +/** + * The `AlignmentToolbar` component renders a dropdown menu that displays alignment options for the selected block in `Toolbar`. + */ +const meta = { + title: 'BlockEditor/AlignmentToolbar', + component: AlignmentToolbar, + argTypes: { + value: { + control: false, + defaultValue: 'undefined', + description: 'The current value of the alignment setting.', + }, + onChange: { + action: 'onChange', + control: false, + description: + "A callback function invoked when the toolbar's alignment value is changed via an interaction with any of the toolbar's buttons. Called with the new alignment value (ie: `left`, `center`, `right`, `undefined`) as the only argument.", + }, + }, +}; +export default meta; + +export const Default = { + render: function Template( { onChange, ...args } ) { + const [ value, setValue ] = useState(); + return ( + <AlignmentToolbar + { ...args } + onChange={ ( ...changeArgs ) => { + onChange( ...changeArgs ); + setValue( ...changeArgs ); + } } + value={ value } + /> + ); + }, +}; diff --git a/packages/block-editor/src/components/alignment-control/stories/index.story.js b/packages/block-editor/src/components/alignment-control/stories/index.story.js new file mode 100644 index 00000000000000..165f9343e1710b --- /dev/null +++ b/packages/block-editor/src/components/alignment-control/stories/index.story.js @@ -0,0 +1,51 @@ +/** + * WordPress dependencies + */ +import { useState } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import { AlignmentControl } from '../'; + +/** + * The `AlignmentControl` component renders a dropdown menu that displays alignment options for the selected block. + * + * This component is mostly used for blocks that display text, such as Heading, Paragraph, Post Author, Post Comments, Verse, Quote, Post Title, etc... And the available alignment options are `left`, `center` or `right` alignment. + * + * If you want to use the alignment control in a toolbar, you should use the `AlignmentToolbar` component instead. + */ +const meta = { + title: 'BlockEditor/AlignmentControl', + component: AlignmentControl, + argTypes: { + value: { + control: false, + defaultValue: 'undefined', + description: 'The current value of the alignment setting.', + }, + onChange: { + action: 'onChange', + control: false, + description: + "A callback function invoked when the toolbar's alignment value is changed via an interaction with any of the toolbar's buttons. Called with the new alignment value (ie: `left`, `center`, `right`, `undefined`) as the only argument.", + }, + }, +}; +export default meta; + +export const Default = { + render: function Template( { onChange, ...args } ) { + const [ value, setValue ] = useState(); + return ( + <AlignmentControl + { ...args } + onChange={ ( ...changeArgs ) => { + onChange( ...changeArgs ); + setValue( ...changeArgs ); + } } + value={ value } + /> + ); + }, +}; diff --git a/packages/block-editor/src/components/alignment-control/test/__snapshots__/index.js.snap b/packages/block-editor/src/components/alignment-control/test/__snapshots__/index.js.snap index f2915ead7417b1..c1383ae8ecc44a 100644 --- a/packages/block-editor/src/components/alignment-control/test/__snapshots__/index.js.snap +++ b/packages/block-editor/src/components/alignment-control/test/__snapshots__/index.js.snap @@ -12,7 +12,7 @@ exports[`AlignmentUI should allow custom alignment controls to be specified 1`] align="custom-left" aria-label="My custom left" aria-pressed="false" - class="components-button components-toolbar__control has-icon" + class="components-button components-toolbar__control is-compact has-icon" data-toolbar-item="true" type="button" > @@ -35,7 +35,7 @@ exports[`AlignmentUI should allow custom alignment controls to be specified 1`] align="custom-right" aria-label="My custom right" aria-pressed="true" - class="components-button components-toolbar__control is-pressed has-icon" + class="components-button components-toolbar__control is-compact is-pressed has-icon" data-toolbar-item="true" type="button" > @@ -100,7 +100,7 @@ exports[`AlignmentUI should match snapshot when controls are visible 1`] = ` align="left" aria-label="Align text left" aria-pressed="true" - class="components-button components-toolbar__control is-pressed has-icon" + class="components-button components-toolbar__control is-compact is-pressed has-icon" data-toolbar-item="true" type="button" > @@ -123,7 +123,7 @@ exports[`AlignmentUI should match snapshot when controls are visible 1`] = ` align="center" aria-label="Align text center" aria-pressed="false" - class="components-button components-toolbar__control has-icon" + class="components-button components-toolbar__control is-compact has-icon" data-toolbar-item="true" type="button" > @@ -146,7 +146,7 @@ exports[`AlignmentUI should match snapshot when controls are visible 1`] = ` align="right" aria-label="Align text right" aria-pressed="false" - class="components-button components-toolbar__control has-icon" + class="components-button components-toolbar__control is-compact has-icon" data-toolbar-item="true" type="button" > diff --git a/packages/block-editor/src/components/audio-player/index.native.js b/packages/block-editor/src/components/audio-player/index.native.js index bee31ea5872ef5..734226408cb923 100644 --- a/packages/block-editor/src/components/audio-player/index.native.js +++ b/packages/block-editor/src/components/audio-player/index.native.js @@ -17,7 +17,7 @@ import { View } from '@wordpress/primitives'; import { Icon } from '@wordpress/components'; import { withPreferredColorScheme } from '@wordpress/compose'; import { __ } from '@wordpress/i18n'; -import { audio, warning } from '@wordpress/icons'; +import { audio, cautionFilled } from '@wordpress/icons'; import { requestImageFailedRetryDialog, requestImageUploadCancelDialog, @@ -167,7 +167,7 @@ function Player( { <View style={ styles.subtitleContainer }> { isUploadFailed && ( <Icon - icon={ warning } + icon={ cautionFilled } style={ { ...styles.errorIcon, ...uploadFailedStyle, diff --git a/packages/block-editor/src/components/background-image-control/index.js b/packages/block-editor/src/components/background-image-control/index.js index 2703aa3988d64e..3411d7d3ee8a96 100644 --- a/packages/block-editor/src/components/background-image-control/index.js +++ b/packages/block-editor/src/components/background-image-control/index.js @@ -24,6 +24,7 @@ import { Placeholder, Spinner, __experimentalDropdownContentWrapper as DropdownContentWrapper, + Button, } from '@wordpress/components'; import { __, _x, sprintf } from '@wordpress/i18n'; import { store as noticesStore } from '@wordpress/notices'; @@ -377,7 +378,9 @@ function BackgroundImageControls( { label={ imgLabel } /> } - variant="secondary" + renderToggle={ ( props ) => ( + <Button { ...props } __next40pxDefaultSize /> + ) } onError={ onUploadError } onReset={ () => { closeAndFocus(); diff --git a/packages/block-editor/src/components/background-image-control/style.scss b/packages/block-editor/src/components/background-image-control/style.scss index cde8044c24c121..b9c94916039c44 100644 --- a/packages/block-editor/src/components/background-image-control/style.scss +++ b/packages/block-editor/src/components/background-image-control/style.scss @@ -23,7 +23,10 @@ .components-dropdown { display: block; - height: 36px; + + .block-editor-global-styles-background-panel__dropdown-toggle { + height: 40px; + } } } @@ -44,7 +47,6 @@ .components-dropdown { display: block; - height: 36px; } button.components-button { diff --git a/packages/block-editor/src/components/block-actions/index.js b/packages/block-editor/src/components/block-actions/index.js index f06c8addedad50..30038522df0edb 100644 --- a/packages/block-editor/src/components/block-actions/index.js +++ b/packages/block-editor/src/components/block-actions/index.js @@ -11,7 +11,6 @@ import { /** * Internal dependencies */ -import { useNotifyCopy } from '../../utils/use-notify-copy'; import usePasteStyles from '../use-paste-styles'; import { store as blockEditorStore } from '../../store'; @@ -76,7 +75,6 @@ export default function BlockActions( { flashBlock, } = useDispatch( blockEditorStore ); - const notifyCopy = useNotifyCopy(); const pasteStyles = usePasteStyles(); return children( { @@ -130,7 +128,6 @@ export default function BlockActions( { if ( clientIds.length === 1 ) { flashBlock( clientIds[ 0 ] ); } - notifyCopy( 'copy', clientIds ); }, async onPasteStyles() { await pasteStyles( getBlocksByClientId( clientIds ) ); diff --git a/packages/block-editor/src/components/block-alignment-control/test/__snapshots__/index.js.snap b/packages/block-editor/src/components/block-alignment-control/test/__snapshots__/index.js.snap index 246e5dca2ae324..9e7e59c0c31447 100644 --- a/packages/block-editor/src/components/block-alignment-control/test/__snapshots__/index.js.snap +++ b/packages/block-editor/src/components/block-alignment-control/test/__snapshots__/index.js.snap @@ -42,7 +42,7 @@ exports[`BlockAlignmentUI should match snapshot when controls are visible 1`] = <button aria-label="None" aria-pressed="false" - class="components-button components-toolbar__control has-icon" + class="components-button components-toolbar__control is-compact has-icon" data-toolbar-item="true" type="button" > @@ -64,7 +64,7 @@ exports[`BlockAlignmentUI should match snapshot when controls are visible 1`] = <button aria-label="Align left" aria-pressed="true" - class="components-button components-toolbar__control is-pressed has-icon" + class="components-button components-toolbar__control is-compact is-pressed has-icon" data-toolbar-item="true" type="button" > @@ -86,7 +86,7 @@ exports[`BlockAlignmentUI should match snapshot when controls are visible 1`] = <button aria-label="Align center" aria-pressed="false" - class="components-button components-toolbar__control has-icon" + class="components-button components-toolbar__control is-compact has-icon" data-toolbar-item="true" type="button" > @@ -108,7 +108,7 @@ exports[`BlockAlignmentUI should match snapshot when controls are visible 1`] = <button aria-label="Align right" aria-pressed="false" - class="components-button components-toolbar__control has-icon" + class="components-button components-toolbar__control is-compact has-icon" data-toolbar-item="true" type="button" > diff --git a/packages/block-editor/src/components/block-alignment-matrix-control/README.md b/packages/block-editor/src/components/block-alignment-matrix-control/README.md index dfb38e15964124..b4267d68fe1fdc 100644 --- a/packages/block-editor/src/components/block-alignment-matrix-control/README.md +++ b/packages/block-editor/src/components/block-alignment-matrix-control/README.md @@ -41,13 +41,36 @@ const controls = ( /> </BlockControls> </> -} +); ``` ### Props -| Name | Type | Default | Description | -| ---------- | ---------- | ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | -| `label` | `string` | `Change matrix alignment` | concise description of tool's functionality. | -| `onChange` | `function` | `noop` | the function to execute upon a user's change of the matrix state | -| `value` | `string` | `center` | describes the content alignment location and can be `top`, `right`, `bottom`, `left`, `topRight`, `bottomRight`, `bottomLeft`, `topLeft` | +### `label` + +- **Type:** `string` +- **Default:** `'Change matrix alignment'` + +Label for the control. + +### `onChange` + +- **Type:** `Function` +- **Default:** `noop` + +Function to execute upon a user's change of the matrix state. + +### `value` + +- **Type:** `string` +- **Default:** `'center'` +- **Options:** `'center'`, `'center center'`, `'center left'`, `'center right'`, `'top center'`, `'top left'`, `'top right'`, `'bottom center'`, `'bottom left'`, `'bottom right'` + +Content alignment location. + +### `isDisabled` + +- **Type:** `boolean` +- **Default:** `false` + +Whether the control should be disabled. \ No newline at end of file diff --git a/packages/block-editor/src/components/block-alignment-matrix-control/index.js b/packages/block-editor/src/components/block-alignment-matrix-control/index.js index cdec41dfc7b978..fef7b424fdc947 100644 --- a/packages/block-editor/src/components/block-alignment-matrix-control/index.js +++ b/packages/block-editor/src/components/block-alignment-matrix-control/index.js @@ -11,6 +11,37 @@ import { const noop = () => {}; +/** + * The alignment matrix control allows users to quickly adjust inner block alignment. + * + * @see https://github.com/WordPress/gutenberg/blob/HEAD/packages/block-editor/src/components/block-alignment-matrix-control/README.md + * + * @example + * ```jsx + * function Example() { + * return ( + * <BlockControls> + * <BlockAlignmentMatrixControl + * label={ __( 'Change content position' ) } + * value="center" + * onChange={ ( nextPosition ) => + * setAttributes( { contentPosition: nextPosition } ) + * } + * /> + * </BlockControls> + * ); + * } + * ``` + * + * @param {Object} props Component props. + * @param {string} props.label Label for the control. Defaults to 'Change matrix alignment'. + * @param {Function} props.onChange Function to execute upon change of matrix state. + * @param {string} props.value Content alignment location. One of: 'center', 'center center', + * 'center left', 'center right', 'top center', 'top left', + * 'top right', 'bottom center', 'bottom left', 'bottom right'. + * @param {boolean} props.isDisabled Whether the control should be disabled. + * @return {Element} The BlockAlignmentMatrixControl component. + */ function BlockAlignmentMatrixControl( props ) { const { label = __( 'Change matrix alignment' ), diff --git a/packages/block-editor/src/components/block-alignment-matrix-control/stories/index.story.js b/packages/block-editor/src/components/block-alignment-matrix-control/stories/index.story.js new file mode 100644 index 00000000000000..c2e1d27ea55b9f --- /dev/null +++ b/packages/block-editor/src/components/block-alignment-matrix-control/stories/index.story.js @@ -0,0 +1,78 @@ +/** + * WordPress dependencies + */ +import { useState } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import BlockAlignmentMatrixControl from '../'; + +const meta = { + title: 'BlockEditor/BlockAlignmentMatrixControl', + component: BlockAlignmentMatrixControl, + parameters: { + docs: { + canvas: { sourceState: 'shown' }, + description: { + component: + 'Renders a control for selecting block alignment using a matrix of alignment options.', + }, + }, + }, + argTypes: { + label: { + control: 'text', + table: { + type: { summary: 'string' }, + defaultValue: { summary: "'Change matrix alignment'" }, + }, + description: 'Label for the control.', + }, + onChange: { + action: 'onChange', + control: { type: null }, + table: { + type: { summary: 'function' }, + defaultValue: { summary: '() => {}' }, + }, + description: + "Function to execute upon a user's change of the matrix state.", + }, + isDisabled: { + control: 'boolean', + table: { + type: { summary: 'boolean' }, + defaultValue: { summary: 'false' }, + }, + description: 'Whether the control should be disabled.', + }, + value: { + control: { type: null }, + table: { + type: { summary: 'string' }, + defaultValue: { summary: "'center'" }, + }, + description: 'Content alignment location.', + }, + }, +}; + +export default meta; + +export const Default = { + render: function Template( { onChange, ...args } ) { + const [ value, setValue ] = useState(); + + return ( + <BlockAlignmentMatrixControl + { ...args } + value={ value } + onChange={ ( ...changeArgs ) => { + onChange( ...changeArgs ); + setValue( ...changeArgs ); + } } + /> + ); + }, +}; diff --git a/packages/block-editor/src/components/block-canvas/index.js b/packages/block-editor/src/components/block-canvas/index.js index c399f38054ed4d..36aca7fa1c7220 100644 --- a/packages/block-editor/src/components/block-canvas/index.js +++ b/packages/block-editor/src/components/block-canvas/index.js @@ -56,7 +56,8 @@ export function ExperimentalBlockCanvas( { return ( <BlockTools __unstableContentRef={ localRef } - style={ { height, display: 'flex' } } + className="block-editor-block-canvas" + style={ { height } } > <EditorStyles styles={ styles } @@ -67,10 +68,6 @@ export function ExperimentalBlockCanvas( { ref={ contentRef } className="editor-styles-wrapper" tabIndex={ -1 } - style={ { - height: '100%', - width: '100%', - } } > { children } </WritingFlow> @@ -81,6 +78,7 @@ export function ExperimentalBlockCanvas( { return ( <BlockTools __unstableContentRef={ localRef } + className="block-editor-block-canvas" style={ { height, display: 'flex' } } > <Iframe diff --git a/packages/block-editor/src/components/block-canvas/style.scss b/packages/block-editor/src/components/block-canvas/style.scss index 8f6064de0b615c..ea54646e64a59a 100644 --- a/packages/block-editor/src/components/block-canvas/style.scss +++ b/packages/block-editor/src/components/block-canvas/style.scss @@ -4,6 +4,7 @@ iframe[name="editor-canvas"] { height: 100%; display: block; // Handles transitions between device previews - @include editor-canvas-resize-animation; + transition: all 400ms cubic-bezier(0.46, 0.03, 0.52, 0.96); + @include reduce-motion("transition"); background-color: $gray-300; } diff --git a/packages/block-editor/src/components/block-card/README.md b/packages/block-editor/src/components/block-card/README.md index 216cf4e3865a04..79a42bc20df74a 100644 --- a/packages/block-editor/src/components/block-card/README.md +++ b/packages/block-editor/src/components/block-card/README.md @@ -21,6 +21,7 @@ const MyBlockCard = () => ( icon={ paragraph } title="Paragraph" description="Start with the basic building block of all narrative." + name="Custom Block" /> ); ``` @@ -45,6 +46,12 @@ The title of the block. The description of the block. +#### name + +- **Type:** `String` + +The custom name of the block. + ## Related components Block Editor components are components that can be used to compose the UI of your block editor. Thus, they can only be used under a [`BlockEditorProvider`](https://github.com/WordPress/gutenberg/blob/HEAD/packages/block-editor/src/components/provider/README.md) in the components tree. diff --git a/packages/block-editor/src/components/block-card/index.js b/packages/block-editor/src/components/block-card/index.js index c8a12a3be5ef6a..525a594702e301 100644 --- a/packages/block-editor/src/components/block-card/index.js +++ b/packages/block-editor/src/components/block-card/index.js @@ -6,22 +6,55 @@ import clsx from 'clsx'; /** * WordPress dependencies */ -import deprecated from '@wordpress/deprecated'; import { Button, __experimentalText as Text, __experimentalVStack as VStack, + privateApis as componentsPrivateApis, } from '@wordpress/components'; +import { useDispatch, useSelect } from '@wordpress/data'; +import deprecated from '@wordpress/deprecated'; +import { __, isRTL } from '@wordpress/i18n'; import { chevronLeft, chevronRight } from '@wordpress/icons'; -import { __, _x, isRTL, sprintf } from '@wordpress/i18n'; -import { useSelect, useDispatch } from '@wordpress/data'; /** * Internal dependencies */ -import BlockIcon from '../block-icon'; +import { unlock } from '../../lock-unlock'; import { store as blockEditorStore } from '../../store'; +import BlockIcon from '../block-icon'; +const { Badge } = unlock( componentsPrivateApis ); + +/** + * A card component that displays block information including title, icon, and description. + * Can be used to show block metadata and navigation controls for parent blocks. + * + * @see https://github.com/WordPress/gutenberg/blob/HEAD/packages/block-editor/src/components/block-card/README.md + * + * @example + * ```jsx + * function Example() { + * return ( + * <BlockCard + * title="My Block" + * icon="smiley" + * description="A simple block example" + * name="Custom Block" + * /> + * ); + * } + * ``` + * + * @param {Object} props Component props. + * @param {string} props.title The title of the block. + * @param {string|Object} props.icon The icon of the block. This can be any of [WordPress' Dashicons](https://developer.wordpress.org/resource/dashicons/), or a custom `svg` element. + * @param {string} props.description The description of the block. + * @param {Object} [props.blockType] Deprecated: Object containing block type data. + * @param {string} [props.className] Additional classes to apply to the card. + * @param {string} [props.name] Custom block name to display before the title. + * @return {Element} Block card component. + */ function BlockCard( { title, icon, description, blockType, className, name } ) { if ( blockType ) { deprecated( '`blockType` property in `BlockCard component`', { @@ -66,14 +99,10 @@ function BlockCard( { title, icon, description, blockType, className, name } ) { <BlockIcon icon={ icon } showColors /> <VStack spacing={ 1 }> <h2 className="block-editor-block-card__title"> - { name?.length - ? sprintf( - // translators: 1: Custom block name. 2: Block title. - _x( '%1$s (%2$s)', 'block label' ), - name, - title - ) - : title } + <span className="block-editor-block-card__name"> + { !! name?.length ? name : title } + </span> + { !! name?.length && <Badge>{ title }</Badge> } </h2> { description && ( <Text className="block-editor-block-card__description"> diff --git a/packages/block-editor/src/components/block-card/style.scss b/packages/block-editor/src/components/block-card/style.scss index 42cf77aa4b0a84..a5cb675597908b 100644 --- a/packages/block-editor/src/components/block-card/style.scss +++ b/packages/block-editor/src/components/block-card/style.scss @@ -7,15 +7,22 @@ .block-editor-block-card__title { font-weight: 500; + display: flex; + align-items: center; + flex-wrap: wrap; + gap: calc($grid-unit-10 / 2) $grid-unit-10; &.block-editor-block-card__title { font-size: $default-font-size; line-height: $default-line-height; margin: 0; - padding: 3px 0; // This makes the title as high as the icon. } } +.block-editor-block-card__name { + padding: 3px 0; // This makes the title as high as the icon. +} + .block-editor-block-card .block-editor-block-icon { flex: 0 0 $button-size-small; margin-left: 0; @@ -27,3 +34,4 @@ .block-editor-block-card.is-synced .block-editor-block-icon { color: var(--wp-block-synced-color); } + diff --git a/packages/block-editor/src/components/block-controls/slot.js b/packages/block-editor/src/components/block-controls/slot.js index ad800b49ab40db..9548b3e5cb0de8 100644 --- a/packages/block-editor/src/components/block-controls/slot.js +++ b/packages/block-editor/src/components/block-controls/slot.js @@ -31,9 +31,10 @@ export default function BlockControlsSlot( { group = 'default', ...props } ) { [ toolbarState, contextState ] ); - const Slot = groups[ group ]?.Slot; - const fills = useSlotFills( Slot?.__unstableName ); - if ( ! Slot ) { + const slotFill = groups[ group ]; + const fills = useSlotFills( slotFill.name ); + + if ( ! slotFill ) { warning( `Unknown BlockControls group "${ group }" provided.` ); return null; } @@ -42,6 +43,7 @@ export default function BlockControlsSlot( { group = 'default', ...props } ) { return null; } + const { Slot } = slotFill; const slot = <Slot { ...props } bubblesVirtually fillProps={ fillProps } />; if ( group === 'default' ) { diff --git a/packages/block-editor/src/components/block-draggable/content.scss b/packages/block-editor/src/components/block-draggable/content.scss index 102230168e2133..25a0f5c2565951 100644 --- a/packages/block-editor/src/components/block-draggable/content.scss +++ b/packages/block-editor/src/components/block-draggable/content.scss @@ -1,13 +1,12 @@ // This creates a "slot" where the block you're dragging appeared. // We use !important as one of the rules are meant to be overridden. .block-editor-block-list__layout .is-dragging { - background-color: currentColor !important; - opacity: 0.05 !important; + opacity: 0.1 !important; border-radius: $radius-small !important; - // Disabling pointer events during the drag event is necessary, - // lest the block might affect your drag operation. - pointer-events: none !important; + iframe { + pointer-events: none; + } // Hide the multi selection indicator when dragging. &::selection { @@ -18,3 +17,10 @@ content: none !important; } } + +// Images are draggable by default, so disable drag for them if not explicitly +// set. This is done so that the block can capture the drag event instead. +.wp-block img:not([draggable]), +.wp-block svg:not([draggable]) { + pointer-events: none; +} diff --git a/packages/block-editor/src/components/block-edit/edit.js b/packages/block-editor/src/components/block-edit/edit.js index 83d0e3f406f829..27d3650f3a0902 100644 --- a/packages/block-editor/src/components/block-edit/edit.js +++ b/packages/block-editor/src/components/block-edit/edit.js @@ -6,18 +6,27 @@ import clsx from 'clsx'; /** * WordPress dependencies */ -import { withFilters } from '@wordpress/components'; import { getBlockDefaultClassName, - hasBlockSupport, getBlockType, + hasBlockSupport, + store as blocksStore, } from '@wordpress/blocks'; -import { useContext, useMemo } from '@wordpress/element'; +import { withFilters } from '@wordpress/components'; +import { useRegistry, useSelect } from '@wordpress/data'; +import { useCallback, useContext, useMemo } from '@wordpress/element'; /** * Internal dependencies */ import BlockContext from '../block-context'; +import isURLLike from '../link-control/is-url-like'; +import { + canBindAttribute, + hasPatternOverridesDefaultBinding, + replacePatternOverridesDefaultBinding, +} from '../../utils/block-bindings'; +import { unlock } from '../../lock-unlock'; /** * Default value used for blocks which do not define their own context needs, @@ -48,27 +57,223 @@ const Edit = ( props ) => { const EditWithFilters = withFilters( 'editor.BlockEdit' )( Edit ); const EditWithGeneratedProps = ( props ) => { - const { attributes = {}, name } = props; + const { name, clientId, attributes, setAttributes } = props; + const registry = useRegistry(); const blockType = getBlockType( name ); const blockContext = useContext( BlockContext ); + const registeredSources = useSelect( + ( select ) => + unlock( select( blocksStore ) ).getAllBlockBindingsSources(), + [] + ); - // Assign context values using the block type's declared context needs. - const context = useMemo( () => { - return blockType && blockType.usesContext + const { blockBindings, context, hasPatternOverrides } = useMemo( () => { + // Assign context values using the block type's declared context needs. + const computedContext = blockType?.usesContext ? Object.fromEntries( Object.entries( blockContext ).filter( ( [ key ] ) => blockType.usesContext.includes( key ) ) ) : DEFAULT_BLOCK_CONTEXT; - }, [ blockType, blockContext ] ); + // Add context requested by Block Bindings sources. + if ( attributes?.metadata?.bindings ) { + Object.values( attributes?.metadata?.bindings || {} ).forEach( + ( binding ) => { + registeredSources[ binding?.source ]?.usesContext?.forEach( + ( key ) => { + computedContext[ key ] = blockContext[ key ]; + } + ); + } + ); + } + return { + blockBindings: replacePatternOverridesDefaultBinding( + name, + attributes?.metadata?.bindings + ), + context: computedContext, + hasPatternOverrides: hasPatternOverridesDefaultBinding( + attributes?.metadata?.bindings + ), + }; + }, [ + name, + blockType?.usesContext, + blockContext, + attributes?.metadata?.bindings, + registeredSources, + ] ); + + const computedAttributes = useSelect( + ( select ) => { + if ( ! blockBindings ) { + return attributes; + } + + const attributesFromSources = {}; + const blockBindingsBySource = new Map(); + + for ( const [ attributeName, binding ] of Object.entries( + blockBindings + ) ) { + const { source: sourceName, args: sourceArgs } = binding; + const source = registeredSources[ sourceName ]; + if ( ! source || ! canBindAttribute( name, attributeName ) ) { + continue; + } + + blockBindingsBySource.set( source, { + ...blockBindingsBySource.get( source ), + [ attributeName ]: { + args: sourceArgs, + }, + } ); + } + + if ( blockBindingsBySource.size ) { + for ( const [ source, bindings ] of blockBindingsBySource ) { + // Get values in batch if the source supports it. + let values = {}; + if ( ! source.getValues ) { + Object.keys( bindings ).forEach( ( attr ) => { + // Default to the the source label when `getValues` doesn't exist. + values[ attr ] = source.label; + } ); + } else { + values = source.getValues( { + select, + context, + clientId, + bindings, + } ); + } + for ( const [ attributeName, value ] of Object.entries( + values + ) ) { + if ( + attributeName === 'url' && + ( ! value || ! isURLLike( value ) ) + ) { + // Return null if value is not a valid URL. + attributesFromSources[ attributeName ] = null; + } else { + attributesFromSources[ attributeName ] = value; + } + } + } + } + + return { + ...attributes, + ...attributesFromSources, + }; + }, + [ + attributes, + blockBindings, + clientId, + context, + name, + registeredSources, + ] + ); + + const setBoundAttributes = useCallback( + ( nextAttributes ) => { + if ( ! blockBindings ) { + setAttributes( nextAttributes ); + return; + } + + registry.batch( () => { + const keptAttributes = { ...nextAttributes }; + const blockBindingsBySource = new Map(); + + // Loop only over the updated attributes to avoid modifying the bound ones that haven't changed. + for ( const [ attributeName, newValue ] of Object.entries( + keptAttributes + ) ) { + if ( + ! blockBindings[ attributeName ] || + ! canBindAttribute( name, attributeName ) + ) { + continue; + } + + const binding = blockBindings[ attributeName ]; + const source = registeredSources[ binding?.source ]; + if ( ! source?.setValues ) { + continue; + } + blockBindingsBySource.set( source, { + ...blockBindingsBySource.get( source ), + [ attributeName ]: { + args: binding.args, + newValue, + }, + } ); + delete keptAttributes[ attributeName ]; + } + + if ( blockBindingsBySource.size ) { + for ( const [ + source, + bindings, + ] of blockBindingsBySource ) { + source.setValues( { + select: registry.select, + dispatch: registry.dispatch, + context, + clientId, + bindings, + } ); + } + } + + const hasParentPattern = !! context[ 'pattern/overrides' ]; + + if ( + // Don't update non-connected attributes if the block is using pattern overrides + // and the editing is happening while overriding the pattern (not editing the original). + ! ( hasPatternOverrides && hasParentPattern ) && + Object.keys( keptAttributes ).length + ) { + // Don't update caption and href until they are supported. + if ( hasPatternOverrides ) { + delete keptAttributes.caption; + delete keptAttributes.href; + } + setAttributes( keptAttributes ); + } + } ); + }, + [ + blockBindings, + clientId, + context, + hasPatternOverrides, + setAttributes, + registeredSources, + name, + registry, + ] + ); if ( ! blockType ) { return null; } if ( blockType.apiVersion > 1 ) { - return <EditWithFilters { ...props } context={ context } />; + return ( + <EditWithFilters + { ...props } + attributes={ computedAttributes } + context={ context } + setAttributes={ setBoundAttributes } + /> + ); } // Generate a class name for the block's editable form. @@ -77,15 +282,17 @@ const EditWithGeneratedProps = ( props ) => { : null; const className = clsx( generatedClassName, - attributes.className, + attributes?.className, props.className ); return ( <EditWithFilters { ...props } - context={ context } + attributes={ computedAttributes } className={ className } + context={ context } + setAttributes={ setBoundAttributes } /> ); }; diff --git a/packages/block-editor/src/components/block-heading-level-dropdown/stories/index.story.js b/packages/block-editor/src/components/block-heading-level-dropdown/stories/index.story.js new file mode 100644 index 00000000000000..a8293258f7a001 --- /dev/null +++ b/packages/block-editor/src/components/block-heading-level-dropdown/stories/index.story.js @@ -0,0 +1,61 @@ +/** + * WordPress dependencies + */ +import { useState } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import HeadingLevelDropdown from '../'; + +const meta = { + title: 'BlockEditor/HeadingLevelDropdown', + component: HeadingLevelDropdown, + parameters: { + docs: { + canvas: { sourceState: 'shown' }, + description: { + component: + 'Dropdown for selecting a heading level (1 through 6) or paragraph (0).', + }, + }, + }, + argTypes: { + value: { + control: { type: null }, + description: 'The chosen heading level.', + }, + options: { + control: 'check', + options: [ 1, 2, 3, 4, 5, 6 ], + description: 'An array of supported heading levels.', + }, + onChange: { + action: 'onChange', + control: { type: null }, + description: 'Function called with the selected value changes.', + }, + }, +}; + +export default meta; + +export const Default = { + render: function Template( { onChange, ...args } ) { + const [ value, setValue ] = useState( args.value ); + + return ( + <HeadingLevelDropdown + { ...args } + value={ value } + onChange={ ( ...changeArgs ) => { + setValue( ...changeArgs ); + onChange( ...changeArgs ); + } } + /> + ); + }, + args: { + value: 2, + }, +}; diff --git a/packages/block-editor/src/components/block-icon/content.scss b/packages/block-editor/src/components/block-icon/content.scss index 033e0d3129d741..ba2446d89f0435 100644 --- a/packages/block-editor/src/components/block-icon/content.scss +++ b/packages/block-editor/src/components/block-icon/content.scss @@ -9,7 +9,7 @@ svg { fill: currentColor; - // Optimizate for high contrast modes. + // Optimize for high contrast modes. // See also https://blogs.windows.com/msedgedev/2020/09/17/styling-for-windows-high-contrast-with-new-standards-for-forced-colors/. @media (forced-colors: active) { fill: CanvasText; diff --git a/packages/block-editor/src/components/block-icon/stories/index.story.js b/packages/block-editor/src/components/block-icon/stories/index.story.js new file mode 100644 index 00000000000000..e30a347005d774 --- /dev/null +++ b/packages/block-editor/src/components/block-icon/stories/index.story.js @@ -0,0 +1,69 @@ +/** + * WordPress dependencies + */ +import { box, button, cog, paragraph } from '@wordpress/icons'; + +/** + * Internal dependencies + */ +import BlockIcon from '../'; + +const meta = { + title: 'BlockEditor/BlockIcon', + component: BlockIcon, + parameters: { + docs: { + description: { + component: + 'The `BlockIcon` component allows to display an icon for a block.', + }, + canvas: { sourceState: 'shown' }, + }, + }, + argTypes: { + icon: { + control: 'select', + options: [ 'paragraph', 'cog', 'box', 'button' ], + mapping: { + paragraph, + cog, + box, + button, + }, + description: + 'The icon of the block. This can be any of [WordPress Dashicons](https://developer.wordpress.org/resource/dashicons/), or a custom `svg` element.', + table: { + type: { summary: 'string | object' }, + }, + }, + showColors: { + control: 'boolean', + description: 'Whether to show background and foreground colors.', + table: { + type: { summary: 'boolean' }, + }, + }, + className: { + control: 'text', + description: 'Additional CSS class for the icon.', + table: { + type: { summary: 'string' }, + }, + }, + context: { + control: 'text', + description: 'Context where the icon is being used.', + table: { + type: { summary: 'string' }, + }, + }, + }, +}; + +export default meta; + +export const Default = { + args: { + icon: 'paragraph', + }, +}; diff --git a/packages/block-editor/src/components/block-icon/style.scss b/packages/block-editor/src/components/block-icon/style.scss index 033e0d3129d741..ba2446d89f0435 100644 --- a/packages/block-editor/src/components/block-icon/style.scss +++ b/packages/block-editor/src/components/block-icon/style.scss @@ -9,7 +9,7 @@ svg { fill: currentColor; - // Optimizate for high contrast modes. + // Optimize for high contrast modes. // See also https://blogs.windows.com/msedgedev/2020/09/17/styling-for-windows-high-contrast-with-new-standards-for-forced-colors/. @media (forced-colors: active) { fill: CanvasText; diff --git a/packages/block-editor/src/components/block-info-slot-fill/index.js b/packages/block-editor/src/components/block-info-slot-fill/index.js deleted file mode 100644 index 8c9503313d754c..00000000000000 --- a/packages/block-editor/src/components/block-info-slot-fill/index.js +++ /dev/null @@ -1,27 +0,0 @@ -/** - * WordPress dependencies - */ -import { privateApis as componentsPrivateApis } from '@wordpress/components'; - -/** - * Internal dependencies - */ -import { unlock } from '../../lock-unlock'; -import { - useBlockEditContext, - mayDisplayControlsKey, -} from '../block-edit/context'; - -const { createPrivateSlotFill } = unlock( componentsPrivateApis ); -const { Fill, Slot } = createPrivateSlotFill( 'BlockInformation' ); - -const BlockInfo = ( props ) => { - const context = useBlockEditContext(); - if ( ! context[ mayDisplayControlsKey ] ) { - return null; - } - return <Fill { ...props } />; -}; -BlockInfo.Slot = ( props ) => <Slot { ...props } />; - -export default BlockInfo; diff --git a/packages/block-editor/src/components/block-inspector/index.js b/packages/block-editor/src/components/block-inspector/index.js index 475d4f6a4b8c2e..3eee5438f1f44b 100644 --- a/packages/block-editor/src/components/block-inspector/index.js +++ b/packages/block-editor/src/components/block-inspector/index.js @@ -26,7 +26,6 @@ import useInspectorControlsTabs from '../inspector-controls-tabs/use-inspector-c import AdvancedControls from '../inspector-controls-tabs/advanced-controls-panel'; import PositionControls from '../inspector-controls-tabs/position-controls-panel'; import useBlockInspectorAnimationSettings from './useBlockInspectorAnimationSettings'; -import BlockInfo from '../block-info-slot-fill'; import BlockQuickNavigation from '../block-quick-navigation'; import { useBorderPanelLabel } from '../../hooks/border'; @@ -40,7 +39,7 @@ function BlockStylesPanel( { clientId } ) { ); } -const BlockInspector = ( { showNoBlockSelectedMessage = true } ) => { +function BlockInspector() { const { count, selectedBlockName, @@ -138,14 +137,11 @@ const BlockInspector = ( { showNoBlockSelectedMessage = true } ) => { ! selectedBlockClientId || isSelectedBlockUnregistered ) { - if ( showNoBlockSelectedMessage ) { - return ( - <span className="block-editor-block-inspector__no-blocks"> - { __( 'No block selected.' ) } - </span> - ); - } - return null; + return ( + <span className="block-editor-block-inspector__no-blocks"> + { __( 'No block selected.' ) } + </span> + ); } return ( @@ -169,7 +165,7 @@ const BlockInspector = ( { showNoBlockSelectedMessage = true } ) => { /> </BlockInspectorSingleBlockWrapper> ); -}; +} const BlockInspectorSingleBlockWrapper = ( { animate, wrapper, children } ) => { return animate ? wrapper( children ) : children; @@ -253,7 +249,6 @@ const BlockInspectorSingleBlock = ( { className={ blockInformation.isSynced && 'is-synced' } /> <BlockVariationTransforms blockClientId={ clientId } /> - <BlockInfo.Slot /> { showTabs && ( <InspectorControlsTabs hasBlockStyles={ hasBlockStyles } diff --git a/packages/block-editor/src/components/block-list/block.js b/packages/block-editor/src/components/block-list/block.js index 6d4655189d9723..29f8a97b031ce2 100644 --- a/packages/block-editor/src/components/block-list/block.js +++ b/packages/block-editor/src/components/block-list/block.js @@ -6,13 +6,7 @@ import clsx from 'clsx'; /** * WordPress dependencies */ -import { - memo, - useCallback, - RawHTML, - useContext, - useMemo, -} from '@wordpress/element'; +import { memo, RawHTML, useContext, useMemo } from '@wordpress/element'; import { getBlockType, getSaveContent, @@ -28,7 +22,7 @@ import { store as blocksStore, } from '@wordpress/blocks'; import { withFilters } from '@wordpress/components'; -import { withDispatch, useDispatch, useSelect } from '@wordpress/data'; +import { withDispatch, useSelect } from '@wordpress/data'; import { compose } from '@wordpress/compose'; import { safeHTML } from '@wordpress/dom'; @@ -103,6 +97,7 @@ function BlockListBlock( { wrapperProps, setAttributes, onReplace, + onRemove, onInsertBlocksAfter, onMerge, toggleSelection, @@ -113,11 +108,6 @@ function BlockListBlock( { themeSupportsLayout, ...context } = useContext( PrivateBlockContext ); - const { removeBlock } = useDispatch( blockEditorStore ); - const onRemove = useCallback( - () => removeBlock( clientId ), - [ clientId, removeBlock ] - ); const parentLayout = useLayout() || {}; @@ -537,6 +527,9 @@ const applyWithDispatch = withDispatch( ( dispatch, ownProps, registry ) => { initialPosition ); }, + onRemove() { + removeBlock( ownProps.clientId ); + }, toggleSelection( selectionEnabled ) { toggleSelection( selectionEnabled ); }, @@ -797,6 +790,7 @@ function BlockListBlockProvider( props ) { mayDisplayParentControls, originalBlockClientId, themeSupportsLayout, + canMove, }; // Here we separate between the props passed to BlockListBlock and any other diff --git a/packages/block-editor/src/components/block-list/content.scss b/packages/block-editor/src/components/block-list/content.scss index 3d3b8517ca09c3..cd517fced833ef 100644 --- a/packages/block-editor/src/components/block-list/content.scss +++ b/packages/block-editor/src/components/block-list/content.scss @@ -427,3 +427,9 @@ _::-webkit-full-page-media, _:future, :root [data-has-multi-selection="true"] .b // Additional -1px is required to avoid sub pixel rounding errors allowing background to show. margin: 0 calc(-1 * var(--wp--style--root--padding-right) - 1px) 0 calc(-1 * var(--wp--style--root--padding-left) - 1px) !important; } + +// This only works in Firefox, Chrome and Safari don't accept a custom cursor +// during drag. +.is-dragging { + cursor: grabbing; +} diff --git a/packages/block-editor/src/components/block-list/index.js b/packages/block-editor/src/components/block-list/index.js index 2d91108ccb4123..bcf6783a10d1c3 100644 --- a/packages/block-editor/src/components/block-list/index.js +++ b/packages/block-editor/src/components/block-list/index.js @@ -12,11 +12,7 @@ import { useDispatch, useRegistry, } from '@wordpress/data'; -import { - useViewportMatch, - useMergeRefs, - useDebounce, -} from '@wordpress/compose'; +import { useMergeRefs, useDebounce } from '@wordpress/compose'; import { createContext, useMemo, @@ -46,7 +42,6 @@ export const IntersectionObserver = createContext(); const pendingBlockVisibilityUpdatesPerRegistry = new WeakMap(); function Root( { className, ...settings } ) { - const isLargeViewport = useViewportMatch( 'medium' ); const { isOutlineMode, isFocusMode, temporarilyEditingAsBlocks } = useSelect( ( select ) => { const { getSettings, getTemporarilyEditingAsBlocks, isTyping } = @@ -105,7 +100,7 @@ function Root( { className, ...settings } ) { ] ), className: clsx( 'is-root-container', className, { 'is-outline-mode': isOutlineMode, - 'is-focus-mode': isFocusMode && isLargeViewport, + 'is-focus-mode': isFocusMode, } ), }, settings diff --git a/packages/block-editor/src/components/block-list/use-block-props/index.js b/packages/block-editor/src/components/block-list/use-block-props/index.js index 25b9a21f0d2867..14cda82fe7cd26 100644 --- a/packages/block-editor/src/components/block-list/use-block-props/index.js +++ b/packages/block-editor/src/components/block-list/use-block-props/index.js @@ -29,7 +29,8 @@ import { useBlockRefProvider } from './use-block-refs'; import { useIntersectionObserver } from './use-intersection-observer'; import { useScrollIntoView } from './use-scroll-into-view'; import { useFlashEditableBlocks } from '../../use-flash-editable-blocks'; -import { canBindBlock } from '../../../hooks/use-bindings-attributes'; +import { canBindBlock } from '../../../utils/block-bindings'; +import { useFirefoxDraggableCompatibility } from './use-firefox-draggable-compatibility'; /** * This hook is used to lightly mark an element as a block element. The element @@ -100,11 +101,13 @@ export function useBlockProps( props = {}, { __unstableIsHtml } = {} ) { isTemporarilyEditingAsBlocks, defaultClassName, isSectionBlock, + canMove, } = useContext( PrivateBlockContext ); // translators: %s: Type of block (i.e. Text, Image etc) const blockLabel = sprintf( __( 'Block: %s' ), blockTitle ); const htmlSuffix = mode === 'html' && ! __unstableIsHtml ? '-visual' : ''; + const ffDragRef = useFirefoxDraggableCompatibility(); const mergedRefs = useMergeRefs( [ props.ref, useFocusFirstElement( { clientId, initialPosition } ), @@ -120,6 +123,7 @@ export function useBlockProps( props = {}, { __unstableIsHtml } = {} ) { isEnabled: isSectionBlock, } ), useScrollIntoView( { isSelected } ), + canMove ? ffDragRef : undefined, ] ); const blockEditContext = useBlockEditContext(); @@ -152,6 +156,7 @@ export function useBlockProps( props = {}, { __unstableIsHtml } = {} ) { return { tabIndex: blockEditingMode === 'disabled' ? -1 : 0, + draggable: canMove && ! hasChildSelected ? true : undefined, ...wrapperProps, ...props, ref: mergedRefs, diff --git a/packages/block-editor/src/components/block-list/use-block-props/use-firefox-draggable-compatibility.js b/packages/block-editor/src/components/block-list/use-block-props/use-firefox-draggable-compatibility.js new file mode 100644 index 00000000000000..a53983b95954ad --- /dev/null +++ b/packages/block-editor/src/components/block-list/use-block-props/use-firefox-draggable-compatibility.js @@ -0,0 +1,83 @@ +/** + * WordPress dependencies + */ +import { useRefEffect } from '@wordpress/compose'; + +const nodesByDocument = new Map(); + +function add( doc, node ) { + let set = nodesByDocument.get( doc ); + if ( ! set ) { + set = new Set(); + nodesByDocument.set( doc, set ); + doc.addEventListener( 'pointerdown', down ); + } + set.add( node ); +} + +function remove( doc, node ) { + const set = nodesByDocument.get( doc ); + if ( set ) { + set.delete( node ); + restore( node ); + if ( set.size === 0 ) { + nodesByDocument.delete( doc ); + doc.removeEventListener( 'pointerdown', down ); + } + } +} + +function restore( node ) { + const prevDraggable = node.getAttribute( 'data-draggable' ); + if ( prevDraggable ) { + node.removeAttribute( 'data-draggable' ); + // Only restore if `draggable` is still removed. It could have been + // changed by React in the meantime. + if ( prevDraggable === 'true' && ! node.getAttribute( 'draggable' ) ) { + node.setAttribute( 'draggable', 'true' ); + } + } +} + +function down( event ) { + const { target } = event; + const { ownerDocument, isContentEditable } = target; + const nodes = nodesByDocument.get( ownerDocument ); + + if ( isContentEditable ) { + // Whenever an editable element is clicked, check which draggable + // blocks contain this element, and temporarily disable draggability. + for ( const node of nodes ) { + if ( + node.getAttribute( 'draggable' ) === 'true' && + node.contains( target ) + ) { + node.removeAttribute( 'draggable' ); + node.setAttribute( 'data-draggable', 'true' ); + } + } + } else { + // Whenever a non-editable element is clicked, re-enable draggability + // for any blocks that were previously disabled. + for ( const node of nodes ) { + restore( node ); + } + } +} + +/** + * In Firefox, the `draggable` and `contenteditable` attributes don't play well + * together. When `contenteditable` is within a `draggable` element, selection + * doesn't get set in the right place. The only solution is to temporarily + * remove the `draggable` attribute clicking inside `contenteditable` elements. + * + * @return {Function} Cleanup function. + */ +export function useFirefoxDraggableCompatibility() { + return useRefEffect( ( node ) => { + add( node.ownerDocument, node ); + return () => { + remove( node.ownerDocument, node ); + }; + }, [] ); +} diff --git a/packages/block-editor/src/components/block-list/use-block-props/use-focus-handler.js b/packages/block-editor/src/components/block-list/use-block-props/use-focus-handler.js index 4e9ffa45725003..9bf02ae981a057 100644 --- a/packages/block-editor/src/components/block-list/use-block-props/use-focus-handler.js +++ b/packages/block-editor/src/components/block-list/use-block-props/use-focus-handler.js @@ -48,7 +48,7 @@ export function useFocusHandler( clientId ) { return; } - // If an inner block is focussed, that block is resposible for + // If an inner block is focussed, that block is responsible for // setting the selected block. if ( ! isInsideRootBlock( node, event.target ) ) { return; diff --git a/packages/block-editor/src/components/block-list/use-block-props/use-selected-block-event-handlers.js b/packages/block-editor/src/components/block-list/use-block-props/use-selected-block-event-handlers.js index 68f8a671adbe9a..0a13ce6700b8e8 100644 --- a/packages/block-editor/src/components/block-list/use-block-props/use-selected-block-event-handlers.js +++ b/packages/block-editor/src/components/block-list/use-block-props/use-selected-block-event-handlers.js @@ -5,12 +5,15 @@ import { isTextField } from '@wordpress/dom'; import { ENTER, BACKSPACE, DELETE } from '@wordpress/keycodes'; import { useSelect, useDispatch } from '@wordpress/data'; import { useRefEffect } from '@wordpress/compose'; +import { createRoot } from '@wordpress/element'; +import { store as blocksStore } from '@wordpress/blocks'; /** * Internal dependencies */ import { store as blockEditorStore } from '../../../store'; import { unlock } from '../../../lock-unlock'; +import BlockDraggableChip from '../../../components/block-draggable/draggable-chip'; /** * Adds block behaviour: @@ -21,12 +24,16 @@ import { unlock } from '../../../lock-unlock'; * @param {string} clientId Block client ID. */ export function useEventHandlers( { clientId, isSelected } ) { - const { getBlockRootClientId, getBlockIndex, isZoomOut } = unlock( - useSelect( blockEditorStore ) - ); - const { insertAfterBlock, removeBlock, resetZoomLevel } = unlock( - useDispatch( blockEditorStore ) - ); + const { getBlockType } = useSelect( blocksStore ); + const { getBlockRootClientId, isZoomOut, hasMultiSelection, getBlockName } = + unlock( useSelect( blockEditorStore ) ); + const { + insertAfterBlock, + removeBlock, + resetZoomLevel, + startDraggingBlocks, + stopDraggingBlocks, + } = unlock( useDispatch( blockEditorStore ) ); return useRefEffect( ( node ) => { @@ -76,7 +83,102 @@ export function useEventHandlers( { clientId, isSelected } ) { * @param {DragEvent} event Drag event. */ function onDragStart( event ) { - event.preventDefault(); + if ( + node !== event.target || + node.isContentEditable || + node.ownerDocument.activeElement !== node || + hasMultiSelection() + ) { + event.preventDefault(); + return; + } + const data = JSON.stringify( { + type: 'block', + srcClientIds: [ clientId ], + srcRootClientId: getBlockRootClientId( clientId ), + } ); + event.dataTransfer.effectAllowed = 'move'; // remove "+" cursor + event.dataTransfer.clearData(); + event.dataTransfer.setData( 'wp-blocks', data ); + const { ownerDocument } = node; + const { defaultView } = ownerDocument; + const selection = defaultView.getSelection(); + selection.removeAllRanges(); + + const domNode = document.createElement( 'div' ); + const root = createRoot( domNode ); + root.render( + <BlockDraggableChip + icon={ getBlockType( getBlockName( clientId ) ).icon } + /> + ); + document.body.appendChild( domNode ); + domNode.style.position = 'absolute'; + domNode.style.top = '0'; + domNode.style.left = '0'; + domNode.style.zIndex = '1000'; + domNode.style.pointerEvents = 'none'; + + // Setting the drag chip as the drag image actually works, but + // the behaviour is slightly different in every browser. In + // Safari, it animates, in Firefox it's slightly transparent... + // So we set a fake drag image and have to reposition it + // ourselves. + const dragElement = ownerDocument.createElement( 'div' ); + // Chrome will show a globe icon if the drag element does not + // have dimensions. + dragElement.style.width = '1px'; + dragElement.style.height = '1px'; + dragElement.style.position = 'fixed'; + dragElement.style.visibility = 'hidden'; + ownerDocument.body.appendChild( dragElement ); + event.dataTransfer.setDragImage( dragElement, 0, 0 ); + + let offset = { x: 0, y: 0 }; + + if ( document !== ownerDocument ) { + const frame = defaultView.frameElement; + if ( frame ) { + const rect = frame.getBoundingClientRect(); + offset = { x: rect.left, y: rect.top }; + } + } + + // chip handle offset + offset.x -= 58; + + function over( e ) { + domNode.style.transform = `translate( ${ + e.clientX + offset.x + }px, ${ e.clientY + offset.y }px )`; + } + + over( event ); + + function end() { + ownerDocument.removeEventListener( 'dragover', over ); + ownerDocument.removeEventListener( 'dragend', end ); + domNode.remove(); + dragElement.remove(); + stopDraggingBlocks(); + document.body.classList.remove( + 'is-dragging-components-draggable' + ); + ownerDocument.documentElement.classList.remove( + 'is-dragging' + ); + } + + ownerDocument.addEventListener( 'dragover', over ); + ownerDocument.addEventListener( 'dragend', end ); + ownerDocument.addEventListener( 'drop', end ); + + startDraggingBlocks( [ clientId ] ); + // Important because it hides the block toolbar. + document.body.classList.add( + 'is-dragging-components-draggable' + ); + ownerDocument.documentElement.classList.add( 'is-dragging' ); } node.addEventListener( 'keydown', onKeyDown ); @@ -91,11 +193,13 @@ export function useEventHandlers( { clientId, isSelected } ) { clientId, isSelected, getBlockRootClientId, - getBlockIndex, insertAfterBlock, removeBlock, isZoomOut, resetZoomLevel, + hasMultiSelection, + startDraggingBlocks, + stopDraggingBlocks, ] ); } diff --git a/packages/block-editor/src/components/block-list/zoom-out-separator.js b/packages/block-editor/src/components/block-list/zoom-out-separator.js index f2e6d050141fb5..86191c1e4ce32c 100644 --- a/packages/block-editor/src/components/block-list/zoom-out-separator.js +++ b/packages/block-editor/src/components/block-list/zoom-out-separator.js @@ -33,6 +33,7 @@ export function ZoomOutSeparator( { insertionPoint, blockInsertionPointVisible, blockInsertionPoint, + blocksBeingDragged, } = useSelect( ( select ) => { const { getInsertionPoint, @@ -40,6 +41,7 @@ export function ZoomOutSeparator( { getSectionRootClientId, isBlockInsertionPointVisible, getBlockInsertionPoint, + getDraggedBlockClientIds, } = unlock( select( blockEditorStore ) ); const root = getSectionRootClientId(); @@ -51,6 +53,7 @@ export function ZoomOutSeparator( { insertionPoint: getInsertionPoint(), blockInsertionPoint: getBlockInsertionPoint(), blockInsertionPointVisible: isBlockInsertionPointVisible(), + blocksBeingDragged: getDraggedBlockClientIds(), }; }, [] ); @@ -78,6 +81,7 @@ export function ZoomOutSeparator( { insertionPoint && insertionPoint.hasOwnProperty( 'index' ) && clientId === sectionClientIds[ insertionPoint.index - 1 ]; + // We want to show the zoom out separator in either of these conditions: // 1. If the inserter has an insertion index set // 2. We are dragging a pattern over an insertion point @@ -97,6 +101,32 @@ export function ZoomOutSeparator( { sectionClientIds[ blockInsertionPoint.index - 1 ] ); } + const blockBeingDraggedClientId = blocksBeingDragged[ 0 ]; + + const isCurrentBlockBeingDragged = blocksBeingDragged.includes( clientId ); + + const blockBeingDraggedIndex = sectionClientIds.indexOf( + blockBeingDraggedClientId + ); + const blockBeingDraggedPreviousSiblingClientId = + blockBeingDraggedIndex > 0 + ? sectionClientIds[ blockBeingDraggedIndex - 1 ] + : null; + + const isCurrentBlockPreviousSiblingOfBlockBeingDragged = + blockBeingDraggedPreviousSiblingClientId === clientId; + + // The separators are visually top/bottom of the block, but in actual fact + // the "top" separator is the "bottom" separator of the previous block. + // Therefore, this logic hides the separator if the current block is being dragged + // or if the current block is the previous sibling of the block being dragged. + if ( + isCurrentBlockBeingDragged || + isCurrentBlockPreviousSiblingOfBlockBeingDragged + ) { + isVisible = false; + } + return ( <AnimatePresence> { isVisible && ( diff --git a/packages/block-editor/src/components/block-lock/modal.js b/packages/block-editor/src/components/block-lock/modal.js index 7d09f7b63f8cd0..df267e97165e36 100644 --- a/packages/block-editor/src/components/block-lock/modal.js +++ b/packages/block-editor/src/components/block-lock/modal.js @@ -24,7 +24,7 @@ import useBlockDisplayInformation from '../use-block-display-information'; import { store as blockEditorStore } from '../../store'; // Entity based blocks which allow edit locking -const ALLOWS_EDIT_LOCKING = [ 'core/block', 'core/navigation' ]; +const ALLOWS_EDIT_LOCKING = [ 'core/navigation' ]; function getTemplateLockValue( lock ) { // Prevents all operations. @@ -99,9 +99,7 @@ export default function BlockLockModal( { clientId, onClose } ) { > <fieldset className="block-editor-block-lock-modal__options"> <legend> - { __( - 'Choose specific attributes to restrict or lock all available options.' - ) } + { __( 'Select the features you want to lock' ) } </legend> { /* * Disable reason: The `list` ARIA role is redundant but @@ -137,7 +135,7 @@ export default function BlockLockModal( { clientId, onClose } ) { <li className="block-editor-block-lock-modal__checklist-item"> <CheckboxControl __nextHasNoMarginBottom - label={ __( 'Restrict editing' ) } + label={ __( 'Lock editing' ) } checked={ !! lock.edit } onChange={ ( edit ) => setLock( ( prevLock ) => ( { @@ -159,7 +157,7 @@ export default function BlockLockModal( { clientId, onClose } ) { <li className="block-editor-block-lock-modal__checklist-item"> <CheckboxControl __nextHasNoMarginBottom - label={ __( 'Disable movement' ) } + label={ __( 'Lock movement' ) } checked={ lock.move } onChange={ ( move ) => setLock( ( prevLock ) => ( { @@ -178,7 +176,7 @@ export default function BlockLockModal( { clientId, onClose } ) { <li className="block-editor-block-lock-modal__checklist-item"> <CheckboxControl __nextHasNoMarginBottom - label={ __( 'Prevent removal' ) } + label={ __( 'Lock removal' ) } checked={ lock.remove } onChange={ ( remove ) => setLock( ( prevLock ) => ( { diff --git a/packages/block-editor/src/components/block-manager/category.js b/packages/block-editor/src/components/block-manager/category.js new file mode 100644 index 00000000000000..79d5896b4502e4 --- /dev/null +++ b/packages/block-editor/src/components/block-manager/category.js @@ -0,0 +1,102 @@ +/** + * WordPress dependencies + */ +import { useCallback } from '@wordpress/element'; +import { useInstanceId } from '@wordpress/compose'; +import { CheckboxControl } from '@wordpress/components'; + +/** + * Internal dependencies + */ +import BlockTypesChecklist from './checklist'; + +function BlockManagerCategory( { + title, + blockTypes, + selectedBlockTypes, + onChange, +} ) { + const instanceId = useInstanceId( BlockManagerCategory ); + + const toggleVisible = useCallback( + ( blockType, nextIsChecked ) => { + if ( nextIsChecked ) { + onChange( [ ...selectedBlockTypes, blockType ] ); + } else { + onChange( + selectedBlockTypes.filter( + ( { name } ) => name !== blockType.name + ) + ); + } + }, + [ selectedBlockTypes, onChange ] + ); + + const toggleAllVisible = useCallback( + ( nextIsChecked ) => { + if ( nextIsChecked ) { + onChange( [ + ...selectedBlockTypes, + ...blockTypes.filter( + ( blockType ) => + ! selectedBlockTypes.find( + ( { name } ) => name === blockType.name + ) + ), + ] ); + } else { + onChange( + selectedBlockTypes.filter( + ( selectedBlockType ) => + ! blockTypes.find( + ( { name } ) => name === selectedBlockType.name + ) + ) + ); + } + }, + [ blockTypes, selectedBlockTypes, onChange ] + ); + + if ( ! blockTypes.length ) { + return null; + } + + const checkedBlockNames = blockTypes + .map( ( { name } ) => name ) + .filter( ( type ) => + ( selectedBlockTypes ?? [] ).some( + ( selectedBlockType ) => selectedBlockType.name === type + ) + ); + + const titleId = 'block-editor-block-manager__category-title-' + instanceId; + + const isAllChecked = checkedBlockNames.length === blockTypes.length; + const isIndeterminate = ! isAllChecked && checkedBlockNames.length > 0; + + return ( + <div + role="group" + aria-labelledby={ titleId } + className="block-editor-block-manager__category" + > + <CheckboxControl + __nextHasNoMarginBottom + checked={ isAllChecked } + onChange={ toggleAllVisible } + className="block-editor-block-manager__category-title" + indeterminate={ isIndeterminate } + label={ <span id={ titleId }>{ title }</span> } + /> + <BlockTypesChecklist + blockTypes={ blockTypes } + value={ checkedBlockNames } + onItemChange={ toggleVisible } + /> + </div> + ); +} + +export default BlockManagerCategory; diff --git a/packages/editor/src/components/block-manager/checklist.js b/packages/block-editor/src/components/block-manager/checklist.js similarity index 70% rename from packages/editor/src/components/block-manager/checklist.js rename to packages/block-editor/src/components/block-manager/checklist.js index 01bd06abdeba86..d5456a14355efb 100644 --- a/packages/editor/src/components/block-manager/checklist.js +++ b/packages/block-editor/src/components/block-manager/checklist.js @@ -1,23 +1,27 @@ /** * WordPress dependencies */ -import { BlockIcon } from '@wordpress/block-editor'; import { CheckboxControl } from '@wordpress/components'; +/** + * Internal dependencies + */ +import BlockIcon from '../block-icon'; + function BlockTypesChecklist( { blockTypes, value, onItemChange } ) { return ( - <ul className="editor-block-manager__checklist"> + <ul className="block-editor-block-manager__checklist"> { blockTypes.map( ( blockType ) => ( <li key={ blockType.name } - className="editor-block-manager__checklist-item" + className="block-editor-block-manager__checklist-item" > <CheckboxControl __nextHasNoMarginBottom label={ blockType.title } checked={ value.includes( blockType.name ) } onChange={ ( ...args ) => - onItemChange( blockType.name, ...args ) + onItemChange( blockType, ...args ) } /> <BlockIcon icon={ blockType.icon } /> diff --git a/packages/editor/src/components/block-manager/index.js b/packages/block-editor/src/components/block-manager/index.js similarity index 54% rename from packages/editor/src/components/block-manager/index.js rename to packages/block-editor/src/components/block-manager/index.js index 4a1145839976f9..30d10e67040c71 100644 --- a/packages/editor/src/components/block-manager/index.js +++ b/packages/block-editor/src/components/block-manager/index.js @@ -2,69 +2,49 @@ * WordPress dependencies */ import { store as blocksStore } from '@wordpress/blocks'; -import { useDispatch, useSelect } from '@wordpress/data'; +import { useSelect } from '@wordpress/data'; import { SearchControl, Button } from '@wordpress/components'; import { __, _n, sprintf } from '@wordpress/i18n'; import { useEffect, useState } from '@wordpress/element'; import { useDebounce } from '@wordpress/compose'; import { speak } from '@wordpress/a11y'; -import { store as preferencesStore } from '@wordpress/preferences'; /** * Internal dependencies */ -import { unlock } from '../../lock-unlock'; -import { store as editorStore } from '../../store'; import BlockManagerCategory from './category'; -export default function BlockManager() { +/** + * Provides a list of blocks with checkboxes. + * + * @param {Object} props Props. + * @param {Array} props.blockTypes An array of blocks. + * @param {Array} props.selectedBlockTypes An array of selected blocks. + * @param {Function} props.onChange Function to be called when the selected blocks change. + */ +export default function BlockManager( { + blockTypes, + selectedBlockTypes, + onChange, +} ) { const debouncedSpeak = useDebounce( speak, 500 ); const [ search, setSearch ] = useState( '' ); - const { showBlockTypes } = unlock( useDispatch( editorStore ) ); - - const { - blockTypes, - categories, - hasBlockSupport, - isMatchingSearchTerm, - numberOfHiddenBlocks, - } = useSelect( ( select ) => { - // Some hidden blocks become unregistered - // by removing for instance the plugin that registered them, yet - // they're still remain as hidden by the user's action. - // We consider "hidden", blocks which were hidden and - // are still registered. - const _blockTypes = select( blocksStore ).getBlockTypes(); - const hiddenBlockTypes = ( - select( preferencesStore ).get( 'core', 'hiddenBlockTypes' ) ?? [] - ).filter( ( hiddenBlock ) => { - return _blockTypes.some( - ( registeredBlock ) => registeredBlock.name === hiddenBlock - ); - } ); - + const { categories, isMatchingSearchTerm } = useSelect( ( select ) => { return { - blockTypes: _blockTypes, categories: select( blocksStore ).getCategories(), - hasBlockSupport: select( blocksStore ).hasBlockSupport, isMatchingSearchTerm: select( blocksStore ).isMatchingSearchTerm, - numberOfHiddenBlocks: - Array.isArray( hiddenBlockTypes ) && hiddenBlockTypes.length, }; }, [] ); - function enableAllBlockTypes( newBlockTypes ) { - const blockNames = newBlockTypes.map( ( { name } ) => name ); - showBlockTypes( blockNames ); + function enableAllBlockTypes() { + onChange( blockTypes ); } - const filteredBlockTypes = blockTypes.filter( - ( blockType ) => - hasBlockSupport( blockType, 'inserter', true ) && - ( ! search || isMatchingSearchTerm( blockType, search ) ) && - ( ! blockType.parent || - blockType.parent.includes( 'core/post-content' ) ) - ); + const filteredBlockTypes = blockTypes.filter( ( blockType ) => { + return ! search || isMatchingSearchTerm( blockType, search ); + } ); + + const numberOfHiddenBlocks = blockTypes.length - selectedBlockTypes.length; // Announce search results on change useEffect( () => { @@ -81,9 +61,9 @@ export default function BlockManager() { }, [ filteredBlockTypes?.length, search, debouncedSpeak ] ); return ( - <div className="editor-block-manager__content"> + <div className="block-editor-block-manager__content"> { !! numberOfHiddenBlocks && ( - <div className="editor-block-manager__disabled-blocks-count"> + <div className="block-editor-block-manager__disabled-blocks-count"> { sprintf( /* translators: %d: number of blocks. */ _n( @@ -96,9 +76,7 @@ export default function BlockManager() { <Button __next40pxDefaultSize variant="link" - onClick={ () => - enableAllBlockTypes( filteredBlockTypes ) - } + onClick={ enableAllBlockTypes } > { __( 'Reset' ) } </Button> @@ -110,16 +88,16 @@ export default function BlockManager() { placeholder={ __( 'Search for a block' ) } value={ search } onChange={ ( nextSearch ) => setSearch( nextSearch ) } - className="editor-block-manager__search" + className="block-editor-block-manager__search" /> <div tabIndex="0" role="region" aria-label={ __( 'Available block types' ) } - className="editor-block-manager__results" + className="block-editor-block-manager__results" > { filteredBlockTypes.length === 0 && ( - <p className="editor-block-manager__no-results"> + <p className="block-editor-block-manager__no-results"> { __( 'No blocks found.' ) } </p> ) } @@ -131,6 +109,8 @@ export default function BlockManager() { ( blockType ) => blockType.category === category.slug ) } + selectedBlockTypes={ selectedBlockTypes } + onChange={ onChange } /> ) ) } <BlockManagerCategory @@ -138,6 +118,8 @@ export default function BlockManager() { blockTypes={ filteredBlockTypes.filter( ( { category } ) => ! category ) } + selectedBlockTypes={ selectedBlockTypes } + onChange={ onChange } /> </div> </div> diff --git a/packages/editor/src/components/block-manager/style.scss b/packages/block-editor/src/components/block-manager/style.scss similarity index 65% rename from packages/editor/src/components/block-manager/style.scss rename to packages/block-editor/src/components/block-manager/style.scss index 411ee9faf34f75..d72c682dcbb5d5 100644 --- a/packages/editor/src/components/block-manager/style.scss +++ b/packages/block-editor/src/components/block-manager/style.scss @@ -1,14 +1,14 @@ -.editor-block-manager__no-results { +.block-editor-block-manager__no-results { font-style: italic; padding: $grid-unit-30 0; text-align: center; } -.editor-block-manager__search { +.block-editor-block-manager__search { margin: $grid-unit-20 0; } -.editor-block-manager__disabled-blocks-count { +.block-editor-block-manager__disabled-blocks-count { border: $border-width solid $gray-300; border-width: $border-width 0; // Cover up horizontal areas off the sides of the box rectangle @@ -19,10 +19,10 @@ position: sticky; // When sticking, tuck the top border beneath the modal header border top: ($grid-unit-05 + 1) * -1; - z-index: z-index(".editor-block-manager__disabled-blocks-count"); + z-index: z-index(".block-editor-block-manager__disabled-blocks-count"); // Stick the category titles to the bottom - ~ .editor-block-manager__results .editor-block-manager__category-title { + ~ .block-editor-block-manager__results .block-editor-block-manager__category-title { top: $grid-unit-40 - 1; } .is-link { @@ -30,32 +30,32 @@ } } -.editor-block-manager__category { +.block-editor-block-manager__category { margin: 0 0 $grid-unit-30 0; } -.editor-block-manager__category-title { +.block-editor-block-manager__category-title { position: sticky; top: - $grid-unit-05; // Offsets the top padding on the modal content container padding: $grid-unit-20 0; background-color: $white; - z-index: z-index(".editor-block-manager__category-title"); + z-index: z-index(".block-editor-block-manager__category-title"); .components-checkbox-control__label { font-weight: 600; } } -.editor-block-manager__checklist { +.block-editor-block-manager__checklist { margin-top: 0; } -.editor-block-manager__category-title, -.editor-block-manager__checklist-item { +.block-editor-block-manager__category-title, +.block-editor-block-manager__checklist-item { border-bottom: 1px solid $gray-300; } -.editor-block-manager__checklist-item { +.block-editor-block-manager__checklist-item { display: flex; justify-content: space-between; align-items: center; @@ -72,11 +72,11 @@ } } -.editor-block-manager__results { +.block-editor-block-manager__results { border-top: $border-width solid $gray-300; } // Remove the top border from results when adjacent to the disabled block count -.editor-block-manager__disabled-blocks-count + .editor-block-manager__results { +.block-editor-block-manager__disabled-blocks-count + .block-editor-block-manager__results { border-top-width: 0; } diff --git a/packages/block-editor/src/components/block-mover/README.md b/packages/block-editor/src/components/block-mover/README.md index 38520072b4ac86..b781de773ef9f3 100644 --- a/packages/block-editor/src/components/block-mover/README.md +++ b/packages/block-editor/src/components/block-mover/README.md @@ -1,12 +1,10 @@ -# Block mover +# BlockMover -Block movers allow moving blocks inside the editor using up and down buttons. +BlockMover component allows moving blocks inside the editor using up and down buttons. ![Block mover screenshot](https://make.wordpress.org/core/files/2020/08/block-mover-screenshot.png) -## Development guidelines - -### Usage +## Usage Shows the block mover buttons in the block toolbar. @@ -15,13 +13,22 @@ import { BlockMover } from '@wordpress/block-editor'; const MyMover = () => <BlockMover clientIds={ [ clientId ] } />; ``` -### Props +## Props -#### clientIds +### clientIds -Blocks IDs +The IDs of the blocks to move. - Type: `Array` +- Required: Yes + +### hideDragHandle + +If this property is true, the drag handle is hidden. + +- Type: `boolean` +- Required: No +- Default: `false` ## Related components diff --git a/packages/block-editor/src/components/block-mover/stories/index.story.js b/packages/block-editor/src/components/block-mover/stories/index.story.js index de30260563f91e..de6d13c797b4d9 100644 --- a/packages/block-editor/src/components/block-mover/stories/index.story.js +++ b/packages/block-editor/src/components/block-mover/stories/index.story.js @@ -14,6 +14,7 @@ import BlockMover from '../'; import { ExperimentalBlockEditorProvider } from '../../provider'; import { store as blockEditorStore } from '../../../store'; +// For the purpose of this story, we need to register the core blocks samples. registerCoreBlocks(); const blocks = [ // vertical @@ -30,81 +31,82 @@ const blocks = [ ] ), ]; -function Provider( { children } ) { - const wrapperStyle = { margin: '24px', position: 'relative' }; - - return ( - <div style={ wrapperStyle }> +/** + * BlockMover component allows moving blocks inside the editor using up and down buttons. + */ +const meta = { + title: 'BlockEditor/BlockMover', + component: BlockMover, + parameters: { + docs: { canvas: { sourceState: 'shown' } }, + }, + decorators: [ + ( Story ) => ( <ExperimentalBlockEditorProvider value={ blocks }> - { children } + <Toolbar label="Block Mover"> + <Story /> + </Toolbar> </ExperimentalBlockEditorProvider> - </div> - ); -} - -function BlockMoverStory() { - const { updateBlockListSettings } = useDispatch( blockEditorStore ); - - useEffect( () => { - /** - * This shouldn't be needed but unfortunatley - * the layout orientation is not declarative, we need - * to render the blocks to update the block settings in the state. - */ - updateBlockListSettings( blocks[ 1 ].clientId, { - orientation: 'horizontal', - } ); - }, [] ); - - return ( - <div> - <p>The mover by default is vertical</p> - <Toolbar label="Block Mover"> - <BlockMover - clientIds={ - blocks.length - ? [ blocks[ 0 ].innerBlocks[ 1 ].clientId ] - : [] - } - /> - </Toolbar> - - <p style={ { marginTop: 36 } }> - But it can also accommodate horizontal blocks. - </p> - <Toolbar label="Block Mover"> - <BlockMover - clientIds={ - blocks.length - ? [ blocks[ 1 ].innerBlocks[ 1 ].clientId ] - : [] - } - /> - </Toolbar> + ), + ], + argTypes: { + clientIds: { + control: { + type: 'none', + }, + description: 'The client IDs of the blocks to move.', + }, + hideDragHandle: { + control: { + type: 'boolean', + }, + description: 'If this property is true, the drag handle is hidden.', + }, + }, +}; +export default meta; - <p style={ { marginTop: 36 } }>We can also hide the drag handle.</p> - <Toolbar label="Block Mover"> - <BlockMover - clientIds={ - blocks.length - ? [ blocks[ 1 ].innerBlocks[ 0 ].clientId ] - : [] - } - hideDragHandle - /> - </Toolbar> - </div> - ); -} +export const Default = { + args: { + clientIds: [ blocks[ 0 ].innerBlocks[ 1 ].clientId ], + }, +}; -export default { - title: 'BlockEditor/BlockMover', +/** + * This story shows the block mover with horizontal orientation. + * It is necessary to render the blocks to update the block settings in the state. + */ +export const Horizontal = { + decorators: [ + ( Story ) => { + const { updateBlockListSettings } = useDispatch( blockEditorStore ); + useEffect( () => { + /** + * This shouldn't be needed but unfortunately + * the layout orientation is not declarative, we need + * to render the blocks to update the block settings in the state. + */ + updateBlockListSettings( blocks[ 1 ].clientId, { + orientation: 'horizontal', + } ); + }, [] ); + return <Story />; + }, + ], + args: { + clientIds: [ blocks[ 1 ].innerBlocks[ 1 ].clientId ], + }, + parameters: { + docs: { canvas: { sourceState: 'hidden' } }, + }, }; -export const _default = () => { - return ( - <Provider> - <BlockMoverStory /> - </Provider> - ); +/** + * You can hide the drag handle by `hideDragHandle` attribute. + */ +export const HideDragHandle = { + args: { + ...Default.args, + hideDragHandle: true, + }, }; diff --git a/packages/block-editor/src/components/block-mover/style.scss b/packages/block-editor/src/components/block-mover/style.scss index c58ac9f19673fc..7d23c0f1e5a988 100644 --- a/packages/block-editor/src/components/block-mover/style.scss +++ b/packages/block-editor/src/components/block-mover/style.scss @@ -71,6 +71,9 @@ // Specificity is necessary to override block toolbar button styles. .components-button.block-editor-block-mover-button { + // Prevent the SVGs inside the button from overflowing the button. + overflow: hidden; + // Focus and toggle pseudo elements. &::before { content: ""; diff --git a/packages/block-editor/src/components/block-parent-selector/index.js b/packages/block-editor/src/components/block-parent-selector/index.js index 9090de42f8b7d7..84b5211089cd95 100644 --- a/packages/block-editor/src/components/block-parent-selector/index.js +++ b/packages/block-editor/src/components/block-parent-selector/index.js @@ -1,7 +1,6 @@ /** * WordPress dependencies */ -import { getBlockType, store as blocksStore } from '@wordpress/blocks'; import { ToolbarButton } from '@wordpress/components'; import { useSelect, useDispatch } from '@wordpress/data'; import { __, sprintf } from '@wordpress/i18n'; @@ -24,31 +23,18 @@ import { unlock } from '../../lock-unlock'; */ export default function BlockParentSelector() { const { selectBlock } = useDispatch( blockEditorStore ); - const { parentClientId, isVisible } = useSelect( ( select ) => { + const { parentClientId } = useSelect( ( select ) => { const { - getBlockName, getBlockParents, getSelectedBlockClientId, - getBlockEditingMode, getParentSectionBlock, } = unlock( select( blockEditorStore ) ); - const { hasBlockSupport } = select( blocksStore ); const selectedBlockClientId = getSelectedBlockClientId(); const parentSection = getParentSectionBlock( selectedBlockClientId ); const parents = getBlockParents( selectedBlockClientId ); const _parentClientId = parentSection ?? parents[ parents.length - 1 ]; - const parentBlockName = getBlockName( _parentClientId ); - const _parentBlockType = getBlockType( parentBlockName ); return { parentClientId: _parentClientId, - isVisible: - _parentClientId && - getBlockEditingMode( _parentClientId ) !== 'disabled' && - hasBlockSupport( - _parentBlockType, - '__experimentalParentSelector', - true - ), }; }, [] ); const blockInformation = useBlockDisplayInformation( parentClientId ); @@ -61,10 +47,6 @@ export default function BlockParentSelector() { highlightParent: true, } ); - if ( ! isVisible ) { - return null; - } - return ( <div className="block-editor-block-parent-selector" 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 8128e89418f45a..0c7e54c3c62b24 100644 --- a/packages/block-editor/src/components/block-patterns-list/index.js +++ b/packages/block-editor/src/components/block-patterns-list/index.js @@ -41,6 +41,7 @@ function BlockPattern( { onHover, showTitlesAsTooltip, category, + isSelected, } ) { const [ isDragging, setIsDragging ] = useState( false ); const { blocks, viewportWidth } = pattern; @@ -114,6 +115,7 @@ function BlockPattern( { pattern.type === INSERTER_PATTERN_TYPES.user && ! pattern.syncStatus, + 'is-selected': isSelected, } ) } /> @@ -192,6 +194,7 @@ function BlockPatternsList( ref ) { const [ activeCompositeId, setActiveCompositeId ] = useState( undefined ); + const [ activePattern, setActivePattern ] = useState( null ); // State to track active pattern useEffect( () => { // Reset the active composite item whenever the available patterns change, @@ -201,6 +204,11 @@ function BlockPatternsList( setActiveCompositeId( firstCompositeItemId ); }, [ blockPatterns ] ); + const handleClickPattern = ( pattern, blocks ) => { + setActivePattern( pattern.name ); + onClickPattern( pattern, blocks ); + }; + return ( <Composite orientation={ orientation } @@ -216,11 +224,14 @@ function BlockPatternsList( key={ pattern.name } id={ pattern.name } pattern={ pattern } - onClick={ onClickPattern } + onClick={ handleClickPattern } onHover={ onHover } isDraggable={ isDraggable } showTitlesAsTooltip={ showTitlesAsTooltip } category={ category } + isSelected={ + !! activePattern && activePattern === pattern.name + } /> ) ) } { pagingProps && <BlockPatternsPaging { ...pagingProps } /> } diff --git a/packages/block-editor/src/components/block-patterns-list/stories/fixtures.js b/packages/block-editor/src/components/block-patterns-list/stories/fixtures.js index 0fd895bbe1716d..7825ad0d1391c6 100644 --- a/packages/block-editor/src/components/block-patterns-list/stories/fixtures.js +++ b/packages/block-editor/src/components/block-patterns-list/stories/fixtures.js @@ -530,6 +530,7 @@ export default [ background: '#000000', }, }, + tagName: 'hr', }, innerBlocks: [], originalContent: 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 c46bb49b9a9016..8b1b0b54c9b1a0 100644 --- a/packages/block-editor/src/components/block-patterns-list/style.scss +++ b/packages/block-editor/src/components/block-patterns-list/style.scss @@ -44,19 +44,29 @@ outline: $border-width solid rgba($black, 0.1); outline-offset: -$border-width; border-radius: $radius-medium; + + transition: outline 0.1s linear; + @include reduce-motion("transition"); } } - &:hover:not(:focus) .block-editor-block-preview__container::after { + // Selected + &.is-selected .block-editor-block-preview__container::after { + outline-color: $gray-900; + outline-width: var(--wp-admin-border-width-focus); + outline-offset: calc(-1 * var(--wp-admin-border-width-focus)); + } + + // Hover state + &:hover .block-editor-block-preview__container::after { outline-color: rgba($black, 0.3); } - &:focus .block-editor-block-preview__container::after { + // Focused state + &[data-focus-visible] .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"); + outline-offset: calc(-1 * var(--wp-admin-border-width-focus)); } .block-editor-patterns__pattern-details:not(:empty) { @@ -68,6 +78,7 @@ .block-editor-patterns__pattern-icon-wrapper { min-width: 24px; height: 24px; + .block-editor-patterns__pattern-icon { fill: var(--wp-block-synced-color); } diff --git a/packages/block-editor/src/components/block-popover/inbetween.js b/packages/block-editor/src/components/block-popover/inbetween.js index 2ed9ee0bcb284f..1d7c1766732409 100644 --- a/packages/block-editor/src/components/block-popover/inbetween.js +++ b/packages/block-editor/src/components/block-popover/inbetween.js @@ -148,6 +148,10 @@ function BlockPopoverInbetween( { ? nextRect.left - previousRect.right : 0; } + + // Avoid a negative width which happens when the next rect + // is on the next line. + width = Math.max( width, 0 ); } return new window.DOMRect( left, top, width, height ); diff --git a/packages/block-editor/src/components/block-preview/style.scss b/packages/block-editor/src/components/block-preview/style.scss index 9bdd85f66445f8..c304f46fa6ad21 100644 --- a/packages/block-editor/src/components/block-preview/style.scss +++ b/packages/block-editor/src/components/block-preview/style.scss @@ -1,6 +1,6 @@ // These rules ensure the preview scales smoothly regardless of the container size. .block-editor-block-preview__container { - // In the component, a top padding is provided as an inline style to provid an aspect-ratio. + // In the component, a top padding is provided as an inline style to provide an aspect-ratio. // This positioning enables the content to sit on top of that padding to fit. position: relative; diff --git a/packages/block-editor/src/components/block-rename/modal.js b/packages/block-editor/src/components/block-rename/modal.js index 2679c8157cc0a2..3f99e71b7b699e 100644 --- a/packages/block-editor/src/components/block-rename/modal.js +++ b/packages/block-editor/src/components/block-rename/modal.js @@ -11,24 +11,44 @@ import { import { __, sprintf } from '@wordpress/i18n'; import { useState } from '@wordpress/element'; import { speak } from '@wordpress/a11y'; +import { useSelect, useDispatch } from '@wordpress/data'; /** * Internal dependencies */ +import { store as blockEditorStore } from '../../store'; +import { useBlockDisplayInformation } from '..'; import isEmptyString from './is-empty-string'; -export default function BlockRenameModal( { - blockName, - originalBlockName, - onClose, - onSave, +export default function BlockRenameModal( { clientId, onClose } ) { + const [ editedBlockName, setEditedBlockName ] = useState(); + + const blockInformation = useBlockDisplayInformation( clientId ); + const { metadata } = useSelect( + ( select ) => { + const { getBlockAttributes } = select( blockEditorStore ); + + return { + metadata: getBlockAttributes( clientId )?.metadata, + }; + }, + [ clientId ] + ); + const { updateBlockAttributes } = useDispatch( blockEditorStore ); + + const blockName = metadata?.name || ''; + const originalBlockName = blockInformation?.title; // Pattern Overrides is a WordPress-only feature but it also uses the Block Binding API. // Ideally this should not be inside the block editor package, but we keep it here for simplicity. - hasOverridesWarning, -} ) { - const [ editedBlockName, setEditedBlockName ] = useState( blockName ); + const hasOverridesWarning = + !! blockName && + !! metadata?.bindings && + Object.values( metadata.bindings ).some( + ( binding ) => binding.source === 'core/pattern-overrides' + ); - const nameHasChanged = editedBlockName !== blockName; + const nameHasChanged = + editedBlockName !== undefined && editedBlockName !== blockName; const nameIsOriginal = editedBlockName === originalBlockName; const nameIsEmpty = isEmptyString( editedBlockName ); @@ -37,6 +57,8 @@ export default function BlockRenameModal( { const autoSelectInputText = ( event ) => event.target.select(); const handleSubmit = () => { + const newName = + nameIsOriginal || nameIsEmpty ? undefined : editedBlockName; const message = nameIsOriginal || nameIsEmpty ? sprintf( @@ -52,7 +74,12 @@ export default function BlockRenameModal( { // Must be assertive to immediately announce change. speak( message, 'assertive' ); - onSave( editedBlockName ); + updateBlockAttributes( [ clientId ], { + metadata: { + ...metadata, + name: newName, + }, + } ); // Immediate close avoids ability to hit save multiple times. onClose(); @@ -81,7 +108,7 @@ export default function BlockRenameModal( { <TextControl __nextHasNoMarginBottom __next40pxDefaultSize - value={ editedBlockName } + value={ editedBlockName ?? blockName } label={ __( 'Name' ) } help={ hasOverridesWarning @@ -105,7 +132,8 @@ export default function BlockRenameModal( { <Button __next40pxDefaultSize - aria-disabled={ ! isNameValid } + accessibleWhenDisabled + disabled={ ! isNameValid } variant="primary" type="submit" > diff --git a/packages/block-editor/src/components/block-rename/rename-control.js b/packages/block-editor/src/components/block-rename/rename-control.js index fb6b446782aa78..641ff7cebe951b 100644 --- a/packages/block-editor/src/components/block-rename/rename-control.js +++ b/packages/block-editor/src/components/block-rename/rename-control.js @@ -2,54 +2,17 @@ * WordPress dependencies */ import { MenuItem } from '@wordpress/components'; -import { useSelect, useDispatch } from '@wordpress/data'; import { __ } from '@wordpress/i18n'; import { useState } from '@wordpress/element'; /** * Internal dependencies */ -import { store as blockEditorStore } from '../../store'; -import { useBlockDisplayInformation } from '..'; -import isEmptyString from './is-empty-string'; import BlockRenameModal from './modal'; export default function BlockRenameControl( { clientId } ) { const [ renamingBlock, setRenamingBlock ] = useState( false ); - const { metadata } = useSelect( - ( select ) => { - const { getBlockAttributes } = select( blockEditorStore ); - - const _metadata = getBlockAttributes( clientId )?.metadata; - return { - metadata: _metadata, - }; - }, - [ clientId ] - ); - - const { updateBlockAttributes } = useDispatch( blockEditorStore ); - - const customName = metadata?.name; - const hasPatternOverrides = - !! customName && - !! metadata?.bindings && - Object.values( metadata.bindings ).some( - ( binding ) => binding.source === 'core/pattern-overrides' - ); - - function onChange( newName ) { - updateBlockAttributes( [ clientId ], { - metadata: { - ...metadata, - name: newName, - }, - } ); - } - - const blockInformation = useBlockDisplayInformation( clientId ); - return ( <> <MenuItem @@ -63,23 +26,8 @@ export default function BlockRenameControl( { clientId } ) { </MenuItem> { renamingBlock && ( <BlockRenameModal - blockName={ customName || '' } - originalBlockName={ blockInformation?.title } - hasOverridesWarning={ hasPatternOverrides } + clientId={ clientId } onClose={ () => setRenamingBlock( false ) } - onSave={ ( newName ) => { - // If the new value is the block's original name (e.g. `Group`) - // or it is an empty string then assume the intent is to reset - // the value. Therefore reset the metadata. - if ( - newName === blockInformation?.title || - isEmptyString( newName ) - ) { - newName = undefined; - } - - onChange( newName ); - } } /> ) } </> diff --git a/packages/block-editor/src/components/block-settings-menu-controls/index.js b/packages/block-editor/src/components/block-settings-menu-controls/index.js index 4ebce4172e9b37..b0755be4c26297 100644 --- a/packages/block-editor/src/components/block-settings-menu-controls/index.js +++ b/packages/block-editor/src/components/block-settings-menu-controls/index.js @@ -55,7 +55,8 @@ const BlockSettingsMenuControlsSlot = ( { fillProps, clientIds = null } ) => { const convertToGroupButtonProps = useConvertToGroupButtonProps( selectedClientIds ); const { isGroupable, isUngroupable } = convertToGroupButtonProps; - const showConvertToGroupButton = isGroupable || isUngroupable; + const showConvertToGroupButton = + ( isGroupable || isUngroupable ) && ! isContentOnly; return ( <Slot 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 5b5360cc48a8fe..43922c28a668e2 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 @@ -19,24 +19,37 @@ import { pipe, useCopyToClipboard } from '@wordpress/compose'; * Internal dependencies */ import BlockActions from '../block-actions'; -import __unstableCommentIconFill from '../../components/collab/block-comment-icon-slot'; +import CommentIconSlotFill from '../../components/collab/block-comment-icon-slot'; import BlockHTMLConvertButton from './block-html-convert-button'; import __unstableBlockSettingsMenuFirstItem from './block-settings-menu-first-item'; import BlockSettingsMenuControls from '../block-settings-menu-controls'; import BlockParentSelectorMenuItem from './block-parent-selector-menu-item'; import { store as blockEditorStore } from '../../store'; import { unlock } from '../../lock-unlock'; +import { useNotifyCopy } from '../../utils/use-notify-copy'; const POPOVER_PROPS = { className: 'block-editor-block-settings-menu__popover', placement: 'bottom-start', }; -function CopyMenuItem( { clientIds, onCopy, label, shortcut } ) { +function CopyMenuItem( { + clientIds, + onCopy, + label, + shortcut, + eventType = 'copy', +} ) { const { getBlocksByClientId } = useSelect( blockEditorStore ); + const notifyCopy = useNotifyCopy(); const ref = useCopyToClipboard( () => serialize( getBlocksByClientId( clientIds ) ), - onCopy + () => { + if ( onCopy && eventType === 'copy' ) { + onCopy(); + } + notifyCopy( eventType, clientIds ); + } ); const copyMenuItemLabel = label ? label : __( 'Copy' ); return ( @@ -57,6 +70,7 @@ export function BlockSettingsDropdown( { const currentClientId = block?.clientId; const count = clientIds.length; const firstBlockClientId = clientIds[ 0 ]; + const { firstParentClientId, parentBlockType, @@ -102,6 +116,7 @@ export function BlockSettingsDropdown( { }, [ firstBlockClientId ] ); + const { getBlockOrder, getSelectedBlockClientIds } = useSelect( blockEditorStore ); @@ -248,15 +263,13 @@ export function BlockSettingsDropdown( { clientId={ firstBlockClientId } /> ) } - { ! isContentOnly && ( - <CopyMenuItem - clientIds={ clientIds } - onCopy={ onCopy } - shortcut={ displayShortcut.primary( - 'c' - ) } - /> - ) } + <CopyMenuItem + clientIds={ clientIds } + onCopy={ onCopy } + shortcut={ displayShortcut.primary( + 'c' + ) } + /> { canDuplicate && ( <MenuItem onClick={ pipe( @@ -295,7 +308,7 @@ export function BlockSettingsDropdown( { </MenuItem> </> ) } - <__unstableCommentIconFill.Slot + <CommentIconSlotFill.Slot fillProps={ { onClose } } /> </MenuGroup> @@ -305,20 +318,23 @@ export function BlockSettingsDropdown( { clientIds={ clientIds } onCopy={ onCopy } label={ __( 'Copy styles' ) } + eventType="copyStyles" /> <MenuItem onClick={ onPasteStyles }> { __( 'Paste styles' ) } </MenuItem> </MenuGroup> ) } - <BlockSettingsMenuControls.Slot - fillProps={ { - onClose, - count, - firstBlockClientId, - } } - clientIds={ clientIds } - /> + { ! isContentOnly && ( + <BlockSettingsMenuControls.Slot + fillProps={ { + onClose, + count, + firstBlockClientId, + } } + clientIds={ clientIds } + /> + ) } { typeof children === 'function' ? children( { onClose } ) : Children.map( ( child ) => diff --git a/packages/block-editor/src/components/block-settings-menu/index.js b/packages/block-editor/src/components/block-settings-menu/index.js index 50e8abe09d018b..1b96f30e130386 100644 --- a/packages/block-editor/src/components/block-settings-menu/index.js +++ b/packages/block-editor/src/components/block-settings-menu/index.js @@ -7,12 +7,12 @@ import { ToolbarGroup, ToolbarItem } from '@wordpress/components'; * Internal dependencies */ import BlockSettingsDropdown from './block-settings-dropdown'; -import __unstableCommentIconToolbarFill from '../../components/collab/block-comment-icon-toolbar-slot'; +import CommentIconToolbarSlotFill from '../../components/collab/block-comment-icon-toolbar-slot'; export function BlockSettingsMenu( { clientIds, ...props } ) { return ( <ToolbarGroup> - <__unstableCommentIconToolbarFill.Slot /> + <CommentIconToolbarSlotFill.Slot /> <ToolbarItem> { ( toggleProps ) => ( diff --git a/packages/block-editor/src/components/block-styles/preview.native.js b/packages/block-editor/src/components/block-styles/preview.native.js index 005c6fac3f2c14..3db7dda724d626 100644 --- a/packages/block-editor/src/components/block-styles/preview.native.js +++ b/packages/block-editor/src/components/block-styles/preview.native.js @@ -33,7 +33,7 @@ function StylePreview( { onPress, isActive, style, url } ) { function onLayout() { const columnsNum = - // To indicate scroll availabilty, there is a need to display additional half the column. + // To indicate scroll availability, there is a need to display additional half the column. Math.floor( BottomSheet.getWidth() / MAX_ITEM_WIDTH ) + HALF_COLUMN; setItemWidth( BottomSheet.getWidth() / columnsNum ); } diff --git a/packages/block-editor/src/components/block-styles/utils.js b/packages/block-editor/src/components/block-styles/utils.js index 511e78da83da60..e4483ec4e695f8 100644 --- a/packages/block-editor/src/components/block-styles/utils.js +++ b/packages/block-editor/src/components/block-styles/utils.js @@ -10,7 +10,7 @@ import { _x } from '@wordpress/i18n'; * @param {Array} styles Block styles. * @param {string} className Class name * - * @return {Object?} The active style. + * @return {?Object} The active style. */ export function getActiveStyle( styles, className ) { for ( const style of new TokenList( className ).values() ) { @@ -34,7 +34,7 @@ export function getActiveStyle( styles, className ) { * Replaces the active style in the block's className. * * @param {string} className Class name. - * @param {Object?} activeStyle The replaced style. + * @param {?Object} activeStyle The replaced style. * @param {Object} newStyle The replacing style. * * @return {string} The updated className. @@ -83,7 +83,7 @@ export function getRenderedStyles( styles ) { * * @param {Array} styles Block styles. * - * @return {Object?} The default style object, if found. + * @return {?Object} The default style object, if found. */ export function getDefaultStyle( styles ) { return styles?.find( ( style ) => style.isDefault ); diff --git a/packages/block-editor/src/components/block-switcher/block-transformations-menu.js b/packages/block-editor/src/components/block-switcher/block-transformations-menu.js index 271b7fabd51731..f2c66e389b650d 100644 --- a/packages/block-editor/src/components/block-switcher/block-transformations-menu.js +++ b/packages/block-editor/src/components/block-switcher/block-transformations-menu.js @@ -27,20 +27,20 @@ import BlockVariationTransformations from './block-variation-transformations'; * @return {Record<string, Object[]>} The grouped block transformations. */ function useGroupedTransforms( possibleBlockTransformations ) { - const priorityContentTranformationBlocks = { + const priorityContentTransformationBlocks = { 'core/paragraph': 1, 'core/heading': 2, 'core/list': 3, 'core/quote': 4, }; const transformations = useMemo( () => { - const priorityTextTranformsNames = Object.keys( - priorityContentTranformationBlocks + const priorityTextTransformsNames = Object.keys( + priorityContentTransformationBlocks ); const groupedPossibleTransforms = possibleBlockTransformations.reduce( ( accumulator, item ) => { const { name } = item; - if ( priorityTextTranformsNames.includes( name ) ) { + if ( priorityTextTransformsNames.includes( name ) ) { accumulator.priorityTextTransformations.push( item ); } else { accumulator.restTransformations.push( item ); @@ -71,8 +71,8 @@ function useGroupedTransforms( possibleBlockTransformations ) { // Order the priority text transformations. transformations.priorityTextTransformations.sort( ( { name: currentName }, { name: nextName } ) => { - return priorityContentTranformationBlocks[ currentName ] < - priorityContentTranformationBlocks[ nextName ] + return priorityContentTransformationBlocks[ currentName ] < + priorityContentTransformationBlocks[ nextName ] ? -1 : 1; } @@ -125,7 +125,7 @@ const BlockTransformationsMenu = ( { /> ) } { priorityTextTransformations.map( ( item ) => ( - <BlockTranformationItem + <BlockTransformationItem key={ item.name } item={ item } onSelect={ onSelect } @@ -151,7 +151,7 @@ function RestTransformationItems( { setHoveredTransformItemName, } ) { return restTransformations.map( ( item ) => ( - <BlockTranformationItem + <BlockTransformationItem key={ item.name } item={ item } onSelect={ onSelect } @@ -160,7 +160,7 @@ function RestTransformationItems( { ) ); } -function BlockTranformationItem( { +function BlockTransformationItem( { item, onSelect, setHoveredTransformItemName, diff --git a/packages/block-editor/src/components/block-switcher/block-variation-transformations.js b/packages/block-editor/src/components/block-switcher/block-variation-transformations.js index b2ecdb7622209f..c63c35fc9ca7f0 100644 --- a/packages/block-editor/src/components/block-switcher/block-variation-transformations.js +++ b/packages/block-editor/src/components/block-switcher/block-variation-transformations.js @@ -74,7 +74,7 @@ const BlockVariationTransformations = ( { /> ) } { transformations?.map( ( item ) => ( - <BlockVariationTranformationItem + <BlockVariationTransformationItem key={ item.name } item={ item } onSelect={ onSelect } @@ -85,7 +85,7 @@ const BlockVariationTransformations = ( { ); }; -function BlockVariationTranformationItem( { +function BlockVariationTransformationItem( { item, onSelect, setHoveredTransformItemName, diff --git a/packages/block-editor/src/components/block-switcher/index.js b/packages/block-editor/src/components/block-switcher/index.js index 79f33bd30d7537..981dd4a395f89a 100644 --- a/packages/block-editor/src/components/block-switcher/index.js +++ b/packages/block-editor/src/components/block-switcher/index.js @@ -18,6 +18,7 @@ import { } from '@wordpress/blocks'; import { useSelect, useDispatch } from '@wordpress/data'; import { copy } from '@wordpress/icons'; +import { store as preferencesStore } from '@wordpress/preferences'; /** * Internal dependencies @@ -81,7 +82,7 @@ function BlockSwitcherDropdownMenuContents( { ); } } - // Simple block tranformation based on the `Block Transforms` API. + // Simple block transformation based on the `Block Transforms` API. function onBlockTransform( name ) { const newBlocks = switchToBlockType( blocks, name ); replaceBlocks( clientIds, newBlocks ); @@ -185,21 +186,6 @@ function BlockSwitcherDropdownMenuContents( { ); } -const BlockIndicator = ( { icon, showTitle, blockTitle } ) => ( - <> - <BlockIcon - className="block-editor-block-switcher__toggle" - icon={ icon } - showColors - /> - { showTitle && blockTitle && ( - <span className="block-editor-block-switcher__toggle-text"> - { blockTitle } - </span> - ) } - </> -); - export const BlockSwitcher = ( { clientIds } ) => { const { hasContentOnlyLocking, @@ -272,6 +258,11 @@ export const BlockSwitcher = ( { clientIds } ) => { clientId: clientIds?.[ 0 ], maximumLength: 35, } ); + const showIconLabels = useSelect( + ( select ) => + select( preferencesStore ).get( 'core', 'showIconLabels' ), + [] + ); if ( invalidBlocks ) { return null; @@ -282,6 +273,11 @@ export const BlockSwitcher = ( { clientIds } ) => { ? blockTitle : __( 'Multiple blocks selected' ); + const blockIndicatorText = + ( isReusable || isTemplate ) && ! showIconLabels && blockTitle + ? blockTitle + : undefined; + const hideDropdown = isDisabled || ( ! hasBlockStyles && ! canRemove ) || @@ -295,12 +291,13 @@ export const BlockSwitcher = ( { clientIds } ) => { className="block-editor-block-switcher__no-switcher-icon" title={ blockSwitcherLabel } icon={ - <BlockIndicator + <BlockIcon + className="block-editor-block-switcher__toggle" icon={ icon } - showTitle={ isReusable || isTemplate } - blockTitle={ blockTitle } + showColors /> } + text={ blockIndicatorText } /> </ToolbarGroup> ); @@ -329,12 +326,13 @@ export const BlockSwitcher = ( { clientIds } ) => { className: 'block-editor-block-switcher__popover', } } icon={ - <BlockIndicator + <BlockIcon + className="block-editor-block-switcher__toggle" icon={ icon } - showTitle={ isReusable || isTemplate } - blockTitle={ blockTitle } + showColors /> } + text={ blockIndicatorText } toggleProps={ { description: blockSwitcherDescription, ...toggleProps, diff --git a/packages/block-editor/src/components/block-switcher/style.scss b/packages/block-editor/src/components/block-switcher/style.scss index 3dc2a7d591c926..62a7bebe95d278 100644 --- a/packages/block-editor/src/components/block-switcher/style.scss +++ b/packages/block-editor/src/components/block-switcher/style.scss @@ -26,15 +26,6 @@ } } -.block-editor-block-switcher__toggle-text { - margin-left: $grid-unit-10; - - // Account for double label when show-text-buttons is set. - .show-icon-labels & { - display: none; - } -} - .components-button.block-editor-block-switcher__no-switcher-icon { display: flex; diff --git a/packages/block-editor/src/components/block-switcher/use-transformed-patterns.js b/packages/block-editor/src/components/block-switcher/use-transformed-patterns.js index 86055d5425255b..dbba208e7c3e17 100644 --- a/packages/block-editor/src/components/block-switcher/use-transformed-patterns.js +++ b/packages/block-editor/src/components/block-switcher/use-transformed-patterns.js @@ -72,7 +72,7 @@ export const getPatternTransformedBlocks = ( // No need to loop through other pattern's blocks. break; } - // Bail eary if a selected block has not been matched. + // Bail early if a selected block has not been matched. if ( ! isMatch ) { return; } diff --git a/packages/block-editor/src/components/block-title/stories/index.story.js b/packages/block-editor/src/components/block-title/stories/index.story.js new file mode 100644 index 00000000000000..dc66fc721e5158 --- /dev/null +++ b/packages/block-editor/src/components/block-title/stories/index.story.js @@ -0,0 +1,76 @@ +/** + * WordPress dependencies + */ +import { registerCoreBlocks } from '@wordpress/block-library'; +import { createBlock } from '@wordpress/blocks'; + +/** + * Internal dependencies + */ +import { ExperimentalBlockEditorProvider } from '../../provider'; +import BlockTitle from '../'; + +// Register core blocks for the story environment +registerCoreBlocks(); + +// Sample blocks for testing +const blocks = [ createBlock( 'core/paragraph' ) ]; + +const meta = { + title: 'BlockEditor/BlockTitle', + component: BlockTitle, + parameters: { + docs: { + canvas: { sourceState: 'shown' }, + description: { + component: + "Renders the block's configured title as a string, or empty if the title cannot be determined.", + }, + }, + }, + decorators: [ + ( Story ) => ( + <ExperimentalBlockEditorProvider value={ blocks }> + <Story /> + </ExperimentalBlockEditorProvider> + ), + ], + argTypes: { + clientId: { + control: { type: null }, + description: 'Client ID of block.', + table: { + type: { + summary: 'string', + }, + }, + }, + maximumLength: { + control: { type: 'number' }, + description: + 'The maximum length that the block title string may be before truncated.', + table: { + type: { + summary: 'number', + }, + }, + }, + context: { + control: { type: 'text' }, + description: 'The context to pass to `getBlockLabel`.', + table: { + type: { + summary: 'string', + }, + }, + }, + }, +}; + +export default meta; + +export const Default = { + args: { + clientId: blocks[ 0 ].clientId, + }, +}; diff --git a/packages/block-editor/src/components/block-title/test/index.js b/packages/block-editor/src/components/block-title/test/index.js index 8a4d3c2f52fd7e..fc6af61bae9e7c 100644 --- a/packages/block-editor/src/components/block-title/test/index.js +++ b/packages/block-editor/src/components/block-title/test/index.js @@ -31,7 +31,9 @@ const blockLabelMap = { }; jest.mock( '@wordpress/blocks', () => { + const actualImplementation = jest.requireActual( '@wordpress/blocks' ); return { + ...actualImplementation, isReusableBlock( { title } ) { return title === 'Reusable Block'; }, diff --git a/packages/block-editor/src/components/block-toolbar/index.js b/packages/block-editor/src/components/block-toolbar/index.js index b3bf7e94accb08..083d77a694a7b2 100644 --- a/packages/block-editor/src/components/block-toolbar/index.js +++ b/packages/block-editor/src/components/block-toolbar/index.js @@ -36,6 +36,7 @@ import __unstableBlockNameContext from './block-name-context'; import NavigableToolbar from '../navigable-toolbar'; import { useHasBlockToolbar } from './use-has-block-toolbar'; import ChangeDesign from './change-design'; +import SwitchSectionStyle from './switch-section-style'; import { unlock } from '../../lock-unlock'; /** @@ -72,6 +73,9 @@ export function PrivateBlockToolbar( { showSlots, showGroupButtons, showLockButtons, + showSwitchSectionStyleButton, + hasFixedToolbar, + isNavigationMode, } = useSelect( ( select ) => { const { getBlockName, @@ -83,8 +87,10 @@ export function PrivateBlockToolbar( { getBlockAttributes, getBlockParentsByBlockName, getTemplateLock, + getSettings, getParentSectionBlock, isZoomOut, + isNavigationMode: _isNavigationMode, } = unlock( select( blockEditorStore ) ); const selectedBlockClientIds = getSelectedBlockClientIds(); const selectedBlockClientId = selectedBlockClientIds[ 0 ]; @@ -117,6 +123,9 @@ export function PrivateBlockToolbar( { const _hasTemplateLock = selectedBlockClientIds.some( ( id ) => getTemplateLock( id ) === 'contentOnly' ); + + const _isZoomOut = isZoomOut(); + return { blockClientId: selectedBlockClientId, blockClientIds: selectedBlockClientIds, @@ -125,8 +134,9 @@ export function PrivateBlockToolbar( { shouldShowVisualToolbar: isValid && isVisual, toolbarKey: `${ selectedBlockClientId }${ parentClientId }`, showParentSelector: - ! isZoomOut() && + ! _isZoomOut && parentBlockType && + editingMode !== 'contentOnly' && getBlockEditingMode( parentClientId ) !== 'disabled' && hasBlockSupport( parentBlockType, @@ -137,10 +147,13 @@ export function PrivateBlockToolbar( { isUsingBindings: _isUsingBindings, hasParentPattern: _hasParentPattern, hasContentOnlyLocking: _hasTemplateLock, - showShuffleButton: isZoomOut(), - showSlots: ! isZoomOut(), - showGroupButtons: ! isZoomOut(), - showLockButtons: ! isZoomOut(), + showShuffleButton: _isZoomOut, + showSlots: ! _isZoomOut, + showGroupButtons: ! _isZoomOut, + showLockButtons: ! _isZoomOut, + showSwitchSectionStyleButton: _isZoomOut, + hasFixedToolbar: getSettings().hasFixedToolbar, + isNavigationMode: _isNavigationMode(), }; }, [] ); @@ -167,6 +180,7 @@ export function PrivateBlockToolbar( { // Shifts the toolbar to make room for the parent block selector. const classes = clsx( 'block-editor-block-contextual-toolbar', { 'has-parent': showParentSelector, + 'is-inverted-toolbar': isNavigationMode && ! hasFixedToolbar, } ); const innerClasses = clsx( 'block-editor-block-toolbar', { @@ -222,6 +236,9 @@ export function PrivateBlockToolbar( { { showShuffleButton && ( <ChangeDesign clientId={ blockClientIds[ 0 ] } /> ) } + { showSwitchSectionStyleButton && ( + <SwitchSectionStyle clientId={ blockClientIds[ 0 ] } /> + ) } { shouldShowVisualToolbar && showSlots && ( <> <BlockControls.Slot diff --git a/packages/block-editor/src/components/block-toolbar/index.native.js b/packages/block-editor/src/components/block-toolbar/index.native.js index ad7d6a16b3b47a..d0188aa669731d 100644 --- a/packages/block-editor/src/components/block-toolbar/index.native.js +++ b/packages/block-editor/src/components/block-toolbar/index.native.js @@ -14,7 +14,7 @@ import UngroupButton from '../ungroup-button'; import { BlockSettingsButton } from '../block-settings'; import { store as blockEditorStore } from '../../store'; -const REMOVE_EMPY_PARENT_BLOCKS = [ +const REMOVE_EMPTY_PARENT_BLOCKS = [ 'core/buttons', 'core/columns', 'core/social-links', @@ -69,7 +69,7 @@ export default function BlockToolbar( { anchorNodeRef } ) { // have inner blocks, ideally we should match the behavior as in // the Web editor and show a placeholder instead of removing the parent. if ( - REMOVE_EMPY_PARENT_BLOCKS.includes( parentBlockName ) && + REMOVE_EMPTY_PARENT_BLOCKS.includes( parentBlockName ) && parentNumberOfInnerBlocks === 1 ) { removeBlock( rootClientId ); diff --git a/packages/block-editor/src/components/block-toolbar/switch-section-style.js b/packages/block-editor/src/components/block-toolbar/switch-section-style.js new file mode 100644 index 00000000000000..be4f041c0e8a48 --- /dev/null +++ b/packages/block-editor/src/components/block-toolbar/switch-section-style.js @@ -0,0 +1,115 @@ +/** + * WordPress dependencies + */ +import { + ToolbarButton, + ToolbarGroup, + Icon, + Path, + SVG, +} from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; +import { useDispatch, useSelect } from '@wordpress/data'; +import { useContext } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import useStylesForBlocks from '../block-styles/use-styles-for-block'; +import { replaceActiveStyle } from '../block-styles/utils'; +import { store as blockEditorStore } from '../../store'; +import { GlobalStylesContext } from '../global-styles'; +import { globalStylesDataKey } from '../../store/private-keys'; +import { getVariationStylesWithRefValues } from '../../hooks/block-style-variation'; + +const styleIcon = ( + <SVG + viewBox="0 0 24 24" + xmlns="http://www.w3.org/2000/svg" + width="24" + height="24" + aria-hidden="true" + focusable="false" + > + <Path d="M17.2 10.9c-.5-1-1.2-2.1-2.1-3.2-.6-.9-1.3-1.7-2.1-2.6L12 4l-1 1.1c-.6.9-1.3 1.7-2 2.6-.8 1.2-1.5 2.3-2 3.2-.6 1.2-1 2.2-1 3 0 3.4 2.7 6.1 6.1 6.1s6.1-2.7 6.1-6.1c0-.8-.3-1.8-1-3z" /> + <Path + stroke="currentColor" + strokeWidth="1.5" + d="M17.2 10.9c-.5-1-1.2-2.1-2.1-3.2-.6-.9-1.3-1.7-2.1-2.6L12 4l-1 1.1c-.6.9-1.3 1.7-2 2.6-.8 1.2-1.5 2.3-2 3.2-.6 1.2-1 2.2-1 3 0 3.4 2.7 6.1 6.1 6.1s6.1-2.7 6.1-6.1c0-.8-.3-1.8-1-3z" + /> + </SVG> +); + +function SwitchSectionStyle( { clientId } ) { + const { stylesToRender, activeStyle, className } = useStylesForBlocks( { + clientId, + } ); + const { updateBlockAttributes } = useDispatch( blockEditorStore ); + + // Get global styles data + const { merged: mergedConfig } = useContext( GlobalStylesContext ); + const { globalSettings, globalStyles, blockName } = useSelect( + ( select ) => { + const settings = select( blockEditorStore ).getSettings(); + return { + globalSettings: settings.__experimentalFeatures, + globalStyles: settings[ globalStylesDataKey ], + blockName: select( blockEditorStore ).getBlockName( clientId ), + }; + }, + [ clientId ] + ); + + // Get the background color for the active style + const activeStyleBackground = activeStyle?.name + ? getVariationStylesWithRefValues( + { + settings: mergedConfig?.settings ?? globalSettings, + styles: mergedConfig?.styles ?? globalStyles, + }, + blockName, + activeStyle.name + )?.color?.background + : undefined; + + if ( ! stylesToRender || stylesToRender.length === 0 ) { + return null; + } + + const handleStyleSwitch = () => { + const currentIndex = stylesToRender.findIndex( + ( style ) => style.name === activeStyle.name + ); + + const nextIndex = ( currentIndex + 1 ) % stylesToRender.length; + const nextStyle = stylesToRender[ nextIndex ]; + + const styleClassName = replaceActiveStyle( + className, + activeStyle, + nextStyle + ); + + updateBlockAttributes( clientId, { + className: styleClassName, + } ); + }; + + return ( + <ToolbarGroup> + <ToolbarButton + onClick={ handleStyleSwitch } + label={ __( 'Shuffle styles' ) } + > + <Icon + icon={ styleIcon } + style={ { + fill: activeStyleBackground || 'transparent', + } } + /> + </ToolbarButton> + </ToolbarGroup> + ); +} + +export default SwitchSectionStyle; diff --git a/packages/block-editor/src/components/block-tools/style.scss b/packages/block-editor/src/components/block-tools/style.scss index b553d42668cf38..35d075c1a99b78 100644 --- a/packages/block-editor/src/components/block-tools/style.scss +++ b/packages/block-editor/src/components/block-tools/style.scss @@ -78,6 +78,7 @@ color: $white; padding: 0; + // TODO: Consider passing size="small" to the Inserter toggle instead. // Special dimensions for this button. min-width: $button-size-small; height: $button-size-small; @@ -139,6 +140,50 @@ border-right-color: $gray-900; } + .is-inverted-toolbar { + background-color: $gray-900; + color: $gray-100; + + &.block-editor-block-contextual-toolbar { + border-color: $gray-800; + } + + button { + color: $gray-300; + + &:hover { + color: $white; + } + + &:focus::before { + box-shadow: inset 0 0 0 1px $gray-900, 0 0 0 var(--wp-admin-border-width-focus) var(--wp-admin-theme-color); + } + + &:disabled, + &[aria-disabled="true"] { + color: $gray-700; + } + } + + .block-editor-block-parent-selector .block-editor-block-parent-selector__button { + border-color: $gray-800; + background-color: $gray-900; + } + + .block-editor-block-switcher__toggle { + color: $gray-100; + } + + .components-toolbar-group, + .components-toolbar { + border-right-color: $gray-800 !important; + } + + .is-pressed { + color: var(--wp-admin-theme-color); + } + } + // Hide the block toolbar if the insertion point is shown. &.is-insertion-point-visible { visibility: hidden; 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 17af902bf9baf2..56b8d46b067844 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 @@ -20,6 +20,8 @@ function ZoomOutModeInserters() { setInserterIsOpened, sectionRootClientId, selectedBlockClientId, + blockInsertionPoint, + insertionPointVisible, } = useSelect( ( select ) => { const { getSettings, @@ -27,6 +29,8 @@ function ZoomOutModeInserters() { getSelectionStart, getSelectedBlockClientId, getSectionRootClientId, + getBlockInsertionPoint, + isBlockInsertionPointVisible, } = unlock( select( blockEditorStore ) ); const root = getSectionRootClientId(); @@ -38,6 +42,8 @@ function ZoomOutModeInserters() { setInserterIsOpened: getSettings().__experimentalSetIsInserterOpened, selectedBlockClientId: getSelectedBlockClientId(), + blockInsertionPoint: getBlockInsertionPoint(), + insertionPointVisible: isBlockInsertionPointVisible(), }; }, [] ); @@ -62,7 +68,19 @@ function ZoomOutModeInserters() { const index = blockOrder.findIndex( ( clientId ) => selectedBlockClientId === clientId ); - const nextClientId = blockOrder[ index + 1 ]; + + const insertionIndex = index + 1; + + const nextClientId = blockOrder[ insertionIndex ]; + + // If the block insertion point is visible, and the insertion + // Indices match then we don't need to render the inserter. + if ( + insertionPointVisible && + blockInsertionPoint?.index === insertionIndex + ) { + return null; + } return ( <BlockPopoverInbetween @@ -73,11 +91,11 @@ function ZoomOutModeInserters() { onClick={ () => { setInserterIsOpened( { rootClientId: sectionRootClientId, - insertionIndex: index + 1, + insertionIndex, tab: 'patterns', category: 'all', } ); - showInsertionPoint( sectionRootClientId, index + 1, { + showInsertionPoint( sectionRootClientId, insertionIndex, { operation: 'insert', } ); } } diff --git a/packages/block-editor/src/components/block-vertical-alignment-control/test/__snapshots__/index.js.snap b/packages/block-editor/src/components/block-vertical-alignment-control/test/__snapshots__/index.js.snap index e58d9a264310a7..e8ad6cddbba56e 100644 --- a/packages/block-editor/src/components/block-vertical-alignment-control/test/__snapshots__/index.js.snap +++ b/packages/block-editor/src/components/block-vertical-alignment-control/test/__snapshots__/index.js.snap @@ -42,7 +42,7 @@ exports[`BlockVerticalAlignmentUI should match snapshot when controls are visibl <button aria-label="Align top" aria-pressed="true" - class="components-button components-toolbar__control is-pressed has-icon" + class="components-button components-toolbar__control is-compact is-pressed has-icon" data-toolbar-item="true" type="button" > @@ -64,7 +64,7 @@ exports[`BlockVerticalAlignmentUI should match snapshot when controls are visibl <button aria-label="Align middle" aria-pressed="false" - class="components-button components-toolbar__control has-icon" + class="components-button components-toolbar__control is-compact has-icon" data-toolbar-item="true" type="button" > @@ -86,7 +86,7 @@ exports[`BlockVerticalAlignmentUI should match snapshot when controls are visibl <button aria-label="Align bottom" aria-pressed="false" - class="components-button components-toolbar__control has-icon" + class="components-button components-toolbar__control is-compact has-icon" data-toolbar-item="true" type="button" > diff --git a/packages/block-editor/src/components/border-radius-control/README.md b/packages/block-editor/src/components/border-radius-control/README.md new file mode 100644 index 00000000000000..7b048dfdb7e0d2 --- /dev/null +++ b/packages/block-editor/src/components/border-radius-control/README.md @@ -0,0 +1,59 @@ +# BorderRadiusControl + +`BorderRadiusControl` is a React component that provides a user interface for managing border radius values. It allows users to control the border radius of each corner independently or link them together for uniform values. + +## Usage + +```jsx +/** + * WordPress dependencies + */ +import { __experimentalBorderRadiusControl as BorderRadiusControl } from '@wordpress/block-editor'; +import { useState } from '@wordpress/element'; + +const MyBorderRadiusControl = () => { + const [values, setValues] = useState({ + topLeft: '10px', + topRight: '10px', + bottomLeft: '10px', + bottomRight: '10px', + }); + + return ( + <BorderRadiusControl + values={values} + onChange={setValues} + /> + ); +}; +``` + +## Props + +### values + +An object containing the border radius values for each corner. + +- **Type:** `Object` +- **Required:** No +- **Default:** `undefined` + +The values object has the following schema: + +| Property | Description | Type | +| ----------- | ------------------------------------ | ------ | +| topLeft | Border radius for top left corner | string | +| topRight | Border radius for top right corner | string | +| bottomLeft | Border radius for bottom left corner | string | +| bottomRight | Border radius for bottom right corner| string | + +Each value should be a valid CSS border radius value (e.g., '10px', '1em'). + +### onChange + +Callback function that is called when any border radius value changes. + +- **Type:** `Function` +- **Required:** Yes + +The function receives the updated values object as its argument. \ No newline at end of file diff --git a/packages/block-editor/src/components/border-radius-control/stories/index.story.js b/packages/block-editor/src/components/border-radius-control/stories/index.story.js new file mode 100644 index 00000000000000..28844a5e5cace1 --- /dev/null +++ b/packages/block-editor/src/components/border-radius-control/stories/index.story.js @@ -0,0 +1,58 @@ +/** + * WordPress dependencies + */ +import { useState } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import BorderRadiusControl from '../'; + +const meta = { + title: 'BlockEditor/BorderRadiusControl', + component: BorderRadiusControl, + parameters: { + docs: { + canvas: { sourceState: 'shown' }, + description: { + component: 'Control to display border radius options.', + }, + }, + }, + argTypes: { + values: { + control: 'object', + description: 'Border radius values.', + table: { + type: { summary: 'object' }, + }, + }, + onChange: { + action: 'onChange', + control: { type: null }, + table: { + type: { summary: 'function' }, + }, + description: 'Callback to handle onChange.', + }, + }, +}; + +export default meta; + +export const Default = { + render: function Template( { onChange, ...args } ) { + const [ values, setValues ] = useState( args.values ); + + return ( + <BorderRadiusControl + { ...args } + values={ values } + onChange={ ( ...changeArgs ) => { + setValues( ...changeArgs ); + onChange( ...changeArgs ); + } } + /> + ); + }, +}; 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 53b15e2fd2cfdd..4cde8c26d75638 100644 --- a/packages/block-editor/src/components/button-block-appender/index.js +++ b/packages/block-editor/src/components/button-block-appender/index.js @@ -7,7 +7,7 @@ import clsx from 'clsx'; * WordPress dependencies */ import { Button } from '@wordpress/components'; -import { forwardRef, useRef } from '@wordpress/element'; +import { forwardRef } from '@wordpress/element'; import { _x, sprintf } from '@wordpress/i18n'; import { Icon, plus } from '@wordpress/icons'; import deprecated from '@wordpress/deprecated'; @@ -16,15 +16,11 @@ import deprecated from '@wordpress/deprecated'; * Internal dependencies */ import Inserter from '../inserter'; -import { useMergeRefs } from '@wordpress/compose'; function ButtonBlockAppender( { rootClientId, className, onFocus, tabIndex, onSelect }, ref ) { - const inserterButtonRef = useRef(); - - const mergedInserterButtonRef = useMergeRefs( [ inserterButtonRef, ref ] ); return ( <Inserter position="bottom center" @@ -34,7 +30,6 @@ function ButtonBlockAppender( if ( onSelect && typeof onSelect === 'function' ) { onSelect( ...args ); } - inserterButtonRef.current?.focus(); } } renderToggle={ ( { onToggle, @@ -61,7 +56,7 @@ function ButtonBlockAppender( return ( <Button __next40pxDefaultSize - ref={ mergedInserterButtonRef } + ref={ ref } onFocus={ onFocus } tabIndex={ tabIndex } className={ clsx( 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 022acf2e1074a4..20791d9751bcd4 100644 --- a/packages/block-editor/src/components/child-layout-control/index.js +++ b/packages/block-editor/src/components/child-layout-control/index.js @@ -9,6 +9,7 @@ import { __experimentalHStack as HStack, __experimentalVStack as VStack, __experimentalToolsPanelItem as ToolsPanelItem, + __experimentalUseCustomUnits as useCustomUnits, Flex, FlexItem, } from '@wordpress/components'; @@ -21,6 +22,7 @@ import { useSelect, useDispatch } from '@wordpress/data'; */ import { useGetNumberOfBlocksBeforeCell } from '../grid/use-get-number-of-blocks-before-cell'; import { store as blockEditorStore } from '../../store'; +import { useSettings } from '../use-settings'; function helpText( selfStretch, parentLayout ) { const { orientation = 'horizontal' } = parentLayout; @@ -98,6 +100,17 @@ function FlexControls( { const hasFlexValue = () => !! selfStretch; const flexResetLabel = orientation === 'horizontal' ? __( 'Width' ) : __( 'Height' ); + const [ availableUnits ] = useSettings( 'spacing.units' ); + const units = useCustomUnits( { + availableUnits: availableUnits || [ + '%', + 'px', + 'em', + 'rem', + 'vh', + 'vw', + ], + } ); const resetFlex = () => { onChange( { selfStretch: undefined, @@ -167,6 +180,7 @@ function FlexControls( { { selfStretch === 'fixed' && ( <UnitControl size="__unstable-large" + units={ units } onChange={ ( value ) => { onChange( { selfStretch, diff --git a/packages/block-editor/src/components/collab/block-comment-icon-slot.js b/packages/block-editor/src/components/collab/block-comment-icon-slot.js index 600db904b28741..bcf8c5f5ff0c5b 100644 --- a/packages/block-editor/src/components/collab/block-comment-icon-slot.js +++ b/packages/block-editor/src/components/collab/block-comment-icon-slot.js @@ -3,10 +3,6 @@ */ import { createSlotFill } from '@wordpress/components'; -const { Fill: __unstableCommentIconFill, Slot } = createSlotFill( - '__unstableCommentIconFill' -); +const CommentIconSlotFill = createSlotFill( Symbol( 'CommentIconSlotFill' ) ); -__unstableCommentIconFill.Slot = Slot; - -export default __unstableCommentIconFill; +export default CommentIconSlotFill; diff --git a/packages/block-editor/src/components/collab/block-comment-icon-toolbar-slot.js b/packages/block-editor/src/components/collab/block-comment-icon-toolbar-slot.js index dd84b284f082ae..056b9a2623262f 100644 --- a/packages/block-editor/src/components/collab/block-comment-icon-toolbar-slot.js +++ b/packages/block-editor/src/components/collab/block-comment-icon-toolbar-slot.js @@ -3,10 +3,8 @@ */ import { createSlotFill } from '@wordpress/components'; -const { Fill: __unstableCommentIconToolbarFill, Slot } = createSlotFill( - '__unstableCommentIconToolbarFill' +const CommentIconToolbarSlotFill = createSlotFill( + Symbol( 'CommentIconToolbarSlotFill' ) ); -__unstableCommentIconToolbarFill.Slot = Slot; - -export default __unstableCommentIconToolbarFill; +export default CommentIconToolbarSlotFill; diff --git a/packages/block-editor/src/components/color-palette/test/__snapshots__/control.js.snap b/packages/block-editor/src/components/color-palette/test/__snapshots__/control.js.snap index 3c4cef664a3103..3d082a14a92bff 100644 --- a/packages/block-editor/src/components/color-palette/test/__snapshots__/control.js.snap +++ b/packages/block-editor/src/components/color-palette/test/__snapshots__/control.js.snap @@ -217,9 +217,9 @@ exports[`ColorPaletteControl matches the snapshot 1`] = ` class="components-circular-option-picker__option-wrapper" > <button - aria-label="Color: red" + aria-label="red" aria-selected="true" - class="components-button components-circular-option-picker__option" + class="components-button components-circular-option-picker__option is-next-40px-default-size" data-active-item="true" id="components-circular-option-picker-0-0" role="option" @@ -247,7 +247,7 @@ exports[`ColorPaletteControl matches the snapshot 1`] = ` class="components-circular-option-picker__custom-clear-wrapper" > <button - class="components-button components-circular-option-picker__clear is-tertiary" + class="components-button components-circular-option-picker__clear is-next-40px-default-size is-tertiary" type="button" > Clear diff --git a/packages/block-editor/src/components/color-palette/with-color-context.js b/packages/block-editor/src/components/color-palette/with-color-context.js index 62b8c1bc4b6181..38c90531edaac1 100644 --- a/packages/block-editor/src/components/color-palette/with-color-context.js +++ b/packages/block-editor/src/components/color-palette/with-color-context.js @@ -10,14 +10,32 @@ import { useSettings } from '../use-settings'; export default createHigherOrderComponent( ( WrappedComponent ) => { return ( props ) => { - const [ colorsFeature, enableCustomColors ] = useSettings( - 'color.palette', - 'color.custom' + // Get the default colors, theme colors, and custom colors + const [ + defaultColors, + themeColors, + customColors, + enableCustomColors, + enableDefaultColors, + ] = useSettings( + 'color.palette.default', + 'color.palette.theme', + 'color.palette.custom', + 'color.custom', + 'color.defaultPalette' ); - const { - colors = colorsFeature, - disableCustomColors = ! enableCustomColors, - } = props; + + const _colors = enableDefaultColors + ? [ + ...( themeColors || [] ), + ...( defaultColors || [] ), + ...( customColors || [] ), + ] + : [ ...( themeColors || [] ), ...( customColors || [] ) ]; + + const { colors = _colors, disableCustomColors = ! enableCustomColors } = + props; + const hasColorsToChoose = ( colors && colors.length > 0 ) || ! disableCustomColors; return ( diff --git a/packages/block-editor/src/components/colors-gradients/dropdown.js b/packages/block-editor/src/components/colors-gradients/dropdown.js index 71b27c06e7ccfc..e667927bee7601 100644 --- a/packages/block-editor/src/components/colors-gradients/dropdown.js +++ b/packages/block-editor/src/components/colors-gradients/dropdown.js @@ -15,6 +15,13 @@ import { __experimentalHStack as HStack, __experimentalToolsPanelItem as ToolsPanelItem, } from '@wordpress/components'; +import { useRef } from '@wordpress/element'; +import { __ } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import { reset as resetIcon } from '@wordpress/icons'; /** * Internal dependencies @@ -76,7 +83,15 @@ const LabeledColorIndicator = ( { colorValue, label } ) => ( const renderToggle = ( settings ) => ( { onToggle, isOpen } ) => { - const { colorValue, label } = settings; + const { + clearable, + colorValue, + gradientValue, + onColorChange, + onGradientChange, + label, + } = settings; + const colorButtonRef = useRef( undefined ); const toggleProps = { onClick: onToggle, @@ -85,15 +100,45 @@ const renderToggle = { 'is-open': isOpen } ), 'aria-expanded': isOpen, + ref: colorButtonRef, + }; + + const clearValue = () => { + if ( colorValue ) { + onColorChange(); + } else if ( gradientValue ) { + onGradientChange(); + } }; + const value = colorValue ?? gradientValue; + return ( - <Button __next40pxDefaultSize { ...toggleProps }> - <LabeledColorIndicator - colorValue={ colorValue } - label={ label } - /> - </Button> + <> + <Button __next40pxDefaultSize { ...toggleProps }> + <LabeledColorIndicator + colorValue={ value } + label={ label } + /> + </Button> + { clearable && value && ( + <Button + __next40pxDefaultSize + label={ __( 'Reset' ) } + className="block-editor-panel-color-gradient-settings__reset" + size="small" + icon={ resetIcon } + onClick={ () => { + clearValue(); + if ( isOpen ) { + onToggle(); + } + // Return focus to parent button + colorButtonRef.current?.focus(); + } } + /> + ) } + </> ); }; @@ -143,8 +188,12 @@ export default function ColorGradientSettingsDropdown( { ...setting, }; const toggleSettings = { - colorValue: setting.gradientValue ?? setting.colorValue, + clearable: setting.clearable, label: setting.label, + colorValue: setting.colorValue, + gradientValue: setting.gradientValue, + onColorChange: setting.onColorChange, + onGradientChange: setting.onGradientChange, }; return ( diff --git a/packages/block-editor/src/components/colors-gradients/style.scss b/packages/block-editor/src/components/colors-gradients/style.scss index fc1b1a4d469033..661318e5582414 100644 --- a/packages/block-editor/src/components/colors-gradients/style.scss +++ b/packages/block-editor/src/components/colors-gradients/style.scss @@ -64,6 +64,7 @@ $swatch-gap: 12px; .block-editor-tools-panel-color-gradient-settings__item { padding: 0; max-width: 100%; + position: relative; // Border styles. border-left: 1px solid $gray-300; @@ -110,6 +111,7 @@ $swatch-gap: 12px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; + max-width: calc(100% - ($button-size-next-default-40px + $grid-unit-05)); } } @@ -120,3 +122,28 @@ $swatch-gap: 12px; flex-shrink: 0; } } + +.block-editor-panel-color-gradient-settings__reset { + position: absolute; + right: 0; + top: $grid-unit; + margin: auto $grid-unit auto; + opacity: 0; + transition: opacity 0.1s ease-in-out; + @include reduce-motion("transition"); + + &.block-editor-panel-color-gradient-settings__reset { + border-radius: $radius-small; + } + + .block-editor-panel-color-gradient-settings__dropdown:hover + &, + &:focus, + &:hover { + opacity: 1; + } + + @media (hover: none) { + // Show reset button on devices that do not support hover. + opacity: 1; + } +} diff --git a/packages/block-editor/src/components/colors-gradients/test/control.js b/packages/block-editor/src/components/colors-gradients/test/control.js index 19640d41daeb35..4c389acda11171 100644 --- a/packages/block-editor/src/components/colors-gradients/test/control.js +++ b/packages/block-editor/src/components/colors-gradients/test/control.js @@ -51,7 +51,7 @@ describe( 'ColorPaletteControl', () => { ).toBeInTheDocument(); // Is showing the two predefined Colors. - expect( screen.getAllByLabelText( /^Color:/ ) ).toHaveLength( 2 ); + expect( screen.getAllByRole( 'option' ) ).toHaveLength( 2 ); } ); it( 'renders the color picker and does not render tabs if it is only possible to select a color', async () => { @@ -80,7 +80,7 @@ describe( 'ColorPaletteControl', () => { ).not.toBeInTheDocument(); // Is showing the two predefined Colors. - expect( screen.getAllByLabelText( /^Color:/ ) ).toHaveLength( 2 ); + expect( screen.getAllByRole( 'option' ) ).toHaveLength( 2 ); } ); it( 'renders the gradient picker and does not render tabs if it is only possible to select a gradient', async () => { diff --git a/packages/block-editor/src/components/contrast-checker/index.native.js b/packages/block-editor/src/components/contrast-checker/index.native.js index edd60473fcc36e..c4f19857ccec7e 100644 --- a/packages/block-editor/src/components/contrast-checker/index.native.js +++ b/packages/block-editor/src/components/contrast-checker/index.native.js @@ -13,7 +13,7 @@ import { speak } from '@wordpress/a11y'; import { __ } from '@wordpress/i18n'; import { useEffect } from '@wordpress/element'; import { usePreferredColorSchemeStyle } from '@wordpress/compose'; -import { Icon, warning } from '@wordpress/icons'; +import { Icon, cautionFilled } from '@wordpress/icons'; /** * Internal dependencies */ @@ -52,7 +52,7 @@ function ContrastCheckerMessage( { return ( <View style={ styles[ 'block-editor-contrast-checker' ] }> - <Icon style={ iconStyle } icon={ warning } /> + <Icon style={ iconStyle } icon={ cautionFilled } /> <Text style={ msgStyle }>{ msg }</Text> </View> ); diff --git a/packages/block-editor/src/components/contrast-checker/stories/index.story.js b/packages/block-editor/src/components/contrast-checker/stories/index.story.js new file mode 100644 index 00000000000000..4518ab2ba7cd67 --- /dev/null +++ b/packages/block-editor/src/components/contrast-checker/stories/index.story.js @@ -0,0 +1,117 @@ +/** + * Internal dependencies + */ +import ContrastChecker from '../'; + +const meta = { + title: 'BlockEditor/ContrastChecker', + component: ContrastChecker, + parameters: { + docs: { + canvas: { sourceState: 'shown' }, + description: { + component: + 'Determines if contrast for text styles is sufficient (WCAG 2.0 AA) when used with a given background color.', + }, + }, + }, + argTypes: { + backgroundColor: { + control: 'color', + description: + 'The background color to check the contrast of text against.', + table: { + type: { + summary: 'string', + }, + }, + }, + fallbackBackgroundColor: { + control: 'color', + description: + 'A fallback background color value, in case `backgroundColor` is not available.', + table: { + type: { + summary: 'string', + }, + }, + }, + textColor: { + control: 'color', + description: + 'The text color to check the contrast of the background against.', + table: { + type: { + summary: 'string', + }, + }, + }, + fallbackTextColor: { + control: 'color', + description: + 'A fallback text color value, in case `textColor` is not available.', + table: { + type: { + summary: 'string', + }, + }, + }, + fontSize: { + control: 'number', + description: + 'The font-size (as a `px` value) of the text to check the contrast against.', + table: { + type: { + summary: 'number', + }, + }, + }, + isLargeText: { + control: 'boolean', + description: + 'Whether the text is large (approximately `24px` or higher).', + table: { + type: { + summary: 'boolean', + }, + }, + }, + linkColor: { + control: 'color', + description: 'The link color to check the contrast against.', + table: { + type: { + summary: 'string', + }, + }, + }, + fallbackLinkColor: { + control: 'color', + description: 'Fallback link color if linkColor is not available.', + table: { + type: { + summary: 'string', + }, + }, + }, + enableAlphaChecker: { + control: 'boolean', + description: 'Whether to enable checking for transparent colors.', + table: { + type: { + summary: 'boolean', + }, + defaultValue: { summary: false }, + }, + }, + }, +}; + +export default meta; + +export const Default = { + args: { + backgroundColor: '#ffffff', + textColor: '#ffffff', + }, +}; diff --git a/packages/block-editor/src/components/date-format-picker/README.md b/packages/block-editor/src/components/date-format-picker/README.md index e057bdc31a1680..f6160cb90955b5 100644 --- a/packages/block-editor/src/components/date-format-picker/README.md +++ b/packages/block-editor/src/components/date-format-picker/README.md @@ -1,17 +1,12 @@ # DateFormatPicker -The `DateFormatPicker` component renders controls that let the user choose a -_date format_. That is, how they want their dates to be formatted. +The `DateFormatPicker` component renders controls that let the user choose a _date format_. That is, how they want their dates to be formatted. -A user can pick _Default_ to use the default date format (usually set at the -site level). +A user can pick _Default_ to use the default date format (usually set at the site level). -Otherwise, a user may choose a suggested date format or type in their own date -format by selecting _Custom_. +Otherwise, a user may choose a suggested date format or type in their own date format by selecting _Custom_. -All date format strings should be in the format accepted by by the [`dateI18n` -function in -`@wordpress/date`](https://github.com/WordPress/gutenberg/tree/trunk/packages/date#datei18n). +All date format strings should be in the format accepted by by the [`dateI18n` function in `@wordpress/date`](https://github.com/WordPress/gutenberg/tree/trunk/packages/date#datei18n). ## Usage @@ -43,16 +38,14 @@ The current date format selected by the user. If `null`, _Default_ is selected. ### `defaultFormat` -The default format string. Used to show to the user what the date will look like -if _Default_ is selected. +The default format string. Used to show to the user what the date will look like if _Default_ is selected. - Type: `string` - Required: Yes ### `onChange` -Called when the user makes a selection, or when the user types in a date format. -`null` indicates that _Default_ is selected. +Called when the user makes a selection, or when the user types in a date format. `null` indicates that _Default_ is selected. - Type: `( format: string|null ) => void` - Required: Yes 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 eb269e03ca5abc..50e09c8e260140 100644 --- a/packages/block-editor/src/components/date-format-picker/index.js +++ b/packages/block-editor/src/components/date-format-picker/index.js @@ -14,7 +14,7 @@ import { } from '@wordpress/components'; // 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. +// somewhat 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 ); @@ -29,21 +29,10 @@ if ( exampleDate.getMonth() === 4 ) { * * @see https://github.com/WordPress/gutenberg/blob/HEAD/packages/block-editor/src/components/date-format-picker/README.md * - * @param {Object} props - * @param {string|null} props.format The selected date - * format. If - * `null`, - * _Default_ is - * selected. - * @param {string} props.defaultFormat The date format that - * will be used if the - * user selects - * 'Default'. - * @param {( format: string|null ) => void} props.onChange Called when a - * selection is - * made. If `null`, - * _Default_ is - * selected. + * @param {Object} props + * @param {string|null} props.format The selected date format. If `null`, _Default_ is selected. + * @param {string} props.defaultFormat The date format that will be used if the user selects 'Default'. + * @param {Function} props.onChange Called when a selection is made. If `null`, _Default_ is selected. */ export default function DateFormatPicker( { format, @@ -51,7 +40,11 @@ export default function DateFormatPicker( { onChange, } ) { return ( - <fieldset className="block-editor-date-format-picker"> + <VStack + as="fieldset" + spacing={ 4 } + className="block-editor-date-format-picker" + > <VisuallyHidden as="legend">{ __( 'Date format' ) }</VisuallyHidden> <ToggleControl __nextHasNoMarginBottom @@ -68,7 +61,7 @@ export default function DateFormatPicker( { { format && ( <NonDefaultControls format={ format } onChange={ onChange } /> ) } - </fieldset> + </VStack> ); } diff --git a/packages/block-editor/src/components/date-format-picker/stories/index.story.js b/packages/block-editor/src/components/date-format-picker/stories/index.story.js new file mode 100644 index 00000000000000..12d7e071054949 --- /dev/null +++ b/packages/block-editor/src/components/date-format-picker/stories/index.story.js @@ -0,0 +1,69 @@ +/** + * WordPress dependencies + */ +import { useState } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import DateFormatPicker from '../'; + +export default { + title: 'BlockEditor/DateFormatPicker', + component: DateFormatPicker, + parameters: { + docs: { + canvas: { sourceState: 'shown' }, + description: { + component: + 'The `DateFormatPicker` component enables users to configure their preferred *date format*. This determines how dates are displayed.', + }, + }, + }, + argTypes: { + defaultFormat: { + control: 'text', + description: + 'The date format that will be used if the user selects "Default".', + table: { + type: { summary: 'string' }, + }, + }, + format: { + control: { type: null }, + description: + 'The selected date format. If `null`, _Default_ is selected.', + table: { + type: { summary: 'string | null' }, + }, + }, + onChange: { + action: 'onChange', + control: { type: null }, + description: + 'Called when a selection is made. If `null`, _Default_ is selected.', + table: { + type: { summary: 'function' }, + }, + }, + }, +}; + +export const Default = { + args: { + defaultFormat: 'M j, Y', + }, + render: function Template( { onChange, ...args } ) { + const [ format, setFormat ] = useState(); + return ( + <DateFormatPicker + { ...args } + onChange={ ( ...changeArgs ) => { + onChange( ...changeArgs ); + setFormat( ...changeArgs ); + } } + format={ format } + /> + ); + }, +}; diff --git a/packages/block-editor/src/components/date-format-picker/style.scss b/packages/block-editor/src/components/date-format-picker/style.scss index 748e43bb8db94a..55f844a9ac887b 100644 --- a/packages/block-editor/src/components/date-format-picker/style.scss +++ b/packages/block-editor/src/components/date-format-picker/style.scss @@ -1,5 +1,7 @@ .block-editor-date-format-picker { - margin-bottom: $grid-unit-20; + margin: 0 0 $grid-unit-20; + padding: 0; + border: none; } .block-editor-date-format-picker__custom-format-select-control__custom-option { diff --git a/packages/block-editor/src/components/default-block-appender/content.scss b/packages/block-editor/src/components/default-block-appender/content.scss index 71ede90d25c0ca..361268fb2b37de 100644 --- a/packages/block-editor/src/components/default-block-appender/content.scss +++ b/packages/block-editor/src/components/default-block-appender/content.scss @@ -42,6 +42,7 @@ color: $white; padding: 0; + // TODO: Consider passing size="small" to the Inserter toggle instead. // Special dimensions for this button. min-width: $button-size-small; height: $button-size-small; diff --git a/packages/block-editor/src/components/dimensions-tool/stories/aspect-ratio-tool.story.js b/packages/block-editor/src/components/dimensions-tool/stories/aspect-ratio-tool.story.js index 9b82404a23c255..aeb8a5f957425f 100644 --- a/packages/block-editor/src/components/dimensions-tool/stories/aspect-ratio-tool.story.js +++ b/packages/block-editor/src/components/dimensions-tool/stories/aspect-ratio-tool.story.js @@ -13,10 +13,11 @@ import { import AspectRatioTool from '../aspect-ratio-tool'; export default { - title: 'BlockEditor (Private APIs)/DimensionsTool/AspectRatioTool', + title: 'BlockEditor/DimensionsTool/AspectRatioTool', component: AspectRatioTool, + tags: [ 'status-private' ], argTypes: { - panelId: { control: { type: null } }, + panelId: { control: false }, onChange: { action: 'changed' }, }, }; diff --git a/packages/block-editor/src/components/dimensions-tool/stories/index.story.js b/packages/block-editor/src/components/dimensions-tool/stories/index.story.js index d9e1a82771282e..0ccfba2b9e97a6 100644 --- a/packages/block-editor/src/components/dimensions-tool/stories/index.story.js +++ b/packages/block-editor/src/components/dimensions-tool/stories/index.story.js @@ -13,10 +13,11 @@ import { import DimensionsTool from '..'; export default { - title: 'BlockEditor (Private APIs)/DimensionsTool', + title: 'BlockEditor/DimensionsTool/DimensionsTool', component: DimensionsTool, + tags: [ 'status-private' ], argTypes: { - panelId: { control: { type: null } }, + panelId: { control: false }, onChange: { action: 'changed' }, }, }; diff --git a/packages/block-editor/src/components/dimensions-tool/stories/scale-tool.story.js b/packages/block-editor/src/components/dimensions-tool/stories/scale-tool.story.js index a5ff9a81b5304b..ea0a3ec194beed 100644 --- a/packages/block-editor/src/components/dimensions-tool/stories/scale-tool.story.js +++ b/packages/block-editor/src/components/dimensions-tool/stories/scale-tool.story.js @@ -13,10 +13,11 @@ import { import ScaleTool from '../scale-tool'; export default { - title: 'BlockEditor (Private APIs)/DimensionsTool/ScaleTool', + title: 'BlockEditor/DimensionsTool/ScaleTool', component: ScaleTool, + tags: [ 'status-private' ], argTypes: { - panelId: { control: { type: null } }, + panelId: { control: false }, onChange: { action: 'changed' }, }, }; diff --git a/packages/block-editor/src/components/dimensions-tool/stories/width-height-tool.story.js b/packages/block-editor/src/components/dimensions-tool/stories/width-height-tool.story.js index 4a9d9782ad16b7..86b3b4b22be60d 100644 --- a/packages/block-editor/src/components/dimensions-tool/stories/width-height-tool.story.js +++ b/packages/block-editor/src/components/dimensions-tool/stories/width-height-tool.story.js @@ -13,10 +13,11 @@ import { import WidthHeightTool from '../width-height-tool'; export default { - title: 'BlockEditor (Private APIs)/DimensionsTool/WidthHeightTool', + title: 'BlockEditor/DimensionsTool/WidthHeightTool', component: WidthHeightTool, + tags: [ 'status-private' ], argTypes: { - panelId: { control: { type: null } }, + panelId: { control: false }, onChange: { action: 'changed' }, }, }; diff --git a/packages/block-editor/src/components/duotone-control/style.scss b/packages/block-editor/src/components/duotone-control/style.scss index 3a3a5a143289b8..35808339462392 100644 --- a/packages/block-editor/src/components/duotone-control/style.scss +++ b/packages/block-editor/src/components/duotone-control/style.scss @@ -4,11 +4,11 @@ $swatch-size: 28px; $swatch-gap: 12px; $popover-width: 260px; -$popover-padding: $grid-unit-20; +$popover-padding: $grid-unit-10; $swatch-columns: math.floor(math.div($popover-width + $swatch-gap - 2 * $popover-padding, $swatch-size + $swatch-gap)); -.block-editor-duotone-control__popover { +.block-editor-duotone-control__popover.components-popover { > .components-popover__content { padding: $popover-padding; width: $popover-width; diff --git a/packages/block-editor/src/components/font-appearance-control/index.js b/packages/block-editor/src/components/font-appearance-control/index.js index 38cb42e394a3bd..62396c2dc7bd64 100644 --- a/packages/block-editor/src/components/font-appearance-control/index.js +++ b/packages/block-editor/src/components/font-appearance-control/index.js @@ -2,6 +2,7 @@ * WordPress dependencies */ import { CustomSelectControl } from '@wordpress/components'; +import deprecated from '@wordpress/deprecated'; import { useMemo } from '@wordpress/element'; import { __, sprintf } from '@wordpress/i18n'; @@ -147,12 +148,27 @@ export default function FontAppearanceControl( props ) { ); }; + if ( + ! __next40pxDefaultSize && + ( otherProps.size === undefined || otherProps.size === 'default' ) + ) { + deprecated( + `36px default size for wp.blockEditor.__experimentalFontAppearanceControl`, + { + since: '6.8', + version: '7.1', + hint: 'Set the `__next40pxDefaultSize` prop to true to start opting into the new default size, which will become the default in a future version.', + } + ); + } + return ( hasStylesOrWeights && ( <CustomSelectControl { ...otherProps } className="components-font-appearance-control" __next40pxDefaultSize={ __next40pxDefaultSize } + __shouldNotWarnDeprecated36pxSize label={ label } describedBy={ getDescribedBy() } options={ selectOptions } diff --git a/packages/block-editor/src/components/font-family/README.md b/packages/block-editor/src/components/font-family/README.md index 57697f595cc800..25190802e5d0bf 100644 --- a/packages/block-editor/src/components/font-family/README.md +++ b/packages/block-editor/src/components/font-family/README.md @@ -29,6 +29,7 @@ const MyFontFamilyControl = () => { setFontFamily( newFontFamily ); } } __nextHasNoMarginBottom + __next40pxDefaultSize /> ); }; diff --git a/packages/block-editor/src/components/font-family/index.js b/packages/block-editor/src/components/font-family/index.js index c87a52b4c676d2..b685e3990287fe 100644 --- a/packages/block-editor/src/components/font-family/index.js +++ b/packages/block-editor/src/components/font-family/index.js @@ -1,7 +1,12 @@ +/** + * External dependencies + */ +import clsx from 'clsx'; + /** * WordPress dependencies */ -import { SelectControl } from '@wordpress/components'; +import { CustomSelectControl } from '@wordpress/components'; import deprecated from '@wordpress/deprecated'; import { __ } from '@wordpress/i18n'; @@ -18,6 +23,7 @@ export default function FontFamilyControl( { value = '', onChange, fontFamilies, + className, ...props } ) { const [ blockLevelFontFamilies ] = useSettings( 'typography.fontFamilies' ); @@ -30,13 +36,15 @@ export default function FontFamilyControl( { } const options = [ - { value: '', label: __( 'Default' ) }, - ...fontFamilies.map( ( { fontFamily, name } ) => { - return { - value: fontFamily, - label: name || fontFamily, - }; - } ), + { + key: '', + name: __( 'Default' ), + }, + ...fontFamilies.map( ( { fontFamily, name } ) => ( { + key: fontFamily, + name: name || fontFamily, + style: { fontFamily }, + } ) ), ]; if ( ! __nextHasNoMarginBottom ) { @@ -50,15 +58,33 @@ export default function FontFamilyControl( { ); } + if ( + ! __next40pxDefaultSize && + ( props.size === undefined || props.size === 'default' ) + ) { + deprecated( + `36px default size for wp.blockEditor.__experimentalFontFamilyControl`, + { + since: '6.8', + version: '7.1', + hint: 'Set the `__next40pxDefaultSize` prop to true to start opting into the new default size, which will become the default in a future version.', + } + ); + } + + const selectedValue = + options.find( ( option ) => option.key === value ) ?? ''; return ( - <SelectControl + <CustomSelectControl __next40pxDefaultSize={ __next40pxDefaultSize } - __nextHasNoMarginBottom={ __nextHasNoMarginBottom } + __shouldNotWarnDeprecated36pxSize label={ __( 'Font' ) } + value={ selectedValue } + onChange={ ( { selectedItem } ) => onChange( selectedItem.key ) } options={ options } - value={ value } - onChange={ onChange } - labelPosition="top" + className={ clsx( 'block-editor-font-family-control', className, { + 'is-next-has-no-margin-bottom': __nextHasNoMarginBottom, + } ) } { ...props } /> ); diff --git a/packages/block-editor/src/components/font-family/stories/index.story.js b/packages/block-editor/src/components/font-family/stories/index.story.js index 54dadeb213f12c..9077c131cbe3bb 100644 --- a/packages/block-editor/src/components/font-family/stories/index.story.js +++ b/packages/block-editor/src/components/font-family/stories/index.story.js @@ -50,5 +50,6 @@ export const Default = { }, ], __nextHasNoMarginBottom: true, + __next40pxDefaultSize: true, }, }; diff --git a/packages/block-editor/src/components/font-family/style.scss b/packages/block-editor/src/components/font-family/style.scss new file mode 100644 index 00000000000000..7ee181ebb79534 --- /dev/null +++ b/packages/block-editor/src/components/font-family/style.scss @@ -0,0 +1,5 @@ +.block-editor-font-family-control { + &:not(.is-next-has-no-margin-bottom) { + margin-bottom: $grid-unit-10; + } +} 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 7c5257ae93bfaa..5d5c02d179307d 100644 --- a/packages/block-editor/src/components/global-styles/color-panel.js +++ b/packages/block-editor/src/components/global-styles/color-panel.js @@ -19,7 +19,7 @@ import { Button, privateApis as componentsPrivateApis, } from '@wordpress/components'; -import { useCallback } from '@wordpress/element'; +import { useCallback, useRef } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; /** @@ -30,6 +30,7 @@ import { useColorsPerOrigin, useGradientsPerOrigin } from './hooks'; import { getValueFromVariable, useToolsPanelDropdownMenuProps } from './utils'; import { setImmutably } from '../../utils/object'; import { unlock } from '../../lock-unlock'; +import { reset as resetIcon } from '@wordpress/icons'; export function useHasColorPanel( settings ) { const hasTextPanel = useHasTextPanel( settings ); @@ -208,6 +209,7 @@ function ColorPanelDropdown( { } ) { const currentTab = tabs.find( ( tab ) => tab.userValue !== undefined ); const { key: firstTabKey, ...firstTab } = tabs[ 0 ] ?? {}; + const colorGradientDropdownButtonRef = useRef( undefined ); return ( <ToolsPanelItem className="block-editor-tools-panel-color-gradient-settings__item" @@ -228,15 +230,35 @@ function ColorPanelDropdown( { { 'is-open': isOpen } ), 'aria-expanded': isOpen, + ref: colorGradientDropdownButtonRef, }; return ( - <Button __next40pxDefaultSize { ...toggleProps }> - <LabeledColorIndicators - indicators={ indicators } - label={ label } - /> - </Button> + <> + <Button { ...toggleProps } __next40pxDefaultSize> + <LabeledColorIndicators + indicators={ indicators } + label={ label } + /> + </Button> + { hasValue() && ( + <Button + __next40pxDefaultSize + label={ __( 'Reset' ) } + className="block-editor-panel-color-gradient-settings__reset" + size="small" + icon={ resetIcon } + onClick={ () => { + resetValue(); + if ( isOpen ) { + onToggle(); + } + // Return focus to parent button + colorGradientDropdownButtonRef.current?.focus(); + } } + /> + ) } + </> ); } } renderContent={ () => ( 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 c19788ebfcb580..385f28b668eda2 100644 --- a/packages/block-editor/src/components/global-styles/dimensions-panel.js +++ b/packages/block-editor/src/components/global-styles/dimensions-panel.js @@ -444,17 +444,6 @@ export default function DimensionsPanel( { const onMouseLeaveControls = () => onVisualize( false ); - const inputProps = { - min: minMarginValue, - onDragStart: () => { - //Reset to 0 in case the value was negative. - setMinMarginValue( 0 ); - }, - onDragEnd: () => { - setMinMarginValue( minimumMargin ); - }, - }; - return ( <Wrapper resetAllFilter={ resetAllFilter } @@ -545,8 +534,10 @@ export default function DimensionsPanel( { units={ units } allowReset={ false } splitOnAxis={ isAxialPadding } - onMouseOver={ onMouseOverPadding } - onMouseOut={ onMouseLeaveControls } + inputProps={ { + onMouseOver: onMouseOverPadding, + onMouseOut: onMouseLeaveControls, + } } /> ) } { showSpacingPresetsControl && ( @@ -581,14 +572,23 @@ export default function DimensionsPanel( { __next40pxDefaultSize values={ marginValues } onChange={ setMarginValues } - inputProps={ inputProps } + inputProps={ { + min: minMarginValue, + onDragStart: () => { + // Reset to 0 in case the value was negative. + setMinMarginValue( 0 ); + }, + onDragEnd: () => { + setMinMarginValue( minimumMargin ); + }, + onMouseOver: onMouseOverMargin, + onMouseOut: onMouseLeaveControls, + } } label={ __( 'Margin' ) } sides={ marginSides } units={ units } allowReset={ false } splitOnAxis={ isAxialMargin } - onMouseOver={ onMouseOverMargin } - onMouseOut={ onMouseLeaveControls } /> ) } { showSpacingPresetsControl && ( diff --git a/packages/block-editor/src/components/global-styles/filters-panel.js b/packages/block-editor/src/components/global-styles/filters-panel.js index 581661e0c84071..64322d0fd5d5c9 100644 --- a/packages/block-editor/src/components/global-styles/filters-panel.js +++ b/packages/block-editor/src/components/global-styles/filters-panel.js @@ -10,10 +10,10 @@ import { __experimentalToolsPanel as ToolsPanel, __experimentalToolsPanelItem as ToolsPanelItem, __experimentalItemGroup as ItemGroup, + __experimentalItem as Item, __experimentalHStack as HStack, __experimentalZStack as ZStack, __experimentalDropdownContentWrapper as DropdownContentWrapper, - Button, MenuGroup, ColorIndicator, DuotonePicker, @@ -144,10 +144,12 @@ export default function FiltersPanel( { const duotonePreset = duotonePalette.find( ( { colors } ) => { return colors === newValue; } ); - const settedValue = duotonePreset + const duotoneValue = duotonePreset ? `var:preset|duotone|${ duotonePreset.slug }` : newValue; - onChange( setImmutably( value, [ 'filter', 'duotone' ], settedValue ) ); + onChange( + setImmutably( value, [ 'filter', 'duotone' ], duotoneValue ) + ); }; const hasDuotone = () => !! value?.filter?.duotone; const resetDuotone = () => setDuotone( undefined ); @@ -189,15 +191,12 @@ export default function FiltersPanel( { return ( <ItemGroup isBordered isSeparated> - <Button - __next40pxDefaultSize - { ...toggleProps } - > + <Item as="button" { ...toggleProps }> <LabeledColorIndicator indicator={ duotone } label={ __( 'Duotone' ) } /> - </Button> + </Item> </ItemGroup> ); } } diff --git a/packages/block-editor/src/components/global-styles/image-settings-panel.js b/packages/block-editor/src/components/global-styles/image-settings-panel.js index 4ebc20ab201983..e6fa7a4414f6c8 100644 --- a/packages/block-editor/src/components/global-styles/image-settings-panel.js +++ b/packages/block-editor/src/components/global-styles/image-settings-panel.js @@ -61,14 +61,14 @@ export default function ImageSettingsPanel( { // "RESET" button ONLY when the user has explicitly set a value in the // Global Styles. hasValue={ () => !! value?.lightbox } - label={ __( 'Expand on click' ) } + label={ __( 'Enlarge on click' ) } onDeselect={ resetLightbox } isShownByDefault panelId={ panelId } > <ToggleControl __nextHasNoMarginBottom - label={ __( 'Expand on click' ) } + label={ __( 'Enlarge on click' ) } checked={ lightboxChecked } onChange={ onChangeLightbox } /> diff --git a/packages/block-editor/src/components/global-styles/test/typography-utils.js b/packages/block-editor/src/components/global-styles/test/typography-utils.js index b094d1e827783e..a27c3ea1024b1c 100644 --- a/packages/block-editor/src/components/global-styles/test/typography-utils.js +++ b/packages/block-editor/src/components/global-styles/test/typography-utils.js @@ -585,7 +585,7 @@ describe( 'typography utils', () => { 'clamp(14px, 0.875rem + ((1vw - 3.2px) * 0.078), 15px)', }, - // Equivalent custom config PHP unit tests in `test_should_covert_font_sizes_to_fluid_values()`. + // Equivalent custom config PHP unit tests in `test_should_convert_font_sizes_to_fluid_values()`. { message: 'should return clamp value using custom fluid config', preset: { diff --git a/packages/block-editor/src/components/global-styles/typography-utils.js b/packages/block-editor/src/components/global-styles/typography-utils.js index 4b7c90ae4f222c..2f4d2b4424a6fb 100644 --- a/packages/block-editor/src/components/global-styles/typography-utils.js +++ b/packages/block-editor/src/components/global-styles/typography-utils.js @@ -45,7 +45,7 @@ import { getFontStylesAndWeights } from '../../utils/get-font-styles-and-weights * @param {Preset} preset * @param {Object} settings * @param {boolean|TypographySettings} settings.typography.fluid Whether fluid typography is enabled, and, optionally, fluid font size options. - * @param {Object?} settings.typography.layout Layout options. + * @param {?Object} settings.typography.layout Layout options. * * @return {string|*} A font-size value or the value of preset.size. */ 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 cd4ad0cea50e0d..2d0a7e46ebb2dd 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 @@ -1337,12 +1337,12 @@ export function processCSSNesting( css, blockSelector ) { processedCSS += `:root :where(${ blockSelector }){${ part.trim() }}`; } else { // If the part contains braces, it's a nested CSS rule. - const splittedPart = part.replace( '}', '' ).split( '{' ); - if ( splittedPart.length !== 2 ) { + const splitPart = part.replace( '}', '' ).split( '{' ); + if ( splitPart.length !== 2 ) { return; } - const [ nestedSelector, cssValue ] = splittedPart; + const [ nestedSelector, cssValue ] = splitPart; // Handle pseudo elements such as ::before, ::after, etc. Regex will also // capture any leading combinator such as >, +, or ~, as well as spaces. diff --git a/packages/block-editor/src/components/grid/grid-visualizer.js b/packages/block-editor/src/components/grid/grid-visualizer.js index 81da0457ffc5ca..9d89866bbff5f7 100644 --- a/packages/block-editor/src/components/grid/grid-visualizer.js +++ b/packages/block-editor/src/components/grid/grid-visualizer.js @@ -62,6 +62,17 @@ const GridVisualizerGrid = forwardRef( observer.observe( element ); observers.push( observer ); } + + const mutationObserver = new window.MutationObserver( () => { + setGridInfo( getGridInfo( gridElement ) ); + } ); + mutationObserver.observe( gridElement, { + attributeFilter: [ 'style', 'class' ], + childList: true, + subtree: true, + } ); + observers.push( mutationObserver ); + return () => { for ( const observer of observers ) { observer.disconnect(); diff --git a/packages/block-editor/src/components/grid/utils.js b/packages/block-editor/src/components/grid/utils.js index fc012c645f0916..21014108085423 100644 --- a/packages/block-editor/src/components/grid/utils.js +++ b/packages/block-editor/src/components/grid/utils.js @@ -160,6 +160,21 @@ export function getGridInfo( gridElement ) { gridElement, 'grid-template-rows' ); + const borderTopWidth = getComputedCSS( gridElement, 'border-top-width' ); + const borderRightWidth = getComputedCSS( + gridElement, + 'border-right-width' + ); + const borderBottomWidth = getComputedCSS( + gridElement, + 'border-bottom-width' + ); + const borderLeftWidth = getComputedCSS( gridElement, 'border-left-width' ); + const paddingTop = getComputedCSS( gridElement, 'padding-top' ); + const paddingRight = getComputedCSS( gridElement, 'padding-right' ); + const paddingBottom = getComputedCSS( gridElement, 'padding-bottom' ); + const paddingLeft = getComputedCSS( gridElement, 'padding-left' ); + const numColumns = gridTemplateColumns.split( ' ' ).length; const numRows = gridTemplateRows.split( ' ' ).length; const numItems = numColumns * numRows; @@ -172,7 +187,10 @@ export function getGridInfo( gridElement ) { gridTemplateColumns, gridTemplateRows, gap: getComputedCSS( gridElement, 'gap' ), - padding: getComputedCSS( gridElement, 'padding' ), + paddingTop: `calc(${ paddingTop } + ${ borderTopWidth })`, + paddingRight: `calc(${ paddingRight } + ${ borderRightWidth })`, + paddingBottom: `calc(${ paddingBottom } + ${ borderBottomWidth })`, + paddingLeft: `calc(${ paddingLeft } + ${ borderLeftWidth })`, }, }; } diff --git a/packages/block-editor/src/components/iframe/content.scss b/packages/block-editor/src/components/iframe/content.scss index 596c177eab2f32..05bbdb25c2dc63 100644 --- a/packages/block-editor/src/components/iframe/content.scss +++ b/packages/block-editor/src/components/iframe/content.scss @@ -4,55 +4,66 @@ .block-editor-iframe__html { transform-origin: top center; - // We don't want to animate the transform of the translateX because it is used - // to "center" the canvas. Leaving it on causes the canvas to slide around in - // odd ways. - @include editor-canvas-resize-animation(transform 0s, scale 0s, padding 0s); + // Prevents a flash of background color change when entering/exiting zoom out + transition: background-color 400ms; &.zoom-out-animation { - // we only want to animate the scaling when entering zoom out. When sidebars - // are toggled, the resizing of the iframe handles scaling the canvas as well, - // and the doubled animations cause very odd animations. - @include editor-canvas-resize-animation(transform 0s); + $scroll-top: var(--wp-block-editor-iframe-zoom-out-scroll-top, 0); + $scroll-top-next: var(--wp-block-editor-iframe-zoom-out-scroll-top-next, 0); + $overflow-behavior: var(--wp-block-editor-iframe-zoom-out-overflow-behavior, scroll); + + position: fixed; + left: 0; + right: 0; + top: calc(-1 * #{$scroll-top}); + bottom: 0; + // Force preserving a scrollbar gutter as scrollbar-gutter isn't supported in all browsers yet, + // and removing the scrollbar causes the content to shift. + overflow-y: $overflow-behavior; } -} -.block-editor-iframe__html.is-zoomed-out { - $scale: var(--wp-block-editor-iframe-zoom-out-scale); - $frame-size: var(--wp-block-editor-iframe-zoom-out-frame-size); - $inner-height: var(--wp-block-editor-iframe-zoom-out-inner-height); - $content-height: var(--wp-block-editor-iframe-zoom-out-content-height); - $scale-container-width: var(--wp-block-editor-iframe-zoom-out-scale-container-width); - $container-width: var(--wp-block-editor-iframe-zoom-out-container-width, 100vw); - // Apply an X translation to center the scaled content within the available space. - transform: translateX(calc((#{$scale-container-width} - #{$container-width}) / 2 / #{$scale})); - scale: #{$scale}; - background-color: $gray-300; - - // Chrome seems to respect that transform scale shouldn't affect the layout size of the element, - // so we need to adjust the height of the content to match the scale by using negative margins. - $extra-content-height: calc(#{$content-height} * (1 - #{$scale})); - $total-frame-height: calc(2 * #{$frame-size} / #{$scale}); - $total-height: calc(#{$extra-content-height} + #{$total-frame-height} + 2px); - margin-bottom: calc(-1 * #{$total-height}); - // Add the top/bottom frame size. We use scaling to account for the left/right, as - // the padding left/right causes the contents to reflow, which breaks the 1:1 scaling - // of the content. - padding-top: calc(#{$frame-size} / #{$scale}); - padding-bottom: calc(#{$frame-size} / #{$scale}); - - body { - min-height: calc((#{$inner-height} - #{$total-frame-height}) / #{$scale}); - - > .is-root-container:not(.wp-block-post-content) { - flex: 1; - display: flex; - flex-direction: column; - height: 100%; - - > main { + &.is-zoomed-out { + $scale: var(--wp-block-editor-iframe-zoom-out-scale, 1); + $frame-size: var(--wp-block-editor-iframe-zoom-out-frame-size, 0); + $inner-height: var(--wp-block-editor-iframe-zoom-out-inner-height); + $content-height: var(--wp-block-editor-iframe-zoom-out-content-height); + $scale-container-width: var(--wp-block-editor-iframe-zoom-out-scale-container-width); + $container-width: var(--wp-block-editor-iframe-zoom-out-container-width, 100vw); + // Apply an X translation to center the scaled content within the available space. + transform: translateX(calc((#{$scale-container-width} - #{$container-width}) / 2 / #{$scale})); + scale: $scale; + background-color: $gray-300; + + // Chrome seems to respect that transform scale shouldn't affect the layout size of the element, + // so we need to adjust the height of the content to match the scale by using negative margins. + $extra-content-height: calc(#{$content-height} * (1 - #{$scale})); + $total-frame-height: calc(2 * #{$frame-size} / #{$scale}); + $total-height: calc(#{$extra-content-height} + #{$total-frame-height} + 2px); + margin-bottom: calc(-1 * #{$total-height}); + + // Add the top/bottom frame size. We use scaling to account for the left/right, as + // the padding left/right causes the contents to reflow, which breaks the 1:1 scaling + // of the content. + padding-top: calc(#{$frame-size} / #{$scale}); + padding-bottom: calc(#{$frame-size} / #{$scale}); + + body { + min-height: calc((#{$inner-height} - #{$total-frame-height}) / #{$scale}); + + > .is-root-container:not(.wp-block-post-content) { flex: 1; + display: flex; + flex-direction: column; + height: 100%; + + > main { + flex: 1; + } } } + + .wp-block[draggable] { + cursor: grab; + } } } diff --git a/packages/block-editor/src/components/iframe/get-compatibility-styles.js b/packages/block-editor/src/components/iframe/get-compatibility-styles.js index fd14e02a219bf6..05ec5968082e22 100644 --- a/packages/block-editor/src/components/iframe/get-compatibility-styles.js +++ b/packages/block-editor/src/components/iframe/get-compatibility-styles.js @@ -2,7 +2,7 @@ let compatibilityStyles = null; /** * Returns a list of stylesheets that target the editor canvas. A stylesheet is - * considered targetting the editor a canvas if it contains the + * considered targeting the editor a canvas if it contains the * `editor-styles-wrapper`, `wp-block`, or `wp-block-*` class selectors. * * Ideally, this hook should be removed in the future and styles should be added diff --git a/packages/block-editor/src/components/iframe/index.js b/packages/block-editor/src/components/iframe/index.js index 76d2e09dfb7a30..3ae01525a80109 100644 --- a/packages/block-editor/src/components/iframe/index.js +++ b/packages/block-editor/src/components/iframe/index.js @@ -12,15 +12,9 @@ import { forwardRef, useMemo, useEffect, - useRef, } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; -import { - useResizeObserver, - useMergeRefs, - useRefEffect, - useDisabled, -} from '@wordpress/compose'; +import { useMergeRefs, useRefEffect, useDisabled } from '@wordpress/compose'; import { __experimentalStyleProvider as StyleProvider } from '@wordpress/components'; import { useSelect } from '@wordpress/data'; @@ -30,6 +24,7 @@ import { useSelect } from '@wordpress/data'; import { useBlockSelectionClearer } from '../block-selection-clearer'; import { useWritingFlow } from '../writing-flow'; import { getCompatibilityStyles } from './get-compatibility-styles'; +import { useScaleCanvas } from './use-scale-canvas'; import { store as blockEditorStore } from '../../store'; function bubbleEvent( event, Constructor, frame ) { @@ -121,15 +116,11 @@ function Iframe( { }; }, [] ); const { styles = '', scripts = '' } = resolvedAssets; + /** @type {[Document, import('react').Dispatch<Document>]} */ const [ iframeDocument, setIframeDocument ] = useState(); - const initialContainerWidthRef = useRef( 0 ); const [ bodyClasses, setBodyClasses ] = useState( [] ); const clearerRef = useBlockSelectionClearer(); const [ before, writingFlowRef, after ] = useWritingFlow(); - const [ contentResizeListener, { height: contentHeight } ] = - useResizeObserver(); - const [ containerResizeListener, { width: containerWidth } ] = - useResizeObserver(); const setRef = useRefEffect( ( node ) => { node._load = () => { @@ -201,7 +192,7 @@ function Iframe( { // Appending a hash to the current URL will not reload the // page. This is useful for e.g. footnotes. const href = event.target.getAttribute( 'href' ); - if ( href.startsWith( '#' ) ) { + if ( href?.startsWith( '#' ) ) { iFrameDocument.defaultView.location.hash = href.slice( 1 ); } @@ -225,48 +216,16 @@ function Iframe( { }; }, [] ); - const [ iframeWindowInnerHeight, setIframeWindowInnerHeight ] = useState(); - - const iframeResizeRef = useRefEffect( ( node ) => { - const nodeWindow = node.ownerDocument.defaultView; - - setIframeWindowInnerHeight( nodeWindow.innerHeight ); - const onResize = () => { - setIframeWindowInnerHeight( nodeWindow.innerHeight ); - }; - nodeWindow.addEventListener( 'resize', onResize ); - return () => { - nodeWindow.removeEventListener( 'resize', onResize ); - }; - }, [] ); - - const [ windowInnerWidth, setWindowInnerWidth ] = useState(); - - const windowResizeRef = useRefEffect( ( node ) => { - const nodeWindow = node.ownerDocument.defaultView; - - setWindowInnerWidth( nodeWindow.innerWidth ); - const onResize = () => { - setWindowInnerWidth( nodeWindow.innerWidth ); - }; - nodeWindow.addEventListener( 'resize', onResize ); - return () => { - nodeWindow.removeEventListener( 'resize', onResize ); - }; - }, [] ); - - const isZoomedOut = scale !== 1; - - useEffect( () => { - if ( ! isZoomedOut ) { - initialContainerWidthRef.current = containerWidth; - } - }, [ containerWidth, isZoomedOut ] ); - - const scaleContainerWidth = Math.max( - initialContainerWidthRef.current, - containerWidth - ); + const { + contentResizeListener, + containerResizeListener, + isZoomedOut, + scaleContainerWidth, + } = useScaleCanvas( { + scale, + frameSize: parseInt( frameSize ), + iframeDocument, + } ); const disabledRef = useDisabled( { isDisabled: ! readonly } ); const bodyRef = useMergeRefs( [ @@ -275,10 +234,6 @@ function Iframe( { clearerRef, writingFlowRef, disabledRef, - // Avoid resize listeners when not needed, these will trigger - // unnecessary re-renders when animating the iframe width, or when - // expanding preview iframes. - isZoomedOut ? iframeResizeRef : null, ] ); // Correct doctype is required to enable rendering in standards @@ -320,118 +275,6 @@ function Iframe( { useEffect( () => cleanup, [ cleanup ] ); - const zoomOutAnimationClassnameRef = useRef( null ); - - // Toggle zoom out CSS Classes only when zoom out mode changes. We could add these into the useEffect - // that controls settings the CSS variables, but then we would need to do more work to ensure we're - // only toggling these when the zoom out mode changes, as that useEffect is also triggered by a large - // number of dependencies. - useEffect( () => { - if ( ! iframeDocument || ! isZoomedOut ) { - return; - } - - const handleZoomOutAnimationClassname = () => { - clearTimeout( zoomOutAnimationClassnameRef.current ); - - iframeDocument.documentElement.classList.add( - 'zoom-out-animation' - ); - - zoomOutAnimationClassnameRef.current = setTimeout( () => { - iframeDocument.documentElement.classList.remove( - 'zoom-out-animation' - ); - }, 400 ); // 400ms should match the animation speed used in components/iframe/content.scss - }; - - handleZoomOutAnimationClassname(); - iframeDocument.documentElement.classList.add( 'is-zoomed-out' ); - - return () => { - handleZoomOutAnimationClassname(); - iframeDocument.documentElement.classList.remove( 'is-zoomed-out' ); - }; - }, [ iframeDocument, isZoomedOut ] ); - - // Calculate the scaling and CSS variables for the zoom out canvas - useEffect( () => { - if ( ! iframeDocument || ! isZoomedOut ) { - return; - } - - const maxWidth = 750; - // Note: When we initialize the zoom out when the canvas is smaller (sidebars open), - // initialContainerWidthRef will be smaller than the full page, and reflow will happen - // when the canvas area becomes larger due to sidebars closing. This is a known but - // minor divergence for now. - - // This scaling calculation has to happen within the JS because CSS calc() can - // only divide and multiply by a unitless value. I.e. calc( 100px / 2 ) is valid - // but calc( 100px / 2px ) is not. - iframeDocument.documentElement.style.setProperty( - '--wp-block-editor-iframe-zoom-out-scale', - scale === 'auto-scaled' - ? ( Math.min( containerWidth, maxWidth ) - - parseInt( frameSize ) * 2 ) / - scaleContainerWidth - : scale - ); - - // frameSize has to be a px value for the scaling and frame size to be computed correctly. - iframeDocument.documentElement.style.setProperty( - '--wp-block-editor-iframe-zoom-out-frame-size', - typeof frameSize === 'number' ? `${ frameSize }px` : frameSize - ); - iframeDocument.documentElement.style.setProperty( - '--wp-block-editor-iframe-zoom-out-content-height', - `${ contentHeight }px` - ); - iframeDocument.documentElement.style.setProperty( - '--wp-block-editor-iframe-zoom-out-inner-height', - `${ iframeWindowInnerHeight }px` - ); - iframeDocument.documentElement.style.setProperty( - '--wp-block-editor-iframe-zoom-out-container-width', - `${ containerWidth }px` - ); - iframeDocument.documentElement.style.setProperty( - '--wp-block-editor-iframe-zoom-out-scale-container-width', - `${ scaleContainerWidth }px` - ); - - return () => { - iframeDocument.documentElement.style.removeProperty( - '--wp-block-editor-iframe-zoom-out-scale' - ); - iframeDocument.documentElement.style.removeProperty( - '--wp-block-editor-iframe-zoom-out-frame-size' - ); - iframeDocument.documentElement.style.removeProperty( - '--wp-block-editor-iframe-zoom-out-content-height' - ); - iframeDocument.documentElement.style.removeProperty( - '--wp-block-editor-iframe-zoom-out-inner-height' - ); - iframeDocument.documentElement.style.removeProperty( - '--wp-block-editor-iframe-zoom-out-container-width' - ); - iframeDocument.documentElement.style.removeProperty( - '--wp-block-editor-iframe-zoom-out-scale-container-width' - ); - }; - }, [ - scale, - frameSize, - iframeDocument, - iframeWindowInnerHeight, - contentHeight, - containerWidth, - windowInnerWidth, - isZoomedOut, - scaleContainerWidth, - ] ); - // Make sure to not render the before and after focusable div elements in view // mode. They're only needed to capture focus in edit mode. const shouldRenderFocusCaptureElements = tabIndex >= 0 && ! isPreviewMode; @@ -487,7 +330,7 @@ function Iframe( { > { iframeDocument && createPortal( - // We want to prevent React events from bubbling throught the iframe + // We want to prevent React events from bubbling through the iframe // we bubble these manually. /* eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions */ <body @@ -511,7 +354,7 @@ function Iframe( { ); return ( - <div className="block-editor-iframe__container" ref={ windowResizeRef }> + <div className="block-editor-iframe__container"> { containerResizeListener } <div className={ clsx( diff --git a/packages/block-editor/src/components/iframe/use-scale-canvas.js b/packages/block-editor/src/components/iframe/use-scale-canvas.js new file mode 100644 index 00000000000000..d2651a75ec86ca --- /dev/null +++ b/packages/block-editor/src/components/iframe/use-scale-canvas.js @@ -0,0 +1,490 @@ +/** + * WordPress dependencies + */ +import { useEffect, useRef, useCallback } from '@wordpress/element'; +import { useReducedMotion, useResizeObserver } from '@wordpress/compose'; + +/** + * @typedef {Object} TransitionState + * @property {number} scaleValue Scale of the canvas. + * @property {number} frameSize Size of the frame/offset around the canvas. + * @property {number} containerHeight containerHeight of the iframe. + * @property {number} scrollTop ScrollTop of the iframe. + * @property {number} scrollHeight ScrollHeight of the iframe. + */ + +/** + * Calculate the scale of the canvas. + * + * @param {Object} options Object of options + * @param {number} options.frameSize Size of the frame/offset around the canvas + * @param {number} options.containerWidth Actual width of the canvas container + * @param {number} options.maxContainerWidth Maximum width of the container to use for the scale calculation. This locks the canvas to a maximum width when zooming out. + * @param {number} options.scaleContainerWidth Width the of the container wrapping the canvas container + * @return {number} Scale value between 0 and/or equal to 1 + */ +function calculateScale( { + frameSize, + containerWidth, + maxContainerWidth, + scaleContainerWidth, +} ) { + return ( + ( Math.min( containerWidth, maxContainerWidth ) - frameSize * 2 ) / + scaleContainerWidth + ); +} + +/** + * Compute the next scrollHeight based on the transition states. + * + * @param {TransitionState} transitionFrom Starting point of the transition + * @param {TransitionState} transitionTo Ending state of the transition + * @return {number} Next scrollHeight based on scale and frame value changes. + */ +function computeScrollHeightNext( transitionFrom, transitionTo ) { + const { scaleValue: prevScale, scrollHeight: prevScrollHeight } = + transitionFrom; + const { frameSize, scaleValue } = transitionTo; + + return prevScrollHeight * ( scaleValue / prevScale ) + frameSize * 2; +} + +/** + * Compute the next scrollTop position after scaling the iframe content. + * + * @param {TransitionState} transitionFrom Starting point of the transition + * @param {TransitionState} transitionTo Ending state of the transition + * @return {number} Next scrollTop position after scaling the iframe content. + */ +function computeScrollTopNext( transitionFrom, transitionTo ) { + const { + containerHeight: prevContainerHeight, + frameSize: prevFrameSize, + scaleValue: prevScale, + scrollTop: prevScrollTop, + } = transitionFrom; + const { containerHeight, frameSize, scaleValue, scrollHeight } = + transitionTo; + // Step 0: Start with the current scrollTop. + let scrollTopNext = prevScrollTop; + // Step 1: Undo the effects of the previous scale and frame around the + // midpoint of the visible area. + scrollTopNext = + ( scrollTopNext + prevContainerHeight / 2 - prevFrameSize ) / + prevScale - + prevContainerHeight / 2; + + // Step 2: Apply the new scale and frame around the midpoint of the + // visible area. + scrollTopNext = + ( scrollTopNext + containerHeight / 2 ) * scaleValue + + frameSize - + containerHeight / 2; + + // Step 3: Handle an edge case so that you scroll to the top of the + // iframe if the top of the iframe content is visible in the container. + // The same edge case for the bottom is skipped because changing content + // makes calculating it impossible. + scrollTopNext = prevScrollTop <= prevFrameSize ? 0 : scrollTopNext; + + // This is the scrollTop value if you are scrolled to the bottom of the + // iframe. We can't just let the browser handle it because we need to + // animate the scaling. + const maxScrollTop = scrollHeight - containerHeight; + + // Step 4: Clamp the scrollTopNext between the minimum and maximum + // possible scrollTop positions. Round the value to avoid subpixel + // truncation by the browser which sometimes causes a 1px error. + return Math.round( + Math.min( Math.max( 0, scrollTopNext ), Math.max( 0, maxScrollTop ) ) + ); +} + +/** + * Generate the keyframes to use for the zoom out animation. + * + * @param {TransitionState} transitionFrom Starting transition state. + * @param {TransitionState} transitionTo Ending transition state. + * @return {Object[]} An array of keyframes to use for the animation. + */ +function getAnimationKeyframes( transitionFrom, transitionTo ) { + const { + scaleValue: prevScale, + frameSize: prevFrameSize, + scrollTop, + } = transitionFrom; + const { scaleValue, frameSize, scrollTop: scrollTopNext } = transitionTo; + + return [ + { + translate: `0 0`, + scale: prevScale, + paddingTop: `${ prevFrameSize / prevScale }px`, + paddingBottom: `${ prevFrameSize / prevScale }px`, + }, + { + translate: `0 ${ scrollTop - scrollTopNext }px`, + scale: scaleValue, + paddingTop: `${ frameSize / scaleValue }px`, + paddingBottom: `${ frameSize / scaleValue }px`, + }, + ]; +} + +/** + * @typedef {Object} ScaleCanvasResult + * @property {boolean} isZoomedOut A boolean indicating if the canvas is zoomed out. + * @property {number} scaleContainerWidth The width of the container used to calculate the scale. + * @property {Object} contentResizeListener A resize observer for the content. + * @property {Object} containerResizeListener A resize observer for the container. + */ + +/** + * Handles scaling the canvas for the zoom out mode and animating between + * the states. + * + * @param {Object} options Object of options. + * @param {number} options.frameSize Size of the frame around the content. + * @param {Document} options.iframeDocument Document of the iframe. + * @param {number} options.maxContainerWidth Max width of the canvas to use as the starting scale point. Defaults to 750. + * @param {number|string} options.scale Scale of the canvas. Can be an decimal between 0 and 1, 1, or 'auto-scaled'. + * @return {ScaleCanvasResult} Properties of the result. + */ +export function useScaleCanvas( { + frameSize, + iframeDocument, + maxContainerWidth = 750, + scale, +} ) { + const [ contentResizeListener, { height: contentHeight } ] = + useResizeObserver(); + const [ + containerResizeListener, + { width: containerWidth, height: containerHeight }, + ] = useResizeObserver(); + + const initialContainerWidthRef = useRef( 0 ); + const isZoomedOut = scale !== 1; + const prefersReducedMotion = useReducedMotion(); + const isAutoScaled = scale === 'auto-scaled'; + // Track if the animation should start when the useEffect runs. + const startAnimationRef = useRef( false ); + // Track the animation so we know if we have an animation running, + // and can cancel it, reverse it, call a finish event, etc. + const animationRef = useRef( null ); + + useEffect( () => { + if ( ! isZoomedOut ) { + initialContainerWidthRef.current = containerWidth; + } + }, [ containerWidth, isZoomedOut ] ); + + const scaleContainerWidth = Math.max( + initialContainerWidthRef.current, + containerWidth + ); + + const scaleValue = isAutoScaled + ? calculateScale( { + frameSize, + containerWidth, + maxContainerWidth, + scaleContainerWidth, + } ) + : scale; + + /** + * The starting transition state for the zoom out animation. + * @type {import('react').RefObject<TransitionState>} + */ + const transitionFromRef = useRef( { + scaleValue, + frameSize, + containerHeight: 0, + scrollTop: 0, + scrollHeight: 0, + } ); + + /** + * The ending transition state for the zoom out animation. + * @type {import('react').RefObject<TransitionState>} + */ + const transitionToRef = useRef( { + scaleValue, + frameSize, + containerHeight: 0, + scrollTop: 0, + scrollHeight: 0, + } ); + + /** + * Start the zoom out animation. This sets the necessary CSS variables + * for animating the canvas and returns the Animation object. + * + * @return {Animation} The animation object for the zoom out animation. + */ + const startZoomOutAnimation = useCallback( () => { + const { scrollTop } = transitionFromRef.current; + const { scrollTop: scrollTopNext } = transitionToRef.current; + + iframeDocument.documentElement.style.setProperty( + '--wp-block-editor-iframe-zoom-out-scroll-top', + `${ scrollTop }px` + ); + + iframeDocument.documentElement.style.setProperty( + '--wp-block-editor-iframe-zoom-out-scroll-top-next', + `${ scrollTopNext }px` + ); + + // If the container has a scrolllbar, force a scrollbar to prevent the content from shifting while animating. + iframeDocument.documentElement.style.setProperty( + '--wp-block-editor-iframe-zoom-out-overflow-behavior', + transitionFromRef.current.scrollHeight === + transitionFromRef.current.containerHeight + ? 'auto' + : 'scroll' + ); + + iframeDocument.documentElement.classList.add( 'zoom-out-animation' ); + + return iframeDocument.documentElement.animate( + getAnimationKeyframes( + transitionFromRef.current, + transitionToRef.current + ), + { + easing: 'cubic-bezier(0.46, 0.03, 0.52, 0.96)', + duration: 400, + } + ); + }, [ iframeDocument ] ); + + /** + * Callback when the zoom out animation is finished. + * - Cleans up animations refs. + * - Adds final CSS vars for scale and frame size to preserve the state. + * - Removes the 'zoom-out-animation' class (which has the fixed positioning). + * - Sets the final scroll position after the canvas is no longer in fixed position. + * - Removes CSS vars related to the animation. + * - Sets the transitionFrom to the transitionTo state to be ready for the next animation. + */ + const finishZoomOutAnimation = useCallback( () => { + startAnimationRef.current = false; + animationRef.current = null; + + // Add our final scale and frame size now that the animation is done. + iframeDocument.documentElement.style.setProperty( + '--wp-block-editor-iframe-zoom-out-scale', + transitionToRef.current.scaleValue + ); + iframeDocument.documentElement.style.setProperty( + '--wp-block-editor-iframe-zoom-out-frame-size', + `${ transitionToRef.current.frameSize }px` + ); + + iframeDocument.documentElement.classList.remove( 'zoom-out-animation' ); + + // Set the final scroll position that was just animated to. + // Disable reason: Eslint isn't smart enough to know that this is a + // DOM element. https://github.com/facebook/react/issues/31483 + // eslint-disable-next-line react-compiler/react-compiler + iframeDocument.documentElement.scrollTop = + transitionToRef.current.scrollTop; + + iframeDocument.documentElement.style.removeProperty( + '--wp-block-editor-iframe-zoom-out-scroll-top' + ); + iframeDocument.documentElement.style.removeProperty( + '--wp-block-editor-iframe-zoom-out-scroll-top-next' + ); + iframeDocument.documentElement.style.removeProperty( + '--wp-block-editor-iframe-zoom-out-overflow-behavior' + ); + + // Update previous values. + transitionFromRef.current = transitionToRef.current; + }, [ iframeDocument ] ); + + const previousIsZoomedOut = useRef( false ); + + /** + * Runs when zoom out mode is toggled, and sets the startAnimation flag + * so the animation will start when the next useEffect runs. We _only_ + * want to animate when the zoom out mode is toggled, not when the scale + * changes due to the container resizing. + */ + useEffect( () => { + const trigger = + iframeDocument && previousIsZoomedOut.current !== isZoomedOut; + + previousIsZoomedOut.current = isZoomedOut; + + if ( ! trigger ) { + return; + } + + startAnimationRef.current = true; + + if ( ! isZoomedOut ) { + return; + } + + iframeDocument.documentElement.classList.add( 'is-zoomed-out' ); + return () => { + iframeDocument.documentElement.classList.remove( 'is-zoomed-out' ); + }; + }, [ iframeDocument, isZoomedOut ] ); + + /** + * This handles: + * 1. Setting the correct scale and vars of the canvas when zoomed out + * 2. If zoom out mode has been toggled, runs the animation of zooming in/out + */ + useEffect( () => { + if ( ! iframeDocument ) { + return; + } + + // We need to update the appropriate scale to exit from. If sidebars have been opened since setting the + // original scale, we will snap to a much smaller scale due to the scale container immediately changing sizes when exiting. + if ( isAutoScaled && transitionFromRef.current.scaleValue !== 1 ) { + // We use containerWidth as the divisor, as scaleContainerWidth will always match the containerWidth when + // exiting. + transitionFromRef.current.scaleValue = calculateScale( { + frameSize: transitionFromRef.current.frameSize, + containerWidth, + maxContainerWidth, + scaleContainerWidth: containerWidth, + } ); + } + + if ( scaleValue < 1 ) { + // If we are not going to animate the transition, set the scale and frame size directly. + // If we are animating, these values will be set when the animation is finished. + // Example: Opening sidebars that reduce the scale of the canvas, but we don't want to + // animate the transition. + if ( ! startAnimationRef.current ) { + iframeDocument.documentElement.style.setProperty( + '--wp-block-editor-iframe-zoom-out-scale', + scaleValue + ); + iframeDocument.documentElement.style.setProperty( + '--wp-block-editor-iframe-zoom-out-frame-size', + `${ frameSize }px` + ); + } + + iframeDocument.documentElement.style.setProperty( + '--wp-block-editor-iframe-zoom-out-content-height', + `${ contentHeight }px` + ); + + iframeDocument.documentElement.style.setProperty( + '--wp-block-editor-iframe-zoom-out-inner-height', + `${ containerHeight }px` + ); + + iframeDocument.documentElement.style.setProperty( + '--wp-block-editor-iframe-zoom-out-container-width', + `${ containerWidth }px` + ); + iframeDocument.documentElement.style.setProperty( + '--wp-block-editor-iframe-zoom-out-scale-container-width', + `${ scaleContainerWidth }px` + ); + } + + /** + * Handle the zoom out animation: + * + * - Get the current scrollTop position. + * - Calculate where the same scroll position is after scaling. + * - Apply fixed positioning to the canvas with a transform offset + * to keep the canvas centered. + * - Animate the scale and padding to the new scale and frame size. + * - After the animation is complete, remove the fixed positioning + * and set the scroll position that keeps everything centered. + */ + if ( startAnimationRef.current ) { + // Don't allow a new transition to start again unless it was started by the zoom out mode changing. + startAnimationRef.current = false; + + /** + * If we already have an animation running, reverse it. + */ + if ( animationRef.current ) { + animationRef.current.reverse(); + // Swap the transition to/from refs so that we set the correct values when + // finishZoomOutAnimation runs. + const tempTransitionFrom = transitionFromRef.current; + const tempTransitionTo = transitionToRef.current; + transitionFromRef.current = tempTransitionTo; + transitionToRef.current = tempTransitionFrom; + } else { + /** + * Start a new zoom animation. + */ + + // We can't trust the set value from contentHeight, as it was measured + // before the zoom out mode was changed. After zoom out mode is changed, + // appenders may appear or disappear, so we need to get the height from + // the iframe at this point when we're about to animate the zoom out. + // The iframe scrollTop, scrollHeight, and clientHeight will all be + // the most accurate. + transitionFromRef.current.scrollTop = + iframeDocument.documentElement.scrollTop; + transitionFromRef.current.scrollHeight = + iframeDocument.documentElement.scrollHeight; + // Use containerHeight, as it's the previous container height before the zoom out animation starts. + transitionFromRef.current.containerHeight = containerHeight; + + transitionToRef.current = { + scaleValue, + frameSize, + containerHeight: + iframeDocument.documentElement.clientHeight, // use clientHeight to get the actual height of the new container after zoom state changes have rendered, as it will be the most up-to-date. + }; + + transitionToRef.current.scrollHeight = computeScrollHeightNext( + transitionFromRef.current, + transitionToRef.current + ); + transitionToRef.current.scrollTop = computeScrollTopNext( + transitionFromRef.current, + transitionToRef.current + ); + + animationRef.current = startZoomOutAnimation(); + + // If the user prefers reduced motion, finish the animation immediately and set the final state. + if ( prefersReducedMotion ) { + finishZoomOutAnimation(); + } else { + animationRef.current.onfinish = finishZoomOutAnimation; + } + } + } + }, [ + startZoomOutAnimation, + finishZoomOutAnimation, + prefersReducedMotion, + isAutoScaled, + scaleValue, + frameSize, + iframeDocument, + contentHeight, + containerWidth, + containerHeight, + maxContainerWidth, + scaleContainerWidth, + ] ); + + return { + isZoomedOut, + scaleContainerWidth, + contentResizeListener, + containerResizeListener, + }; +} diff --git a/packages/block-editor/src/components/image-editor/use-save-image.js b/packages/block-editor/src/components/image-editor/use-save-image.js index 094ce1600545b5..980ac9b8b1123c 100644 --- a/packages/block-editor/src/components/image-editor/use-save-image.js +++ b/packages/block-editor/src/components/image-editor/use-save-image.js @@ -10,6 +10,12 @@ import { __, sprintf } from '@wordpress/i18n'; import { store as noticesStore } from '@wordpress/notices'; import { __unstableStripHTML as stripHTML } from '@wordpress/dom'; +const messages = { + crop: __( 'Image cropped.' ), + rotate: __( 'Image rotated.' ), + cropAndRotate: __( 'Image cropped and rotated.' ), +}; + export default function useSaveImage( { crop, rotation, @@ -18,7 +24,8 @@ export default function useSaveImage( { onSaveImage, onFinishEditing, } ) { - const { createErrorNotice } = useDispatch( noticesStore ); + const { createErrorNotice, createSuccessNotice } = + useDispatch( noticesStore ); const [ isInProgress, setIsInProgress ] = useState( false ); const cancel = useCallback( () => { @@ -61,6 +68,9 @@ export default function useSaveImage( { return; } + const modifierType = + modifiers.length === 1 ? modifiers[ 0 ].type : 'cropAndRotate'; + apiFetch( { path: `/wp/v2/media/${ id }/edit`, method: 'POST', @@ -71,11 +81,25 @@ export default function useSaveImage( { id: response.id, url: response.source_url, } ); + createSuccessNotice( messages[ modifierType ], { + type: 'snackbar', + actions: [ + { + label: __( 'Undo' ), + onClick: () => { + onSaveImage( { + id, + url, + } ); + }, + }, + ], + } ); } ) .catch( ( error ) => { createErrorNotice( sprintf( - /* translators: 1. Error message */ + /* translators: %s: Error message. */ __( 'Could not edit image. %s' ), stripHTML( error.message ) ), @@ -96,6 +120,7 @@ export default function useSaveImage( { url, onSaveImage, createErrorNotice, + createSuccessNotice, onFinishEditing, ] ); diff --git a/packages/block-editor/src/components/image-size-control/index.js b/packages/block-editor/src/components/image-size-control/index.js index b5bb705ab101c1..1cd2b51054e7ee 100644 --- a/packages/block-editor/src/components/image-size-control/index.js +++ b/packages/block-editor/src/components/image-size-control/index.js @@ -8,7 +8,7 @@ import { __experimentalToggleGroupControl as ToggleGroupControl, __experimentalToggleGroupControlOption as ToggleGroupControlOption, } from '@wordpress/components'; -import { __ } from '@wordpress/i18n'; +import { __, sprintf } from '@wordpress/i18n'; /** * Internal dependencies @@ -137,7 +137,11 @@ export default function ImageSizeControl( { <ToggleGroupControlOption key={ scale } value={ scale } - label={ `${ scale }%` } + label={ sprintf( + /* translators: Percentage value. */ + __( '%d%%' ), + scale + ) } /> ); } ) } diff --git a/packages/block-editor/src/components/index.js b/packages/block-editor/src/components/index.js index 29bb71b682e970..cf9167e4781576 100644 --- a/packages/block-editor/src/components/index.js +++ b/packages/block-editor/src/components/index.js @@ -67,10 +67,13 @@ export { JustifyToolbar, JustifyContentControl, } from './justify-content-control'; -export { default as __experimentalLinkControl } from './link-control'; -export { default as __experimentalLinkControlSearchInput } from './link-control/search-input'; -export { default as __experimentalLinkControlSearchResults } from './link-control/search-results'; -export { default as __experimentalLinkControlSearchItem } from './link-control/search-item'; +export { + default as LinkControl, + DeprecatedExperimentalLinkControl as __experimentalLinkControl, +} from './link-control'; +export { __experimentalLinkControlSearchInput } from './link-control/search-input'; +export { __experimentalLinkControlSearchResults } from './link-control/search-results'; +export { __experimentalLinkControlSearchItem } from './link-control/search-item'; export { default as LineHeightControl } from './line-height-control'; export { default as __experimentalListView } from './list-view'; export { default as MediaReplaceFlow } from './media-replace-flow'; diff --git a/packages/block-editor/src/components/inner-blocks/README.md b/packages/block-editor/src/components/inner-blocks/README.md index 92d4fdb5739cec..71f45b61bb1e9c 100644 --- a/packages/block-editor/src/components/inner-blocks/README.md +++ b/packages/block-editor/src/components/inner-blocks/README.md @@ -125,7 +125,7 @@ Template locking of `InnerBlocks` is similar to [Custom Post Type templates lock Template locking allows locking the `InnerBlocks` area for the current template. _Options:_ -- `contentOnly` — prevents all operations. Additionally, the block types that don't have content are hidden from the list view and can't gain focus within the block list. Unlike the other lock types, this is not overrideable by children. +- `contentOnly` — prevents all operations. Additionally, the block types that don't have content are hidden from the list view and can't gain focus within the block list. Unlike the other lock types, this is not overridable by children. - `'all'` — prevents all operations. It is not possible to insert new blocks. Move existing blocks or delete them. - `'insert'` — prevents inserting or removing blocks, but allows moving existing ones. - `false` — prevents locking from being applied to an `InnerBlocks` area even if a parent block contains locking. ( Boolean ) diff --git a/packages/block-editor/src/components/inner-blocks/use-inner-block-template-sync.js b/packages/block-editor/src/components/inner-blocks/use-inner-block-template-sync.js index fd801779372aac..505785c87914d7 100644 --- a/packages/block-editor/src/components/inner-blocks/use-inner-block-template-sync.js +++ b/packages/block-editor/src/components/inner-blocks/use-inner-block-template-sync.js @@ -7,7 +7,7 @@ import fastDeepEqual from 'fast-deep-equal/es6'; * WordPress dependencies */ import { useRef, useLayoutEffect } from '@wordpress/element'; -import { useSelect, useDispatch } from '@wordpress/data'; +import { useRegistry } from '@wordpress/data'; import { synchronizeBlocksWithTemplate } from '@wordpress/blocks'; /** @@ -42,14 +42,7 @@ export default function useInnerBlockTemplateSync( ) { // Instead of adding a useSelect mapping here, please add to the useSelect // mapping in InnerBlocks! Every subscription impacts performance. - - const { - getBlocks, - getSelectedBlocksInitialCaretPosition, - isBlockSelected, - } = useSelect( blockEditorStore ); - const { replaceInnerBlocks, __unstableMarkNextChangeAsNotPersistent } = - useDispatch( blockEditorStore ); + const registry = useRegistry(); // Maintain a reference to the previous value so we can do a deep equality check. const existingTemplateRef = useRef( null ); @@ -57,6 +50,14 @@ export default function useInnerBlockTemplateSync( useLayoutEffect( () => { let isCancelled = false; + const { + getBlocks, + getSelectedBlocksInitialCaretPosition, + isBlockSelected, + } = registry.select( blockEditorStore ); + const { replaceInnerBlocks, __unstableMarkNextChangeAsNotPersistent } = + registry.dispatch( blockEditorStore ); + // There's an implicit dependency between useInnerBlockTemplateSync and useNestedSettingsUpdate // The former needs to happen after the latter and since the latter is using microtasks to batch updates (performance optimization), // we need to schedule this one in a microtask as well. @@ -110,5 +111,11 @@ export default function useInnerBlockTemplateSync( return () => { isCancelled = true; }; - }, [ template, templateLock, clientId ] ); + }, [ + template, + templateLock, + clientId, + registry, + templateInsertUpdatesSelection, + ] ); } diff --git a/packages/block-editor/src/components/inserter-draggable-blocks/index.js b/packages/block-editor/src/components/inserter-draggable-blocks/index.js index 5a63535be3d3ce..ebef6304937aa7 100644 --- a/packages/block-editor/src/components/inserter-draggable-blocks/index.js +++ b/packages/block-editor/src/components/inserter-draggable-blocks/index.js @@ -2,12 +2,9 @@ * WordPress dependencies */ import { Draggable } from '@wordpress/components'; -import { - createBlock, - serialize, - store as blocksStore, -} from '@wordpress/blocks'; +import { createBlock, store as blocksStore } from '@wordpress/blocks'; import { useDispatch, useSelect } from '@wordpress/data'; +import { useMemo } from '@wordpress/element'; /** * Internal dependencies @@ -24,11 +21,6 @@ const InserterDraggableBlocks = ( { children, pattern, } ) => { - const transferData = { - type: 'inserter', - blocks, - }; - const blockTypeIcon = useSelect( ( select ) => { const { getBlockType } = select( blocksStore ); @@ -43,6 +35,13 @@ const InserterDraggableBlocks = ( { useDispatch( blockEditorStore ) ); + const patternBlock = useMemo( () => { + return pattern?.type === INSERTER_PATTERN_TYPES.user && + pattern?.syncStatus !== 'unsynced' + ? [ createBlock( 'core/block', { ref: pattern.id } ) ] + : undefined; + }, [ pattern?.type, pattern?.syncStatus, pattern?.id ] ); + if ( ! isEnabled ) { return children( { draggable: false, @@ -51,21 +50,21 @@ const InserterDraggableBlocks = ( { } ); } + const draggableBlocks = patternBlock ?? blocks; return ( <Draggable __experimentalTransferDataType="wp-blocks" - transferData={ transferData } + transferData={ { type: 'inserter', blocks: draggableBlocks } } onDragStart={ ( event ) => { startDragging(); - const parsedBlocks = - pattern?.type === INSERTER_PATTERN_TYPES.user && - pattern?.syncStatus !== 'unsynced' - ? [ createBlock( 'core/block', { ref: pattern.id } ) ] - : blocks; - event.dataTransfer.setData( - 'text/html', - serialize( parsedBlocks ) - ); + for ( const block of draggableBlocks ) { + const type = `wp-block:${ block.name }`; + // This will fill in the dataTransfer.types array so that + // the drop zone can check if the draggable is eligible. + // Unfortuantely, on drag start, we don't have access to the + // actual data, only the data keys/types. + event.dataTransfer.items.add( '', type ); + } } } onDragEnd={ () => { stopDragging(); diff --git a/packages/block-editor/src/components/inserter/block-patterns-explorer/index.js b/packages/block-editor/src/components/inserter/block-patterns-explorer/index.js index 93a03ee200497e..2bc41a7176954c 100644 --- a/packages/block-editor/src/components/inserter/block-patterns-explorer/index.js +++ b/packages/block-editor/src/components/inserter/block-patterns-explorer/index.js @@ -14,9 +14,8 @@ import { usePatternCategories } from '../block-patterns-tab/use-pattern-categori function PatternsExplorer( { initialCategory, rootClientId } ) { const [ searchValue, setSearchValue ] = useState( '' ); - const [ selectedCategory, setSelectedCategory ] = useState( - initialCategory?.name - ); + const [ selectedCategory, setSelectedCategory ] = + useState( initialCategory ); const patternCategories = usePatternCategories( rootClientId ); diff --git a/packages/block-editor/src/components/inserter/block-patterns-tab/index.js b/packages/block-editor/src/components/inserter/block-patterns-tab/index.js index 01e41111b7c890..f250ed6f12ebad 100644 --- a/packages/block-editor/src/components/inserter/block-patterns-tab/index.js +++ b/packages/block-editor/src/components/inserter/block-patterns-tab/index.js @@ -3,9 +3,8 @@ */ import { useState } from '@wordpress/element'; import { useViewportMatch } from '@wordpress/compose'; -import { Button, Spinner } from '@wordpress/components'; +import { Button } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; -import { useSelect } from '@wordpress/data'; /** * Internal dependencies @@ -16,8 +15,6 @@ import { PatternCategoryPreviews } from './pattern-category-previews'; import { usePatternCategories } from './use-pattern-categories'; import CategoryTabs from '../category-tabs'; import InserterNoResults from '../no-results'; -import { store as blockEditorStore } from '../../../store'; -import { unlock } from '../../../lock-unlock'; function BlockPatternsTab( { onSelectCategory, @@ -31,19 +28,6 @@ function BlockPatternsTab( { const categories = usePatternCategories( rootClientId ); const isMobile = useViewportMatch( 'medium', '<' ); - const isResolvingPatterns = useSelect( - ( select ) => - unlock( select( blockEditorStore ) ).isResolvingPatterns(), - [] - ); - - if ( isResolvingPatterns ) { - return ( - <div className="block-editor-inserter__patterns-loading"> - <Spinner /> - </div> - ); - } if ( ! categories.length ) { return <InserterNoResults />; @@ -86,7 +70,9 @@ function BlockPatternsTab( { ) } { showPatternsExplorer && ( <PatternsExplorerModal - initialCategory={ selectedCategory || categories[ 0 ] } + initialCategory={ + selectedCategory?.name || categories[ 0 ]?.name + } patternCategories={ categories } onModalClose={ () => setShowPatternsExplorer( false ) } rootClientId={ rootClientId } 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 a19a579ae5c0cf..f9af2b6f8c42d2 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 @@ -17,7 +17,6 @@ import { __experimentalText as Text, FlexBlock, } from '@wordpress/components'; -import { useSelect } from '@wordpress/data'; /** * Internal dependencies @@ -34,8 +33,6 @@ import { starterPatternsCategory, INSERTER_PATTERN_TYPES, } from './utils'; -import { store as blockEditorStore } from '../../../store'; -import { unlock } from '../../../lock-unlock'; const noop = () => {}; @@ -46,10 +43,6 @@ export function PatternCategoryPreviews( { category, showTitlesAsTooltip, } ) { - const isZoomOutMode = useSelect( - ( select ) => unlock( select( blockEditorStore ) ).isZoomOut(), - [] - ); const [ allPatterns, , onClickPattern ] = usePatternsState( onInsert, rootClientId, @@ -76,19 +69,19 @@ export function PatternCategoryPreviews( { return false; } - if ( category.name === allPatternsCategory.name ) { + if ( category.name === allPatternsCategory?.name ) { return true; } if ( - category.name === myPatternsCategory.name && + category.name === myPatternsCategory?.name && pattern.type === INSERTER_PATTERN_TYPES.user ) { return true; } if ( - category.name === starterPatternsCategory.name && + category.name === starterPatternsCategory?.name && pattern.blockTypes?.includes( 'core/post-content' ) ) { return true; @@ -156,7 +149,7 @@ export function PatternCategoryPreviews( { level={ 4 } as="div" > - { category.label } + { category?.label } </Heading> </FlexBlock> <PatternsFilter @@ -179,15 +172,13 @@ export function PatternCategoryPreviews( { </VStack> { currentCategoryPatterns.length > 0 && ( <> - { isZoomOutMode && ( - <Text - size="12" - as="p" - className="block-editor-inserter__help-text" - > - { __( 'Drag and drop patterns into the canvas.' ) } - </Text> - ) } + <Text + size="12" + as="p" + className="block-editor-inserter__help-text" + > + { __( 'Drag and drop patterns into the canvas.' ) } + </Text> <BlockPatternsList ref={ scrollContainerRef } blockPatterns={ pagingProps.categoryPatterns } 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 766082bd7690d9..590a3056907af0 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 @@ -25,7 +25,7 @@ import { const getShouldDisableSyncFilter = ( sourceFilter ) => sourceFilter !== 'all' && sourceFilter !== 'user'; const getShouldHideSourcesFilter = ( category ) => { - return category.name === myPatternsCategory.name; + return category?.name === myPatternsCategory.name; }; const PATTERN_SOURCE_MENU_OPTIONS = [ @@ -57,10 +57,10 @@ export function PatternsFilter( { } ) { // If the category is `myPatterns` then we need to set the source filter to `user`, but // we do this by deriving from props rather than calling setPatternSourceFilter otherwise - // the user may be confused when switching to another category if the haven't explicity set + // the user may be confused when switching to another category if the haven't explicitly set // this filter themselves. const currentPatternSourceFilter = - category.name === myPatternsCategory.name + category?.name === myPatternsCategory.name ? INSERTER_PATTERN_TYPES.user : patternSourceFilter; 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 844d5dd341437e..d37a6ca5694b09 100644 --- a/packages/block-editor/src/components/inserter/block-types-tab.js +++ b/packages/block-editor/src/components/inserter/block-types-tab.js @@ -186,7 +186,7 @@ export function BlockTypesTab( continue; } - if ( rootClientId && item.isAllowedInCurrentRoot ) { + if ( item.isAllowedInCurrentRoot ) { itemsForCurrentRoot.push( item ); } else { itemsRemaining.push( item ); 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 7b6baaab398f82..7f5f9ba3f65ad6 100644 --- a/packages/block-editor/src/components/inserter/category-tabs/index.js +++ b/packages/block-editor/src/components/inserter/category-tabs/index.js @@ -6,7 +6,7 @@ import { privateApis as componentsPrivateApis, __unstableMotion as motion, } from '@wordpress/components'; -import { useState, useEffect } from '@wordpress/element'; +import { useState } from '@wordpress/element'; /** * Internal dependencies @@ -35,14 +35,13 @@ function CategoryTabs( { const selectedTabId = selectedCategory ? selectedCategory.name : null; const [ activeTabId, setActiveId ] = useState(); const firstTabId = categories?.[ 0 ]?.name; - useEffect( () => { - // If there is no active tab, make the first tab the active tab, so that - // when focus is moved to the tablist, the first tab will be focused - // despite not being selected - if ( selectedTabId === null && ! activeTabId && firstTabId ) { - setActiveId( firstTabId ); - } - }, [ selectedTabId, activeTabId, firstTabId, setActiveId ] ); + + // If there is no active tab, make the first tab the active tab, so that + // when focus is moved to the tablist, the first tab will be focused + // despite not being selected + if ( selectedTabId === null && ! activeTabId && firstTabId ) { + setActiveId( firstTabId ); + } return ( <Tabs @@ -65,9 +64,10 @@ function CategoryTabs( { <Tabs.Tab key={ category.name } tabId={ category.name } - aria-label={ category.label } aria-current={ - category === selectedCategory ? 'true' : undefined + category.name === selectedCategory?.name + ? 'true' + : undefined } > { category.label } diff --git a/packages/block-editor/src/components/inserter/index.js b/packages/block-editor/src/components/inserter/index.js index 1af81d0231a1a8..59d78a6f0edc6c 100644 --- a/packages/block-editor/src/components/inserter/index.js +++ b/packages/block-editor/src/components/inserter/index.js @@ -29,7 +29,6 @@ const defaultRenderToggle = ( { blockTitle, hasSingleBlockType, toggleProps = {}, - prioritizePatterns, } ) => { const { as: Wrapper = Button, @@ -45,8 +44,6 @@ const defaultRenderToggle = ( { _x( 'Add %s', 'directly add the only allowed block' ), blockTitle ); - } else if ( ! label && prioritizePatterns ) { - label = __( 'Add pattern' ); } else if ( ! label ) { label = _x( 'Add block', 'Generic label for block inserter button' ); } @@ -63,6 +60,7 @@ const defaultRenderToggle = ( { return ( <Wrapper + __next40pxDefaultSize={ toggleProps.as ? undefined : true } icon={ plus } label={ label } tooltipPosition="bottom" @@ -113,7 +111,6 @@ class Inserter extends Component { toggleProps, hasItems, renderToggle = defaultRenderToggle, - prioritizePatterns, } = this.props; return renderToggle( { @@ -124,7 +121,6 @@ class Inserter extends Component { hasSingleBlockType, directInsertBlock, toggleProps, - prioritizePatterns, } ); } @@ -147,7 +143,6 @@ class Inserter extends Component { // This prop is experimental to give some time for the quick inserter to mature // Feel free to make them stable after a few releases. __experimentalIsQuick: isQuick, - prioritizePatterns, onSelectOrClose, selectBlockOnInsert, } = this.props; @@ -171,7 +166,6 @@ class Inserter extends Component { rootClientId={ rootClientId } clientId={ clientId } isAppender={ isAppender } - prioritizePatterns={ prioritizePatterns } selectBlockOnInsert={ selectBlockOnInsert } /> ); @@ -230,7 +224,6 @@ export default compose( [ hasInserterItems, getAllowedBlocks, getDirectInsertBlock, - getSettings, } = select( blockEditorStore ); const { getBlockVariations } = select( blocksStore ); @@ -243,8 +236,6 @@ export default compose( [ const directInsertBlock = shouldDirectInsert && getDirectInsertBlock( rootClientId ); - const settings = getSettings(); - const hasSingleBlockType = allowedBlocks?.length === 1 && getBlockVariations( allowedBlocks[ 0 ].name, 'inserter' ) @@ -262,9 +253,6 @@ export default compose( [ allowedBlockType, directInsertBlock, rootClientId, - prioritizePatterns: - settings.__experimentalPreferPatternsOnRoot && - ! rootClientId, }; } ), diff --git a/packages/block-editor/src/components/inserter/menu.js b/packages/block-editor/src/components/inserter/menu.js index 8dc2f64063c8e3..f1e387c5a59634 100644 --- a/packages/block-editor/src/components/inserter/menu.js +++ b/packages/block-editor/src/components/inserter/menu.js @@ -54,10 +54,17 @@ function InserterMenu( }, ref ) { - const isZoomOutMode = useSelect( - ( select ) => unlock( select( blockEditorStore ) ).isZoomOut(), - [] - ); + const { isZoomOutMode, hasSectionRootClientId } = useSelect( ( select ) => { + const { isZoomOut, getSectionRootClientId } = unlock( + select( blockEditorStore ) + ); + + return { + isZoomOutMode: isZoomOut(), + hasSectionRootClientId: !! getSectionRootClientId(), + }; + }, [] ); + const [ filterValue, setFilterValue, delayedFilterValue ] = useDebouncedInput( __experimentalFilterValue ); const [ hoveredItem, setHoveredItem ] = useState( null ); @@ -77,11 +84,15 @@ function InserterMenu( if ( isZoomOutMode ) { return 'patterns'; } + + return 'blocks'; } const [ selectedTab, setSelectedTab ] = useState( getInitialTab() ); const shouldUseZoomOut = - selectedTab === 'patterns' || selectedTab === 'media'; + hasSectionRootClientId && + ( selectedTab === 'patterns' || selectedTab === 'media' ); + useZoomOut( shouldUseZoomOut && isLargeViewport ); const [ destinationRootClientId, onInsertBlocks, onToggleInsertionPoint ] = diff --git a/packages/block-editor/src/components/inserter/quick-inserter.js b/packages/block-editor/src/components/inserter/quick-inserter.js index 9f393a7ce15202..498030a0019dcc 100644 --- a/packages/block-editor/src/components/inserter/quick-inserter.js +++ b/packages/block-editor/src/components/inserter/quick-inserter.js @@ -16,21 +16,17 @@ import { useSelect } from '@wordpress/data'; */ import InserterSearchResults from './search-results'; import useInsertionPoint from './hooks/use-insertion-point'; -import usePatternsState from './hooks/use-patterns-state'; import useBlockTypesState from './hooks/use-block-types-state'; import { store as blockEditorStore } from '../../store'; const SEARCH_THRESHOLD = 6; const SHOWN_BLOCK_TYPES = 6; -const SHOWN_BLOCK_PATTERNS = 2; -const SHOWN_BLOCK_PATTERNS_WITH_PRIORITIZATION = 4; export default function QuickInserter( { onSelect, rootClientId, clientId, isAppender, - prioritizePatterns, selectBlockOnInsert, hasSearch = true, } ) { @@ -47,12 +43,6 @@ export default function QuickInserter( { onInsertBlocks, true ); - const [ patterns ] = usePatternsState( - onInsertBlocks, - destinationRootClientId, - undefined, - true - ); const { setInserterIsOpened, insertionIndex } = useSelect( ( select ) => { @@ -70,12 +60,7 @@ export default function QuickInserter( { [ clientId ] ); - const showPatterns = - patterns.length && ( !! filterValue || prioritizePatterns ); - const showSearch = - hasSearch && - ( ( showPatterns && patterns.length > SEARCH_THRESHOLD ) || - blockTypes.length > SEARCH_THRESHOLD ); + const showSearch = hasSearch && blockTypes.length > SEARCH_THRESHOLD; useEffect( () => { if ( setInserterIsOpened ) { @@ -94,13 +79,6 @@ export default function QuickInserter( { } ); }; - let maxBlockPatterns = 0; - if ( showPatterns ) { - maxBlockPatterns = prioritizePatterns - ? SHOWN_BLOCK_PATTERNS_WITH_PRIORITIZATION - : SHOWN_BLOCK_PATTERNS; - } - return ( <div className={ clsx( 'block-editor-inserter__quick-inserter', { @@ -128,10 +106,9 @@ export default function QuickInserter( { rootClientId={ rootClientId } clientId={ clientId } isAppender={ isAppender } - maxBlockPatterns={ maxBlockPatterns } + maxBlockPatterns={ 0 } maxBlockTypes={ SHOWN_BLOCK_TYPES } isDraggable={ false } - prioritizePatterns={ prioritizePatterns } selectBlockOnInsert={ selectBlockOnInsert } isQuick /> diff --git a/packages/block-editor/src/components/inserter/reusable-blocks-tab.native.js b/packages/block-editor/src/components/inserter/reusable-blocks-tab.native.js index 97f3ab7c3ce4fa..cd49284da8b2d4 100644 --- a/packages/block-editor/src/components/inserter/reusable-blocks-tab.native.js +++ b/packages/block-editor/src/components/inserter/reusable-blocks-tab.native.js @@ -29,7 +29,7 @@ function ReusableBlocksTab( { onSelect, rootClientId, listProps } ) { return filterInserterItems( inserterItems, { onlyReusable: true } ); }, [ inserterItems ] ); - const sections = [ createInserterSection( { key: 'reuseable', items } ) ]; + const sections = [ createInserterSection( { key: 'reusable', items } ) ]; return ( <BlockTypesList diff --git a/packages/block-editor/src/components/inspector-controls-tabs/position-controls-panel.js b/packages/block-editor/src/components/inspector-controls-tabs/position-controls-panel.js index 9a3670b5deb286..17f65d58b8f74b 100644 --- a/packages/block-editor/src/components/inspector-controls-tabs/position-controls-panel.js +++ b/packages/block-editor/src/components/inspector-controls-tabs/position-controls-panel.js @@ -2,11 +2,11 @@ * WordPress dependencies */ import { - PanelBody, __experimentalUseSlotFills as useSlotFills, + __experimentalToolsPanel as ToolsPanel, + __experimentalToolsPanelItem as ToolsPanelItem, } from '@wordpress/components'; -import { useSelect } from '@wordpress/data'; -import { useLayoutEffect, useState } from '@wordpress/element'; +import { useDispatch, useSelect } from '@wordpress/data'; import { __ } from '@wordpress/i18n'; /** @@ -15,47 +15,80 @@ import { __ } from '@wordpress/i18n'; import InspectorControlsGroups from '../inspector-controls/groups'; import { default as InspectorControls } from '../inspector-controls'; import { store as blockEditorStore } from '../../store'; +import { useToolsPanelDropdownMenuProps } from '../global-styles/utils'; +import { cleanEmptyObject } from '../../hooks/utils'; const PositionControlsPanel = () => { - const [ initialOpen, setInitialOpen ] = useState(); + const { selectedClientIds, selectedBlocks, hasPositionAttribute } = + useSelect( ( select ) => { + const { getBlocksByClientId, getSelectedBlockClientIds } = + select( blockEditorStore ); - // Determine whether the panel should be expanded. - const { multiSelectedBlocks } = useSelect( ( select ) => { - const { getBlocksByClientId, getSelectedBlockClientIds } = - select( blockEditorStore ); - const clientIds = getSelectedBlockClientIds(); - return { - multiSelectedBlocks: getBlocksByClientId( clientIds ), - }; - }, [] ); + const selectedBlockClientIds = getSelectedBlockClientIds(); + const _selectedBlocks = getBlocksByClientId( + selectedBlockClientIds + ); - useLayoutEffect( () => { - // If any selected block has a position set, open the panel by default. - // The first block's value will still be used within the control though. - if ( initialOpen === undefined ) { - setInitialOpen( - multiSelectedBlocks.some( + return { + selectedClientIds: selectedBlockClientIds, + selectedBlocks: _selectedBlocks, + hasPositionAttribute: _selectedBlocks?.some( ( { attributes } ) => !! attributes?.style?.position?.type - ) - ); + ), + }; + }, [] ); + + const { updateBlockAttributes } = useDispatch( blockEditorStore ); + const dropdownMenuProps = useToolsPanelDropdownMenuProps(); + + function resetPosition() { + if ( ! selectedClientIds?.length || ! selectedBlocks?.length ) { + return; } - }, [ initialOpen, multiSelectedBlocks, setInitialOpen ] ); + + const attributesByClientId = Object.fromEntries( + selectedBlocks?.map( ( { clientId, attributes } ) => [ + clientId, + { + style: cleanEmptyObject( { + ...attributes?.style, + position: { + ...attributes?.style?.position, + type: undefined, + top: undefined, + right: undefined, + bottom: undefined, + left: undefined, + }, + } ), + }, + ] ) + ); + + updateBlockAttributes( selectedClientIds, attributesByClientId, true ); + } return ( - <PanelBody + <ToolsPanel className="block-editor-block-inspector__position" - title={ __( 'Position' ) } - initialOpen={ initialOpen ?? false } + label={ __( 'Position' ) } + resetAll={ resetPosition } + dropdownMenuProps={ dropdownMenuProps } > - <InspectorControls.Slot group="position" /> - </PanelBody> + <ToolsPanelItem + isShownByDefault={ hasPositionAttribute } + label={ __( 'Position' ) } + hasValue={ () => hasPositionAttribute } + onDeselect={ resetPosition } + > + <InspectorControls.Slot group="position" /> + </ToolsPanelItem> + </ToolsPanel> ); }; const PositionControls = () => { - const fills = useSlotFills( - InspectorControlsGroups.position.Slot.__unstableName - ); + const fills = useSlotFills( InspectorControlsGroups.position.name ); const hasFills = Boolean( fills && fills.length ); if ( ! hasFills ) { diff --git a/packages/block-editor/src/components/inspector-controls-tabs/use-inspector-controls-tabs.js b/packages/block-editor/src/components/inspector-controls-tabs/use-inspector-controls-tabs.js index 6a80d47f024816..c0655f4d85f3f4 100644 --- a/packages/block-editor/src/components/inspector-controls-tabs/use-inspector-controls-tabs.js +++ b/packages/block-editor/src/components/inspector-controls-tabs/use-inspector-controls-tabs.js @@ -46,18 +46,18 @@ export default function useInspectorControlsTabs( blockName ) { // List View Tab: If there are any fills for the list group add that tab. const listViewDisabled = useIsListViewTabDisabled( blockName ); - const listFills = useSlotFills( listGroup.Slot.__unstableName ); + const listFills = useSlotFills( listGroup.name ); const hasListFills = ! listViewDisabled && !! listFills && listFills.length; // Styles Tab: Add this tab if there are any fills for block supports // e.g. border, color, spacing, typography, etc. const styleFills = [ - ...( useSlotFills( borderGroup.Slot.__unstableName ) || [] ), - ...( useSlotFills( colorGroup.Slot.__unstableName ) || [] ), - ...( useSlotFills( dimensionsGroup.Slot.__unstableName ) || [] ), - ...( useSlotFills( stylesGroup.Slot.__unstableName ) || [] ), - ...( useSlotFills( typographyGroup.Slot.__unstableName ) || [] ), - ...( useSlotFills( effectsGroup.Slot.__unstableName ) || [] ), + ...( useSlotFills( borderGroup.name ) || [] ), + ...( useSlotFills( colorGroup.name ) || [] ), + ...( useSlotFills( dimensionsGroup.name ) || [] ), + ...( useSlotFills( stylesGroup.name ) || [] ), + ...( useSlotFills( typographyGroup.name ) || [] ), + ...( useSlotFills( effectsGroup.name ) || [] ), ]; const hasStyleFills = styleFills.length; @@ -67,12 +67,12 @@ export default function useInspectorControlsTabs( blockName ) { // the advanced controls slot as well to ensure they are rendered. const advancedFills = [ ...( useSlotFills( InspectorAdvancedControls.slotName ) || [] ), - ...( useSlotFills( bindingsGroup.Slot.__unstableName ) || [] ), + ...( useSlotFills( bindingsGroup.name ) || [] ), ]; const settingsFills = [ - ...( useSlotFills( defaultGroup.Slot.__unstableName ) || [] ), - ...( useSlotFills( positionGroup.Slot.__unstableName ) || [] ), + ...( useSlotFills( defaultGroup.name ) || [] ), + ...( useSlotFills( positionGroup.name ) || [] ), ...( hasListFills && hasStyleFills > 1 ? advancedFills : [] ), ]; diff --git a/packages/block-editor/src/components/inspector-controls/README.md b/packages/block-editor/src/components/inspector-controls/README.md index fd4847f53472b2..916f53b5f42d8b 100644 --- a/packages/block-editor/src/components/inspector-controls/README.md +++ b/packages/block-editor/src/components/inspector-controls/README.md @@ -116,6 +116,7 @@ registerBlockType( 'my-plugin/inspector-controls-example', { <TextControl __nextHasNoMarginBottom + __next40pxDefaultSize label="Text Field" help="Additional help text" value={ textField } @@ -208,6 +209,7 @@ function MyBlockEdit( { attributes, setAttributes } ) { <InspectorAdvancedControls> <TextControl __nextHasNoMarginBottom + __next40pxDefaultSize label="HTML anchor" value={ attributes.anchor } onChange={ ( nextValue ) => { diff --git a/packages/block-editor/src/components/inspector-controls/slot.js b/packages/block-editor/src/components/inspector-controls/slot.js index cc32b1c88480ed..4957ca90b5679a 100644 --- a/packages/block-editor/src/components/inspector-controls/slot.js +++ b/packages/block-editor/src/components/inspector-controls/slot.js @@ -1,11 +1,7 @@ /** * WordPress dependencies */ -import { - __experimentalUseSlotFills as useSlotFills, - __unstableMotionContext as MotionContext, -} from '@wordpress/components'; -import { useContext, useMemo } from '@wordpress/element'; +import { __experimentalUseSlotFills as useSlotFills } from '@wordpress/components'; import warning from '@wordpress/warning'; import deprecated from '@wordpress/deprecated'; @@ -34,23 +30,10 @@ export default function InspectorControlsSlot( { ); group = __experimentalGroup; } - const Slot = groups[ group ]?.Slot; - const fills = useSlotFills( Slot?.__unstableName ); + const slotFill = groups[ group ]; + const fills = useSlotFills( slotFill?.name ); - const motionContextValue = useContext( MotionContext ); - - const computedFillProps = useMemo( - () => ( { - ...( fillProps ?? {} ), - forwardedContext: [ - ...( fillProps?.forwardedContext ?? [] ), - [ MotionContext.Provider, { value: motionContextValue } ], - ], - } ), - [ motionContextValue, fillProps ] - ); - - if ( ! Slot ) { + if ( ! slotFill ) { warning( `Unknown InspectorControls group "${ group }" provided.` ); return null; } @@ -59,19 +42,19 @@ export default function InspectorControlsSlot( { return null; } + const { Slot } = slotFill; + if ( label ) { return ( <BlockSupportToolsPanel group={ group } label={ label }> <BlockSupportSlotContainer { ...props } - fillProps={ computedFillProps } + fillProps={ fillProps } Slot={ Slot } /> </BlockSupportToolsPanel> ); } - return ( - <Slot { ...props } fillProps={ computedFillProps } bubblesVirtually /> - ); + return <Slot { ...props } fillProps={ fillProps } bubblesVirtually />; } diff --git a/packages/block-editor/src/components/keyboard-shortcuts/index.js b/packages/block-editor/src/components/keyboard-shortcuts/index.js index 9b838446469229..fcc2cea4043753 100644 --- a/packages/block-editor/src/components/keyboard-shortcuts/index.js +++ b/packages/block-editor/src/components/keyboard-shortcuts/index.js @@ -29,8 +29,8 @@ function KeyboardShortcutsRegister() { category: 'block', description: __( 'Remove the selected block(s).' ), keyCombination: { - modifier: 'access', - character: 'z', + modifier: 'shift', + character: 'backspace', }, } ); diff --git a/packages/block-editor/src/components/letter-spacing-control/README.md b/packages/block-editor/src/components/letter-spacing-control/README.md index ef119bbc943d2f..535ca2ae8cbbff 100644 --- a/packages/block-editor/src/components/letter-spacing-control/README.md +++ b/packages/block-editor/src/components/letter-spacing-control/README.md @@ -12,13 +12,14 @@ This component is used for blocks that display text, commonly inside a Renders a letter spacing control. ```jsx -import { LetterSpacingControl } from '@wordpress/block-editor'; +import { __experimentalLetterSpacingControl as LetterSpacingControl } from '@wordpress/block-editor'; const MyLetterSpacingControl = () => ( <LetterSpacingControl value={ value } onChange={ onChange } __unstableInputWidth="auto" + __next40pxDefaultSize /> ); ``` diff --git a/packages/block-editor/src/components/letter-spacing-control/index.js b/packages/block-editor/src/components/letter-spacing-control/index.js index 1577e184c4a06c..1edbe65a3737e6 100644 --- a/packages/block-editor/src/components/letter-spacing-control/index.js +++ b/packages/block-editor/src/components/letter-spacing-control/index.js @@ -5,6 +5,7 @@ import { __experimentalUnitControl as UnitControl, __experimentalUseCustomUnits as useCustomUnits, } from '@wordpress/components'; +import deprecated from '@wordpress/deprecated'; import { __ } from '@wordpress/i18n'; /** @@ -35,9 +36,25 @@ export default function LetterSpacingControl( { availableUnits: availableUnits || [ 'px', 'em', 'rem' ], defaultValues: { px: 2, em: 0.2, rem: 0.2 }, } ); + + if ( + ! __next40pxDefaultSize && + ( otherProps.size === undefined || otherProps.size === 'default' ) + ) { + deprecated( + `36px default size for wp.blockEditor.__experimentalLetterSpacingControl`, + { + since: '6.8', + version: '7.1', + hint: 'Set the `__next40pxDefaultSize` prop to true to start opting into the new default size, which will become the default in a future version.', + } + ); + } + return ( <UnitControl __next40pxDefaultSize={ __next40pxDefaultSize } + __shouldNotWarnDeprecated36pxSize { ...otherProps } label={ __( 'Letter spacing' ) } value={ value } diff --git a/packages/block-editor/src/components/line-height-control/README.md b/packages/block-editor/src/components/line-height-control/README.md index 89bcc69622367f..2f719b5a7210e6 100644 --- a/packages/block-editor/src/components/line-height-control/README.md +++ b/packages/block-editor/src/components/line-height-control/README.md @@ -18,6 +18,7 @@ const MyLineHeightControl = () => ( <LineHeightControl value={ lineHeight } onChange={ onChange } + __next40pxDefaultSize /> ); ``` diff --git a/packages/block-editor/src/components/line-height-control/index.js b/packages/block-editor/src/components/line-height-control/index.js index b2c99c03f87840..ea692ceb452e3a 100644 --- a/packages/block-editor/src/components/line-height-control/index.js +++ b/packages/block-editor/src/components/line-height-control/index.js @@ -3,6 +3,7 @@ */ import { __ } from '@wordpress/i18n'; import { __experimentalNumberControl as NumberControl } from '@wordpress/components'; +import deprecated from '@wordpress/deprecated'; /** * Internal dependencies @@ -89,10 +90,22 @@ const LineHeightControl = ( { onChange( `${ nextValue }` ); }; + if ( + ! __next40pxDefaultSize && + ( otherProps.size === undefined || otherProps.size === 'default' ) + ) { + deprecated( `36px default size for wp.blockEditor.LineHeightControl`, { + since: '6.8', + version: '7.1', + hint: 'Set the `__next40pxDefaultSize` prop to true to start opting into the new default size, which will become the default in a future version.', + } ); + } + return ( <div className="block-editor-line-height-control"> <NumberControl { ...otherProps } + __shouldNotWarnDeprecated36pxSize __next40pxDefaultSize={ __next40pxDefaultSize } __unstableInputWidth={ __unstableInputWidth } __unstableStateReducer={ stateReducer } diff --git a/packages/block-editor/src/components/line-height-control/index.native.js b/packages/block-editor/src/components/line-height-control/index.native.js index c48ca0aa5a35f2..c6584b2279c23c 100644 --- a/packages/block-editor/src/components/line-height-control/index.native.js +++ b/packages/block-editor/src/components/line-height-control/index.native.js @@ -14,7 +14,7 @@ export default function LineHeightControl( { value: lineHeight, onChange } ) { return ( <UnitControl label={ __( 'Line Height' ) } - // Set minimun value of 1 since lower values break on Android + // Set minimum value of 1 since lower values break on Android min={ 1 } max={ 5 } step={ STEP } diff --git a/packages/block-editor/src/components/line-height-control/stories/index.story.js b/packages/block-editor/src/components/line-height-control/stories/index.story.js index 6d26fe2220fd23..f9f8c7eef12554 100644 --- a/packages/block-editor/src/components/line-height-control/stories/index.story.js +++ b/packages/block-editor/src/components/line-height-control/stories/index.story.js @@ -22,6 +22,7 @@ const Template = ( props ) => { export const Default = Template.bind( {} ); Default.args = { + __next40pxDefaultSize: true, __unstableInputWidth: '100px', }; diff --git a/packages/block-editor/src/components/line-height-control/test/index.js b/packages/block-editor/src/components/line-height-control/test/index.js index b98bc93c48a83a..488d22b768114e 100644 --- a/packages/block-editor/src/components/line-height-control/test/index.js +++ b/packages/block-editor/src/components/line-height-control/test/index.js @@ -19,7 +19,13 @@ const SPIN = STEP * SPIN_FACTOR; const ControlledLineHeightControl = () => { const [ value, setValue ] = useState(); - return <LineHeightControl value={ value } onChange={ setValue } />; + return ( + <LineHeightControl + value={ value } + onChange={ setValue } + __next40pxDefaultSize + /> + ); }; describe( 'LineHeightControl', () => { diff --git a/packages/block-editor/src/components/link-control/README.md b/packages/block-editor/src/components/link-control/README.md index 5c31c4c14371c9..29cc918031e443 100644 --- a/packages/block-editor/src/components/link-control/README.md +++ b/packages/block-editor/src/components/link-control/README.md @@ -50,7 +50,7 @@ Consumers who which to take advantage of this functionality should ensure that t When creating links the `LinkControl` component will handle two kinds of input from users: 1. Entity searches - the user may input free-text based search queries for entities retrieved from remote data sources (in the context of WordPress these are post-type entities). For example, a user might search for a `Page` they have just created by name (eg: About) and the UI will return a matching result if found. -2. Direct entry - the user may also enter any arbitrary URL-like text. This includes full URLs (https://), URL fragements (eg: `#myinternallink`), `tel` protocol links (eg: `tel: 0800 1234`) and `mailto` protocol links (eg: `mailto: hello@wordpress.org`). +2. Direct entry - the user may also enter any arbitrary URL-like text. This includes full URLs (https://), URL fragments (eg: `#myinternallink`), `tel` protocol links (eg: `tel: 0800 1234`) and `mailto` protocol links (eg: `mailto: hello@wordpress.org`). In addition, `<LinkControl>` also allows for on the fly creation of links based on the **current content of the `<input>` element**. When enabled, a default "Create new" search suggestion is appended to all non-URL-like search results. @@ -79,7 +79,7 @@ The resulting default properties of `value` include: - `title` (`string`, optional): Link title. - `opensInNewTab` (`boolean`, optional): Whether link should open in a new browser tab. This value is only assigned when not providing a custom `settings` prop. -Note: `<LinkControl>` maintains an internal state tracking temporary user edits to the link `value` prior to submission. To avoid unwanted synchronization of this internal value, it is advised that the `value` prop is stablized (likely via memozation) before it is passed to the component. This will avoid unwanted loss of any changes users have may made whilst interacting with the control. +Note: `<LinkControl>` maintains an internal state tracking temporary user edits to the link `value` prior to submission. To avoid unwanted synchronization of this internal value, it is advised that the `value` prop is stabilized (likely via memozation) before it is passed to the component. This will avoid unwanted loss of any changes users have may made whilst interacting with the control. ```jsx const memoizedValue = useMemo( diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index 0f2ae4a0e05d26..c08daae0f8728d 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -34,6 +34,7 @@ import useCreatePage from './use-create-page'; import useInternalValue from './use-internal-value'; import { ViewerFill } from './viewer-slot'; import { DEFAULT_LINK_SETTINGS } from './constants'; +import deprecated from '@wordpress/deprecated'; /** * Default properties associated with a link control value. @@ -264,7 +265,7 @@ function LinkControl( { const handleSelectSuggestion = ( updatedValue ) => { // Suggestions may contains "settings" values (e.g. `opensInNewTab`) - // which should not overide any existing settings values set by the + // which should not override any existing settings values set by the // user. This filters out any settings values from the suggestion. const nonSettingsChanges = Object.keys( updatedValue ).reduce( ( acc, key ) => { @@ -500,4 +501,13 @@ function LinkControl( { LinkControl.ViewerFill = ViewerFill; LinkControl.DEFAULT_LINK_SETTINGS = DEFAULT_LINK_SETTINGS; +export const DeprecatedExperimentalLinkControl = ( props ) => { + deprecated( 'wp.blockEditor.__experimentalLinkControl', { + since: '6.8', + alternative: 'wp.blockEditor.LinkControl', + } ); + + return <LinkControl { ...props } />; +}; + export default LinkControl; 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 3f109b8a371552..2debdec296d948 100644 --- a/packages/block-editor/src/components/link-control/search-input.js +++ b/packages/block-editor/src/components/link-control/search-input.js @@ -11,6 +11,7 @@ import { URLInput } from '../'; import LinkControlSearchResults from './search-results'; import { CREATE_TYPE } from './constants'; import useSearchHandler from './use-search-handler'; +import deprecated from '@wordpress/deprecated'; // Must be a function as otherwise URLInput will default // to the fetchLinkSuggestions passed in block editor settings @@ -156,3 +157,11 @@ const LinkControlSearchInput = forwardRef( ); export default LinkControlSearchInput; + +export const __experimentalLinkControlSearchInput = ( props ) => { + deprecated( 'wp.blockEditor.__experimentalLinkControlSearchInput', { + since: '6.8', + } ); + + return <LinkControlSearchInput { ...props } />; +}; diff --git a/packages/block-editor/src/components/link-control/search-item.js b/packages/block-editor/src/components/link-control/search-item.js index fa8d1540b3daed..27084e5e77b96a 100644 --- a/packages/block-editor/src/components/link-control/search-item.js +++ b/packages/block-editor/src/components/link-control/search-item.js @@ -17,6 +17,7 @@ import { import { __unstableStripHTML as stripHTML } from '@wordpress/dom'; import { safeDecodeURI, filterURLForDisplay, getPath } from '@wordpress/url'; import { pipe } from '@wordpress/compose'; +import deprecated from '@wordpress/deprecated'; const ICONS_MAP = { post: postList, @@ -160,3 +161,11 @@ function getVisualTypeName( suggestion ) { } export default LinkControlSearchItem; + +export const __experimentalLinkControlSearchItem = ( props ) => { + deprecated( 'wp.blockEditor.__experimentalLinkControlSearchItem', { + since: '6.8', + } ); + + return <LinkControlSearchItem { ...props } />; +}; diff --git a/packages/block-editor/src/components/link-control/search-results.js b/packages/block-editor/src/components/link-control/search-results.js index 29558f69291c57..6b405b0df1770c 100644 --- a/packages/block-editor/src/components/link-control/search-results.js +++ b/packages/block-editor/src/components/link-control/search-results.js @@ -15,8 +15,9 @@ import clsx from 'clsx'; import LinkControlSearchCreate from './search-create-button'; import LinkControlSearchItem from './search-item'; import { CREATE_TYPE, LINK_ENTRY_TYPES } from './constants'; +import deprecated from '@wordpress/deprecated'; -export default function LinkControlSearchResults( { +function LinkControlSearchResults( { withCreateSuggestion, currentInputValue, handleSuggestionClick, @@ -121,3 +122,13 @@ export default function LinkControlSearchResults( { </div> ); } + +export default LinkControlSearchResults; + +export const __experimentalLinkControlSearchResults = ( props ) => { + deprecated( 'wp.blockEditor.__experimentalLinkControlSearchResults', { + since: '6.8', + } ); + + return <LinkControlSearchResults { ...props } />; +}; diff --git a/packages/block-editor/src/components/link-control/test/index.js b/packages/block-editor/src/components/link-control/test/index.js index bd97fec4ba0073..b56fcc528c96f3 100644 --- a/packages/block-editor/src/components/link-control/test/index.js +++ b/packages/block-editor/src/components/link-control/test/index.js @@ -31,7 +31,7 @@ const mockFetchSearchSuggestions = jest.fn(); /** * The call to the real method `fetchRichUrlData` is wrapped in a promise in order to make it cancellable. * Therefore if we pass any value as the mock of `fetchRichUrlData` then ALL of the tests will require - * addition code to handle the async nature of `fetchRichUrlData`. This is unecessary. Instead we default + * addition code to handle the async nature of `fetchRichUrlData`. This is unnecessary. Instead we default * to an undefined value which will ensure that the code under test does not call `fetchRichUrlData`. Only * when we are testing the "rich previews" to we update this value with a true mock. */ @@ -354,7 +354,7 @@ describe( 'Basic rendering', () => { it( 'should display human friendly error message if value URL prop is empty when component is forced into no-editing (preview) mode', async () => { // Why do we need this test? - // Occasionally `forceIsEditingLink` is set explictly to `false` which causes the Link UI to render + // Occasionally `forceIsEditingLink` is set explicitly to `false` which causes the Link UI to render // it's preview even if the `value` has no URL. // for an example of this see the usage in the following file whereby forceIsEditingLink is used to start/stop editing mode: // https://github.com/WordPress/gutenberg/blob/fa5728771df7cdc86369f7157d6aa763649937a7/packages/format-library/src/link/inline.js#L151. @@ -2422,7 +2422,7 @@ describe( 'Controlling link title text', () => { it.each( [ [ '', 'Testing' ], - [ '(with leading and traling whitespace)', ' Testing ' ], + [ '(with leading and trailing whitespace)', ' Testing ' ], [ // Note: link control should always preserve the original value. // The consumer is responsible for filtering or otherwise handling the value. diff --git a/packages/block-editor/src/components/link-control/use-search-handler.js b/packages/block-editor/src/components/link-control/use-search-handler.js index 455d228be1974b..e081dcb4712483 100644 --- a/packages/block-editor/src/components/link-control/use-search-handler.js +++ b/packages/block-editor/src/components/link-control/use-search-handler.js @@ -94,7 +94,7 @@ const handleEntitySearch = async ( return isURLLike( val ) || ! withCreateSuggestion ? results : results.concat( { - // the `id` prop is intentionally ommitted here because it + // the `id` prop is intentionally omitted here because it // is never exposed as part of the component's public API. // see: https://github.com/WordPress/gutenberg/pull/19775#discussion_r378931316. title: val, // Must match the existing `<input>`s text value. diff --git a/packages/block-editor/src/components/list-view/README.md b/packages/block-editor/src/components/list-view/README.md index 0db077c6412494..ae8836b7635889 100644 --- a/packages/block-editor/src/components/list-view/README.md +++ b/packages/block-editor/src/components/list-view/README.md @@ -15,7 +15,7 @@ In addition to presenting the structure of the blocks in the editor, the ListVie ### Usage -Renders a list view with default syles. +Renders a list view with default styles. ```jsx import { ListView } from '@wordpress/block-editor'; diff --git a/packages/block-editor/src/components/list-view/block-select-button.js b/packages/block-editor/src/components/list-view/block-select-button.js index 3b21fd4a04e6d0..3afbf3f5b5bc16 100644 --- a/packages/block-editor/src/components/list-view/block-select-button.js +++ b/packages/block-editor/src/components/list-view/block-select-button.js @@ -9,6 +9,7 @@ import clsx from 'clsx'; import { __experimentalHStack as HStack, __experimentalTruncate as Truncate, + privateApis as componentsPrivateApis, } from '@wordpress/components'; import { forwardRef } from '@wordpress/element'; import { Icon, lockSmall as lock, pinSmall } from '@wordpress/icons'; @@ -25,6 +26,8 @@ import ListViewExpander from './expander'; import { useBlockLock } from '../block-lock'; import useListViewImages from './use-list-view-images'; import { store as blockEditorStore } from '../../store'; +import { unlock } from '../../lock-unlock'; +const { Badge } = unlock( componentsPrivateApis ); function ListViewBlockSelectButton( { @@ -117,12 +120,9 @@ function ListViewBlockSelectButton( </span> { blockInformation?.anchor && ( <span className="block-editor-list-view-block-select-button__anchor-wrapper"> - <Truncate - className="block-editor-list-view-block-select-button__anchor" - ellipsizeMode="auto" - > + <Badge className="block-editor-list-view-block-select-button__anchor"> { blockInformation.anchor } - </Truncate> + </Badge> </span> ) } { isSticky && ( diff --git a/packages/block-editor/src/components/list-view/style.scss b/packages/block-editor/src/components/list-view/style.scss index 2916622efabee9..138029262cd7f5 100644 --- a/packages/block-editor/src/components/list-view/style.scss +++ b/packages/block-editor/src/components/list-view/style.scss @@ -44,7 +44,7 @@ svg { fill: currentColor; - // Optimizate for high contrast modes. + // Optimize for high contrast modes. // See also https://blogs.windows.com/msedgedev/2020/09/17/styling-for-windows-high-contrast-with-new-standards-for-forced-colors/. @media (forced-colors: active) { fill: CanvasText; @@ -408,15 +408,11 @@ position: absolute; right: 0; transform: translateY(-50%); - background: rgba($black, 0.1); - border-radius: $radius-x-small; - padding: 2px 6px; - max-width: 100%; - box-sizing: border-box; } &.is-selected .block-editor-list-view-block-select-button__anchor { background: rgba($black, 0.3); + color: $white; } .block-editor-list-view-block-select-button__lock, @@ -484,7 +480,7 @@ $block-navigation-max-indent: 8; .block-editor-list-view-leaf[aria-level="#{ $i + 1 }"] .block-editor-list-view__expander { @if $i - 1 >= 0 { - margin-left: ($grid-unit-30 * $i); // Effectivly centers the expander below the parent's icon. + margin-left: ($grid-unit-30 * $i); // Effectively centers the expander below the parent's icon. } @else { margin-left: 0; } @@ -553,13 +549,18 @@ svg { } .list-view-appender .block-editor-inserter__toggle { - background-color: #1e1e1e; - color: #fff; - margin: $grid-unit-10 0 0 24px; - height: 24px; - min-width: 24px; + background-color: $gray-900; + color: $white; + margin: $grid-unit-10 0 0 $grid-unit-30; + height: $button-size-small; padding: 0; + // TODO: Consider passing size="small" to the Inserter toggle instead. + // Special dimensions for this button. + &.has-icon.is-next-40px-default-size { + min-width: $button-size-small; + } + &:hover, &:focus { background: var(--wp-admin-theme-color); diff --git a/packages/block-editor/src/components/media-placeholder/content.scss b/packages/block-editor/src/components/media-placeholder/content.scss index 2f7bb2e673f12e..45c8f95280376d 100644 --- a/packages/block-editor/src/components/media-placeholder/content.scss +++ b/packages/block-editor/src/components/media-placeholder/content.scss @@ -1,11 +1,3 @@ -.block-editor-media-placeholder__url-input-form { - min-width: 260px; - - @include break-small() { - width: 300px; - } -} - .block-editor-media-placeholder__cancel-button.is-link { margin: 1em; display: block; diff --git a/packages/block-editor/src/components/media-placeholder/index.js b/packages/block-editor/src/components/media-placeholder/index.js index 07a3f8829e71d1..e19e350f959b26 100644 --- a/packages/block-editor/src/components/media-placeholder/index.js +++ b/packages/block-editor/src/components/media-placeholder/index.js @@ -15,11 +15,10 @@ import { __experimentalInputControlSuffixWrapper as InputControlSuffixWrapper, withFilters, } from '@wordpress/components'; -import { __ } from '@wordpress/i18n'; +import { __, _x } from '@wordpress/i18n'; import { useState, useEffect } from '@wordpress/element'; import { useSelect } from '@wordpress/data'; import { keyboardReturn } from '@wordpress/icons'; -import { pasteHandler } from '@wordpress/blocks'; import deprecated from '@wordpress/deprecated'; /** @@ -29,6 +28,7 @@ import MediaUpload from '../media-upload'; import MediaUploadCheck from '../media-upload/check'; import URLPopover from '../url-popover'; import { store as blockEditorStore } from '../../store'; +import { parseDropEvent } from '../use-on-block-drop'; const noop = () => {}; @@ -47,6 +47,7 @@ const InsertFromURLPopover = ( { <InputControl __next40pxDefaultSize label={ __( 'URL' ) } + type="url" hideLabelFromVision placeholder={ __( 'Paste or type URL' ) } onChange={ onChange } @@ -229,56 +230,45 @@ export function MediaPlaceholder( { } ); }; - async function handleBlocksDrop( blocks ) { - if ( ! blocks || ! Array.isArray( blocks ) ) { - return; - } + async function handleBlocksDrop( event ) { + const { blocks } = parseDropEvent( event ); - function recursivelyFindMediaFromBlocks( _blocks ) { - return _blocks.flatMap( ( block ) => - ( block.name === 'core/image' || - block.name === 'core/audio' || - block.name === 'core/video' ) && - block.attributes.url - ? [ block ] - : recursivelyFindMediaFromBlocks( block.innerBlocks ) - ); - } - - const mediaBlocks = recursivelyFindMediaFromBlocks( blocks ); - - if ( ! mediaBlocks.length ) { + if ( ! blocks?.length ) { return; } const uploadedMediaList = await Promise.all( - mediaBlocks.map( ( block ) => - block.attributes.id - ? block.attributes - : new Promise( ( resolve, reject ) => { - window - .fetch( block.attributes.url ) - .then( ( response ) => response.blob() ) - .then( ( blob ) => - mediaUpload( { - filesList: [ blob ], - additionalData: { - title: block.attributes.title, - alt_text: block.attributes.alt, - caption: block.attributes.caption, - }, - onFileChange: ( [ media ] ) => { - if ( media.id ) { - resolve( media ); - } - }, - allowedTypes, - onError: reject, - } ) - ) - .catch( () => resolve( block.attributes.url ) ); - } ) - ) + blocks.map( ( block ) => { + const blockType = block.name.split( '/' )[ 1 ]; + if ( block.attributes.id ) { + block.attributes.type = blockType; + return block.attributes; + } + return new Promise( ( resolve, reject ) => { + window + .fetch( block.attributes.url ) + .then( ( response ) => response.blob() ) + .then( ( blob ) => + mediaUpload( { + filesList: [ blob ], + additionalData: { + title: block.attributes.title, + alt_text: block.attributes.alt, + caption: block.attributes.caption, + type: blockType, + }, + onFileChange: ( [ media ] ) => { + if ( media.id ) { + resolve( media ); + } + }, + allowedTypes, + onError: reject, + } ) + ) + .catch( () => resolve( block.attributes.url ) ); + } ); + } ) ).catch( ( err ) => onError( err ) ); if ( multiple ) { @@ -288,11 +278,6 @@ export function MediaPlaceholder( { } } - async function onHTMLDrop( HTML ) { - const blocks = pasteHandler( { HTML } ); - return await handleBlocksDrop( blocks ); - } - const onUpload = ( event ) => { onFilesUpload( event.target.files ); }; @@ -380,7 +365,24 @@ export function MediaPlaceholder( { } return ( - <DropZone onFilesDrop={ onFilesUpload } onHTMLDrop={ onHTMLDrop } /> + <DropZone + onFilesDrop={ onFilesUpload } + onDrop={ handleBlocksDrop } + isEligible={ ( dataTransfer ) => { + const prefix = 'wp-block:core/'; + const types = []; + for ( const type of dataTransfer.types ) { + if ( type.startsWith( prefix ) ) { + types.push( type.slice( prefix.length ) ); + } + } + return ( + types.every( ( type ) => + allowedTypes.includes( type ) + ) && ( multiple ? true : types.length === 1 ) + ); + } } + /> ); }; @@ -481,7 +483,7 @@ export function MediaPlaceholder( { ) } onClick={ openFileDialog } > - { __( 'Upload' ) } + { _x( 'Upload', 'verb' ) } </Button> { uploadMediaLibraryButton } { renderUrlSelectionUI() } @@ -511,7 +513,7 @@ export function MediaPlaceholder( { 'block-editor-media-placeholder__upload-button' ) } > - { __( 'Upload' ) } + { _x( 'Upload', 'verb' ) } </Button> ) } onChange={ onUpload } diff --git a/packages/block-editor/src/components/media-placeholder/style.scss b/packages/block-editor/src/components/media-placeholder/style.scss new file mode 100644 index 00000000000000..6eff22f75d8ff5 --- /dev/null +++ b/packages/block-editor/src/components/media-placeholder/style.scss @@ -0,0 +1,7 @@ +.block-editor-media-placeholder__url-input-form { + min-width: 260px; + + @include break-small() { + width: 300px; + } +} diff --git a/packages/block-editor/src/components/media-replace-flow/README.md b/packages/block-editor/src/components/media-replace-flow/README.md index a5808ab9561980..b3427efffbcf1f 100644 --- a/packages/block-editor/src/components/media-replace-flow/README.md +++ b/packages/block-editor/src/components/media-replace-flow/README.md @@ -98,3 +98,10 @@ If passed, children are rendered inside the dropdown. - Required: No If passed, children are rendered inside the dropdown. If a function is provided for this prop, it will receive an object with the `onClose` prop as an argument. + +### renderToggle + +- Type: `func` +- Required: No + +If passed, it will be used to render the provided button instead of the default one. It should accept and pass through `button` props to a `button` element. diff --git a/packages/block-editor/src/components/media-replace-flow/index.js b/packages/block-editor/src/components/media-replace-flow/index.js index 0da15033a86bf0..53c2a66634f0ae 100644 --- a/packages/block-editor/src/components/media-replace-flow/index.js +++ b/packages/block-editor/src/components/media-replace-flow/index.js @@ -1,21 +1,15 @@ -/** - * External dependencies - */ -import clsx from 'clsx'; - /** * WordPress dependencies */ -import { useRef } from '@wordpress/element'; -import { __ } from '@wordpress/i18n'; +import { __, _x } from '@wordpress/i18n'; import { speak } from '@wordpress/a11y'; import { FormFileUpload, NavigableMenu, MenuItem, - ToolbarButton, Dropdown, withFilters, + ToolbarButton, } from '@wordpress/components'; import { useSelect, withDispatch } from '@wordpress/data'; import { DOWN } from '@wordpress/keycodes'; @@ -60,12 +54,9 @@ const MediaReplaceFlow = ( { addToGallery, handleUpload = true, popoverProps, + renderToggle, } ) => { - const mediaUpload = useSelect( ( select ) => { - return select( blockEditorStore ).getSettings().mediaUpload; - }, [] ); - const canUpload = !! mediaUpload; - const editMediaButtonRef = useRef(); + const { getSettings } = useSelect( blockEditorStore ); const errorNoticeID = `block-editor/media-replace-flow/error-notice/${ ++uniqueId }`; const onUploadError = ( message ) => { @@ -107,7 +98,7 @@ const MediaReplaceFlow = ( { return onSelect( files ); } onFilesUpload( files ); - mediaUpload( { + getSettings().mediaUpload( { allowedTypes, filesList: files, onFileChange: ( [ media ] ) => { @@ -141,17 +132,27 @@ const MediaReplaceFlow = ( { <Dropdown popoverProps={ popoverProps } contentClassName="block-editor-media-replace-flow__options" - renderToggle={ ( { isOpen, onToggle } ) => ( - <ToolbarButton - ref={ editMediaButtonRef } - aria-expanded={ isOpen } - aria-haspopup="true" - onClick={ onToggle } - onKeyDown={ openOnArrowDown } - > - { name } - </ToolbarButton> - ) } + renderToggle={ ( { isOpen, onToggle } ) => { + if ( renderToggle ) { + return renderToggle( { + 'aria-expanded': isOpen, + 'aria-haspopup': 'true', + onClick: onToggle, + onKeyDown: openOnArrowDown, + children: name, + } ); + } + return ( + <ToolbarButton + aria-expanded={ isOpen } + aria-haspopup="true" + onClick={ onToggle } + onKeyDown={ openOnArrowDown } + > + { name } + </ToolbarButton> + ); + } } renderContent={ ( { onClose } ) => ( <> <NavigableMenu className="block-editor-media-replace-flow__media-upload-menu"> @@ -188,7 +189,7 @@ const MediaReplaceFlow = ( { openFileDialog(); } } > - { __( 'Upload' ) } + { _x( 'Upload', 'verb' ) } </MenuItem> ); } } @@ -219,15 +220,7 @@ const MediaReplaceFlow = ( { </NavigableMenu> { onSelectURL && ( // eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions - <form - className={ clsx( - 'block-editor-media-flow__url-input', - { - 'has-siblings': - canUpload || onToggleFeaturedImage, - } - ) } - > + <form className="block-editor-media-flow__url-input"> <span className="block-editor-media-replace-flow__image-url-label"> { __( 'Current media URL:' ) } </span> @@ -238,7 +231,6 @@ const MediaReplaceFlow = ( { showSuggestions={ false } onChange={ ( { url } ) => { onSelectURL( url ); - editMediaButtonRef.current.focus(); } } /> </form> diff --git a/packages/block-editor/src/components/media-replace-flow/style.scss b/packages/block-editor/src/components/media-replace-flow/style.scss index 61df542cf58404..d9d8d1c98c11f5 100644 --- a/packages/block-editor/src/components/media-replace-flow/style.scss +++ b/packages/block-editor/src/components/media-replace-flow/style.scss @@ -9,17 +9,17 @@ margin-left: 4px; } +.block-editor-media-replace-flow__media-upload-menu:not(:empty) + .block-editor-media-flow__url-input { + border-top: $border-width solid $gray-900; + margin-top: $grid-unit-10; + padding-bottom: $grid-unit-10; +} + .block-editor-media-flow__url-input { margin-right: -$grid-unit-10; margin-left: -$grid-unit-10; padding: $grid-unit-20; - &.has-siblings { - border-top: $border-width solid $gray-900; - margin-top: $grid-unit-10; - padding-bottom: $grid-unit-10; - } - .block-editor-media-replace-flow__image-url-label { display: block; top: $grid-unit-20; diff --git a/packages/block-editor/src/components/multi-selection-inspector/index.js b/packages/block-editor/src/components/multi-selection-inspector/index.js index f5e7f696347686..23d890d79fff4d 100644 --- a/packages/block-editor/src/components/multi-selection-inspector/index.js +++ b/packages/block-editor/src/components/multi-selection-inspector/index.js @@ -3,9 +3,8 @@ */ import { sprintf, _n } from '@wordpress/i18n'; import { useSelect } from '@wordpress/data'; -import { serialize } from '@wordpress/blocks'; -import { count as wordCount } from '@wordpress/wordcount'; import { copy } from '@wordpress/icons'; +import { __experimentalHStack as HStack } from '@wordpress/components'; /** * Internal dependencies @@ -14,33 +13,24 @@ import BlockIcon from '../block-icon'; import { store as blockEditorStore } from '../../store'; export default function MultiSelectionInspector() { - const { blocks } = useSelect( ( select ) => { - const { getMultiSelectedBlocks } = select( blockEditorStore ); - return { - blocks: getMultiSelectedBlocks(), - }; - }, [] ); - const words = wordCount( serialize( blocks ), 'words' ); - + const selectedBlockCount = useSelect( + ( select ) => select( blockEditorStore ).getSelectedBlockCount(), + [] + ); return ( - <div className="block-editor-multi-selection-inspector__card"> + <HStack + justify="flex-start" + spacing={ 2 } + className="block-editor-multi-selection-inspector__card" + > <BlockIcon icon={ copy } showColors /> - <div className="block-editor-multi-selection-inspector__card-content"> - <div className="block-editor-multi-selection-inspector__card-title"> - { sprintf( - /* translators: %d: number of blocks */ - _n( '%d Block', '%d Blocks', blocks.length ), - blocks.length - ) } - </div> - <div className="block-editor-multi-selection-inspector__card-description"> - { sprintf( - /* translators: %d: number of words */ - _n( '%d word selected.', '%d words selected.', words ), - words - ) } - </div> + <div className="block-editor-multi-selection-inspector__card-title"> + { sprintf( + /* translators: %d: number of blocks */ + _n( '%d Block', '%d Blocks', selectedBlockCount ), + selectedBlockCount + ) } </div> - </div> + </HStack> ); } diff --git a/packages/block-editor/src/components/multi-selection-inspector/style.scss b/packages/block-editor/src/components/multi-selection-inspector/style.scss index 61bf5f8cdb3820..e37245d58f5dd5 100644 --- a/packages/block-editor/src/components/multi-selection-inspector/style.scss +++ b/packages/block-editor/src/components/multi-selection-inspector/style.scss @@ -1,25 +1,13 @@ .block-editor-multi-selection-inspector__card { - display: flex; - align-items: flex-start; padding: $grid-unit-20; } -.block-editor-multi-selection-inspector__card-content { - flex-grow: 1; -} - .block-editor-multi-selection-inspector__card-title { font-weight: 500; - margin-bottom: 5px; -} - -.block-editor-multi-selection-inspector__card-description { - font-size: $default-font-size; } .block-editor-multi-selection-inspector__card .block-editor-block-icon { margin-left: -2px; - margin-right: 10px; padding: 0 3px; width: $button-size; height: $button-size-small; diff --git a/packages/block-editor/src/components/observe-typing/index.js b/packages/block-editor/src/components/observe-typing/index.js index 75afc4bbdf0f96..b9307dc11bad34 100644 --- a/packages/block-editor/src/components/observe-typing/index.js +++ b/packages/block-editor/src/components/observe-typing/index.js @@ -111,7 +111,7 @@ export function useMouseMoveTypingReset() { * Sets and removes the `isTyping` flag based on user actions: * * - Sets the flag if the user types within the given element. - * - Removes the flag when the user selects some text, focusses a non-text + * - Removes the flag when the user selects some text, focuses a non-text * field, presses ESC or TAB, or moves the mouse in the document. */ export function useTypingObserver() { diff --git a/packages/block-editor/src/components/plain-text/README.md b/packages/block-editor/src/components/plain-text/README.md index aa15758118afdc..1e0a7888ed1e4d 100644 --- a/packages/block-editor/src/components/plain-text/README.md +++ b/packages/block-editor/src/components/plain-text/README.md @@ -6,11 +6,11 @@ Render an auto-growing textarea allow users to fill any textual content. ### `value: string` -_Required._ String value of the textarea +_Required._ String value of the textarea. ### `onChange( value: string ): Function` -_Required._ Called when the value changes. +_Required._ Function called when the text value changes. You can also pass any extra prop to the textarea rendered by this component. diff --git a/packages/block-editor/src/components/plain-text/index.js b/packages/block-editor/src/components/plain-text/index.js index 4bd6681f4eb079..d28aabebf7a140 100644 --- a/packages/block-editor/src/components/plain-text/index.js +++ b/packages/block-editor/src/components/plain-text/index.js @@ -15,7 +15,41 @@ import { forwardRef } from '@wordpress/element'; import EditableText from '../editable-text'; /** + * Render an auto-growing textarea allow users to fill any textual content. + * * @see https://github.com/WordPress/gutenberg/blob/HEAD/packages/block-editor/src/components/plain-text/README.md + * + * @example + * ```jsx + * import { registerBlockType } from '@wordpress/blocks'; + * import { PlainText } from '@wordpress/block-editor'; + * + * registerBlockType( 'my-plugin/example-block', { + * // ... + * + * attributes: { + * content: { + * type: 'string', + * }, + * }, + * + * edit( { className, attributes, setAttributes } ) { + * return ( + * <PlainText + * className={ className } + * value={ attributes.content } + * onChange={ ( content ) => setAttributes( { content } ) } + * /> + * ); + * }, + * } ); + * ```` + * + * @param {Object} props Component props. + * @param {string} props.value String value of the textarea. + * @param {Function} props.onChange Function called when the text value changes. + * @param {Object} [props.ref] The component forwards the `ref` property to the `TextareaAutosize` component. + * @return {Element} Plain text component */ const PlainText = forwardRef( ( { __experimentalVersion, ...props }, ref ) => { if ( __experimentalVersion === 2 ) { diff --git a/packages/block-editor/src/components/plain-text/stories/index.story.js b/packages/block-editor/src/components/plain-text/stories/index.story.js new file mode 100644 index 00000000000000..d1a6253c0870a7 --- /dev/null +++ b/packages/block-editor/src/components/plain-text/stories/index.story.js @@ -0,0 +1,75 @@ +/** + * WordPress dependencies + */ +import { useState } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import PlainText from '..'; + +const meta = { + title: 'BlockEditor/PlainText', + component: PlainText, + parameters: { + docs: { + canvas: { sourceState: 'shown' }, + description: { + component: + 'PlainText renders an auto-growing textarea that allows users to enter any textual content.', + }, + }, + }, + argTypes: { + value: { + control: { + type: null, + }, + table: { + type: { + summary: 'string', + }, + }, + description: 'String value of the textarea.', + }, + onChange: { + action: 'onChange', + control: { + type: null, + }, + table: { + type: { + summary: 'function', + }, + }, + description: 'Function called when the text value changes.', + }, + className: { + control: 'text', + table: { + type: { + summary: 'string', + }, + }, + description: 'Additional class name for the PlainText.', + }, + }, +}; + +export default meta; + +export const Default = { + render: function Template( { onChange, ...args } ) { + const [ value, setValue ] = useState(); + return ( + <PlainText + { ...args } + onChange={ ( ...changeArgs ) => { + onChange( ...changeArgs ); + setValue( ...changeArgs ); + } } + value={ value } + /> + ); + }, +}; diff --git a/packages/block-editor/src/components/provider/index.js b/packages/block-editor/src/components/provider/index.js index abbb122ae3a0e0..4f3cb8867f1d43 100644 --- a/packages/block-editor/src/components/provider/index.js +++ b/packages/block-editor/src/components/provider/index.js @@ -2,8 +2,12 @@ * WordPress dependencies */ import { useDispatch } from '@wordpress/data'; -import { useEffect } from '@wordpress/element'; +import { useEffect, useMemo } from '@wordpress/element'; import { SlotFillProvider } from '@wordpress/components'; +import { + MediaUploadProvider, + store as uploadStore, +} from '@wordpress/upload-media'; /** * Internal dependencies @@ -14,12 +18,71 @@ import { store as blockEditorStore } from '../../store'; import { BlockRefsProvider } from './block-refs-provider'; import { unlock } from '../../lock-unlock'; import KeyboardShortcuts from '../keyboard-shortcuts'; +import useMediaUploadSettings from './use-media-upload-settings'; /** @typedef {import('@wordpress/data').WPDataRegistry} WPDataRegistry */ +const noop = () => {}; + +/** + * Upload a media file when the file upload button is activated + * or when adding a file to the editor via drag & drop. + * + * @param {WPDataRegistry} registry + * @param {Object} $3 Parameters object passed to the function. + * @param {Array} $3.allowedTypes Array with the types of media that can be uploaded, if unset all types are allowed. + * @param {Object} $3.additionalData Additional data to include in the request. + * @param {Array<File>} $3.filesList List of files. + * @param {Function} $3.onError Function called when an error happens. + * @param {Function} $3.onFileChange Function called each time a file or a temporary representation of the file is available. + * @param {Function} $3.onSuccess Function called once a file has completely finished uploading, including thumbnails. + * @param {Function} $3.onBatchSuccess Function called once all files in a group have completely finished uploading, including thumbnails. + */ +function mediaUpload( + registry, + { + allowedTypes, + additionalData = {}, + filesList, + onError = noop, + onFileChange, + onSuccess, + onBatchSuccess, + } +) { + void registry.dispatch( uploadStore ).addItems( { + files: filesList, + onChange: onFileChange, + onSuccess, + onBatchSuccess, + onError: ( { message } ) => onError( message ), + additionalData, + allowedTypes, + } ); +} + export const ExperimentalBlockEditorProvider = withRegistryProvider( ( props ) => { - const { children, settings, stripExperimentalSettings = false } = props; + const { + settings: _settings, + registry, + stripExperimentalSettings = false, + } = props; + + const mediaUploadSettings = useMediaUploadSettings( _settings ); + + let settings = _settings; + + if ( window.__experimentalMediaProcessing && _settings.mediaUpload ) { + // Create a new variable so that the original props.settings.mediaUpload is not modified. + settings = useMemo( + () => ( { + ..._settings, + mediaUpload: mediaUpload.bind( null, registry ), + } ), + [ _settings, registry ] + ); + } const { __experimentalUpdateSettings } = unlock( useDispatch( blockEditorStore ) @@ -44,12 +107,25 @@ export const ExperimentalBlockEditorProvider = withRegistryProvider( // Syncs the entity provider with changes in the block-editor store. useBlockSync( props ); - return ( + const children = ( <SlotFillProvider passthrough> { ! settings?.isPreviewMode && <KeyboardShortcuts.Register /> } - <BlockRefsProvider>{ children }</BlockRefsProvider> + <BlockRefsProvider>{ props.children }</BlockRefsProvider> </SlotFillProvider> ); + + if ( window.__experimentalMediaProcessing ) { + return ( + <MediaUploadProvider + settings={ mediaUploadSettings } + useSubRegistry={ false } + > + { children } + </MediaUploadProvider> + ); + } + + return children; } ); diff --git a/packages/block-editor/src/components/provider/test/use-block-sync.js b/packages/block-editor/src/components/provider/test/use-block-sync.js index aae5e517c63029..b2afdb942e66fa 100644 --- a/packages/block-editor/src/components/provider/test/use-block-sync.js +++ b/packages/block-editor/src/components/provider/test/use-block-sync.js @@ -22,7 +22,9 @@ jest.mock( '../../../store/actions', () => { ...actions, resetBlocks: jest.fn( actions.resetBlocks ), replaceInnerBlocks: jest.fn( actions.replaceInnerBlocks ), - setHasControlledInnerBlocks: jest.fn( actions.replaceInnerBlocks ), + setHasControlledInnerBlocks: jest.fn( + actions.setHasControlledInnerBlocks + ), }; } ); 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 fa148109d510f6..3cc2b21b141e67 100644 --- a/packages/block-editor/src/components/provider/use-block-sync.js +++ b/packages/block-editor/src/components/provider/use-block-sync.js @@ -33,7 +33,7 @@ const noop = () => {}; * the template part in the block editor back to the entity and vice-versa. * * Here are some of its basic functions: - * - Initalizes the block-editor store for the given clientID to the blocks + * - Initializes the block-editor store for the given clientID to the blocks * given via props. * - Adds incoming changes (like undo) to the block-editor store. * - Adds outgoing changes (like editing content) to the controlling entity, @@ -49,7 +49,7 @@ const noop = () => {}; * root controller rather than an inner block * controller. * @param {Object[]} props.value The control value for the blocks. This value - * is used to initalize the block-editor store + * is used to initialize the block-editor store * and for resetting the blocks to incoming * changes like undo. * @param {Object} props.selection The selection state responsible to restore the selection on undo/redo. diff --git a/packages/block-editor/src/components/provider/use-media-upload-settings.js b/packages/block-editor/src/components/provider/use-media-upload-settings.js new file mode 100644 index 00000000000000..40390a77e746ef --- /dev/null +++ b/packages/block-editor/src/components/provider/use-media-upload-settings.js @@ -0,0 +1,25 @@ +/** + * WordPress dependencies + */ +import { useMemo } from '@wordpress/element'; + +/** + * React hook used to compute the media upload settings to use in the post editor. + * + * @param {Object} settings Media upload settings prop. + * + * @return {Object} Media upload settings. + */ +function useMediaUploadSettings( settings = {} ) { + return useMemo( + () => ( { + mediaUpload: settings.mediaUpload, + mediaSideload: settings.mediaSideload, + maxUploadFileSize: settings.maxUploadFileSize, + allowedMimeTypes: settings.allowedMimeTypes, + } ), + [ settings ] + ); +} + +export default useMediaUploadSettings; diff --git a/packages/block-editor/src/components/resolution-tool/index.js b/packages/block-editor/src/components/resolution-tool/index.js index df43cb6acb096d..b73a2d5f249723 100644 --- a/packages/block-editor/src/components/resolution-tool/index.js +++ b/packages/block-editor/src/components/resolution-tool/index.js @@ -33,6 +33,7 @@ export default function ResolutionTool( { options = DEFAULT_SIZE_OPTIONS, defaultValue = DEFAULT_SIZE_OPTIONS[ 0 ].value, isShownByDefault = true, + resetAllFilter, } ) { const displayValue = value ?? defaultValue; return ( @@ -42,6 +43,7 @@ export default function ResolutionTool( { onDeselect={ () => onChange( defaultValue ) } isShownByDefault={ isShownByDefault } panelId={ panelId } + resetAllFilter={ resetAllFilter } > <SelectControl __nextHasNoMarginBottom diff --git a/packages/block-editor/src/components/resolution-tool/stories/index.story.js b/packages/block-editor/src/components/resolution-tool/stories/index.story.js index ed598acd4df98f..08cf9ef6c53782 100644 --- a/packages/block-editor/src/components/resolution-tool/stories/index.story.js +++ b/packages/block-editor/src/components/resolution-tool/stories/index.story.js @@ -1,7 +1,7 @@ /** * WordPress dependencies */ -import { useState } from '@wordpress/element'; +import { useReducer } from '@wordpress/element'; import { Panel, __experimentalToolsPanel as ToolsPanel, @@ -13,30 +13,106 @@ import { import ResolutionTool from '..'; export default { - title: 'BlockEditor (Private APIs)/ResolutionControl', + title: 'BlockEditor/ResolutionControl', component: ResolutionTool, + tags: [ 'status-private' ], + parameters: { + docs: { + canvas: { sourceState: 'shown' }, + description: { + component: + 'A control for selecting image resolution with preset size options.', + }, + }, + }, argTypes: { - panelId: { control: { type: null } }, - onChange: { action: 'changed' }, + value: { + control: { type: null }, + description: 'Currently selected resolution value.', + table: { type: { summary: 'string' } }, + }, + onChange: { + action: 'onChange', + control: { type: null }, + description: 'Handles change in resolution selection.', + table: { + type: { summary: 'function' }, + }, + }, + options: { + control: 'object', + description: 'Array of resolution options to display.', + table: { + type: { summary: 'array' }, + }, + }, + defaultValue: { + control: 'radio', + options: [ 'thumbnail', 'medium', 'large', 'full' ], + description: 'Default resolution value.', + table: { + type: { summary: 'string' }, + }, + }, + isShownByDefault: { + control: 'boolean', + description: + 'Whether the control is shown by default in the panel.', + table: { + type: { summary: 'boolean' }, + }, + }, + panelId: { + control: { type: null }, + description: 'ID of the parent tools panel.', + table: { + type: { summary: 'string' }, + }, + }, }, }; -export const Default = ( { panelId, onChange: onChangeProp, ...props } ) => { - const [ resolution, setResolution ] = useState( undefined ); - const resetAll = () => { - setResolution( undefined ); +export const Default = ( { + label, + panelId, + onChange: onChangeProp, + ...props +} ) => { + const [ attributes, setAttributes ] = useReducer( + ( prevState, nextState ) => ( { ...prevState, ...nextState } ), + {} + ); + const { resolution } = attributes; + const resetAll = ( resetFilters = [] ) => { + let newAttributes = {}; + + resetFilters.forEach( ( resetFilter ) => { + newAttributes = { + ...newAttributes, + ...resetFilter( newAttributes ), + }; + } ); + + setAttributes( newAttributes ); onChangeProp( undefined ); }; return ( <Panel> - <ToolsPanel panelId={ panelId } resetAll={ resetAll }> + <ToolsPanel + label={ label } + panelId={ panelId } + resetAll={ resetAll } + > <ResolutionTool panelId={ panelId } onChange={ ( newValue ) => { - setResolution( newValue ); + setAttributes( { resolution: newValue } ); onChangeProp( newValue ); } } value={ resolution } + resetAllFilter={ () => ( { + resolution: undefined, + } ) } { ...props } /> </ToolsPanel> @@ -44,5 +120,7 @@ export const Default = ( { panelId, onChange: onChangeProp, ...props } ) => { ); }; Default.args = { + label: 'Settings', + defaultValue: 'full', panelId: 'panel-id', }; diff --git a/packages/block-editor/src/components/responsive-block-control/index.js b/packages/block-editor/src/components/responsive-block-control/index.js index 148ba9600f0032..388e7ec543693a 100644 --- a/packages/block-editor/src/components/responsive-block-control/index.js +++ b/packages/block-editor/src/components/responsive-block-control/index.js @@ -57,7 +57,7 @@ function ResponsiveBlockControl( props ) { ); const toggleHelpText = __( - 'Toggle between using the same value for all screen sizes or using a unique value per screen size.' + 'Choose whether to use the same value for all screen sizes or a unique value for each screen size.' ); const defaultControl = renderDefaultControl( diff --git a/packages/block-editor/src/components/rich-text/README.md b/packages/block-editor/src/components/rich-text/README.md index 11ea1c75204dd7..f08d75c5bec45e 100644 --- a/packages/block-editor/src/components/rich-text/README.md +++ b/packages/block-editor/src/components/rich-text/README.md @@ -52,7 +52,7 @@ _Optional._ By default, all registered formats are allowed. This setting can be tagName="h2" identifier="content" value={ attributes.content } - allowedFormats={ [ 'core/bold', 'core/italic' ] } // Allow the content to be made bold or italic, but do not allow othe formatting options + allowedFormats={ [ 'core/bold', 'core/italic' ] } // Allow the content to be made bold or italic, but do not allow other formatting options onChange={ ( content ) => setAttributes( { content } ) } placeholder={ __( 'Heading...' ) } /> diff --git a/packages/block-editor/src/components/rich-text/content.scss b/packages/block-editor/src/components/rich-text/content.scss index 6f118479fc6b03..67e14b0882d7ef 100644 --- a/packages/block-editor/src/components/rich-text/content.scss +++ b/packages/block-editor/src/components/rich-text/content.scss @@ -13,16 +13,6 @@ &:focus { // Removes outline added by the browser. outline: none; - - [data-rich-text-format-boundary] { - border-radius: $radius-small; - } - } -} - -.block-editor-rich-text__editable { - > p:first-child { - margin-top: 0; } } @@ -40,3 +30,18 @@ figcaption.block-editor-rich-text__editable [data-rich-text-placeholder]::before background: rgb(255, 255, 0); } } + +[data-rich-text-comment], +[data-rich-text-format-boundary] { + border-radius: $radius-small; +} + +[data-rich-text-comment] { + background-color: currentColor; + + span { + filter: invert(100%); + color: currentColor; + padding: 0 2px; + } +} diff --git a/packages/block-editor/src/components/rich-text/event-listeners/delete.js b/packages/block-editor/src/components/rich-text/event-listeners/delete.js index ae3fd733bb94e1..8373ca3c9f72ae 100644 --- a/packages/block-editor/src/components/rich-text/event-listeners/delete.js +++ b/packages/block-editor/src/components/rich-text/event-listeners/delete.js @@ -6,7 +6,7 @@ import { isCollapsed, isEmpty } from '@wordpress/rich-text'; export default ( props ) => ( element ) => { function onKeyDown( event ) { - const { keyCode } = event; + const { keyCode, shiftKey } = event; if ( event.defaultPrevented ) { return; @@ -30,6 +30,11 @@ export default ( props ) => ( element ) => { return; } + // Exclude shift+backspace as they are shortcuts for deleting blocks. + if ( shiftKey ) { + return; + } + if ( onMerge ) { onMerge( ! isReverse ); } diff --git a/packages/block-editor/src/components/rich-text/event-listeners/input-rules.js b/packages/block-editor/src/components/rich-text/event-listeners/input-rules.js index 4a1e8400e35a17..4618e17b11fbbe 100644 --- a/packages/block-editor/src/components/rich-text/event-listeners/input-rules.js +++ b/packages/block-editor/src/components/rich-text/event-listeners/input-rules.js @@ -112,12 +112,12 @@ export default ( props ) => ( element ) => { const value = getValue(); const transformed = formatTypes.reduce( - ( accumlator, { __unstableInputRule } ) => { + ( accumulator, { __unstableInputRule } ) => { if ( __unstableInputRule ) { - accumlator = __unstableInputRule( accumlator ); + accumulator = __unstableInputRule( accumulator ); } - return accumlator; + return accumulator; }, preventEventDiscovery( value ) ); diff --git a/packages/block-editor/src/components/rich-text/index.js b/packages/block-editor/src/components/rich-text/index.js index 8f179d08570ad1..768ffbb0cdd2dc 100644 --- a/packages/block-editor/src/components/rich-text/index.js +++ b/packages/block-editor/src/components/rich-text/index.js @@ -39,7 +39,7 @@ import FormatEdit from './format-edit'; import { getAllowedFormats } from './utils'; import { Content, valueToHTMLString } from './content'; import { withDeprecations } from './with-deprecations'; -import { canBindBlock } from '../../hooks/use-bindings-attributes'; +import { canBindBlock } from '../../utils/block-bindings'; import BlockContext from '../block-context'; export const keyboardShortcutContext = createContext(); @@ -431,6 +431,11 @@ export function RichTextWrapper( aria-multiline={ ! disableLineBreaks } aria-readonly={ shouldDisableEditing } { ...props } + // Unset draggable (coming from block props) for contentEditable + // elements because it will interfere with multi block selection + // when the contentEditable and draggable elements are the same + // element. + draggable={ undefined } aria-label={ bindingsLabel || props[ 'aria-label' ] || placeholder } diff --git a/packages/block-editor/src/components/rich-text/native/use-format-types.js b/packages/block-editor/src/components/rich-text/native/use-format-types.js index ff65d7421ae5cc..f5535826b5b78a 100644 --- a/packages/block-editor/src/components/rich-text/native/use-format-types.js +++ b/packages/block-editor/src/components/rich-text/native/use-format-types.js @@ -35,7 +35,7 @@ const interactiveContentTags = new Set( [ * @param {Object} $0 Options * @param {string} $0.clientId Block client ID. * @param {string} $0.identifier Block attribute. - * @param {boolean} $0.withoutInteractiveFormatting Whether to clean the interactive formattings or not. + * @param {boolean} $0.withoutInteractiveFormatting Whether to clean the interactive formatting or not. * @param {Array} $0.allowedFormats Allowed formats */ export function useFormatTypes( { diff --git a/packages/block-editor/src/components/rich-text/use-format-types.js b/packages/block-editor/src/components/rich-text/use-format-types.js index 3c9b3b62ef78a5..0bbebbf262367d 100644 --- a/packages/block-editor/src/components/rich-text/use-format-types.js +++ b/packages/block-editor/src/components/rich-text/use-format-types.js @@ -59,7 +59,7 @@ function getPrefixedSelectKeys( selected, prefix ) { * @param {Object} $0 Options * @param {string} $0.clientId Block client ID. * @param {string} $0.identifier Block attribute. - * @param {boolean} $0.withoutInteractiveFormatting Whether to clean the interactive formattings or not. + * @param {boolean} $0.withoutInteractiveFormatting Whether to clean the interactive formatting or not. * @param {Array} $0.allowedFormats Allowed formats */ export function useFormatTypes( { diff --git a/packages/block-editor/src/components/spacing-sizes-control/style.scss b/packages/block-editor/src/components/spacing-sizes-control/style.scss index a387e5369d01ed..26f3dc586bb54b 100644 --- a/packages/block-editor/src/components/spacing-sizes-control/style.scss +++ b/packages/block-editor/src/components/spacing-sizes-control/style.scss @@ -4,40 +4,11 @@ margin-bottom: 0; } - .is-marked { - .components-range-control__track { - transition: width ease 0.1s; - @include reduce-motion("transition"); - } - - .components-range-control__thumb-wrapper { - transition: left ease 0.1s; - @include reduce-motion("transition"); - } - } - .spacing-sizes-control__range-control, .spacing-sizes-control__custom-value-range { flex: 1; margin-bottom: 0; // Needed for some instances of the range control, such as the Spacer block. } - - .components-range-control__mark { - transform: translateX(-50%); - height: $grid-unit-05; - width: math.div($grid-unit-05, 2); - background-color: $white; - z-index: 1; - top: -#{$grid-unit-05}; - } - - .components-range-control__marks { - margin-top: 17px; - } - - .components-range-control__thumb-wrapper { - z-index: 3; - } } .spacing-sizes-control__header { diff --git a/packages/block-editor/src/components/tabbed-sidebar/README.md b/packages/block-editor/src/components/tabbed-sidebar/README.md index 42001dbfc79cb0..35c44730ffc15b 100644 --- a/packages/block-editor/src/components/tabbed-sidebar/README.md +++ b/packages/block-editor/src/components/tabbed-sidebar/README.md @@ -1,21 +1,19 @@ -# Tabbed Panel +# TabbedSidebar -The `TabbedPanel` component is used to create the secondary panels in the editor. +The `TabbedSidebar` component is used to create secondary panels in the editor with tabbed navigation. ## Development guidelines -This acts as a wrapper for the `Tabs` component, but adding conventions that can be shared between all secondary panels, for example: +This acts as a wrapper for the `Tabs` component, adding conventions that can be shared between all secondary panels, including: - A close button - Tabs that fill the panel -- Custom scollbars +- Custom scrollbars ### Usage -Renders a block alignment toolbar with alignments options. - ```jsx -import { TabbedSidebar } from '@wordpress/components'; +import { TabbedSidebar } from '@wordpress/block-editor'; const MyTabbedSidebar = () => ( <TabbedSidebar @@ -23,7 +21,7 @@ const MyTabbedSidebar = () => ( { name: 'slug-1', title: _x( 'Title 1', 'context' ), - panel: <PanelContents>, + panel: <PanelContents />, panelRef: useRef('an-optional-ref'), }, { @@ -35,6 +33,8 @@ const MyTabbedSidebar = () => ( onClose={ onClickCloseButton } onSelect={ onSelectTab } defaultTabId="slug-1" + selectedTab="slug-1" + closeButtonLabel="Close sidebar" ref={ tabsRef } /> ); @@ -47,30 +47,41 @@ const MyTabbedSidebar = () => ( - **Type:** `String` - **Default:** `undefined` -This is passed to the `Tabs` component so it can handle the tab to select by default when it component renders. +The ID of the tab to be selected by default when the component renders. ### `onClose` - **Type:** `Function` -The function that is called when the close button is clicked. +Function called when the close button is clicked. ### `onSelect` - **Type:** `Function` -This is passed to the `Tabs` component - it will be called when a tab has been selected. It is passed the selected tab's ID as an argument. +Function called when a tab is selected. Receives the selected tab's ID as an argument. ### `selectedTab` - **Type:** `String` - **Default:** `undefined` -This is passed to the `Tabs` component - it will display this tab as selected. +The ID of the currently selected tab. ### `tabs` - **Type:** `Array` - **Default:** `undefined` -An array of tabs which will be rendered as `TabList` and `TabPanel` components. +Array of tab objects. Each tab should have: + +- `name` (string): Unique identifier for the tab +- `title` (string): Display title for the tab +- `panel` (React.Node): Content to display in the tab panel +- `panelRef` (React.Ref, optional): Reference to the tab panel element + +#### `closeButtonLabel` + +- **Type:** `String` + +Accessibility label for the close button. \ No newline at end of file diff --git a/packages/block-editor/src/components/tabbed-sidebar/index.js b/packages/block-editor/src/components/tabbed-sidebar/index.js index c9ff6bbf6555f0..f142f538cfe8f9 100644 --- a/packages/block-editor/src/components/tabbed-sidebar/index.js +++ b/packages/block-editor/src/components/tabbed-sidebar/index.js @@ -15,6 +15,44 @@ import { unlock } from '../../lock-unlock'; const { Tabs } = unlock( componentsPrivateApis ); +/** + * A component that creates a tabbed sidebar with a close button. + * + * @see https://github.com/WordPress/gutenberg/blob/HEAD/packages/block-editor/src/components/tabbed-sidebar/README.md + * + * @example + * ```jsx + * function MyTabbedSidebar() { + * return ( + * <TabbedSidebar + * tabs={ [ + * { + * name: 'tab1', + * title: 'Settings', + * panel: <PanelContents />, + * } + * ] } + * onClose={ () => {} } + * onSelect={ () => {} } + * defaultTabId="tab1" + * selectedTab="tab1" + * closeButtonLabel="Close sidebar" + * /> + * ); + * } + * ``` + * + * @param {Object} props Component props. + * @param {string} [props.defaultTabId] The ID of the tab to be selected by default when the component renders. + * @param {Function} props.onClose Function called when the close button is clicked. + * @param {Function} props.onSelect Function called when a tab is selected. Receives the selected tab's ID as an argument. + * @param {string} props.selectedTab The ID of the currently selected tab. + * @param {Array} props.tabs Array of tab objects. Each tab should have: name (string), title (string), + * panel (React.Node), and optionally panelRef (React.Ref). + * @param {string} props.closeButtonLabel Accessibility label for the close button. + * @param {Object} ref Forward ref to the tabs list element. + * @return {Element} The tabbed sidebar component. + */ function TabbedSidebar( { defaultTabId, onClose, onSelect, selectedTab, tabs, closeButtonLabel }, ref diff --git a/packages/block-editor/src/components/tabbed-sidebar/stories/index.story.js b/packages/block-editor/src/components/tabbed-sidebar/stories/index.story.js new file mode 100644 index 00000000000000..49825be19b90c3 --- /dev/null +++ b/packages/block-editor/src/components/tabbed-sidebar/stories/index.story.js @@ -0,0 +1,104 @@ +/** + * WordPress dependencies + */ +import { useState } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import TabbedSidebar from '../'; + +const meta = { + title: 'BlockEditor/TabbedSidebar', + component: TabbedSidebar, + tags: [ 'status-private' ], + parameters: { + docs: { + canvas: { sourceState: 'shown' }, + description: { + component: + 'A component that creates a tabbed sidebar with a close button.', + }, + }, + }, + argTypes: { + defaultTabId: { + control: { type: null }, + table: { + type: { summary: 'string' }, + }, + description: + 'The ID of the tab to be selected by default when the component renders.', + }, + onClose: { + action: 'onClose', + control: { type: null }, + table: { + type: { summary: 'function' }, + }, + description: 'Function called when the close button is clicked.', + }, + onSelect: { + action: 'onSelect', + control: { type: null }, + table: { + type: { summary: 'function' }, + }, + description: + "Function called when a tab is selected. Receives the selected tab's ID as an argument.", + }, + selectedTab: { + control: { type: null }, + table: { + type: { summary: 'string' }, + }, + description: 'The ID of the currently selected tab.', + }, + tabs: { + control: { type: 'array' }, + table: { + type: { summary: 'array' }, + }, + description: + 'Array of tab objects. Each tab should have: name (string), title (string), panel (React.Node), and optionally panelRef (React.Ref).', + }, + closeButtonLabel: { + control: { type: 'text' }, + table: { + type: { summary: 'string' }, + }, + description: 'Accessibility label for the close button.', + }, + }, +}; + +export default meta; + +const DEMO_TABS = [ + { name: 'tab1', title: 'Settings' }, + { name: 'tab2', title: 'Styles' }, + { name: 'tab3', title: 'Advanced' }, +]; + +export const Default = { + render: function Template( { onSelect, onClose, ...args } ) { + const [ selectedTab, setSelectedTab ] = useState(); + + return ( + <TabbedSidebar + { ...args } + selectedTab={ selectedTab } + onSelect={ ( ...changeArgs ) => { + onSelect( ...changeArgs ); + setSelectedTab( ...changeArgs ); + } } + onClose={ onClose } + /> + ); + }, + args: { + tabs: DEMO_TABS, + defaultTabId: 'tab1', + closeButtonLabel: 'Close Sidebar', + }, +}; diff --git a/packages/block-editor/src/components/text-alignment-control/README.md b/packages/block-editor/src/components/text-alignment-control/README.md new file mode 100644 index 00000000000000..243a5fec7938b7 --- /dev/null +++ b/packages/block-editor/src/components/text-alignment-control/README.md @@ -0,0 +1,49 @@ +# TextAlignmentControl + +The `TextAlignmentControl` component is responsible for rendering a control element that allows users to select and apply text alignment options to blocks or elements in the Gutenberg editor. It provides an intuitive interface for aligning text with options such as `left`, `center` and `right`. + +## Usage + +Renders the Text Alignment Component with `left`, `center` and `right` alignment options. + +```jsx +import { TextAlignmentControl } from '@wordpress/block-editor'; + +const MyTextAlignmentControlComponent = () => ( + <TextAlignmentControl + value={ textAlign } + onChange={ ( value ) => { + setAttributes( { textAlign: value } ); + } } + /> +); +``` + +## Props + +### `value` + +- **Type:** `String` +- **Default:** `undefined` +- **Options:** `left`, `center`, `right`, `justify` + +The current value of the text alignment setting. You may only choose from the `Options` listed above. + +### `onChange` + +- **Type:** `Function` + +A callback function invoked when the text alignment value is changed via an interaction with any of the options. The function is called with the new alignment value (`left`, `center`, `right`) as the only argument. + +### `className` + +- **Type:** `String` + +Class name to add to the control for custom styling. + +### `options` + +- **Type:** `Array` +- **Default:** [`left`, `center`, `right`] + +An array that determines which alignment options will be available in the control. You can pass an array of alignment values to customize the options. diff --git a/packages/block-editor/src/components/text-alignment-control/stories/index.story.js b/packages/block-editor/src/components/text-alignment-control/stories/index.story.js index b2c171497acb0a..076535ab330d69 100644 --- a/packages/block-editor/src/components/text-alignment-control/stories/index.story.js +++ b/packages/block-editor/src/components/text-alignment-control/stories/index.story.js @@ -8,32 +8,70 @@ import { useState } from '@wordpress/element'; */ import TextAlignmentControl from '../'; -export default { +const meta = { title: 'BlockEditor/TextAlignmentControl', component: TextAlignmentControl, + tags: [ 'status-private' ], + parameters: { + docs: { + canvas: { sourceState: 'shown' }, + description: { + component: 'Control to facilitate text alignment selections.', + }, + }, + }, argTypes: { - onChange: { action: 'onChange' }, - className: { control: 'text' }, + value: { + control: { type: null }, + description: 'Currently selected text alignment value.', + table: { + type: { + summary: 'string', + }, + }, + }, + onChange: { + action: 'onChange', + control: { type: null }, + description: 'Handles change in text alignment selection.', + table: { + type: { + summary: 'function', + }, + }, + }, options: { control: 'check', + description: 'Array of text alignment options to display.', options: [ 'left', 'center', 'right', 'justify' ], + table: { + type: { summary: 'array' }, + }, + }, + className: { + control: 'text', + description: 'Class name to add to the control.', + table: { + type: { summary: 'string' }, + }, }, - value: { control: { type: null } }, }, }; -const Template = ( { onChange, ...args } ) => { - const [ value, setValue ] = useState(); - return ( - <TextAlignmentControl - { ...args } - onChange={ ( ...changeArgs ) => { - onChange( ...changeArgs ); - setValue( ...changeArgs ); - } } - value={ value } - /> - ); -}; +export default meta; -export const Default = Template.bind( {} ); +export const Default = { + render: function Template( { onChange, ...args } ) { + const [ value, setValue ] = useState(); + return ( + <TextAlignmentControl + { ...args } + onChange={ ( ...changeArgs ) => { + onChange( ...changeArgs ); + setValue( ...changeArgs ); + } } + value={ value } + /> + ); + }, +}; diff --git a/packages/block-editor/src/components/text-decoration-control/README.md b/packages/block-editor/src/components/text-decoration-control/README.md index a606140baa330e..87fb6e89bd5712 100644 --- a/packages/block-editor/src/components/text-decoration-control/README.md +++ b/packages/block-editor/src/components/text-decoration-control/README.md @@ -28,7 +28,6 @@ Then, you can use the component in your block editor UI: ### `value` - **Type:** `String` -- **Default:** `none` - **Options:** `none`, `underline`, `line-through` The current value of the Text Decoration setting. You may only choose from the `Options` listed above. diff --git a/packages/block-editor/src/components/text-decoration-control/stories/index.story.js b/packages/block-editor/src/components/text-decoration-control/stories/index.story.js index 2212b484185cde..d139b30a2bb4b5 100644 --- a/packages/block-editor/src/components/text-decoration-control/stories/index.story.js +++ b/packages/block-editor/src/components/text-decoration-control/stories/index.story.js @@ -8,26 +8,61 @@ import { useState } from '@wordpress/element'; */ import TextDecorationControl from '../'; -export default { +const meta = { title: 'BlockEditor/TextDecorationControl', component: TextDecorationControl, + parameters: { + docs: { + canvas: { sourceState: 'shown' }, + description: { + component: 'Control to facilitate text decoration selections.', + }, + }, + }, argTypes: { - onChange: { action: 'onChange' }, + value: { + control: { type: null }, + description: 'Currently selected text decoration.', + table: { + type: { + summary: 'string', + }, + }, + }, + onChange: { + action: 'onChange', + control: { type: null }, + description: 'Handles change in text decoration selection.', + table: { + type: { + summary: 'function', + }, + }, + }, + className: { + control: 'text', + description: 'Additional class name to apply.', + table: { + type: { summary: 'string' }, + }, + }, }, }; -const Template = ( { onChange, ...args } ) => { - const [ value, setValue ] = useState(); - return ( - <TextDecorationControl - { ...args } - onChange={ ( ...changeArgs ) => { - onChange( ...changeArgs ); - setValue( ...changeArgs ); - } } - value={ value } - /> - ); -}; +export default meta; -export const Default = Template.bind( {} ); +export const Default = { + render: function Template( { onChange, ...args } ) { + const [ value, setValue ] = useState(); + return ( + <TextDecorationControl + { ...args } + onChange={ ( ...changeArgs ) => { + onChange( ...changeArgs ); + setValue( ...changeArgs ); + } } + value={ value } + /> + ); + }, +}; diff --git a/packages/block-editor/src/components/text-transform-control/README.md b/packages/block-editor/src/components/text-transform-control/README.md index 2d40cc16ba86f8..cd23461d3eb332 100644 --- a/packages/block-editor/src/components/text-transform-control/README.md +++ b/packages/block-editor/src/components/text-transform-control/README.md @@ -1,8 +1,8 @@ # TextTransformControl The `TextTransformControl` component is responsible for rendering a control element that allows users to select and apply text transformation options to blocks or elements in the Gutenberg editor. It provides an intuitive interface for changing the text appearance by applying different transformations such as `none`, `uppercase`, `lowercase`, `capitalize`. - -![TextTransformConrol Element in Inspector Control](https://raw.githubusercontent.com/WordPress/gutenberg/HEAD/docs/assets/text-transform-component.png?raw=true) + +![TextTransformControl Element in Inspector Control](https://raw.githubusercontent.com/WordPress/gutenberg/HEAD/docs/assets/text-transform-component.png?raw=true) ## Development guidelines @@ -28,7 +28,6 @@ const MyTextTransformControlComponent = () => ( ### `value` - **Type:** `String` -- **Default:** `none` - **Options:** `none`, `uppercase`, `lowercase`, `capitalize` The current value of the Text Transform setting. You may only choose from the `Options` listed above. @@ -37,4 +36,4 @@ The current value of the Text Transform setting. You may only choose from the `O - **Type:** `Function` -A callback function invoked when the Text Transform value is changed via an interaction with any of the buttons. Called with the Text Transform value (`none`, `uppercase`, `lowercase`, `capitalize`) as the only argument. \ No newline at end of file +A callback function invoked when the Text Transform value is changed via an interaction with any of the buttons. Called with the Text Transform value (`none`, `uppercase`, `lowercase`, `capitalize`) as the only argument. diff --git a/packages/block-editor/src/components/text-transform-control/stories/index.story.js b/packages/block-editor/src/components/text-transform-control/stories/index.story.js index 96dd8ed479dc4e..77dc550368da19 100644 --- a/packages/block-editor/src/components/text-transform-control/stories/index.story.js +++ b/packages/block-editor/src/components/text-transform-control/stories/index.story.js @@ -8,26 +8,63 @@ import { useState } from '@wordpress/element'; */ import TextTransformControl from '../'; -export default { +const meta = { title: 'BlockEditor/TextTransformControl', component: TextTransformControl, + parameters: { + docs: { + canvas: { sourceState: 'shown' }, + description: { + component: + 'Control to facilitate text transformation selections.', + }, + }, + }, argTypes: { - onChange: { action: 'onChange' }, + onChange: { + action: 'onChange', + control: { + type: null, + }, + description: 'Handles change in text transform selection.', + table: { + type: { + summary: 'function', + }, + }, + }, + className: { + control: { type: 'text' }, + description: 'Class name to add to the control.', + table: { + type: { summary: 'string' }, + }, + }, + value: { + control: { type: null }, + description: 'Currently selected text transform.', + table: { + type: { summary: 'string' }, + }, + }, }, }; -const Template = ( { onChange, ...args } ) => { - const [ value, setValue ] = useState(); - return ( - <TextTransformControl - { ...args } - onChange={ ( ...changeArgs ) => { - onChange( ...changeArgs ); - setValue( ...changeArgs ); - } } - value={ value } - /> - ); -}; +export default meta; + +export const Default = { + render: function Template( { onChange, ...args } ) { + const [ value, setValue ] = useState(); -export const Default = Template.bind( {} ); + return ( + <TextTransformControl + { ...args } + onChange={ ( ...changeArgs ) => { + onChange( ...changeArgs ); + setValue( ...changeArgs ); + } } + value={ value } + /> + ); + }, +}; diff --git a/packages/block-editor/src/components/typewriter/index.js b/packages/block-editor/src/components/typewriter/index.js index b5e230d314a7e2..76a6870788c80a 100644 --- a/packages/block-editor/src/components/typewriter/index.js +++ b/packages/block-editor/src/components/typewriter/index.js @@ -193,7 +193,7 @@ export function useTypewriter() { } /** - * Checks if the current situation is elegible for scroll: + * Checks if the current situation is eligible for scroll: * - There should be one and only one block selected. * - The component must contain the selection. * - The active element must be contenteditable. diff --git a/packages/block-editor/src/components/unit-control/README.md b/packages/block-editor/src/components/unit-control/README.md index 7cd5269f00d032..e44ffb494deff1 100644 --- a/packages/block-editor/src/components/unit-control/README.md +++ b/packages/block-editor/src/components/unit-control/README.md @@ -34,7 +34,7 @@ const Example = () => { ### Props -#### disabledUnits +#### disableUnits If true, the unit `<select>` is hidden. diff --git a/packages/block-editor/src/components/unit-control/stories/index.story.js b/packages/block-editor/src/components/unit-control/stories/index.story.js new file mode 100644 index 00000000000000..4f840daa3e3826 --- /dev/null +++ b/packages/block-editor/src/components/unit-control/stories/index.story.js @@ -0,0 +1,124 @@ +/** + * WordPress dependencies + */ +import { useState } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import UnitControl from '../'; + +const meta = { + title: 'BlockEditor/UnitControl', + component: UnitControl, + parameters: { + docs: { + canvas: { sourceState: 'shown' }, + description: { + component: + 'UnitControl allows the user to set a numeric quantity as well as a unit.', + }, + }, + }, + argTypes: { + onChange: { + action: 'onChange', + description: 'Callback function when the value changes.', + table: { + type: { summary: 'function' }, + }, + }, + onUnitChange: { + action: 'onUnitChange', + description: 'Callback function when the unit changes.', + table: { + type: { summary: 'function' }, + }, + }, + labelPosition: { + control: 'radio', + options: [ 'top', 'side', 'bottom', 'edge' ], + description: 'The position of the label.', + table: { + type: { summary: 'string' }, + }, + }, + label: { + control: 'text', + description: 'The label for the control.', + table: { + type: { summary: 'string' }, + }, + }, + value: { + control: { type: null }, + description: 'The value of the control.', + table: { + type: { summary: 'string' }, + }, + }, + size: { + control: 'radio', + options: [ 'default', 'small' ], + description: 'The size of the control.', + table: { + type: { summary: 'string' }, + defaultValue: { summary: 'default' }, + }, + }, + disabled: { + control: 'boolean', + description: 'Whether the control is disabled.', + table: { + type: { summary: 'boolean' }, + defaultValue: { summary: false }, + }, + }, + disableUnits: { + control: 'boolean', + description: 'If true, the unit select is hidden.', + table: { + type: { summary: 'boolean' }, + defaultValue: { summary: false }, + }, + }, + isPressEnterToChange: { + control: 'boolean', + description: + 'If true, the ENTER key press is required to trigger onChange. Change is also triggered on blur.', + table: { + type: { summary: 'boolean' }, + defaultValue: { summary: false }, + }, + }, + isUnitSelectTabbable: { + control: 'boolean', + description: 'Determines if the unit select is tabbable.', + table: { + type: { summary: 'boolean' }, + defaultValue: { summary: true }, + }, + }, + }, +}; + +export default meta; + +export const Default = { + render: function Template( { onChange, ...args } ) { + const [ value, setValue ] = useState(); + return ( + <UnitControl + { ...args } + value={ value } + onChange={ ( ...changeArgs ) => { + onChange( ...changeArgs ); + setValue( ...changeArgs ); + } } + /> + ); + }, + args: { + label: 'Label', + }, +}; diff --git a/packages/block-editor/src/components/url-popover/image-url-input-ui.js b/packages/block-editor/src/components/url-popover/image-url-input-ui.js index c68cf5e58b9530..7a9414c1fd3c1a 100644 --- a/packages/block-editor/src/components/url-popover/image-url-input-ui.js +++ b/packages/block-editor/src/components/url-popover/image-url-input-ui.js @@ -265,14 +265,14 @@ const ImageURLInputUI = ( { <div className="block-editor-url-popover__expand-on-click"> <Icon icon={ fullscreen } /> <div className="text"> - <p>{ __( 'Expand on click' ) }</p> + <p>{ __( 'Enlarge on click' ) }</p> <p className="description"> { __( 'Scales the image with a lightbox effect' ) } </p> </div> <Button icon={ linkOff } - label={ __( 'Disable expand on click' ) } + label={ __( 'Disable enlarge on click' ) } onClick={ () => { onSetLightbox?.( false ); } } @@ -372,7 +372,7 @@ const ImageURLInputUI = ( { stopEditLink(); } } > - { __( 'Expand on click' ) } + { __( 'Enlarge on click' ) } </MenuItem> ) } </NavigableMenu> 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 ff919710a2284e..9c932d3c86f54b 100644 --- a/packages/block-editor/src/components/use-block-commands/index.js +++ b/packages/block-editor/src/components/use-block-commands/index.js @@ -89,7 +89,7 @@ const getTransformCommands = () => } } - // Simple block tranformation based on the `Block Transforms` API. + // Simple block transformation based on the `Block Transforms` API. function onBlockTransform( name ) { const newBlocks = switchToBlockType( blocks, name ); replaceBlocks( clientIds, newBlocks ); diff --git a/packages/block-editor/src/components/use-block-drop-zone/index.js b/packages/block-editor/src/components/use-block-drop-zone/index.js index 2a3e4948d40b3b..529eb199fb76a0 100644 --- a/packages/block-editor/src/components/use-block-drop-zone/index.js +++ b/packages/block-editor/src/components/use-block-drop-zone/index.js @@ -332,6 +332,7 @@ export default function useBlockDropZone( { isGroupable, isZoomOut, getSectionRootClientId, + getBlockParents, } = unlock( useSelect( blockEditorStore ) ); const { showInsertionPoint, @@ -358,13 +359,29 @@ export default function useBlockDropZone( { // So, ensure that the drag state is set when the user drags over a drop zone. startDragging(); } + + const draggedBlockClientIds = getDraggedBlockClientIds(); + const targetParents = [ + targetRootClientId, + ...getBlockParents( targetRootClientId, true ), + ]; + + // Check if the target is within any of the dragged blocks. + const isTargetWithinDraggedBlocks = draggedBlockClientIds.some( + ( clientId ) => targetParents.includes( clientId ) + ); + + if ( isTargetWithinDraggedBlocks ) { + return; + } + const allowedBlocks = getAllowedBlocks( targetRootClientId ); const targetBlockName = getBlockNamesByClientId( [ targetRootClientId, ] )[ 0 ]; const draggedBlockNames = getBlockNamesByClientId( - getDraggedBlockClientIds() + draggedBlockClientIds ); const isBlockDroppingAllowed = isDropTargetValid( getBlockType, @@ -439,7 +456,14 @@ export default function useBlockDropZone( { const [ targetIndex, operation, nearestSide ] = dropTargetPosition; - if ( isZoomOut() && operation !== 'insert' ) { + const isTargetIndexEmptyDefaultBlock = + blocksData[ targetIndex ]?.isUnmodifiedDefaultBlock; + + if ( + isZoomOut() && + ! isTargetIndexEmptyDefaultBlock && + operation !== 'insert' + ) { return; } diff --git a/packages/block-editor/src/components/use-moving-animation/index.js b/packages/block-editor/src/components/use-moving-animation/index.js index 602b683150d0cc..89e21a43905210 100644 --- a/packages/block-editor/src/components/use-moving-animation/index.js +++ b/packages/block-editor/src/components/use-moving-animation/index.js @@ -52,6 +52,7 @@ function useMovingAnimation( { triggerAnimationOnChange, clientId } ) { isFirstMultiSelectedBlock, isBlockMultiSelected, isAncestorMultiSelected, + isDraggingBlocks, } = useSelect( blockEditorStore ); // Whenever the trigger changes, we need to take a snapshot of the current @@ -73,8 +74,14 @@ function useMovingAnimation( { triggerAnimationOnChange, clientId } ) { const isSelected = isBlockSelected( clientId ); const adjustScrolling = isSelected || isFirstMultiSelectedBlock( clientId ); + const isDragging = isDraggingBlocks(); function preserveScrollPosition() { + // The user already scrolled when dragging blocks. + if ( isDragging ) { + return; + } + if ( adjustScrolling && prevRect ) { const blockRect = ref.current.getBoundingClientRect(); const diff = blockRect.top - prevRect.top; @@ -89,7 +96,7 @@ function useMovingAnimation( { triggerAnimationOnChange, clientId } ) { // motion, if the user is typing (insertion by Enter), or if the block // count exceeds the threshold (insertion caused all the blocks that // follow to animate). - // To do: consider enableing the _moving_ animation even for large + // To do: consider enabling the _moving_ animation even for large // posts, while only disabling the _insertion_ animation? const disableAnimation = window.matchMedia( '(prefers-reduced-motion: reduce)' ).matches || @@ -107,6 +114,13 @@ function useMovingAnimation( { triggerAnimationOnChange, clientId } ) { isSelected || isBlockMultiSelected( clientId ) || isAncestorMultiSelected( clientId ); + + // The user already dragged the blocks to the new position, so don't + // animate the dragged blocks. + if ( isPartOfSelection && isDragging ) { + return; + } + // Make sure the other blocks move under the selected block(s). const zIndex = isPartOfSelection ? '1' : ''; @@ -153,6 +167,7 @@ function useMovingAnimation( { triggerAnimationOnChange, clientId } ) { isFirstMultiSelectedBlock, isBlockMultiSelected, isAncestorMultiSelected, + isDraggingBlocks, ] ); return ref; diff --git a/packages/block-editor/src/components/use-resize-canvas/index.js b/packages/block-editor/src/components/use-resize-canvas/index.js index 3b4d97a097964c..0aa46c9c3278f7 100644 --- a/packages/block-editor/src/components/use-resize-canvas/index.js +++ b/packages/block-editor/src/components/use-resize-canvas/index.js @@ -60,7 +60,7 @@ export default function useResizeCanvas( deviceType ) { marginLeft: marginHorizontal, marginRight: marginHorizontal, height, - overflowY: 'auto', + maxWidth: '100%', }; default: return { diff --git a/packages/block-editor/src/components/use-settings/README.md b/packages/block-editor/src/components/use-settings/README.md index 68f580aa357bea..a444a9531a9e74 100644 --- a/packages/block-editor/src/components/use-settings/README.md +++ b/packages/block-editor/src/components/use-settings/README.md @@ -5,7 +5,7 @@ It does the lookup of the settings in the following order: 1. Third parties can provide the settings for the block using the filter `blockEditor.useSetting.before`. -2. If no third parties have provided this setting, then it looks up in the block instance hierachy starting from the current block and working its way upwards to its ancestors. +2. If no third parties have provided this setting, then it looks up in the block instance hierarchy starting from the current block and working its way upwards to its ancestors. 3. If that doesn't prove to be successful in getting a value, then it falls back to the settings from the block editor store. 4. If none of the above steps prove to be successful, then it's likely to be a deprecated setting and the deprecated setting is used instead. diff --git a/packages/block-editor/src/components/warning/content.scss b/packages/block-editor/src/components/warning/content.scss index 9380a224b2ff95..7796dbc831abbe 100644 --- a/packages/block-editor/src/components/warning/content.scss +++ b/packages/block-editor/src/components/warning/content.scss @@ -18,7 +18,7 @@ margin: 0; } - // Required extra-specifity to override paragraph block styles. + // Required extra-specificity to override paragraph block styles. p.block-editor-warning__message.block-editor-warning__message { min-height: auto; } diff --git a/packages/block-editor/src/components/warning/index.js b/packages/block-editor/src/components/warning/index.js index 8b6279bef6044d..17a014107b43af 100644 --- a/packages/block-editor/src/components/warning/index.js +++ b/packages/block-editor/src/components/warning/index.js @@ -6,7 +6,6 @@ import clsx from 'clsx'; /** * WordPress dependencies */ -import { Children } from '@wordpress/element'; import { DropdownMenu, MenuGroup, MenuItem } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import { moreVertical } from '@wordpress/icons'; @@ -20,10 +19,10 @@ function Warning( { className, actions, children, secondaryActions } ) { { children } </p> - { ( Children.count( actions ) > 0 || secondaryActions ) && ( + { ( actions?.length > 0 || secondaryActions ) && ( <div className="block-editor-warning__actions"> - { Children.count( actions ) > 0 && - Children.map( actions, ( action, i ) => ( + { actions?.length > 0 && + actions.map( ( action, i ) => ( <span key={ i } className="block-editor-warning__action" diff --git a/packages/block-editor/src/components/warning/stories/index.story.js b/packages/block-editor/src/components/warning/stories/index.story.js new file mode 100644 index 00000000000000..ee881059f302d7 --- /dev/null +++ b/packages/block-editor/src/components/warning/stories/index.story.js @@ -0,0 +1,86 @@ +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; +import { Button } from '@wordpress/components'; + +/** + * Internal dependencies + */ +import Warning from '../'; + +const meta = { + title: 'BlockEditor/Warning', + component: Warning, + parameters: { + docs: { + canvas: { sourceState: 'shown' }, + description: { + component: + 'Displays a warning message with optional action buttons and secondary actions dropdown.', + }, + }, + }, + argTypes: { + children: { + control: 'text', + description: + 'Intended to represent the block to which the warning pertains.', + table: { + type: { summary: 'string|element' }, + }, + }, + className: { + control: 'text', + description: 'Classes to pass to element.', + table: { + type: { summary: 'string' }, + }, + }, + actions: { + control: 'object', + description: + 'An array of elements to be rendered as action buttons in the warning element.', + table: { + type: { summary: 'Element[]' }, + }, + }, + secondaryActions: { + control: 'object', + description: + 'An array of { title, onClick } to be rendered as options in a dropdown of secondary actions.', + table: { + type: { summary: '{ title: string, onClick: Function }[]' }, + }, + }, + }, +}; + +export default meta; + +export const Default = { + args: { + children: __( 'This block ran into an issue.' ), + }, +}; + +export const WithActions = { + args: { + ...Default.args, + actions: [ + <Button key="fix-issue" __next40pxDefaultSize variant="primary"> + { __( 'Fix issue' ) } + </Button>, + ], + }, +}; + +export const WithSecondaryActions = { + args: { + ...Default.args, + secondaryActions: [ + { title: __( 'Get help' ) }, + { title: __( 'Remove block' ) }, + ], + }, +}; diff --git a/packages/block-editor/src/components/warning/test/index.js b/packages/block-editor/src/components/warning/test/index.js index 6a0373da611c01..bdb910f68c16e0 100644 --- a/packages/block-editor/src/components/warning/test/index.js +++ b/packages/block-editor/src/components/warning/test/index.js @@ -18,7 +18,9 @@ describe( 'Warning', () => { it( 'should show primary actions', () => { render( - <Warning actions={ <button>Click me</button> }>Message</Warning> + <Warning actions={ [ <button key="test">Click me</button> ] }> + Message + </Warning> ); expect( diff --git a/packages/block-editor/src/components/writing-flow/test/index.js b/packages/block-editor/src/components/writing-flow/test/index.js index 4d19417cf36e54..edb594a6a7d183 100644 --- a/packages/block-editor/src/components/writing-flow/test/index.js +++ b/packages/block-editor/src/components/writing-flow/test/index.js @@ -50,7 +50,7 @@ describe( 'isNavigationCandidate', () => { } ); } ); - it( 'should return false if vertically navigating inputs with vertial support like number', () => { + it( 'should return false if vertically navigating inputs with vertical support like number', () => { [ UP, DOWN ].forEach( ( keyCode ) => { const result = isNavigationCandidate( elements.inputNumber, diff --git a/packages/block-editor/src/components/writing-flow/use-drag-selection.js b/packages/block-editor/src/components/writing-flow/use-drag-selection.js index 1569c45a7c6769..ea4c09b3dc9577 100644 --- a/packages/block-editor/src/components/writing-flow/use-drag-selection.js +++ b/packages/block-editor/src/components/writing-flow/use-drag-selection.js @@ -80,7 +80,17 @@ export default function useDragSelection() { } ); } + let lastMouseDownTarget; + + function onMouseDown( { target } ) { + lastMouseDownTarget = target; + } + function onMouseLeave( { buttons, target, relatedTarget } ) { + if ( ! target.contains( lastMouseDownTarget ) ) { + return; + } + // If we're moving into a child element, ignore. We're tracking // the mouse leaving the element to a parent, no a child. if ( target.contains( relatedTarget ) ) { @@ -141,6 +151,7 @@ export default function useDragSelection() { } node.addEventListener( 'mouseout', onMouseLeave ); + node.addEventListener( 'mousedown', onMouseDown ); return () => { node.removeEventListener( 'mouseout', onMouseLeave ); diff --git a/packages/block-editor/src/components/writing-flow/use-tab-nav.js b/packages/block-editor/src/components/writing-flow/use-tab-nav.js index 16a18358fb2ede..46c40d56fe96d9 100644 --- a/packages/block-editor/src/components/writing-flow/use-tab-nav.js +++ b/packages/block-editor/src/components/writing-flow/use-tab-nav.js @@ -35,6 +35,11 @@ export default function useTabNav() { const noCaptureRef = useRef(); function onFocusCapture( event ) { + const canvasElement = + container.current.ownerDocument === event.target.ownerDocument + ? container.current + : container.current.ownerDocument.defaultView.frameElement; + // Do not capture incoming focus if set by us in WritingFlow. if ( noCaptureRef.current ) { noCaptureRef.current = null; @@ -64,17 +69,15 @@ export default function useTabNav() { .focus(); } // If we don't have any section blocks, focus the section root. - else { + else if ( sectionRootClientId ) { container.current .querySelector( `[data-block="${ sectionRootClientId }"]` ) .focus(); + } else { + // If we don't have any section root, focus the canvas. + canvasElement.focus(); } } else { - const canvasElement = - container.current.ownerDocument === event.target.ownerDocument - ? container.current - : container.current.ownerDocument.defaultView.frameElement; - const isBefore = // eslint-disable-next-line no-bitwise event.target.compareDocumentPosition( canvasElement ) & diff --git a/packages/block-editor/src/components/writing-mode-control/stories/index.story.js b/packages/block-editor/src/components/writing-mode-control/stories/index.story.js new file mode 100644 index 00000000000000..ea4bd65a37a000 --- /dev/null +++ b/packages/block-editor/src/components/writing-mode-control/stories/index.story.js @@ -0,0 +1,56 @@ +/** + * WordPress dependencies + */ +import { useState } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import WritingModeControl from '../'; + +const meta = { + title: 'BlockEditor/WritingModeControl', + component: WritingModeControl, + parameters: { + docs: { + canvas: { sourceState: 'shown' }, + description: { + component: 'Control to facilitate writing mode selections.', + }, + }, + }, + argTypes: { + value: { + control: { type: null }, + description: 'Currently selected writing mode.', + }, + className: { + control: 'text', + description: 'Class name to add to the control.', + }, + onChange: { + action: 'onChange', + control: { type: null }, + description: 'Handles change in the writing mode selection.', + }, + }, +}; + +export default meta; + +export const Default = { + render: function Template( { onChange, ...args } ) { + const [ value, setValue ] = useState(); + + return ( + <WritingModeControl + { ...args } + onChange={ ( ...changeArgs ) => { + onChange( ...changeArgs ); + setValue( ...changeArgs ); + } } + value={ value } + /> + ); + }, +}; diff --git a/packages/block-editor/src/hooks/background.js b/packages/block-editor/src/hooks/background.js index 3755aecbcb9d0b..6268ff31b29890 100644 --- a/packages/block-editor/src/hooks/background.js +++ b/packages/block-editor/src/hooks/background.js @@ -177,6 +177,11 @@ export function BackgroundImagePanel( { }, }; + const defaultControls = getBlockSupport( name, [ + BACKGROUND_SUPPORT_KEY, + 'defaultControls', + ] ); + return ( <StylesBackgroundPanel inheritedValue={ inheritedValue } @@ -185,6 +190,7 @@ export function BackgroundImagePanel( { defaultValues={ BACKGROUND_BLOCK_DEFAULT_VALUES } settings={ updatedSettings } onChange={ onChange } + defaultControls={ defaultControls } value={ style } /> ); diff --git a/packages/block-editor/src/hooks/block-bindings.js b/packages/block-editor/src/hooks/block-bindings.js index 615804a311c0fb..11e17aba3b30da 100644 --- a/packages/block-editor/src/hooks/block-bindings.js +++ b/packages/block-editor/src/hooks/block-bindings.js @@ -26,12 +26,12 @@ import { useViewportMatch } from '@wordpress/compose'; import { canBindAttribute, getBindableAttributes, -} from '../hooks/use-bindings-attributes'; + useBlockBindingsUtils, +} from '../utils/block-bindings'; import { unlock } from '../lock-unlock'; import InspectorControls from '../components/inspector-controls'; import BlockContext from '../components/block-context'; import { useBlockEditContext } from '../components/block-edit'; -import { useBlockBindingsUtils } from '../utils/block-bindings'; import { store as blockEditorStore } from '../store'; const { Menu } = unlock( componentsPrivateApis ); @@ -51,7 +51,7 @@ const useToolsPanelDropdownMenuProps = () => { : {}; }; -function BlockBindingsPanelDropdown( { fieldsList, attribute, binding } ) { +function BlockBindingsPanelMenuContent( { fieldsList, attribute, binding } ) { const { clientId } = useBlockEditContext(); const registeredSources = getBlockBindingsSources(); const { updateBlockBindings } = useBlockBindingsUtils(); @@ -179,22 +179,21 @@ function EditableBlockBindingsPanelItems( { placement={ isMobile ? 'bottom-start' : 'left-start' } - gutter={ isMobile ? 8 : 36 } - trigger={ - <Item> - <BlockBindingsAttribute - attribute={ attribute } - binding={ binding } - fieldsList={ fieldsList } - /> - </Item> - } > - <BlockBindingsPanelDropdown - fieldsList={ fieldsList } - attribute={ attribute } - binding={ binding } - /> + <Menu.TriggerButton render={ <Item /> }> + <BlockBindingsAttribute + attribute={ attribute } + binding={ binding } + fieldsList={ fieldsList } + /> + </Menu.TriggerButton> + <Menu.Popover gutter={ isMobile ? 8 : 36 }> + <BlockBindingsPanelMenuContent + fieldsList={ fieldsList } + attribute={ attribute } + binding={ binding } + /> + </Menu.Popover> </Menu> </ToolsPanelItem> ); @@ -300,13 +299,17 @@ export const BlockBindingsPanel = ( { name: blockName, metadata } ) => { /> ) } </ItemGroup> - <ItemGroup> - <Text variant="muted"> + { /* + Use a div element to make the ToolsPanelHiddenInnerWrapper + toggle the visibility of this help text automatically. + */ } + <Text as="div" variant="muted"> + <p> { __( 'Attributes connected to custom fields or other dynamic data.' ) } - </Text> - </ItemGroup> + </p> + </Text> </ToolsPanel> </InspectorControls> ); diff --git a/packages/block-editor/src/hooks/color.scss b/packages/block-editor/src/hooks/color.scss index 8be620bf40d938..0f6fa60a6224ed 100644 --- a/packages/block-editor/src/hooks/color.scss +++ b/packages/block-editor/src/hooks/color.scss @@ -1,12 +1,5 @@ .color-block-support-panel { .block-editor-contrast-checker { - /** - * Contrast checkers are forced to the bottom of the panel so all - * injected color controls can appear as a single item group without - * the contrast checkers suddenly appearing between items. - */ - /* stylelint-disable-next-line property-disallowed-list -- This should be removed when https://github.com/WordPress/gutenberg/issues/58936 is fixed. */ - order: 9999; grid-column: span 2; margin-top: $grid-unit-20; diff --git a/packages/block-editor/src/hooks/custom-class-name.js b/packages/block-editor/src/hooks/custom-class-name.js index 91ef07506fd95c..a7f8cab8c82237 100644 --- a/packages/block-editor/src/hooks/custom-class-name.js +++ b/packages/block-editor/src/hooks/custom-class-name.js @@ -123,7 +123,8 @@ export function addTransforms( result, source, index, results ) { // if source N does not exists we do nothing. if ( source[ index ] ) { const originClassName = source[ index ]?.attributes.className; - if ( originClassName ) { + // Avoid overriding classes if the transformed block already includes them. + if ( originClassName && result.attributes.className === undefined ) { return { ...result, attributes: { diff --git a/packages/block-editor/src/hooks/font-family.js b/packages/block-editor/src/hooks/font-family.js index e5d8e02ab8ec02..ba9a66a8bcf04f 100644 --- a/packages/block-editor/src/hooks/font-family.js +++ b/packages/block-editor/src/hooks/font-family.js @@ -13,7 +13,7 @@ import { shouldSkipSerialization } from './utils'; import { TYPOGRAPHY_SUPPORT_KEY } from './typography'; import { unlock } from '../lock-unlock'; -export const FONT_FAMILY_SUPPORT_KEY = 'typography.fontFamily'; +export const FONT_FAMILY_SUPPORT_KEY = 'typography.__experimentalFontFamily'; const { kebabCase } = unlock( componentsPrivateApis ); /** diff --git a/packages/block-editor/src/hooks/gap.js b/packages/block-editor/src/hooks/gap.js index debe1ad1690782..c5c4bbb7130350 100644 --- a/packages/block-editor/src/hooks/gap.js +++ b/packages/block-editor/src/hooks/gap.js @@ -8,7 +8,7 @@ import { getSpacingPresetCssVar } from '../components/spacing-sizes-control/util * The string check is for backwards compatibility before Gutenberg supported * split gap values (row and column) and the value was a string n + unit. * - * @param {string? | Object?} blockGapValue A block gap string or axial object value, e.g., '10px' or { top: '10px', left: '10px'}. + * @param {?string | ?Object} blockGapValue A block gap string or axial object value, e.g., '10px' or { top: '10px', left: '10px'}. * @return {Object|null} A value to pass to the BoxControl component. */ export function getGapBoxControlValueFromStyle( blockGapValue ) { @@ -26,8 +26,8 @@ export function getGapBoxControlValueFromStyle( blockGapValue ) { /** * Returns a CSS value for the `gap` property from a given blockGap style. * - * @param {string? | Object?} blockGapValue A block gap string or axial object value, e.g., '10px' or { top: '10px', left: '10px'}. - * @param {string?} defaultValue A default gap value. + * @param {?string | ?Object} blockGapValue A block gap string or axial object value, e.g., '10px' or { top: '10px', left: '10px'}. + * @param {?string} defaultValue A default gap value. * @return {string|null} The concatenated gap value (row and column). */ export function getGapCSSValue( blockGapValue, defaultValue = '0' ) { diff --git a/packages/block-editor/src/hooks/index.js b/packages/block-editor/src/hooks/index.js index 66ff60b691b66f..7f9b29376ad1fb 100644 --- a/packages/block-editor/src/hooks/index.js +++ b/packages/block-editor/src/hooks/index.js @@ -32,7 +32,6 @@ import './metadata'; import blockHooks from './block-hooks'; import blockBindingsPanel from './block-bindings'; import './block-renaming'; -import './use-bindings-attributes'; import './grid-visualizer'; createBlockEditFilter( diff --git a/packages/block-editor/src/hooks/index.native.js b/packages/block-editor/src/hooks/index.native.js index c7f9df868f2bd7..0e4c2aa276fd40 100644 --- a/packages/block-editor/src/hooks/index.native.js +++ b/packages/block-editor/src/hooks/index.native.js @@ -33,3 +33,4 @@ export { getColorClassesAndStyles, useColorProps } from './use-color-props'; export { getSpacingClassesAndStyles } from './use-spacing-props'; export { useCachedTruthy } from './use-cached-truthy'; export { useEditorWrapperStyles } from './use-editor-wrapper-styles'; +export { getTypographyClassesAndStyles } from './use-typography-props'; diff --git a/packages/block-editor/src/hooks/supports.js b/packages/block-editor/src/hooks/supports.js index c0b6bb2cc8b271..75f2bdf2dc219e 100644 --- a/packages/block-editor/src/hooks/supports.js +++ b/packages/block-editor/src/hooks/supports.js @@ -9,17 +9,17 @@ const ALIGN_WIDE_SUPPORT_KEY = 'alignWide'; const BORDER_SUPPORT_KEY = '__experimentalBorder'; const COLOR_SUPPORT_KEY = 'color'; const CUSTOM_CLASS_NAME_SUPPORT_KEY = 'customClassName'; -const FONT_FAMILY_SUPPORT_KEY = 'typography.fontFamily'; +const FONT_FAMILY_SUPPORT_KEY = 'typography.__experimentalFontFamily'; const FONT_SIZE_SUPPORT_KEY = 'typography.fontSize'; const LINE_HEIGHT_SUPPORT_KEY = 'typography.lineHeight'; /** * Key within block settings' support array indicating support for font style. */ -const FONT_STYLE_SUPPORT_KEY = 'typography.fontStyle'; +const FONT_STYLE_SUPPORT_KEY = 'typography.__experimentalFontStyle'; /** * Key within block settings' support array indicating support for font weight. */ -const FONT_WEIGHT_SUPPORT_KEY = 'typography.fontWeight'; +const FONT_WEIGHT_SUPPORT_KEY = 'typography.__experimentalFontWeight'; /** * Key within block settings' supports array indicating support for text * align e.g. settings found in `block.json`. @@ -34,7 +34,7 @@ const TEXT_COLUMNS_SUPPORT_KEY = 'typography.textColumns'; * Key within block settings' supports array indicating support for text * decorations e.g. settings found in `block.json`. */ -const TEXT_DECORATION_SUPPORT_KEY = 'typography.textDecoration'; +const TEXT_DECORATION_SUPPORT_KEY = 'typography.__experimentalTextDecoration'; /** * Key within block settings' supports array indicating support for writing mode * e.g. settings found in `block.json`. @@ -44,13 +44,13 @@ const WRITING_MODE_SUPPORT_KEY = 'typography.__experimentalWritingMode'; * Key within block settings' supports array indicating support for text * transforms e.g. settings found in `block.json`. */ -const TEXT_TRANSFORM_SUPPORT_KEY = 'typography.textTransform'; +const TEXT_TRANSFORM_SUPPORT_KEY = 'typography.__experimentalTextTransform'; /** * Key within block settings' supports array indicating support for letter-spacing * e.g. settings found in `block.json`. */ -const LETTER_SPACING_SUPPORT_KEY = 'typography.letterSpacing'; +const LETTER_SPACING_SUPPORT_KEY = 'typography.__experimentalLetterSpacing'; const LAYOUT_SUPPORT_KEY = 'layout'; const TYPOGRAPHY_SUPPORT_KEYS = [ LINE_HEIGHT_SUPPORT_KEY, diff --git a/packages/block-editor/src/hooks/test/font-size.js b/packages/block-editor/src/hooks/test/font-size.js index 11cd024bf8a285..dc3fc9100e4759 100644 --- a/packages/block-editor/src/hooks/test/font-size.js +++ b/packages/block-editor/src/hooks/test/font-size.js @@ -19,6 +19,15 @@ import { import _fontSize from '../font-size'; const noop = () => {}; +const EMPTY_ARRAY = []; +const EMPTY_OBJECT = {}; +const fontSizes = [ + { + name: 'A larger font', + size: '32px', + slug: 'larger', + }, +]; function addUseSettingFilter( callback ) { addFilter( @@ -55,13 +64,7 @@ describe( 'useBlockProps', () => { registerBlockType( blockSettings.name, blockSettings ); addUseSettingFilter( ( result, path ) => { if ( 'typography.fontSizes' === path ) { - return [ - { - name: 'A larger font', - size: '32px', - slug: 'larger', - }, - ]; + return fontSizes; } if ( 'typography.fluid' === path ) { @@ -69,7 +72,7 @@ describe( 'useBlockProps', () => { } if ( 'layout' === path ) { - return {}; + return EMPTY_OBJECT; } return result; @@ -95,7 +98,7 @@ describe( 'useBlockProps', () => { registerBlockType( blockSettings.name, blockSettings ); addUseSettingFilter( ( result, path ) => { if ( 'typography.fontSizes' === path ) { - return []; + return EMPTY_ARRAY; } if ( 'typography.fluid' === path ) { @@ -103,7 +106,7 @@ describe( 'useBlockProps', () => { } if ( 'layout' === path ) { - return {}; + return EMPTY_OBJECT; } return result; @@ -132,7 +135,7 @@ describe( 'useBlockProps', () => { registerBlockType( blockSettings.name, blockSettings ); addUseSettingFilter( ( result, path ) => { if ( 'typography.fontSizes' === path ) { - return []; + return EMPTY_ARRAY; } if ( 'typography.fluid' === path ) { @@ -140,7 +143,7 @@ describe( 'useBlockProps', () => { } if ( 'layout' === path ) { - return {}; + return EMPTY_OBJECT; } return result; diff --git a/packages/block-editor/src/hooks/typography.js b/packages/block-editor/src/hooks/typography.js index ab9a464fe5efbc..cf3f4327c8f034 100644 --- a/packages/block-editor/src/hooks/typography.js +++ b/packages/block-editor/src/hooks/typography.js @@ -27,12 +27,12 @@ function omit( object, keys ) { ); } -const LETTER_SPACING_SUPPORT_KEY = 'typography.letterSpacing'; -const TEXT_TRANSFORM_SUPPORT_KEY = 'typography.textTransform'; -const TEXT_DECORATION_SUPPORT_KEY = 'typography.textDecoration'; +const LETTER_SPACING_SUPPORT_KEY = 'typography.__experimentalLetterSpacing'; +const TEXT_TRANSFORM_SUPPORT_KEY = 'typography.__experimentalTextTransform'; +const TEXT_DECORATION_SUPPORT_KEY = 'typography.__experimentalTextDecoration'; const TEXT_COLUMNS_SUPPORT_KEY = 'typography.textColumns'; -const FONT_STYLE_SUPPORT_KEY = 'typography.fontStyle'; -const FONT_WEIGHT_SUPPORT_KEY = 'typography.fontWeight'; +const FONT_STYLE_SUPPORT_KEY = 'typography.__experimentalFontStyle'; +const FONT_WEIGHT_SUPPORT_KEY = 'typography.__experimentalFontWeight'; const WRITING_MODE_SUPPORT_KEY = 'typography.__experimentalWritingMode'; export const TYPOGRAPHY_SUPPORT_KEY = 'typography'; export const TYPOGRAPHY_SUPPORT_KEYS = [ diff --git a/packages/block-editor/src/hooks/use-bindings-attributes.js b/packages/block-editor/src/hooks/use-bindings-attributes.js deleted file mode 100644 index fdc617fda20c05..00000000000000 --- a/packages/block-editor/src/hooks/use-bindings-attributes.js +++ /dev/null @@ -1,322 +0,0 @@ -/** - * WordPress dependencies - */ -import { store as blocksStore } from '@wordpress/blocks'; -import { createHigherOrderComponent } from '@wordpress/compose'; -import { useRegistry, useSelect } from '@wordpress/data'; -import { useCallback, useMemo, useContext } from '@wordpress/element'; -import { addFilter } from '@wordpress/hooks'; - -/** - * Internal dependencies - */ -import isURLLike from '../components/link-control/is-url-like'; -import { unlock } from '../lock-unlock'; -import BlockContext from '../components/block-context'; - -/** @typedef {import('@wordpress/compose').WPHigherOrderComponent} WPHigherOrderComponent */ -/** @typedef {import('@wordpress/blocks').WPBlockSettings} WPBlockSettings */ - -/** - * Given a binding of block attributes, returns a higher order component that - * overrides its `attributes` and `setAttributes` props to sync any changes needed. - * - * @return {WPHigherOrderComponent} Higher-order component. - */ - -const BLOCK_BINDINGS_ALLOWED_BLOCKS = { - 'core/paragraph': [ 'content' ], - 'core/heading': [ 'content' ], - 'core/image': [ 'id', 'url', 'title', 'alt' ], - '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. - * - * @param {string} blockName - The block name. - * @return {boolean} Whether it is possible to bind the block to sources. - */ -export function canBindBlock( blockName ) { - return blockName in BLOCK_BINDINGS_ALLOWED_BLOCKS; -} - -/** - * Based on the given block name and attribute name, - * check if it is possible to bind the block attribute. - * - * @param {string} blockName - The block name. - * @param {string} attributeName - The attribute name. - * @return {boolean} Whether it is possible to bind the block attribute. - */ -export function canBindAttribute( blockName, attributeName ) { - return ( - canBindBlock( blockName ) && - BLOCK_BINDINGS_ALLOWED_BLOCKS[ blockName ].includes( attributeName ) - ); -} - -export function getBindableAttributes( blockName ) { - return BLOCK_BINDINGS_ALLOWED_BLOCKS[ blockName ]; -} - -export const withBlockBindingSupport = createHigherOrderComponent( - ( BlockEdit ) => ( props ) => { - const registry = useRegistry(); - const blockContext = useContext( BlockContext ); - const sources = useSelect( ( select ) => - unlock( select( blocksStore ) ).getAllBlockBindingsSources() - ); - const { name, clientId, context, setAttributes } = props; - const blockBindings = useMemo( - () => - replacePatternOverrideDefaultBindings( - name, - props.attributes.metadata?.bindings - ), - [ props.attributes.metadata?.bindings, name ] - ); - - // While this hook doesn't directly call any selectors, `useSelect` is - // used purposely here to ensure `boundAttributes` is updated whenever - // there are attribute updates. - // `source.getValues` may also call a selector via `registry.select`. - const updatedContext = {}; - const boundAttributes = useSelect( - ( select ) => { - if ( ! blockBindings ) { - return; - } - - const attributes = {}; - - const blockBindingsBySource = new Map(); - - for ( const [ attributeName, binding ] of Object.entries( - blockBindings - ) ) { - const { source: sourceName, args: sourceArgs } = binding; - const source = sources[ sourceName ]; - if ( - ! source || - ! canBindAttribute( name, attributeName ) - ) { - continue; - } - - // Populate context. - for ( const key of source.usesContext || [] ) { - updatedContext[ key ] = blockContext[ key ]; - } - - blockBindingsBySource.set( source, { - ...blockBindingsBySource.get( source ), - [ attributeName ]: { - args: sourceArgs, - }, - } ); - } - - if ( blockBindingsBySource.size ) { - for ( const [ - source, - bindings, - ] of blockBindingsBySource ) { - // Get values in batch if the source supports it. - let values = {}; - if ( ! source.getValues ) { - Object.keys( bindings ).forEach( ( attr ) => { - // Default to the the source label when `getValues` doesn't exist. - values[ attr ] = source.label; - } ); - } else { - values = source.getValues( { - select, - context: updatedContext, - clientId, - bindings, - } ); - } - for ( const [ attributeName, value ] of Object.entries( - values - ) ) { - if ( - attributeName === 'url' && - ( ! value || ! isURLLike( value ) ) - ) { - // Return null if value is not a valid URL. - attributes[ attributeName ] = null; - } else { - attributes[ attributeName ] = value; - } - } - } - } - - return attributes; - }, - [ blockBindings, name, clientId, updatedContext, sources ] - ); - - const hasParentPattern = !! updatedContext[ 'pattern/overrides' ]; - const hasPatternOverridesDefaultBinding = - props.attributes.metadata?.bindings?.[ DEFAULT_ATTRIBUTE ] - ?.source === 'core/pattern-overrides'; - - const _setAttributes = useCallback( - ( nextAttributes ) => { - registry.batch( () => { - if ( ! blockBindings ) { - setAttributes( nextAttributes ); - return; - } - - const keptAttributes = { ...nextAttributes }; - const blockBindingsBySource = new Map(); - - // Loop only over the updated attributes to avoid modifying the bound ones that haven't changed. - for ( const [ attributeName, newValue ] of Object.entries( - keptAttributes - ) ) { - if ( - ! blockBindings[ attributeName ] || - ! canBindAttribute( name, attributeName ) - ) { - continue; - } - - const binding = blockBindings[ attributeName ]; - const source = sources[ binding?.source ]; - if ( ! source?.setValues ) { - continue; - } - blockBindingsBySource.set( source, { - ...blockBindingsBySource.get( source ), - [ attributeName ]: { - args: binding.args, - newValue, - }, - } ); - delete keptAttributes[ attributeName ]; - } - - if ( blockBindingsBySource.size ) { - for ( const [ - source, - bindings, - ] of blockBindingsBySource ) { - source.setValues( { - select: registry.select, - dispatch: registry.dispatch, - context: updatedContext, - clientId, - bindings, - } ); - } - } - - if ( - // Don't update non-connected attributes if the block is using pattern overrides - // and the editing is happening while overriding the pattern (not editing the original). - ! ( - hasPatternOverridesDefaultBinding && - hasParentPattern - ) && - Object.keys( keptAttributes ).length - ) { - // Don't update caption and href until they are supported. - if ( hasPatternOverridesDefaultBinding ) { - delete keptAttributes?.caption; - delete keptAttributes?.href; - } - setAttributes( keptAttributes ); - } - } ); - }, - [ - registry, - blockBindings, - name, - clientId, - updatedContext, - setAttributes, - sources, - hasPatternOverridesDefaultBinding, - hasParentPattern, - ] - ); - - return ( - <> - <BlockEdit - { ...props } - attributes={ { ...props.attributes, ...boundAttributes } } - setAttributes={ _setAttributes } - context={ { ...context, ...updatedContext } } - /> - </> - ); - }, - 'withBlockBindingSupport' -); - -/** - * Filters a registered block's settings to enhance a block's `edit` component - * to upgrade bound attributes. - * - * @param {WPBlockSettings} settings - Registered block settings. - * @param {string} name - Block name. - * @return {WPBlockSettings} Filtered block settings. - */ -function shimAttributeSource( settings, name ) { - if ( ! canBindBlock( name ) ) { - return settings; - } - - return { - ...settings, - edit: withBlockBindingSupport( settings.edit ), - }; -} - -addFilter( - 'blocks.registerBlockType', - 'core/editor/custom-sources-backwards-compatibility/shim-attribute-source', - shimAttributeSource -); diff --git a/packages/block-editor/src/hooks/use-zoom-out.js b/packages/block-editor/src/hooks/use-zoom-out.js index bcf5d9ff882f7b..5c37822eba4b38 100644 --- a/packages/block-editor/src/hooks/use-zoom-out.js +++ b/packages/block-editor/src/hooks/use-zoom-out.js @@ -2,42 +2,82 @@ * WordPress dependencies */ import { useSelect, useDispatch } from '@wordpress/data'; -import { useEffect } from '@wordpress/element'; +import { useEffect, useRef, useContext } from '@wordpress/element'; /** * Internal dependencies */ import { store as blockEditorStore } from '../store'; import { unlock } from '../lock-unlock'; +import BlockContext from '../components/block-context'; /** * A hook used to set the editor mode to zoomed out mode, invoking the hook sets the mode. + * Concepts: + * - If we most recently changed the zoom level for them (in or out), we always resetZoomLevel() level when unmounting. + * - If the user most recently changed the zoom level (manually toggling), we do nothing when unmounting. * - * @param {boolean} zoomOut If we should enter into zoomOut mode or not + * @param {boolean} enabled If we should enter into zoomOut mode or not */ -export function useZoomOut( zoomOut = true ) { +export function useZoomOut( enabled = true ) { + const { postId } = useContext( BlockContext ); const { setZoomLevel, resetZoomLevel } = unlock( useDispatch( blockEditorStore ) ); - const { isZoomOut } = unlock( useSelect( blockEditorStore ) ); + + /** + * We need access to both the value and the function. The value is to trigger a useEffect hook + * and the function is to check zoom out within another hook without triggering a re-render. + */ + const { isZoomedOut, isZoomOut } = useSelect( ( select ) => { + const { isZoomOut: _isZoomOut } = unlock( select( blockEditorStore ) ); + return { + isZoomedOut: _isZoomOut(), + isZoomOut: _isZoomOut, + }; + }, [] ); + + const controlZoomLevelRef = useRef( false ); + const isEnabledRef = useRef( enabled ); + const postIdRef = useRef( postId ); + + /** + * This hook tracks if the zoom state was changed manually by the user via clicking + * the zoom out button. We only want this to run when isZoomedOut changes, so we use + * a ref to track the enabled state. + */ + useEffect( () => { + // If the zoom state changed (isZoomOut) and it does not match the requested zoom + // state (zoomOut), then it means the user manually changed the zoom state while + // this hook was mounted, and we should no longer control the zoom state. + if ( isZoomedOut !== isEnabledRef.current ) { + controlZoomLevelRef.current = false; + } + }, [ isZoomedOut ] ); useEffect( () => { - const isZoomOutOnMount = isZoomOut(); + isEnabledRef.current = enabled; - return () => { - if ( isZoomOutOnMount ) { + // If the user created a new post/page, we should take control of the zoom level. + if ( postIdRef.current !== postId ) { + controlZoomLevelRef.current = true; + } + + if ( enabled !== isZoomOut() ) { + controlZoomLevelRef.current = true; + + if ( enabled ) { setZoomLevel( 'auto-scaled' ); } else { resetZoomLevel(); } - }; - }, [] ); - - useEffect( () => { - if ( zoomOut ) { - setZoomLevel( 'auto-scaled' ); - } else { - resetZoomLevel(); } - }, [ zoomOut, setZoomLevel, resetZoomLevel ] ); + + return () => { + // If we are controlling zoom level and are zoomed out, reset the zoom level. + if ( controlZoomLevelRef.current && isZoomOut() ) { + resetZoomLevel(); + } + }; + }, [ enabled, isZoomOut, postId, resetZoomLevel, setZoomLevel ] ); } diff --git a/packages/block-editor/src/layouts/flex.js b/packages/block-editor/src/layouts/flex.js index 81718449695651..f57ccbde466169 100644 --- a/packages/block-editor/src/layouts/flex.js +++ b/packages/block-editor/src/layouts/flex.js @@ -66,24 +66,27 @@ export default { onChange, layoutBlockSupport = {}, } ) { - const { allowOrientation = true } = layoutBlockSupport; + const { allowOrientation = true, allowJustification = true } = + layoutBlockSupport; return ( <> <Flex> - <FlexItem> - <FlexLayoutJustifyContentControl - layout={ layout } - onChange={ onChange } - /> - </FlexItem> - <FlexItem> - { allowOrientation && ( + { allowJustification && ( + <FlexItem> + <FlexLayoutJustifyContentControl + layout={ layout } + onChange={ onChange } + /> + </FlexItem> + ) } + { allowOrientation && ( + <FlexItem> <OrientationControl layout={ layout } onChange={ onChange } /> - ) } - </FlexItem> + </FlexItem> + ) } </Flex> <FlexWrapControl layout={ layout } onChange={ onChange } /> </> @@ -94,17 +97,22 @@ export default { onChange, layoutBlockSupport, } ) { - if ( layoutBlockSupport?.allowSwitching ) { + const { allowVerticalAlignment = true, allowJustification = true } = + layoutBlockSupport; + + if ( ! allowJustification && ! allowVerticalAlignment ) { return null; } - const { allowVerticalAlignment = true } = layoutBlockSupport; + return ( <BlockControls group="block" __experimentalShareWithChildBlocks> - <FlexLayoutJustifyContentControl - layout={ layout } - onChange={ onChange } - isToolbar - /> + { allowJustification && ( + <FlexLayoutJustifyContentControl + layout={ layout } + onChange={ onChange } + isToolbar + /> + ) } { allowVerticalAlignment && ( <FlexLayoutVerticalAlignmentControl layout={ layout } diff --git a/packages/block-editor/src/private-apis.js b/packages/block-editor/src/private-apis.js index 5067317e3f2465..21b9f4bca7fc3d 100644 --- a/packages/block-editor/src/private-apis.js +++ b/packages/block-editor/src/private-apis.js @@ -13,11 +13,11 @@ import { normalizeString, } from './components/inserter/search-items'; import { PrivateListView } from './components/list-view'; -import BlockInfo from './components/block-info-slot-fill'; import { useHasBlockToolbar } from './components/block-toolbar/use-has-block-toolbar'; import { cleanEmptyObject } from './hooks/utils'; import BlockQuickNavigation from './components/block-quick-navigation'; import { LayoutStyle } from './components/block-list/layout'; +import BlockManager from './components/block-manager'; import { BlockRemovalWarningModal } from './components/block-removal-warning-modal'; import { setBackgroundStyleDefaults, @@ -48,8 +48,8 @@ import { PrivatePublishDateTimePicker } from './components/publish-date-time-pic import useSpacingSizes from './components/spacing-sizes-control/hooks/use-spacing-sizes'; import useBlockDisplayTitle from './components/block-title/use-block-display-title'; import TabbedSidebar from './components/tabbed-sidebar'; -import __unstableCommentIconFill from './components/collab/block-comment-icon-slot'; -import __unstableCommentIconToolbarFill from './components/collab/block-comment-icon-toolbar-slot'; +import CommentIconSlotFill from './components/collab/block-comment-icon-slot'; +import CommentIconToolbarSlotFill from './components/collab/block-comment-icon-toolbar-slot'; /** * Private @wordpress/block-editor APIs. */ @@ -66,11 +66,11 @@ lock( privateApis, { normalizeString, PrivateListView, ResizableBoxPopover, - BlockInfo, useHasBlockToolbar, cleanEmptyObject, BlockQuickNavigation, LayoutStyle, + BlockManager, BlockRemovalWarningModal, useLayoutClasses, useLayoutStyles, @@ -95,6 +95,6 @@ lock( privateApis, { __unstableBlockStyleVariationOverridesWithConfig, setBackgroundStyleDefaults, sectionRootClientIdKey, - __unstableCommentIconFill, - __unstableCommentIconToolbarFill, + CommentIconSlotFill, + CommentIconToolbarSlotFill, } ); diff --git a/packages/block-editor/src/store/actions.js b/packages/block-editor/src/store/actions.js index 6edc5cb4da0393..32362968325efe 100644 --- a/packages/block-editor/src/store/actions.js +++ b/packages/block-editor/src/store/actions.js @@ -1266,7 +1266,7 @@ export const mergeBlocks = offset !== undefined && // We cannot restore text selection if the RichText identifier // is not a defined block attribute key. This can be the case if the - // fallback intance ID is used to store selection (and no RichText + // fallback instance ID is used to store selection (and no RichText // identifier is set), or when the identifier is wrong. !! attributeDefinition; diff --git a/packages/block-editor/src/store/private-actions.js b/packages/block-editor/src/store/private-actions.js index e79833e0a73da7..f085eb2807c6fd 100644 --- a/packages/block-editor/src/store/private-actions.js +++ b/packages/block-editor/src/store/private-actions.js @@ -26,6 +26,7 @@ const castArray = ( maybeArray ) => const privateSettings = [ 'inserterMediaCategories', 'blockInspectorAnimation', + 'mediaSideload', ]; /** diff --git a/packages/block-editor/src/store/private-selectors.js b/packages/block-editor/src/store/private-selectors.js index f1b5abd7789a11..72b87a59e8f571 100644 --- a/packages/block-editor/src/store/private-selectors.js +++ b/packages/block-editor/src/store/private-selectors.js @@ -112,12 +112,12 @@ function getEnabledClientIdsTreeUnmemoized( state, rootClientId ) { export const getEnabledClientIdsTree = createRegistrySelector( ( select ) => createSelector( getEnabledClientIdsTreeUnmemoized, ( state ) => [ state.blocks.order, + state.derivedBlockEditingModes, + state.derivedNavModeBlockEditingModes, state.blockEditingModes, state.settings.templateLock, state.blockListSettings, select( STORE_NAME ).__unstableGetEditorMode( state ), - state.zoomLevel, - getSectionRootClientId( state ), ] ) ); @@ -330,7 +330,7 @@ function mapUserPattern( id: userPattern.id, type: INSERTER_PATTERN_TYPES.user, title: userPattern.title.raw, - categories: userPattern.wp_pattern_category.map( ( catId ) => { + categories: userPattern.wp_pattern_category?.map( ( catId ) => { const category = __experimentalUserPatternCategories.find( ( { id } ) => id === catId ); @@ -406,21 +406,6 @@ export const getAllPatterns = createRegistrySelector( ( select ) => }, getAllPatternsDependants( select ) ) ); -export const isResolvingPatterns = createRegistrySelector( ( select ) => - createSelector( ( state ) => { - const blockPatternsSelect = state.settings[ selectBlockPatternsKey ]; - const reusableBlocksSelect = state.settings[ reusableBlocksSelectKey ]; - return ( - ( blockPatternsSelect - ? blockPatternsSelect( select ) === undefined - : false ) || - ( reusableBlocksSelect - ? reusableBlocksSelect( select ) === undefined - : false ) - ); - }, getAllPatternsDependants( select ) ) -); - const EMPTY_ARRAY = []; export const getReusableBlocks = createRegistrySelector( @@ -517,13 +502,23 @@ export const getParentSectionBlock = ( state, clientId ) => { * @return {boolean} Whether the block is a content locking parent. */ export function isSectionBlock( state, clientId ) { + const blockName = getBlockName( state, clientId ); + if ( + blockName === 'core/block' || + getTemplateLock( state, clientId ) === 'contentOnly' + ) { + return true; + } + + // Template parts become sections in navigation mode. + const _isNavigationMode = isNavigationMode( state ); + if ( _isNavigationMode && blockName === 'core/template-part' ) { + return true; + } + const sectionRootClientId = getSectionRootClientId( state ); const sectionClientIds = getBlockOrder( state, sectionRootClientId ); - return ( - getBlockName( state, clientId ) === 'core/block' || - getTemplateLock( state, clientId ) === 'contentOnly' || - ( isNavigationMode( state ) && sectionClientIds.includes( clientId ) ) - ); + return _isNavigationMode && sectionClientIds.includes( clientId ); } /** diff --git a/packages/block-editor/src/store/reducer.js b/packages/block-editor/src/store/reducer.js index 2f0fa70d616fd9..fc3803462d8920 100644 --- a/packages/block-editor/src/store/reducer.js +++ b/packages/block-editor/src/store/reducer.js @@ -9,12 +9,20 @@ import fastDeepEqual from 'fast-deep-equal/es6'; import { pipe } from '@wordpress/compose'; import { combineReducers, select } from '@wordpress/data'; import deprecated from '@wordpress/deprecated'; -import { store as blocksStore } from '@wordpress/blocks'; +import { + store as blocksStore, + privateApis as blocksPrivateApis, +} from '@wordpress/blocks'; + /** * Internal dependencies */ import { PREFERENCES_DEFAULTS, SETTINGS_DEFAULTS } from './defaults'; import { insertAt, moveTo } from './array'; +import { sectionRootClientIdKey } from './private-keys'; +import { unlock } from '../lock-unlock'; + +const { isContentBlock } = unlock( blocksPrivateApis ); const identity = ( x ) => x; @@ -1956,8 +1964,14 @@ export function temporarilyEditingFocusModeRevert( state = '', action ) { export function blockEditingModes( state = new Map(), action ) { switch ( action.type ) { case 'SET_BLOCK_EDITING_MODE': + if ( state.get( action.clientId ) === action.mode ) { + return state; + } return new Map( state ).set( action.clientId, action.mode ); case 'UNSET_BLOCK_EDITING_MODE': { + if ( ! state.has( action.clientId ) ) { + return state; + } const newState = new Map( state ); newState.delete( action.clientId ); return newState; @@ -2131,6 +2145,719 @@ const combinedReducers = combineReducers( { zoomLevel, } ); +/** + * Retrieves a block's tree structure, handling both controlled and uncontrolled inner blocks. + * + * @param {Object} state The current state object. + * @param {string} clientId The client ID of the block to retrieve. + * + * @return {Object|undefined} The block tree object, or undefined if not found. For controlled blocks, + * returns a merged tree with controlled inner blocks. + */ +function getBlockTreeBlock( state, clientId ) { + if ( clientId === '' ) { + const rootBlock = state.blocks.tree.get( clientId ); + + if ( ! rootBlock ) { + return; + } + + // Patch the root block to have a clientId property. + // TODO - consider updating the blocks reducer so that the root block has this property. + return { + clientId: '', + ...rootBlock, + }; + } + + if ( ! state.blocks.controlledInnerBlocks[ clientId ] ) { + return state.blocks.tree.get( clientId ); + } + + const controlledTree = state.blocks.tree.get( `controlled||${ clientId }` ); + const regularTree = state.blocks.tree.get( clientId ); + + return { + ...regularTree, + innerBlocks: controlledTree?.innerBlocks, + }; +} + +/** + * Recursively traverses through a block tree of a given block and executes a callback for each block. + * + * @param {Object} state The store state. + * @param {string} clientId The clientId of the block to start traversing from. + * @param {Function} callback Function to execute for each block encountered during traversal. + * The callback receives the current block as its argument. + */ +function traverseBlockTree( state, clientId, callback ) { + const tree = getBlockTreeBlock( state, clientId ); + if ( ! tree ) { + return; + } + + callback( tree ); + + if ( ! tree?.innerBlocks?.length ) { + return; + } + + for ( const innerBlock of tree?.innerBlocks ) { + traverseBlockTree( state, innerBlock.clientId, callback ); + } +} + +/** + * Checks if a block has a parent in a list of client IDs, and if so returns the client ID of the parent. + * + * @param {Object} state The current state object. + * @param {string} clientId The client ID of the block to search the parents of. + * @param {Array} clientIds The client IDs of the blocks to check. + * + * @return {string|undefined} The client ID of the parent block if found, undefined otherwise. + */ +function findParentInClientIdsList( state, clientId, clientIds ) { + if ( ! clientIds.length ) { + return; + } + + let parent = state.blocks.parents.get( clientId ); + while ( parent !== undefined ) { + if ( clientIds.includes( parent ) ) { + return parent; + } + parent = state.blocks.parents.get( parent ); + } +} + +/** + * Checks if a block has any bindings in its metadata attributes. + * + * @param {Object} block The block object to check for bindings. + * @return {boolean} True if the block has bindings, false otherwise. + */ +function hasBindings( block ) { + return ( + block?.attributes?.metadata?.bindings && + Object.keys( block?.attributes?.metadata?.bindings ).length + ); +} + +/** + * Computes and returns derived block editing modes for a given block tree. + * + * This function calculates the editing modes for each block in the tree, taking into account + * various factors such as zoom level, navigation mode, sections, and synced patterns. + * + * @param {Object} state The current state object. + * @param {boolean} isNavMode Whether the navigation mode is active. + * @param {string} treeClientId The client ID of the root block for the tree. Defaults to an empty string. + * @return {Map} A Map containing the derived block editing modes, keyed by block client ID. + */ +function getDerivedBlockEditingModesForTree( + state, + isNavMode = false, + treeClientId = '' +) { + const isZoomedOut = + state?.zoomLevel < 100 || state?.zoomLevel === 'auto-scaled'; + const derivedBlockEditingModes = new Map(); + + // When there are sections, the majority of blocks are disabled, + // so the default block editing mode is set to disabled. + const sectionRootClientId = state.settings?.[ sectionRootClientIdKey ]; + const sectionClientIds = state.blocks.order.get( sectionRootClientId ); + const hasDisabledBlocks = Array.from( state.blockEditingModes ).some( + ( [ , mode ] ) => mode === 'disabled' + ); + const templatePartClientIds = []; + const syncedPatternClientIds = []; + + Object.keys( state.blocks.controlledInnerBlocks ).forEach( ( clientId ) => { + const block = state.blocks.byClientId?.get( clientId ); + + if ( block?.name === 'core/template-part' ) { + templatePartClientIds.push( clientId ); + } + + if ( block?.name === 'core/block' ) { + syncedPatternClientIds.push( clientId ); + } + } ); + + traverseBlockTree( state, treeClientId, ( block ) => { + const { clientId, name: blockName } = block; + + // If the block already has an explicit block editing mode set, + // don't override it. + if ( state.blockEditingModes.has( clientId ) ) { + return; + } + + // Disabled explicit block editing modes are inherited by children. + // It's an expensive calculation, so only do it if there are disabled blocks. + if ( hasDisabledBlocks ) { + // Look through parents to find one with an explicit block editing mode. + let ancestorBlockEditingMode; + let parent = state.blocks.parents.get( clientId ); + while ( parent !== undefined ) { + // There's a chance we only just calculated this for the parent, + // if so we can return that value for a faster lookup. + if ( derivedBlockEditingModes.has( parent ) ) { + ancestorBlockEditingMode = + derivedBlockEditingModes.get( parent ); + } else if ( state.blockEditingModes.has( parent ) ) { + // Checking the explicit block editing mode will be slower, + // as the block editing mode is more likely to be set on a + // distant ancestor. + ancestorBlockEditingMode = + state.blockEditingModes.get( parent ); + } + if ( ancestorBlockEditingMode ) { + break; + } + parent = state.blocks.parents.get( parent ); + } + + // If the ancestor block editing mode is disabled, it's inherited by the child. + if ( ancestorBlockEditingMode === 'disabled' ) { + derivedBlockEditingModes.set( clientId, 'disabled' ); + return; + } + } + + if ( isZoomedOut || isNavMode ) { + // If the root block is the section root set its editing mode to contentOnly. + if ( clientId === sectionRootClientId ) { + derivedBlockEditingModes.set( clientId, 'contentOnly' ); + return; + } + + // There are no sections, so everything else is disabled. + if ( ! sectionClientIds?.length ) { + derivedBlockEditingModes.set( clientId, 'disabled' ); + return; + } + + if ( sectionClientIds.includes( clientId ) ) { + derivedBlockEditingModes.set( clientId, 'contentOnly' ); + return; + } + + // If zoomed out, all blocks that aren't sections or the section root are + // disabled. + if ( isZoomedOut ) { + derivedBlockEditingModes.set( clientId, 'disabled' ); + return; + } + + const isInSection = !! findParentInClientIdsList( + state, + clientId, + sectionClientIds + ); + if ( ! isInSection ) { + if ( clientId === '' ) { + derivedBlockEditingModes.set( clientId, 'disabled' ); + return; + } + + // Allow selection of template parts outside of sections. + if ( blockName === 'core/template-part' ) { + derivedBlockEditingModes.set( clientId, 'contentOnly' ); + return; + } + + const isInTemplatePart = !! findParentInClientIdsList( + state, + clientId, + templatePartClientIds + ); + // Allow contentOnly blocks in template parts outside of sections + // to be editable. Only disable blocks that don't fit this criteria. + if ( ! isInTemplatePart && ! isContentBlock( blockName ) ) { + derivedBlockEditingModes.set( clientId, 'disabled' ); + return; + } + } + + // Handle synced pattern content so the inner blocks of a synced pattern are + // properly disabled. + if ( syncedPatternClientIds.length ) { + const parentPatternClientId = findParentInClientIdsList( + state, + clientId, + syncedPatternClientIds + ); + + if ( parentPatternClientId ) { + // This is a pattern nested in another pattern, it should be disabled. + if ( + findParentInClientIdsList( + state, + parentPatternClientId, + syncedPatternClientIds + ) + ) { + derivedBlockEditingModes.set( clientId, 'disabled' ); + return; + } + + if ( hasBindings( block ) ) { + derivedBlockEditingModes.set( clientId, 'contentOnly' ); + return; + } + + // Synced pattern content without a binding isn't editable + // from the instance, the user has to edit the pattern source, + // so return 'disabled'. + derivedBlockEditingModes.set( clientId, 'disabled' ); + return; + } + } + + if ( blockName && isContentBlock( blockName ) ) { + derivedBlockEditingModes.set( clientId, 'contentOnly' ); + return; + } + + derivedBlockEditingModes.set( clientId, 'disabled' ); + return; + } + + if ( syncedPatternClientIds.length ) { + // Synced pattern blocks (core/block). + if ( syncedPatternClientIds.includes( clientId ) ) { + // This is a pattern nested in another pattern, it should be disabled. + if ( + findParentInClientIdsList( + state, + clientId, + syncedPatternClientIds + ) + ) { + derivedBlockEditingModes.set( clientId, 'disabled' ); + return; + } + + // Else do nothing, use the default block editing mode. + return; + } + + // Inner blocks of synced patterns. + const parentPatternClientId = findParentInClientIdsList( + state, + clientId, + syncedPatternClientIds + ); + if ( parentPatternClientId ) { + // This is a pattern nested in another pattern, it should be disabled. + if ( + findParentInClientIdsList( + state, + parentPatternClientId, + syncedPatternClientIds + ) + ) { + derivedBlockEditingModes.set( clientId, 'disabled' ); + return; + } + + if ( hasBindings( block ) ) { + derivedBlockEditingModes.set( clientId, 'contentOnly' ); + return; + } + + // Synced pattern content without a binding isn't editable + // from the instance, the user has to edit the pattern source, + // so return 'disabled'. + derivedBlockEditingModes.set( clientId, 'disabled' ); + } + } + } ); + + return derivedBlockEditingModes; +} + +/** + * Updates the derived block editing modes based on added and removed blocks. + * + * This function handles the updating of block editing modes when blocks are added, + * removed, or moved within the editor. + * + * It only returns a value when modifications are made to the block editing modes. + * + * @param {Object} options The options for updating derived block editing modes. + * @param {Object} options.prevState The previous state object. + * @param {Object} options.nextState The next state object. + * @param {Array} [options.addedBlocks] An array of blocks that were added. + * @param {Array} [options.removedClientIds] An array of client IDs of blocks that were removed. + * @param {boolean} [options.isNavMode] Whether the navigation mode is active. + * @return {Map|undefined} The updated derived block editing modes, or undefined if no changes were made. + */ +function getDerivedBlockEditingModesUpdates( { + prevState, + nextState, + addedBlocks, + removedClientIds, + isNavMode = false, +} ) { + const prevDerivedBlockEditingModes = isNavMode + ? prevState.derivedNavModeBlockEditingModes + : prevState.derivedBlockEditingModes; + let nextDerivedBlockEditingModes; + + // Perform removals before additions to handle cases like the `MOVE_BLOCKS_TO_POSITION` action. + // That action removes a set of clientIds, but adds the same blocks back in a different location. + // If removals were performed after additions, those moved clientIds would be removed incorrectly. + removedClientIds?.forEach( ( clientId ) => { + // The actions only receive parent block IDs for removal. + // Recurse through the block tree to ensure all blocks are removed. + // Specifically use the previous state, before the blocks were removed. + traverseBlockTree( prevState, clientId, ( block ) => { + if ( prevDerivedBlockEditingModes.has( block.clientId ) ) { + if ( ! nextDerivedBlockEditingModes ) { + nextDerivedBlockEditingModes = new Map( + prevDerivedBlockEditingModes + ); + } + nextDerivedBlockEditingModes.delete( block.clientId ); + } + } ); + } ); + + addedBlocks?.forEach( ( addedBlock ) => { + traverseBlockTree( nextState, addedBlock.clientId, ( block ) => { + const updates = getDerivedBlockEditingModesForTree( + nextState, + isNavMode, + block.clientId + ); + + if ( updates.size ) { + if ( ! nextDerivedBlockEditingModes ) { + nextDerivedBlockEditingModes = new Map( [ + ...( prevDerivedBlockEditingModes?.size + ? prevDerivedBlockEditingModes + : [] ), + ...updates, + ] ); + } else { + nextDerivedBlockEditingModes = new Map( [ + ...( nextDerivedBlockEditingModes?.size + ? nextDerivedBlockEditingModes + : [] ), + ...updates, + ] ); + } + } + } ); + } ); + + return nextDerivedBlockEditingModes; +} + +/** + * Higher-order reducer that adds derived block editing modes to the state. + * + * This function wraps a reducer and enhances it to handle actions that affect + * block editing modes. It updates the derivedBlockEditingModes in the state + * based on various actions such as adding, removing, or moving blocks, or changing + * the editor mode. + * + * @param {Function} reducer The original reducer function to be wrapped. + * @return {Function} A new reducer function that includes derived block editing modes handling. + */ +export function withDerivedBlockEditingModes( reducer ) { + return ( state, action ) => { + const nextState = reducer( state, action ); + + // An exception is needed here to still recompute the block editing modes when + // the editor mode changes since the editor mode isn't stored within the + // block editor state and changing it won't trigger an altered new state. + if ( action.type !== 'SET_EDITOR_MODE' && nextState === state ) { + return state; + } + + switch ( action.type ) { + case 'REMOVE_BLOCKS': { + const nextDerivedBlockEditingModes = + getDerivedBlockEditingModesUpdates( { + prevState: state, + nextState, + removedClientIds: action.clientIds, + isNavMode: false, + } ); + const nextDerivedNavModeBlockEditingModes = + getDerivedBlockEditingModesUpdates( { + prevState: state, + nextState, + removedClientIds: action.clientIds, + isNavMode: true, + } ); + + if ( + nextDerivedBlockEditingModes || + nextDerivedNavModeBlockEditingModes + ) { + return { + ...nextState, + derivedBlockEditingModes: + nextDerivedBlockEditingModes ?? + state.derivedBlockEditingModes, + derivedNavModeBlockEditingModes: + nextDerivedNavModeBlockEditingModes ?? + state.derivedNavModeBlockEditingModes, + }; + } + break; + } + case 'RECEIVE_BLOCKS': + case 'INSERT_BLOCKS': { + const nextDerivedBlockEditingModes = + getDerivedBlockEditingModesUpdates( { + prevState: state, + nextState, + addedBlocks: action.blocks, + isNavMode: false, + } ); + const nextDerivedNavModeBlockEditingModes = + getDerivedBlockEditingModesUpdates( { + prevState: state, + nextState, + addedBlocks: action.blocks, + isNavMode: true, + } ); + + if ( + nextDerivedBlockEditingModes || + nextDerivedNavModeBlockEditingModes + ) { + return { + ...nextState, + derivedBlockEditingModes: + nextDerivedBlockEditingModes ?? + state.derivedBlockEditingModes, + derivedNavModeBlockEditingModes: + nextDerivedNavModeBlockEditingModes ?? + state.derivedNavModeBlockEditingModes, + }; + } + break; + } + case 'SET_BLOCK_EDITING_MODE': + case 'UNSET_BLOCK_EDITING_MODE': + case 'SET_HAS_CONTROLLED_INNER_BLOCKS': { + const updatedBlock = getBlockTreeBlock( + nextState, + action.clientId + ); + + // The block might have been removed in which case it'll be + // handled by the `REMOVE_BLOCKS` action. + if ( ! updatedBlock ) { + break; + } + + const nextDerivedBlockEditingModes = + getDerivedBlockEditingModesUpdates( { + prevState: state, + nextState, + removedClientIds: [ action.clientId ], + addedBlocks: [ updatedBlock ], + isNavMode: false, + } ); + const nextDerivedNavModeBlockEditingModes = + getDerivedBlockEditingModesUpdates( { + prevState: state, + nextState, + removedClientIds: [ action.clientId ], + addedBlocks: [ updatedBlock ], + isNavMode: true, + } ); + + if ( + nextDerivedBlockEditingModes || + nextDerivedNavModeBlockEditingModes + ) { + return { + ...nextState, + derivedBlockEditingModes: + nextDerivedBlockEditingModes ?? + state.derivedBlockEditingModes, + derivedNavModeBlockEditingModes: + nextDerivedNavModeBlockEditingModes ?? + state.derivedNavModeBlockEditingModes, + }; + } + break; + } + case 'REPLACE_BLOCKS': { + const nextDerivedBlockEditingModes = + getDerivedBlockEditingModesUpdates( { + prevState: state, + nextState, + addedBlocks: action.blocks, + removedClientIds: action.clientIds, + isNavMode: false, + } ); + const nextDerivedNavModeBlockEditingModes = + getDerivedBlockEditingModesUpdates( { + prevState: state, + nextState, + addedBlocks: action.blocks, + removedClientIds: action.clientIds, + isNavMode: true, + } ); + + if ( + nextDerivedBlockEditingModes || + nextDerivedNavModeBlockEditingModes + ) { + return { + ...nextState, + derivedBlockEditingModes: + nextDerivedBlockEditingModes ?? + state.derivedBlockEditingModes, + derivedNavModeBlockEditingModes: + nextDerivedNavModeBlockEditingModes ?? + state.derivedNavModeBlockEditingModes, + }; + } + break; + } + case 'REPLACE_INNER_BLOCKS': { + // Get the clientIds of the blocks that are being replaced + // from the old state, before they were removed. + const removedClientIds = state.blocks.order.get( + action.rootClientId + ); + const nextDerivedBlockEditingModes = + getDerivedBlockEditingModesUpdates( { + prevState: state, + nextState, + addedBlocks: action.blocks, + removedClientIds, + isNavMode: false, + } ); + const nextDerivedNavModeBlockEditingModes = + getDerivedBlockEditingModesUpdates( { + prevState: state, + nextState, + addedBlocks: action.blocks, + removedClientIds, + isNavMode: true, + } ); + + if ( + nextDerivedBlockEditingModes || + nextDerivedNavModeBlockEditingModes + ) { + return { + ...nextState, + derivedBlockEditingModes: + nextDerivedBlockEditingModes ?? + state.derivedBlockEditingModes, + derivedNavModeBlockEditingModes: + nextDerivedNavModeBlockEditingModes ?? + state.derivedNavModeBlockEditingModes, + }; + } + break; + } + case 'MOVE_BLOCKS_TO_POSITION': { + const addedBlocks = action.clientIds.map( ( clientId ) => { + return nextState.blocks.byClientId.get( clientId ); + } ); + const nextDerivedBlockEditingModes = + getDerivedBlockEditingModesUpdates( { + prevState: state, + nextState, + addedBlocks, + removedClientIds: action.clientIds, + isNavMode: false, + } ); + const nextDerivedNavModeBlockEditingModes = + getDerivedBlockEditingModesUpdates( { + prevState: state, + nextState, + addedBlocks, + removedClientIds: action.clientIds, + isNavMode: true, + } ); + + if ( + nextDerivedBlockEditingModes || + nextDerivedNavModeBlockEditingModes + ) { + return { + ...nextState, + derivedBlockEditingModes: + nextDerivedBlockEditingModes ?? + state.derivedBlockEditingModes, + derivedNavModeBlockEditingModes: + nextDerivedNavModeBlockEditingModes ?? + state.derivedNavModeBlockEditingModes, + }; + } + break; + } + case 'UPDATE_SETTINGS': { + // Recompute the entire tree if the section root changes. + if ( + state?.settings?.[ sectionRootClientIdKey ] !== + nextState?.settings?.[ sectionRootClientIdKey ] + ) { + return { + ...nextState, + derivedBlockEditingModes: + getDerivedBlockEditingModesForTree( + nextState, + false /* Nav mode off */ + ), + derivedNavModeBlockEditingModes: + getDerivedBlockEditingModesForTree( + nextState, + true /* Nav mode on */ + ), + }; + } + break; + } + case 'RESET_BLOCKS': + case 'SET_EDITOR_MODE': + case 'RESET_ZOOM_LEVEL': + case 'SET_ZOOM_LEVEL': { + // Recompute the entire tree if the editor mode or zoom level changes, + // or if all the blocks are reset. + return { + ...nextState, + derivedBlockEditingModes: + getDerivedBlockEditingModesForTree( + nextState, + false /* Nav mode off */ + ), + derivedNavModeBlockEditingModes: + getDerivedBlockEditingModesForTree( + nextState, + true /* Nav mode on */ + ), + }; + } + } + + // If there's no change, the derivedBlockEditingModes from the previous + // state need to be preserved. + nextState.derivedBlockEditingModes = + state?.derivedBlockEditingModes ?? new Map(); + nextState.derivedNavModeBlockEditingModes = + state?.derivedNavModeBlockEditingModes ?? new Map(); + + return nextState; + }; +} + function withAutomaticChangeReset( reducer ) { return ( state, action ) => { const nextState = reducer( state, action ); @@ -2184,4 +2911,7 @@ function withAutomaticChangeReset( reducer ) { }; } -export default withAutomaticChangeReset( combinedReducers ); +export default pipe( + withDerivedBlockEditingModes, + withAutomaticChangeReset +)( combinedReducers ); diff --git a/packages/block-editor/src/store/selectors.js b/packages/block-editor/src/store/selectors.js index 598b6b4ea480de..22d725bbcd65de 100644 --- a/packages/block-editor/src/store/selectors.js +++ b/packages/block-editor/src/store/selectors.js @@ -128,7 +128,7 @@ export function isBlockValid( state, clientId ) { * @param {Object} state Editor state. * @param {string} clientId Block client ID. * - * @return {Object?} Block attributes. + * @return {?Object} Block attributes. */ export function getBlockAttributes( state, clientId ) { const block = state.blocks.byClientId.get( clientId ); @@ -1586,14 +1586,14 @@ export function getTemplateLock( state, rootClientId ) { * @param {string|Object} blockNameOrType The block type object, e.g., the response * from the block directory; or a string name of * an installed block type, e.g.' core/paragraph'. - * @param {Set} checkedBlocks Set of block names that have already been checked. + * @param {?string} rootClientId Optional root client ID of block list. * * @return {boolean} Whether the given block type is allowed to be inserted. */ const isBlockVisibleInTheInserter = ( state, blockNameOrType, - checkedBlocks = new Set() + rootClientId = null ) => { let blockType; let blockName; @@ -1621,26 +1621,19 @@ const isBlockVisibleInTheInserter = ( return false; } - if ( checkedBlocks.has( blockName ) ) { - return false; - } - - checkedBlocks.add( blockName ); - // If parent blocks are not visible, child blocks should be hidden too. - if ( Array.isArray( blockType.parent ) ) { - return blockType.parent.some( - ( name ) => - ( blockName !== name && - isBlockVisibleInTheInserter( - state, - name, - checkedBlocks - ) ) || - // Exception for blocks with post-content parent, - // the root level is often consider as "core/post-content". - // This exception should only apply to the post editor ideally though. - name === 'core/post-content' + const parents = ( + Array.isArray( blockType.parent ) ? blockType.parent : [] + ).concat( Array.isArray( blockType.ancestor ) ? blockType.ancestor : [] ); + if ( parents.length > 0 ) { + const rootBlockName = getBlockName( state, rootClientId ); + // This is an exception to the rule that says that all blocks are visible in the inserter. + // Blocks that require a given parent or ancestor are only visible if we're within that parent. + return ( + parents.includes( 'core/post-content' ) || + parents.includes( rootBlockName ) || + getBlockParentsByBlockName( state, rootClientId, parents ).length > + 0 ); } @@ -1665,7 +1658,7 @@ const canInsertBlockTypeUnmemoized = ( blockName, rootClientId = null ) => { - if ( ! isBlockVisibleInTheInserter( state, blockName ) ) { + if ( ! isBlockVisibleInTheInserter( state, blockName, rootClientId ) ) { return false; } @@ -1965,7 +1958,7 @@ const canIncludeBlockTypeInInserter = ( state, blockType, rootClientId ) => { }; /** - * Return a function to be used to tranform a block variation to an inserter item + * Return a function to be used to transform a block variation to an inserter item * * @param {Object} state Global State * @param {Object} item Denormalized inserter item @@ -1999,7 +1992,7 @@ const getItemFromVariation = ( state, item ) => ( variation ) => { * Returns the calculated frecency. * * 'frecency' is a heuristic (https://en.wikipedia.org/wiki/Frecency) - * that combines block usage frequenty and recency. + * that combines block usage frequency and recency. * * @param {number} time When the last insert occurred as a UNIX epoch * @param {number} count The number of inserts that have occurred. @@ -2028,7 +2021,7 @@ const calculateFrecency = ( time, count ) => { /** * Returns a function that accepts a block type and builds an item to be shown * in a specific context. It's used for building items for Inserter and available - * block Transfroms list. + * block Transforms list. * * @param {Object} state Editor state. * @param {Object} options Options object for handling the building of a block type. @@ -2072,6 +2065,7 @@ const buildBlockTypeItem = category: blockType.category, keywords: blockType.keywords, parent: blockType.parent, + ancestor: blockType.ancestor, variations: inserterVariations, example: blockType.example, utility: 1, // Deprecated. @@ -2086,7 +2080,7 @@ const buildBlockTypeItem = * inserter and handle its selection. * * The 'frecency' property is a heuristic (https://en.wikipedia.org/wiki/Frecency) - * that combines block usage frequenty and recency. + * that combines block usage frequency and recency. * * Items are returned ordered descendingly by their 'utility' and 'frecency'. * @@ -2169,7 +2163,11 @@ export const getInserterItems = createRegistrySelector( ( select ) => } else { blockTypeInserterItems = blockTypeInserterItems .filter( ( blockType ) => - isBlockVisibleInTheInserter( state, blockType ) + isBlockVisibleInTheInserter( + state, + blockType, + rootClientId + ) ) .map( ( blockType ) => ( { ...blockType, @@ -2238,7 +2236,7 @@ export const getInserterItems = createRegistrySelector( ( select ) => * transform list and handle its selection. * * The 'frecency' property is a heuristic (https://en.wikipedia.org/wiki/Frecency) - * that combines block usage frequenty and recency. + * that combines block usage frequency and recency. * * Items are returned ordered descendingly by their 'frecency'. * @@ -2402,7 +2400,7 @@ export const __experimentalGetAllowedBlocks = createSelector( * @typedef {Object} WPDirectInsertBlock * @property {string} name The type of block. * @property {?Object} attributes Attributes to pass to the newly created block. - * @property {?Array<string>} attributesToCopy Attributes to be copied from adjecent blocks when inserted. + * @property {?Array<string>} attributesToCopy Attributes to be copied from adjacent blocks when inserted. */ export function getDirectInsertBlock( state, rootClientId = null ) { if ( ! rootClientId ) { @@ -2501,7 +2499,11 @@ export const __experimentalGetAllowedPatterns = createRegistrySelector( name, rootClientId ) - : isBlockVisibleInTheInserter( state, name ) + : isBlockVisibleInTheInserter( + state, + name, + rootClientId + ) ) ); @@ -2520,7 +2522,7 @@ export const __experimentalGetAllowedPatterns = createRegistrySelector( * or blocks transformations. * * @param {Object} state Editor state. - * @param {string|string[]} blockNames Block's name or array of block names to find matching pattens. + * @param {string|string[]} blockNames Block's name or array of block names to find matching patterns. * @param {?string} rootClientId Optional target root client ID. * * @return {Array} The list of matched block patterns based on declared `blockTypes` and block name. @@ -2774,6 +2776,9 @@ export function isNavigationMode( state ) { */ export const __unstableGetEditorMode = createRegistrySelector( ( select ) => ( state ) => { + if ( ! window?.__experimentalEditorWriteMode ) { + return 'edit'; + } return ( state.settings.editorTool ?? select( preferencesStore ).get( 'core', 'editorTool' ) @@ -2936,7 +2941,7 @@ export const __unstableGetVisibleBlocks = createSelector( ); export function __unstableHasActiveBlockOverlayActive( state, clientId ) { - // Prevent overlay on blocks with a non-default editing mode. If the mdoe is + // Prevent overlay on blocks with a non-default editing mode. If the mode is // 'disabled' then the overlay is redundant since the block can't be // selected. If the mode is 'contentOnly' then the overlay is redundant // since there will be no controls to interact with once selected. @@ -2997,14 +3002,6 @@ export function __unstableIsWithinBlockOverlay( state, clientId ) { return false; } -function isWithinBlock( state, clientId, parentClientId ) { - let parent = state.blocks.parents.get( clientId ); - while ( !! parent && parent !== parentClientId ) { - parent = state.blocks.parents.get( parent ); - } - return parent === parentClientId; -} - /** * @typedef {import('../components/block-editing-mode').BlockEditingMode} BlockEditingMode */ @@ -3046,78 +3043,36 @@ export const getBlockEditingMode = createRegistrySelector( clientId = ''; } - // In zoom-out mode, override the behavior set by - // __unstableSetBlockEditingMode to only allow editing the top-level - // sections. - if ( isZoomOut( state ) ) { - const sectionRootClientId = getSectionRootClientId( state ); - - if ( clientId === '' /* ROOT_CONTAINER_CLIENT_ID */ ) { - return sectionRootClientId ? 'disabled' : 'contentOnly'; - } - if ( clientId === sectionRootClientId ) { - return 'contentOnly'; - } - const sectionsClientIds = getBlockOrder( - state, - sectionRootClientId - ); - - // Sections are always contentOnly. - if ( sectionsClientIds?.includes( clientId ) ) { - return 'contentOnly'; - } - - return 'disabled'; + const isNavMode = isNavigationMode( state ); + + // If the editor is currently not in navigation mode, check if the clientId + // has an editing mode set in the regular derived map. + // There may be an editing mode set here for synced patterns or in zoomed out + // mode. + if ( + ! isNavMode && + state.derivedBlockEditingModes?.has( clientId ) + ) { + return state.derivedBlockEditingModes.get( clientId ); } - const editorMode = __unstableGetEditorMode( state ); - if ( editorMode === 'navigation' ) { - const sectionRootClientId = getSectionRootClientId( state ); - - // The root section is "default mode" - if ( clientId === sectionRootClientId ) { - return 'default'; - } - - // Sections should always be contentOnly in navigation mode. - const sectionsClientIds = getBlockOrder( - state, - sectionRootClientId - ); - if ( sectionsClientIds.includes( clientId ) ) { - return 'contentOnly'; - } - - // Blocks outside sections should be disabled. - const isWithinSectionRoot = isWithinBlock( - state, - clientId, - sectionRootClientId - ); - if ( ! isWithinSectionRoot ) { - return 'disabled'; - } - - // The rest of the blocks depend on whether they are content blocks or not. - // This "flattens" the sections tree. - const name = getBlockName( state, clientId ); - const { hasContentRoleAttribute } = unlock( - select( blocksStore ) - ); - const isContent = hasContentRoleAttribute( name ); - - return isContent ? 'contentOnly' : 'disabled'; + // If the editor *is* in navigation mode, the block editing mode states + // are stored in the derivedNavModeBlockEditingModes map. + if ( + isNavMode && + state.derivedNavModeBlockEditingModes?.has( clientId ) + ) { + return state.derivedNavModeBlockEditingModes.get( clientId ); } - // In normal mode, consider that an explicitely set editing mode takes over. + // In normal mode, consider that an explicitly set editing mode takes over. const blockEditingMode = state.blockEditingModes.get( clientId ); if ( blockEditingMode ) { return blockEditingMode; } // In normal mode, top level is default mode. - if ( ! clientId ) { + if ( clientId === '' ) { return 'default'; } @@ -3132,9 +3087,7 @@ export const getBlockEditingMode = createRegistrySelector( const isContent = hasContentRoleAttribute( name ); return isContent ? 'contentOnly' : 'disabled'; } - // Otherwise, check if there's an ancestor that is contentOnly - const parentMode = getBlockEditingMode( state, rootClientId ); - return parentMode === 'contentOnly' ? 'default' : parentMode; + return 'default'; } ); diff --git a/packages/block-editor/src/store/test/private-selectors.js b/packages/block-editor/src/store/test/private-selectors.js index fb1d736e175af0..bf0e18c7a24d6a 100644 --- a/packages/block-editor/src/store/test/private-selectors.js +++ b/packages/block-editor/src/store/test/private-selectors.js @@ -122,6 +122,7 @@ describe( 'private selectors', () => { '9b9c5c3f-2e46-4f02-9e14-9fe9515b958f': {}, }, blockEditingModes: new Map( [] ), + derivedBlockEditingModes: new Map( [] ), }; const hasContentRoleAttribute = jest.fn( () => false ); @@ -129,6 +130,7 @@ describe( 'private selectors', () => { getBlockEditingMode.registry = { select: jest.fn( () => ( { hasContentRoleAttribute, + get, } ) ), }; __unstableGetEditorMode.registry = { @@ -141,6 +143,7 @@ describe( 'private selectors', () => { const state = { ...baseState, blockEditingModes: new Map( [] ), + derivedBlockEditingModes: new Map( [] ), }; expect( isBlockSubtreeDisabled( @@ -156,6 +159,12 @@ describe( 'private selectors', () => { blockEditingModes: new Map( [ [ 'ef45d5fd-5234-4fd5-ac4f-c3736c7f9337', 'disabled' ], ] ), + derivedBlockEditingModes: new Map( [ + [ 'b26fc763-417d-4f01-b81c-2ec61e14a972', 'disabled' ], + [ '9b9c5c3f-2e46-4f02-9e14-9fe9515b958f', 'disabled' ], + [ 'b3247f75-fd94-4fef-97f9-5bfd162cc416', 'disabled' ], + [ 'e178812d-ce5e-48c7-a945-8ae4ffcbbb7c', 'disabled' ], + ] ), }; expect( isBlockSubtreeDisabled( @@ -165,10 +174,18 @@ describe( 'private selectors', () => { ).toBe( true ); } ); - it( 'should return true when top level block is disabled via inheritence and there are no editing modes within it', () => { + it( 'should return true when top level block is disabled via inheritance and there are no editing modes within it', () => { const state = { ...baseState, blockEditingModes: new Map( [ [ '', 'disabled' ] ] ), + derivedBlockEditingModes: new Map( [ + [ '6cf70164-9097-4460-bcbf-200560546988', 'disabled' ], + [ 'ef45d5fd-5234-4fd5-ac4f-c3736c7f9337', 'disabled' ], + [ 'b26fc763-417d-4f01-b81c-2ec61e14a972', 'disabled' ], + [ '9b9c5c3f-2e46-4f02-9e14-9fe9515b958f', 'disabled' ], + [ 'b3247f75-fd94-4fef-97f9-5bfd162cc416', 'disabled' ], + [ 'e178812d-ce5e-48c7-a945-8ae4ffcbbb7c', 'disabled' ], + ] ), }; expect( isBlockSubtreeDisabled( @@ -185,6 +202,11 @@ describe( 'private selectors', () => { [ 'ef45d5fd-5234-4fd5-ac4f-c3736c7f9337', 'disabled' ], [ 'b3247f75-fd94-4fef-97f9-5bfd162cc416', 'disabled' ], ] ), + derivedBlockEditingModes: new Map( [ + [ 'b26fc763-417d-4f01-b81c-2ec61e14a972', 'disabled' ], + [ '9b9c5c3f-2e46-4f02-9e14-9fe9515b958f', 'disabled' ], + [ 'e178812d-ce5e-48c7-a945-8ae4ffcbbb7c', 'disabled' ], + ] ), }; expect( isBlockSubtreeDisabled( @@ -201,6 +223,11 @@ describe( 'private selectors', () => { [ 'ef45d5fd-5234-4fd5-ac4f-c3736c7f9337', 'disabled' ], [ 'b3247f75-fd94-4fef-97f9-5bfd162cc416', 'default' ], ] ), + derivedBlockEditingModes: new Map( [ + [ 'b26fc763-417d-4f01-b81c-2ec61e14a972', 'disabled' ], + [ '9b9c5c3f-2e46-4f02-9e14-9fe9515b958f', 'disabled' ], + [ 'e178812d-ce5e-48c7-a945-8ae4ffcbbb7c', 'disabled' ], + ] ), }; expect( isBlockSubtreeDisabled( @@ -210,13 +237,20 @@ describe( 'private selectors', () => { ).toBe( false ); } ); - it( 'should return false when top level block is disabled via inheritence and there are non-disabled editing modes within it', () => { + it( 'should return false when top level block is disabled via inheritance and there are non-disabled editing modes within it', () => { const state = { ...baseState, blockEditingModes: new Map( [ [ '', 'disabled' ], [ 'b3247f75-fd94-4fef-97f9-5bfd162cc416', 'default' ], ] ), + derivedBlockEditingModes: new Map( [ + [ '6cf70164-9097-4460-bcbf-200560546988', 'disabled' ], + [ 'ef45d5fd-5234-4fd5-ac4f-c3736c7f9337', 'disabled' ], + [ 'b26fc763-417d-4f01-b81c-2ec61e14a972', 'disabled' ], + [ '9b9c5c3f-2e46-4f02-9e14-9fe9515b958f', 'disabled' ], + [ 'e178812d-ce5e-48c7-a945-8ae4ffcbbb7c', 'disabled' ], + ] ), }; expect( isBlockSubtreeDisabled( @@ -302,6 +336,7 @@ describe( 'private selectors', () => { const state = { ...baseState, blockEditingModes: new Map( [] ), + derivedBlockEditingModes: new Map( [] ), }; expect( getEnabledClientIdsTree( state ) ).toEqual( [ { @@ -339,6 +374,7 @@ describe( 'private selectors', () => { const state = { ...baseState, blockEditingModes: new Map( [] ), + derivedBlockEditingModes: new Map( [] ), }; expect( getEnabledClientIdsTree( @@ -374,6 +410,10 @@ describe( 'private selectors', () => { [ 'b26fc763-417d-4f01-b81c-2ec61e14a972', 'contentOnly' ], [ '9b9c5c3f-2e46-4f02-9e14-9fe9515b958f', 'contentOnly' ], ] ), + derivedBlockEditingModes: new Map( [ + [ '6cf70164-9097-4460-bcbf-200560546988', 'disabled' ], + [ 'ef45d5fd-5234-4fd5-ac4f-c3736c7f9337', 'disabled' ], + ] ), }; expect( getEnabledClientIdsTree( state ) ).toEqual( [ { @@ -411,6 +451,7 @@ describe( 'private selectors', () => { ] ), }, blockEditingModes: new Map(), + derivedBlockEditingModes: new Map(), }; expect( getEnabledBlockParents( @@ -432,7 +473,7 @@ describe( 'private selectors', () => { ], [ 'e178812d-ce5e-48c7-a945-8ae4ffcbbb7c', - '9b9c5c3f-2e46-4f02-9e14-9fe9515b958f', + 'ef45d5fd-5234-4fd5-ac4f-c3736c7f9337', ], [ '4c2b7140-fffd-44b4-b2a7-820c670a6514', @@ -441,6 +482,7 @@ describe( 'private selectors', () => { ] ), order: new Map( [ + [ '', [ 'ef45d5fd-5234-4fd5-ac4f-c3736c7f9337' ] ], [ 'ef45d5fd-5234-4fd5-ac4f-c3736c7f9337', [ @@ -452,12 +494,15 @@ describe( 'private selectors', () => { 'e178812d-ce5e-48c7-a945-8ae4ffcbbb7c', [ '4c2b7140-fffd-44b4-b2a7-820c670a6514' ], ], - [ '', [ 'ef45d5fd-5234-4fd5-ac4f-c3736c7f9337' ] ], ] ), }, blockEditingModes: new Map( [ [ '', 'disabled' ], - [ '9b9c5c3f-2e46-4f02-9e14-9fe9515b958f', 'default' ], + [ 'e178812d-ce5e-48c7-a945-8ae4ffcbbb7c', 'default' ], + ] ), + derivedBlockEditingModes: new Map( [ + [ 'ef45d5fd-5234-4fd5-ac4f-c3736c7f9337', 'disabled' ], + [ '9b9c5c3f-2e46-4f02-9e14-9fe9515b958f', 'disabled' ], ] ), blockListSettings: {}, }; @@ -466,10 +511,7 @@ describe( 'private selectors', () => { state, '4c2b7140-fffd-44b4-b2a7-820c670a6514' ) - ).toEqual( [ - '9b9c5c3f-2e46-4f02-9e14-9fe9515b958f', - 'e178812d-ce5e-48c7-a945-8ae4ffcbbb7c', - ] ); + ).toEqual( [ 'e178812d-ce5e-48c7-a945-8ae4ffcbbb7c' ] ); } ); it( 'should order from bottom to top if ascending is true', () => { @@ -492,6 +534,7 @@ describe( 'private selectors', () => { ], ] ), order: new Map( [ + [ '', [ 'ef45d5fd-5234-4fd5-ac4f-c3736c7f9337' ] ], [ 'ef45d5fd-5234-4fd5-ac4f-c3736c7f9337', [ '9b9c5c3f-2e46-4f02-9e14-9fe9515b958f' ], @@ -504,13 +547,15 @@ describe( 'private selectors', () => { 'e178812d-ce5e-48c7-a945-8ae4ffcbbb7c', [ '4c2b7140-fffd-44b4-b2a7-820c670a6514' ], ], - [ '', [ 'ef45d5fd-5234-4fd5-ac4f-c3736c7f9337' ] ], ] ), }, blockEditingModes: new Map( [ [ '', 'disabled' ], [ '9b9c5c3f-2e46-4f02-9e14-9fe9515b958f', 'default' ], ] ), + derivedBlockEditingModes: new Map( [ + [ 'ef45d5fd-5234-4fd5-ac4f-c3736c7f9337', 'disabled' ], + ] ), blockListSettings: {}, }; expect( diff --git a/packages/block-editor/src/store/test/reducer.js b/packages/block-editor/src/store/test/reducer.js index c99d639ba8a09e..6706ff2fbb59e2 100644 --- a/packages/block-editor/src/store/test/reducer.js +++ b/packages/block-editor/src/store/test/reducer.js @@ -10,7 +10,9 @@ import { registerBlockType, unregisterBlockType, createBlock, + privateApis, } from '@wordpress/blocks'; +import { combineReducers } from '@wordpress/data'; /** * Internal dependencies @@ -38,8 +40,30 @@ import { blockEditingModes, openedBlockSettingsMenu, expandedBlock, + zoomLevel, + withDerivedBlockEditingModes, } from '../reducer'; +import { unlock } from '../../lock-unlock'; +import { sectionRootClientIdKey } from '.././private-keys'; + +const { isContentBlock } = unlock( privateApis ); + +jest.mock( '@wordpress/data/src/select', () => { + const actualSelect = jest.requireActual( '@wordpress/data/src/select' ); + + return { + select: jest.fn( ( ...args ) => actualSelect.select( ...args ) ), + }; +} ); + +jest.mock( '@wordpress/blocks/src/api/utils', () => { + return { + ...jest.requireActual( '@wordpress/blocks/src/api/utils' ), + isContentBlock: jest.fn(), + }; +} ); + const noop = () => {}; describe( 'state', () => { @@ -3544,4 +3568,835 @@ describe( 'state', () => { expect( state ).toBe( null ); } ); } ); + + describe( 'withDerivedBlockEditingModes', () => { + const testReducer = withDerivedBlockEditingModes( + combineReducers( { + blocks, + settings, + zoomLevel, + blockEditingModes, + } ) + ); + + function dispatchActions( actions, reducer, initialState = {} ) { + return actions.reduce( ( _state, action ) => { + return reducer( _state, action ); + }, initialState ); + } + + beforeEach( () => { + isContentBlock.mockImplementation( + ( blockName ) => blockName === 'core/paragraph' + ); + } ); + + afterAll( () => { + isContentBlock.mockRestore(); + } ); + + describe( 'edit mode', () => { + let initialState; + beforeAll( () => { + initialState = dispatchActions( + [ + { + type: 'UPDATE_SETTINGS', + settings: { + [ sectionRootClientIdKey ]: '', + }, + }, + { + type: 'RESET_BLOCKS', + blocks: [ + { + name: 'core/group', + clientId: 'group-1', + attributes: {}, + innerBlocks: [ + { + name: 'core/paragraph', + clientId: 'paragraph-1', + attributes: {}, + innerBlocks: [], + }, + { + name: 'core/group', + clientId: 'group-2', + attributes: {}, + innerBlocks: [ + { + name: 'core/paragraph', + clientId: 'paragraph-2', + attributes: {}, + innerBlocks: [], + }, + ], + }, + ], + }, + ], + }, + ], + testReducer + ); + } ); + + it( 'returns no block editing modes when zoomed out / navigation mode are not active and there are no synced patterns', () => { + expect( initialState.derivedBlockEditingModes ).toEqual( + new Map() + ); + } ); + } ); + + describe( 'synced patterns', () => { + let initialState; + beforeAll( () => { + // Simulates how the editor typically inserts controlled blocks, + // - first the pattern is inserted with no inner blocks. + // - next the pattern is marked as a controlled block. + // - finally, once the inner blocks of the pattern are received, they're inserted. + // This process is repeated for the two patterns in this test. + initialState = dispatchActions( + [ + { + type: 'UPDATE_SETTINGS', + settings: { + [ sectionRootClientIdKey ]: '', + }, + }, + { + type: 'RESET_BLOCKS', + blocks: [ + { + name: 'core/group', + clientId: 'group-1', + attributes: {}, + innerBlocks: [ + { + name: 'core/paragraph', + clientId: 'paragraph-1', + attributes: {}, + innerBlocks: [], + }, + { + name: 'core/group', + clientId: 'group-2', + attributes: {}, + innerBlocks: [ + { + name: 'core/paragraph', + clientId: 'paragraph-2', + attributes: {}, + innerBlocks: [], + }, + ], + }, + ], + }, + ], + }, + { + type: 'INSERT_BLOCKS', + rootClientId: '', + blocks: [ + { + name: 'core/block', + clientId: 'root-pattern', + attributes: {}, + innerBlocks: [], + }, + ], + }, + { + type: 'SET_HAS_CONTROLLED_INNER_BLOCKS', + clientId: 'root-pattern', + hasControlledInnerBlocks: true, + }, + { + type: 'REPLACE_INNER_BLOCKS', + rootClientId: 'root-pattern', + blocks: [ + { + name: 'core/block', + clientId: 'nested-pattern', + attributes: {}, + innerBlocks: [], + }, + { + name: 'core/paragraph', + clientId: 'pattern-paragraph', + attributes: {}, + innerBlocks: [], + }, + { + name: 'core/group', + clientId: 'pattern-group', + attributes: {}, + innerBlocks: [ + { + name: 'core/paragraph', + clientId: + 'pattern-paragraph-with-overrides', + attributes: { + metadata: { + bindings: { + __default: + 'core/pattern-overrides', + }, + }, + }, + innerBlocks: [], + }, + ], + }, + ], + }, + { + type: 'SET_HAS_CONTROLLED_INNER_BLOCKS', + clientId: 'nested-pattern', + hasControlledInnerBlocks: true, + }, + { + type: 'REPLACE_INNER_BLOCKS', + rootClientId: 'nested-pattern', + blocks: [ + { + name: 'core/paragraph', + clientId: 'nested-paragraph', + attributes: {}, + innerBlocks: [], + }, + { + name: 'core/group', + clientId: 'nested-group', + attributes: {}, + innerBlocks: [ + { + name: 'core/paragraph', + clientId: + 'nested-paragraph-with-overrides', + attributes: { + metadata: { + bindings: { + __default: + 'core/pattern-overrides', + }, + }, + }, + innerBlocks: [], + }, + ], + }, + ], + }, + ], + testReducer, + initialState + ); + } ); + + it( 'returns the expected block editing modes for synced patterns', () => { + // Only the parent pattern and its own children that have bindings + // are in contentOnly mode. All other blocks are disabled. + expect( initialState.derivedBlockEditingModes ).toEqual( + new Map( + Object.entries( { + 'pattern-paragraph': 'disabled', + 'pattern-group': 'disabled', + 'pattern-paragraph-with-overrides': 'contentOnly', + 'nested-pattern': 'disabled', + 'nested-paragraph': 'disabled', + 'nested-group': 'disabled', + 'nested-paragraph-with-overrides': 'disabled', + } ) + ) + ); + } ); + + it( 'returns the expected block editing modes for synced patterns in navigation mode', () => { + expect( initialState.derivedNavModeBlockEditingModes ).toEqual( + new Map( + Object.entries( { + '': 'contentOnly', // Section root. + 'group-1': 'contentOnly', // Section. + 'paragraph-1': 'contentOnly', // Content block in section. + 'group-2': 'disabled', + 'paragraph-2': 'contentOnly', // Content block in section. + 'root-pattern': 'contentOnly', // Section. + 'pattern-paragraph': 'disabled', + 'pattern-group': 'disabled', + 'pattern-paragraph-with-overrides': 'contentOnly', // Pattern child with bindings. + 'nested-pattern': 'disabled', + 'nested-paragraph': 'disabled', + 'nested-group': 'disabled', + 'nested-paragraph-with-overrides': 'disabled', + } ) + ) + ); + } ); + + it( 'removes block editing modes when synced patterns are removed', () => { + const { derivedBlockEditingModes } = dispatchActions( + [ + { + type: 'REMOVE_BLOCKS', + clientIds: [ 'root-pattern' ], + }, + ], + testReducer, + initialState + ); + + expect( derivedBlockEditingModes ).toEqual( new Map() ); + } ); + + it( 'returns the expected block editing modes for synced patterns when switching to zoomed out mode', () => { + const { derivedBlockEditingModes } = dispatchActions( + [ + { + type: 'SET_ZOOM_LEVEL', + zoom: 'auto-scaled', + }, + ], + testReducer, + initialState + ); + + expect( derivedBlockEditingModes ).toEqual( + new Map( + Object.entries( { + '': 'contentOnly', // Section root. + 'group-1': 'contentOnly', // Section. + 'paragraph-1': 'disabled', + 'group-2': 'disabled', + 'paragraph-2': 'disabled', + 'root-pattern': 'contentOnly', // Pattern and section. + 'pattern-paragraph': 'disabled', + 'pattern-group': 'disabled', + 'pattern-paragraph-with-overrides': 'disabled', + 'nested-pattern': 'disabled', + 'nested-paragraph': 'disabled', + 'nested-group': 'disabled', + 'nested-paragraph-with-overrides': 'disabled', + } ) + ) + ); + } ); + } ); + + describe( 'navigation mode', () => { + let initialState; + + beforeAll( () => { + initialState = dispatchActions( + [ + { + type: 'UPDATE_SETTINGS', + settings: { + [ sectionRootClientIdKey ]: 'section-root', + }, + }, + { + type: 'RESET_BLOCKS', + blocks: [ + { + name: 'core/template-part', + clientId: 'header', + attributes: {}, + innerBlocks: [], + }, + { + name: 'core/group', + clientId: 'section-root', + attributes: {}, + innerBlocks: [ + { + name: 'core/group', + clientId: 'group-1', + attributes: {}, + innerBlocks: [ + { + name: 'core/paragraph', + clientId: 'paragraph-1', + attributes: {}, + innerBlocks: [], + }, + { + name: 'core/group', + clientId: 'group-2', + attributes: {}, + innerBlocks: [ + { + name: 'core/paragraph', + clientId: + 'paragraph-2', + attributes: {}, + innerBlocks: [], + }, + ], + }, + ], + }, + ], + }, + { + name: 'core/template-part', + clientId: 'footer', + attributes: {}, + innerBlocks: [], + }, + ], + }, + { + type: 'SET_HAS_CONTROLLED_INNER_BLOCKS', + clientId: 'header', + hasControlledInnerBlocks: true, + }, + { + type: 'REPLACE_INNER_BLOCKS', + rootClientId: 'header', + blocks: [ + { + name: 'core/group', + clientId: 'header-group', + attributes: {}, + innerBlocks: [ + { + name: 'core/paragraph', + clientId: 'header-paragraph', + attributes: {}, + innerBlocks: [], + }, + ], + }, + ], + }, + { + type: 'SET_HAS_CONTROLLED_INNER_BLOCKS', + clientId: 'footer', + hasControlledInnerBlocks: true, + }, + { + type: 'REPLACE_INNER_BLOCKS', + rootClientId: 'footer', + blocks: [ + { + name: 'core/paragraph', + clientId: 'footer-paragraph', + attributes: {}, + innerBlocks: [], + }, + ], + }, + ], + testReducer + ); + } ); + + it( 'returns the expected block editing modes', () => { + expect( initialState.derivedNavModeBlockEditingModes ).toEqual( + new Map( + Object.entries( { + '': 'disabled', + header: 'contentOnly', // Template part. + 'header-group': 'disabled', // Content block in template part. + 'header-paragraph': 'contentOnly', // Content block in template part. + footer: 'contentOnly', // Template part. + 'footer-paragraph': 'contentOnly', // Content block in template part. + 'section-root': 'contentOnly', // Section root. + 'group-1': 'contentOnly', // Section block. + 'paragraph-1': 'contentOnly', // Content block in section. + 'group-2': 'disabled', // Non-content block in section. + 'paragraph-2': 'contentOnly', // Content block in section. + } ) + ) + ); + } ); + + it( 'allows content blocks to be disabled explicitly using the block editing mode', () => { + const { + derivedNavModeBlockEditingModes, + blockEditingModes: _blockEditingModes, + } = dispatchActions( + [ + { + type: 'SET_BLOCK_EDITING_MODE', + clientId: 'paragraph-1', + mode: 'disabled', + }, + ], + testReducer, + initialState + ); + + // Paragraph 1 is explicitly disabled and omitted from the + // derived block editing modes. + expect( _blockEditingModes ).toEqual( + new Map( + Object.entries( { + 'paragraph-1': 'disabled', + } ) + ) + ); + expect( derivedNavModeBlockEditingModes ).toEqual( + new Map( + Object.entries( { + '': 'disabled', + header: 'contentOnly', + 'header-group': 'disabled', + 'header-paragraph': 'contentOnly', + footer: 'contentOnly', + 'footer-paragraph': 'contentOnly', + 'section-root': 'contentOnly', + 'group-1': 'contentOnly', + 'group-2': 'disabled', + 'paragraph-2': 'contentOnly', + } ) + ) + ); + } ); + + it( 'removes block editing modes when blocks are removed', () => { + const { derivedNavModeBlockEditingModes } = dispatchActions( + [ + { + type: 'REMOVE_BLOCKS', + clientIds: [ 'group-2' ], + }, + ], + testReducer, + initialState + ); + + expect( derivedNavModeBlockEditingModes ).toEqual( + new Map( + Object.entries( { + '': 'disabled', + header: 'contentOnly', // Template part. + 'header-group': 'disabled', // Content block in template part. + 'header-paragraph': 'contentOnly', // Content block in template part. + footer: 'contentOnly', // Template part. + 'footer-paragraph': 'contentOnly', // Content block in template part. + 'section-root': 'contentOnly', + 'group-1': 'contentOnly', + 'paragraph-1': 'contentOnly', + } ) + ) + ); + } ); + + it( 'updates block editing modes when new blocks are inserted', () => { + const { derivedNavModeBlockEditingModes } = dispatchActions( + [ + { + type: 'INSERT_BLOCKS', + rootClientId: 'section-root', + blocks: [ + { + name: 'core/group', + clientId: 'group-3', + attributes: {}, + innerBlocks: [ + { + name: 'core/paragraph', + clientId: 'paragraph-3', + attributes: {}, + innerBlocks: [], + }, + { + name: 'core/group', + clientId: 'group-4', + attributes: {}, + innerBlocks: [], + }, + ], + }, + ], + }, + ], + testReducer, + initialState + ); + + expect( derivedNavModeBlockEditingModes ).toEqual( + new Map( + Object.entries( { + '': 'disabled', // Section root. + header: 'contentOnly', // Template part. + 'header-group': 'disabled', // Content block in template part. + 'header-paragraph': 'contentOnly', // Content block in template part. + footer: 'contentOnly', // Template part. + 'footer-paragraph': 'contentOnly', // Content block in template part. + 'section-root': 'contentOnly', // Section root. + 'group-1': 'contentOnly', // Section block. + 'paragraph-1': 'contentOnly', // Content block in section. + 'group-2': 'disabled', // Non-content block in section. + 'paragraph-2': 'contentOnly', // Content block in section. + 'group-3': 'contentOnly', // New section block. + 'paragraph-3': 'contentOnly', // New content block in section. + 'group-4': 'disabled', // Non-content block in section. + } ) + ) + ); + } ); + + it( 'updates block editing modes when blocks are moved to a new position', () => { + const { derivedNavModeBlockEditingModes } = dispatchActions( + [ + { + type: 'MOVE_BLOCKS_TO_POSITION', + clientIds: [ 'group-2' ], + fromRootClientId: 'group-1', + toRootClientId: 'section-root', + }, + ], + testReducer, + initialState + ); + expect( derivedNavModeBlockEditingModes ).toEqual( + new Map( + Object.entries( { + '': 'disabled', // Section root. + header: 'contentOnly', // Template part. + 'header-group': 'disabled', // Content block in template part. + 'header-paragraph': 'contentOnly', // Content block in template part. + footer: 'contentOnly', // Template part. + 'footer-paragraph': 'contentOnly', // Content block in template part. + 'section-root': 'contentOnly', // Section root. + 'group-1': 'contentOnly', // Section block. + 'paragraph-1': 'contentOnly', // Content block in section. + 'group-2': 'contentOnly', // New section block. + 'paragraph-2': 'contentOnly', // Still a content block in a section. + } ) + ) + ); + } ); + + it( 'handles changes to the section root', () => { + const { derivedNavModeBlockEditingModes } = dispatchActions( + [ + { + type: 'UPDATE_SETTINGS', + settings: { + [ sectionRootClientIdKey ]: 'group-1', + }, + }, + ], + testReducer, + initialState + ); + + expect( derivedNavModeBlockEditingModes ).toEqual( + new Map( + Object.entries( { + '': 'disabled', + header: 'contentOnly', // Template part. + 'header-group': 'disabled', // Content block in template part. + 'header-paragraph': 'contentOnly', // Content block in template part. + footer: 'contentOnly', // Template part. + 'footer-paragraph': 'contentOnly', // Content block in template part. + 'section-root': 'disabled', + 'group-1': 'contentOnly', // New section root. + 'paragraph-1': 'contentOnly', // Section and content block + 'group-2': 'contentOnly', // Section. + 'paragraph-2': 'contentOnly', // Content block. + } ) + ) + ); + } ); + } ); + + describe( 'zoom out mode', () => { + let initialState; + + beforeAll( () => { + initialState = dispatchActions( + [ + { + type: 'UPDATE_SETTINGS', + settings: { + [ sectionRootClientIdKey ]: '', + }, + }, + { + type: 'SET_ZOOM_LEVEL', + zoom: 'auto-scaled', + }, + { + type: 'RESET_BLOCKS', + blocks: [ + { + name: 'core/group', + clientId: 'group-1', + attributes: {}, + innerBlocks: [ + { + name: 'core/paragraph', + clientId: 'paragraph-1', + attributes: {}, + innerBlocks: [], + }, + { + name: 'core/group', + clientId: 'group-2', + attributes: {}, + innerBlocks: [ + { + name: 'core/paragraph', + clientId: 'paragraph-2', + attributes: {}, + innerBlocks: [], + }, + ], + }, + ], + }, + ], + }, + ], + testReducer + ); + } ); + + it( 'returns the expected block editing modes', () => { + expect( initialState.derivedBlockEditingModes ).toEqual( + new Map( + Object.entries( { + '': 'contentOnly', // Section root. + 'group-1': 'contentOnly', // Section block. + 'paragraph-1': 'disabled', + 'group-2': 'disabled', + 'paragraph-2': 'disabled', + } ) + ) + ); + } ); + + it( 'removes block editing modes when blocks are removed', () => { + const { derivedBlockEditingModes } = dispatchActions( + [ + { + type: 'REMOVE_BLOCKS', + clientIds: [ 'group-2' ], + }, + ], + testReducer, + initialState + ); + + expect( derivedBlockEditingModes ).toEqual( + new Map( + Object.entries( { + '': 'contentOnly', + 'group-1': 'contentOnly', + 'paragraph-1': 'disabled', + } ) + ) + ); + } ); + + it( 'updates block editing modes when new blocks are inserted', () => { + const { derivedBlockEditingModes } = dispatchActions( + [ + { + type: 'INSERT_BLOCKS', + rootClientId: '', + blocks: [ + { + name: 'core/group', + clientId: 'group-3', + attributes: {}, + innerBlocks: [ + { + name: 'core/paragraph', + clientId: 'paragraph-3', + attributes: {}, + innerBlocks: [], + }, + { + name: 'core/group', + clientId: 'group-4', + attributes: {}, + innerBlocks: [], + }, + ], + }, + ], + }, + ], + testReducer, + initialState + ); + + expect( derivedBlockEditingModes ).toEqual( + new Map( + Object.entries( { + '': 'contentOnly', // Section root. + 'group-1': 'contentOnly', // Section block. + 'paragraph-1': 'disabled', + 'group-2': 'disabled', + 'paragraph-2': 'disabled', + 'group-3': 'contentOnly', // New section block. + 'paragraph-3': 'disabled', + 'group-4': 'disabled', + } ) + ) + ); + } ); + + it( 'updates block editing modes when blocks are moved to a new position', () => { + const { derivedBlockEditingModes } = dispatchActions( + [ + { + type: 'MOVE_BLOCKS_TO_POSITION', + clientIds: [ 'group-2' ], + fromRootClientId: 'group-1', + toRootClientId: '', + }, + ], + testReducer, + initialState + ); + expect( derivedBlockEditingModes ).toEqual( + new Map( + Object.entries( { + '': 'contentOnly', // Section root. + 'group-1': 'contentOnly', // Section block. + 'paragraph-1': 'disabled', + 'group-2': 'contentOnly', // New section block. + 'paragraph-2': 'disabled', + } ) + ) + ); + } ); + + it( 'handles changes to the section root', () => { + const { derivedBlockEditingModes } = dispatchActions( + [ + { + type: 'UPDATE_SETTINGS', + settings: { + [ sectionRootClientIdKey ]: 'group-1', + }, + }, + ], + testReducer, + initialState + ); + + expect( derivedBlockEditingModes ).toEqual( + new Map( + Object.entries( { + '': 'disabled', + 'group-1': 'contentOnly', // New section root. + 'paragraph-1': 'contentOnly', // Section block. + 'group-2': 'contentOnly', // Section block. + 'paragraph-2': 'disabled', + } ) + ) + ); + } ); + } ); + } ); } ); diff --git a/packages/block-editor/src/store/test/selectors.js b/packages/block-editor/src/store/test/selectors.js index 00aa085f667093..587e1036e405e2 100644 --- a/packages/block-editor/src/store/test/selectors.js +++ b/packages/block-editor/src/store/test/selectors.js @@ -9,14 +9,12 @@ import { import { RawHTML } from '@wordpress/element'; import { symbol } from '@wordpress/icons'; import { select, dispatch } from '@wordpress/data'; -import { store as preferencesStore } from '@wordpress/preferences'; /** * Internal dependencies */ import * as selectors from '../selectors'; import { store } from '../'; -import { sectionRootClientIdKey } from '../private-keys'; import { lock } from '../../lock-unlock'; const { @@ -3326,7 +3324,7 @@ describe( 'selectors', () => { settings: {}, blockEditingModes: new Map(), }; - expect( canInsertBlocks( state, [ '2', '3' ], '1' ) ).toBe( true ); + expect( canInsertBlocks( state, [ '2' ], '1' ) ).toBe( true ); } ); it( 'should deny blocks', () => { @@ -3536,7 +3534,7 @@ describe( 'selectors', () => { beforeAll( () => { registerBlockType( 'core/with-tranforms-a', { category: 'text', - title: 'Tranforms a', + title: 'Transforms a', edit: () => {}, save: () => {}, transforms: { @@ -3565,7 +3563,7 @@ describe( 'selectors', () => { } ); registerBlockType( 'core/with-tranforms-b', { category: 'text', - title: 'Tranforms b', + title: 'Transforms b', edit: () => {}, save: () => {}, transforms: { @@ -3580,7 +3578,7 @@ describe( 'selectors', () => { } ); registerBlockType( 'core/with-tranforms-c', { category: 'text', - title: 'Tranforms c', + title: 'Transforms c', edit: () => {}, save: () => {}, transforms: { @@ -4467,31 +4465,22 @@ describe( 'getBlockEditingMode', () => { '9b9c5c3f-2e46-4f02-9e14-9fe9515b958f': {}, }, blockEditingModes: new Map( [] ), - }; - - const navigationModeStateWithRootSection = { - ...baseState, - settings: { - [ sectionRootClientIdKey ]: 'ef45d5fd-5234-4fd5-ac4f-c3736c7f9337', // The group is the "main" container - }, + derivedBlockEditingModes: new Map( [] ), }; const hasContentRoleAttribute = jest.fn( () => false ); + const get = jest.fn( () => 'edit' ); - const fauxPrivateAPIs = {}; + const mockedSelectors = { get }; - lock( fauxPrivateAPIs, { + lock( mockedSelectors, { hasContentRoleAttribute, } ); getBlockEditingMode.registry = { - select: jest.fn( () => fauxPrivateAPIs ), + select: jest.fn( () => mockedSelectors ), }; - afterEach( () => { - dispatch( preferencesStore ).set( 'core', 'editorTool', undefined ); - } ); - it( 'should return default by default', () => { expect( getBlockEditingMode( @@ -4531,6 +4520,13 @@ describe( 'getBlockEditingMode', () => { blockEditingModes: new Map( [ [ 'ef45d5fd-5234-4fd5-ac4f-c3736c7f9337', 'disabled' ], ] ), + derivedBlockEditingModes: new Map( [ + [ 'b26fc763-417d-4f01-b81c-2ec61e14a972', 'disabled' ], + [ '9b9c5c3f-2e46-4f02-9e14-9fe9515b958f', 'disabled' ], + [ 'b3247f75-fd94-4fef-97f9-5bfd162cc416', 'disabled' ], + [ 'e178812d-ce5e-48c7-a945-8ae4ffcbbb7c', 'disabled' ], + [ '9b9c5c3f-2e46-4f02-9e14-9fed515b958s', 'disabled' ], + ] ), }; expect( getBlockEditingMode( state, 'b3247f75-fd94-4fef-97f9-5bfd162cc416' ) @@ -4557,6 +4553,12 @@ describe( 'getBlockEditingMode', () => { [ 'ef45d5fd-5234-4fd5-ac4f-c3736c7f9337', 'default' ], [ '9b9c5c3f-2e46-4f02-9e14-9fe9515b958f', 'disabled' ], ] ), + derivedBlockEditingModes: new Map( [ + [ '6cf70164-9097-4460-bcbf-200560546988', 'disabled' ], + [ 'b3247f75-fd94-4fef-97f9-5bfd162cc416', 'disabled' ], + [ 'e178812d-ce5e-48c7-a945-8ae4ffcbbb7c', 'disabled' ], + [ '9b9c5c3f-2e46-4f02-9e14-9fed515b958s', 'disabled' ], + ] ), }; expect( getBlockEditingMode( state, 'b3247f75-fd94-4fef-97f9-5bfd162cc416' ) @@ -4567,6 +4569,15 @@ describe( 'getBlockEditingMode', () => { const state = { ...baseState, blockEditingModes: new Map( [ [ '', 'disabled' ] ] ), + derivedBlockEditingModes: new Map( [ + [ '6cf70164-9097-4460-bcbf-200560546988', 'disabled' ], + [ 'ef45d5fd-5234-4fd5-ac4f-c3736c7f9337', 'disabled' ], + [ 'b26fc763-417d-4f01-b81c-2ec61e14a972', 'disabled' ], + [ '9b9c5c3f-2e46-4f02-9e14-9fe9515b958f', 'disabled' ], + [ 'b3247f75-fd94-4fef-97f9-5bfd162cc416', 'disabled' ], + [ 'e178812d-ce5e-48c7-a945-8ae4ffcbbb7c', 'disabled' ], + [ '9b9c5c3f-2e46-4f02-9e14-9fed515b958s', 'disabled' ], + ] ), }; expect( getBlockEditingMode( state, 'b3247f75-fd94-4fef-97f9-5bfd162cc416' ) @@ -4614,69 +4625,4 @@ describe( 'getBlockEditingMode', () => { getBlockEditingMode( state, 'b3247f75-fd94-4fef-97f9-5bfd162cc416' ) ).toBe( 'contentOnly' ); } ); - - it( 'in navigation mode, the root section container is default', () => { - dispatch( preferencesStore ).set( 'core', 'editorTool', 'navigation' ); - expect( - getBlockEditingMode( - navigationModeStateWithRootSection, - 'ef45d5fd-5234-4fd5-ac4f-c3736c7f9337' - ) - ).toBe( 'default' ); - } ); - - it( 'in navigation mode, anything outside the section container is disabled', () => { - dispatch( preferencesStore ).set( 'core', 'editorTool', 'navigation' ); - expect( - getBlockEditingMode( - navigationModeStateWithRootSection, - '6cf70164-9097-4460-bcbf-200560546988' - ) - ).toBe( 'disabled' ); - } ); - - it( 'in navigation mode, sections are contentOnly', () => { - dispatch( preferencesStore ).set( 'core', 'editorTool', 'navigation' ); - expect( - getBlockEditingMode( - navigationModeStateWithRootSection, - 'b26fc763-417d-4f01-b81c-2ec61e14a972' - ) - ).toBe( 'contentOnly' ); - expect( - getBlockEditingMode( - navigationModeStateWithRootSection, - '9b9c5c3f-2e46-4f02-9e14-9fe9515b958f' - ) - ).toBe( 'contentOnly' ); - } ); - - it( 'in navigation mode, blocks with content attributes within sections are contentOnly', () => { - dispatch( preferencesStore ).set( 'core', 'editorTool', 'navigation' ); - hasContentRoleAttribute.mockReturnValueOnce( true ); - expect( - getBlockEditingMode( - navigationModeStateWithRootSection, - 'b3247f75-fd94-4fef-97f9-5bfd162cc416' - ) - ).toBe( 'contentOnly' ); - - hasContentRoleAttribute.mockReturnValueOnce( true ); - expect( - getBlockEditingMode( - navigationModeStateWithRootSection, - 'e178812d-ce5e-48c7-a945-8ae4ffcbbb7c' - ) - ).toBe( 'contentOnly' ); - } ); - - it( 'in navigation mode, blocks without content attributes within sections are disabled', () => { - dispatch( preferencesStore ).set( 'core', 'editorTool', 'navigation' ); - expect( - getBlockEditingMode( - navigationModeStateWithRootSection, - '9b9c5c3f-2e46-4f02-9e14-9fed515b958s' - ) - ).toBe( 'disabled' ); - } ); } ); diff --git a/packages/block-editor/src/style.scss b/packages/block-editor/src/style.scss index 512169351fe1fb..dd559922c48159 100644 --- a/packages/block-editor/src/style.scss +++ b/packages/block-editor/src/style.scss @@ -10,6 +10,7 @@ @import "./components/block-card/style.scss"; @import "./components/block-compare/style.scss"; @import "./components/block-draggable/style.scss"; +@import "./components/block-manager/style.scss"; @import "./components/block-mover/style.scss"; @import "./components/block-navigation/style.scss"; @import "./components/block-patterns-list/style.scss"; @@ -27,6 +28,7 @@ @import "./components/date-format-picker/style.scss"; @import "./components/duotone-control/style.scss"; @import "./components/font-appearance-control/style.scss"; +@import "./components/font-family/style.scss"; @import "./components/global-styles/style.scss"; @import "./components/grid/style.scss"; @import "./components/height-control/style.scss"; @@ -38,6 +40,7 @@ @import "./components/justify-content-control/style.scss"; @import "./components/link-control/style.scss"; @import "./components/list-view/style.scss"; +@import "./components/media-placeholder/style.scss"; @import "./components/media-replace-flow/style.scss"; @import "./components/multi-selection-inspector/style.scss"; @import "./components/responsive-block-control/style.scss"; diff --git a/packages/block-editor/src/utils/block-bindings.js b/packages/block-editor/src/utils/block-bindings.js index dcf80d985473b2..9a4c6acf9a9032 100644 --- a/packages/block-editor/src/utils/block-bindings.js +++ b/packages/block-editor/src/utils/block-bindings.js @@ -9,10 +9,105 @@ import { useDispatch, useRegistry } from '@wordpress/data'; import { store as blockEditorStore } from '../store'; import { useBlockEditContext } from '../components/block-edit'; +const DEFAULT_ATTRIBUTE = '__default'; +const PATTERN_OVERRIDES_SOURCE = 'core/pattern-overrides'; +const BLOCK_BINDINGS_ALLOWED_BLOCKS = { + 'core/paragraph': [ 'content' ], + 'core/heading': [ 'content' ], + 'core/image': [ 'id', 'url', 'title', 'alt' ], + 'core/button': [ 'url', 'text', 'linkTarget', 'rel' ], +}; + +/** + * Checks if the given object is empty. + * + * @param {?Object} object The object to check. + * + * @return {boolean} Whether the object is empty. + */ function isObjectEmpty( object ) { return ! object || Object.keys( object ).length === 0; } +/** + * Based on the given block name, checks if it is possible to bind the block. + * + * @param {string} blockName The name of the block. + * + * @return {boolean} Whether it is possible to bind the block to sources. + */ +export function canBindBlock( blockName ) { + return blockName in BLOCK_BINDINGS_ALLOWED_BLOCKS; +} + +/** + * Based on the given block name and attribute name, checks if it is possible to bind the block attribute. + * + * @param {string} blockName The name of the block. + * @param {string} attributeName The name of attribute. + * + * @return {boolean} Whether it is possible to bind the block attribute. + */ +export function canBindAttribute( blockName, attributeName ) { + return ( + canBindBlock( blockName ) && + BLOCK_BINDINGS_ALLOWED_BLOCKS[ blockName ].includes( attributeName ) + ); +} + +/** + * Gets the bindable attributes for a given block. + * + * @param {string} blockName The name of the block. + * + * @return {string[]} The bindable attributes for the block. + */ +export function getBindableAttributes( blockName ) { + return BLOCK_BINDINGS_ALLOWED_BLOCKS[ blockName ]; +} + +/** + * Checks if the block has the `__default` binding for pattern overrides. + * + * @param {?Record<string, object>} bindings A block's bindings from the metadata attribute. + * + * @return {boolean} Whether the block has the `__default` binding for pattern overrides. + */ +export function hasPatternOverridesDefaultBinding( bindings ) { + return bindings?.[ DEFAULT_ATTRIBUTE ]?.source === PATTERN_OVERRIDES_SOURCE; +} + +/** + * 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 {?Record<string, object>} bindings A block's bindings from the metadata attribute. + * + * @return {Object} The bindings with default replaced for pattern overrides. + */ +export function replacePatternOverridesDefaultBinding( blockName, bindings ) { + // The `__default` binding currently only works for pattern overrides. + if ( hasPatternOverridesDefaultBinding( bindings ) ) { + 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: PATTERN_OVERRIDES_SOURCE }; + bindingsWithDefaults[ attributeName ] = bindingSource; + } + + return bindingsWithDefaults; + } + + return bindings; +} + /** * Contains utils to update the block `bindings` metadata. * diff --git a/packages/block-editor/src/utils/test/sorting.js b/packages/block-editor/src/utils/test/sorting.js index f1038cda5809cc..faf2f02ad67563 100644 --- a/packages/block-editor/src/utils/test/sorting.js +++ b/packages/block-editor/src/utils/test/sorting.js @@ -37,7 +37,7 @@ describe( 'orderBy', () => { expect( orderBy( input, 'x' ) ).toEqual( expected ); } ); - it( 'should maintain original order of equal items in descencing order', () => { + it( 'should maintain original order of equal items in descending order', () => { const a = { x: 1, a: 1 }; const b = { x: 1, b: 2 }; const c = { x: 0 }; diff --git a/packages/block-editor/src/utils/transform-styles/index.js b/packages/block-editor/src/utils/transform-styles/index.js index 170f770d63d5d4..b05a625a1e80e8 100644 --- a/packages/block-editor/src/utils/transform-styles/index.js +++ b/packages/block-editor/src/utils/transform-styles/index.js @@ -160,7 +160,7 @@ function transformStyle( /** * @typedef {Object} EditorStyle * @property {string} css the CSS block(s), as a single string. - * @property {?string} baseURL the base URL to be used as the reference when rewritting urls. + * @property {?string} baseURL the base URL to be used as the reference when rewriting urls. * @property {?string[]} ignoredSelectors the selectors not to wrap. */ diff --git a/packages/block-editor/src/utils/use-notify-copy.js b/packages/block-editor/src/utils/use-notify-copy.js index 0f98577f11bf65..51742f476a5fd2 100644 --- a/packages/block-editor/src/utils/use-notify-copy.js +++ b/packages/block-editor/src/utils/use-notify-copy.js @@ -17,47 +17,55 @@ export function useNotifyCopy() { const { getBlockType } = useSelect( blocksStore ); const { createSuccessNotice } = useDispatch( noticesStore ); - return useCallback( ( eventType, selectedBlockClientIds ) => { - let notice = ''; - if ( selectedBlockClientIds.length === 1 ) { - const clientId = selectedBlockClientIds[ 0 ]; - const title = getBlockType( getBlockName( clientId ) )?.title; - notice = - eventType === 'copy' - ? sprintf( - // Translators: Name of the block being copied, e.g. "Paragraph". - __( 'Copied "%s" to clipboard.' ), - title - ) - : sprintf( - // Translators: Name of the block being cut, e.g. "Paragraph". - __( 'Moved "%s" to clipboard.' ), - title - ); - } else { - notice = - eventType === 'copy' - ? sprintf( - // Translators: %d: Number of blocks being copied. - _n( - 'Copied %d block to clipboard.', - 'Copied %d blocks to clipboard.', - selectedBlockClientIds.length - ), - selectedBlockClientIds.length - ) - : sprintf( - // Translators: %d: Number of blocks being cut. - _n( - 'Moved %d block to clipboard.', - 'Moved %d blocks to clipboard.', - selectedBlockClientIds.length - ), - selectedBlockClientIds.length - ); - } - createSuccessNotice( notice, { - type: 'snackbar', - } ); - }, [] ); + return useCallback( + ( eventType, selectedBlockClientIds ) => { + let notice = ''; + + if ( eventType === 'copyStyles' ) { + notice = __( 'Styles copied to clipboard.' ); + } else if ( selectedBlockClientIds.length === 1 ) { + const clientId = selectedBlockClientIds[ 0 ]; + const title = getBlockType( getBlockName( clientId ) )?.title; + + if ( eventType === 'copy' ) { + notice = sprintf( + // Translators: Name of the block being copied, e.g. "Paragraph". + __( 'Copied "%s" to clipboard.' ), + title + ); + } else { + notice = sprintf( + // Translators: Name of the block being cut, e.g. "Paragraph". + __( 'Moved "%s" to clipboard.' ), + title + ); + } + } else if ( eventType === 'copy' ) { + notice = sprintf( + // Translators: %d: Number of blocks being copied. + _n( + 'Copied %d block to clipboard.', + 'Copied %d blocks to clipboard.', + selectedBlockClientIds.length + ), + selectedBlockClientIds.length + ); + } else { + notice = sprintf( + // Translators: %d: Number of blocks being moved. + _n( + 'Moved %d block to clipboard.', + 'Moved %d blocks to clipboard.', + selectedBlockClientIds.length + ), + selectedBlockClientIds.length + ); + } + + createSuccessNotice( notice, { + type: 'snackbar', + } ); + }, + [ createSuccessNotice, getBlockName, getBlockType ] + ); } diff --git a/packages/block-editor/tsconfig.json b/packages/block-editor/tsconfig.json index a3c7d1ffd88077..30fe326d35b83e 100644 --- a/packages/block-editor/tsconfig.json +++ b/packages/block-editor/tsconfig.json @@ -1,10 +1,6 @@ { "$schema": "https://json.schemastore.org/tsconfig.json", "extends": "../../tsconfig.base.json", - "compilerOptions": { - "rootDir": "src", - "declarationDir": "build-types" - }, "references": [ { "path": "../a11y" }, { "path": "../api-fetch" }, @@ -31,11 +27,13 @@ { "path": "../style-engine" }, { "path": "../token-list" }, { "path": "../url" }, + { "path": "../upload-media" }, { "path": "../warning" }, { "path": "../wordcount" } ], // NOTE: This package is being progressively typed. You are encouraged to // expand this array with files which can be type-checked. At some point in // the future, this can be simplified to an `includes` of `src/**/*`. - "files": [ "src/components/block-context/index.js", "src/utils/dom.js" ] + "files": [ "src/components/block-context/index.js", "src/utils/dom.js" ], + "include": [] } diff --git a/packages/block-library/CHANGELOG.md b/packages/block-library/CHANGELOG.md index 4b5cf59f81910d..c819c35987ef91 100644 --- a/packages/block-library/CHANGELOG.md +++ b/packages/block-library/CHANGELOG.md @@ -2,6 +2,16 @@ ## Unreleased +## 9.16.0 (2025-01-15) + +## 9.15.0 (2025-01-02) + +## 9.14.0 (2024-12-11) + +## 9.13.0 (2024-11-27) + +## 9.12.0 (2024-11-16) + ## 9.11.0 (2024-10-30) ## 9.10.0 (2024-10-16) diff --git a/packages/block-library/package.json b/packages/block-library/package.json index b196e53e5cd0f9..d189a95ba2e474 100644 --- a/packages/block-library/package.json +++ b/packages/block-library/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/block-library", - "version": "9.11.0", + "version": "9.16.0", "description": "Block library for the WordPress editor.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -28,6 +28,7 @@ "wpScript": true, "wpScriptModuleExports": { "./file/view": "./build-module/file/view.js", + "./form/view": "./build-module/form/view.js", "./image/view": "./build-module/image/view.js", "./navigation/view": "./build-module/navigation/view.js", "./query/view": "./build-module/query/view.js", @@ -40,39 +41,39 @@ ], "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/a11y": "*", - "@wordpress/api-fetch": "*", - "@wordpress/autop": "*", - "@wordpress/blob": "*", - "@wordpress/block-editor": "*", - "@wordpress/blocks": "*", - "@wordpress/components": "*", - "@wordpress/compose": "*", - "@wordpress/core-data": "*", - "@wordpress/data": "*", - "@wordpress/date": "*", - "@wordpress/deprecated": "*", - "@wordpress/dom": "*", - "@wordpress/element": "*", - "@wordpress/escape-html": "*", - "@wordpress/hooks": "*", - "@wordpress/html-entities": "*", - "@wordpress/i18n": "*", - "@wordpress/icons": "*", - "@wordpress/interactivity": "*", - "@wordpress/interactivity-router": "*", - "@wordpress/keyboard-shortcuts": "*", - "@wordpress/keycodes": "*", - "@wordpress/notices": "*", - "@wordpress/patterns": "*", - "@wordpress/primitives": "*", - "@wordpress/private-apis": "*", - "@wordpress/reusable-blocks": "*", - "@wordpress/rich-text": "*", - "@wordpress/server-side-render": "*", - "@wordpress/url": "*", - "@wordpress/viewport": "*", - "@wordpress/wordcount": "*", + "@wordpress/a11y": "file:../a11y", + "@wordpress/api-fetch": "file:../api-fetch", + "@wordpress/autop": "file:../autop", + "@wordpress/blob": "file:../blob", + "@wordpress/block-editor": "file:../block-editor", + "@wordpress/blocks": "file:../blocks", + "@wordpress/components": "file:../components", + "@wordpress/compose": "file:../compose", + "@wordpress/core-data": "file:../core-data", + "@wordpress/data": "file:../data", + "@wordpress/date": "file:../date", + "@wordpress/deprecated": "file:../deprecated", + "@wordpress/dom": "file:../dom", + "@wordpress/element": "file:../element", + "@wordpress/escape-html": "file:../escape-html", + "@wordpress/hooks": "file:../hooks", + "@wordpress/html-entities": "file:../html-entities", + "@wordpress/i18n": "file:../i18n", + "@wordpress/icons": "file:../icons", + "@wordpress/interactivity": "file:../interactivity", + "@wordpress/interactivity-router": "file:../interactivity-router", + "@wordpress/keyboard-shortcuts": "file:../keyboard-shortcuts", + "@wordpress/keycodes": "file:../keycodes", + "@wordpress/notices": "file:../notices", + "@wordpress/patterns": "file:../patterns", + "@wordpress/primitives": "file:../primitives", + "@wordpress/private-apis": "file:../private-apis", + "@wordpress/reusable-blocks": "file:../reusable-blocks", + "@wordpress/rich-text": "file:../rich-text", + "@wordpress/server-side-render": "file:../server-side-render", + "@wordpress/url": "file:../url", + "@wordpress/viewport": "file:../viewport", + "@wordpress/wordcount": "file:../wordcount", "change-case": "^4.1.2", "clsx": "^2.1.1", "colord": "^2.7.0", diff --git a/packages/block-library/src/archives/edit.js b/packages/block-library/src/archives/edit.js index 60b8715988ed94..81c5a735acf480 100644 --- a/packages/block-library/src/archives/edit.js +++ b/packages/block-library/src/archives/edit.js @@ -2,70 +2,128 @@ * WordPress dependencies */ import { - PanelBody, ToggleControl, SelectControl, Disabled, + __experimentalToolsPanel as ToolsPanel, + __experimentalToolsPanelItem as ToolsPanelItem, } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import { InspectorControls, useBlockProps } from '@wordpress/block-editor'; import ServerSideRender from '@wordpress/server-side-render'; +/** + * Internal dependencies + */ +import { useToolsPanelDropdownMenuProps } from '../utils/hooks'; + export default function ArchivesEdit( { attributes, setAttributes } ) { const { showLabel, showPostCounts, displayAsDropdown, type } = attributes; + const dropdownMenuProps = useToolsPanelDropdownMenuProps(); + return ( <> <InspectorControls> - <PanelBody title={ __( 'Settings' ) }> - <ToggleControl - __nextHasNoMarginBottom + <ToolsPanel + label={ __( 'Settings' ) } + resetAll={ () => { + setAttributes( { + displayAsDropdown: false, + showLabel: false, + showPostCounts: false, + type: 'monthly', + } ); + } } + dropdownMenuProps={ dropdownMenuProps } + > + <ToolsPanelItem label={ __( 'Display as dropdown' ) } - checked={ displayAsDropdown } - onChange={ () => - setAttributes( { - displayAsDropdown: ! displayAsDropdown, - } ) + isShownByDefault + hasValue={ () => displayAsDropdown } + onDeselect={ () => + setAttributes( { displayAsDropdown: false } ) } - /> - { displayAsDropdown && ( + > <ToggleControl __nextHasNoMarginBottom - label={ __( 'Show label' ) } - checked={ showLabel } + label={ __( 'Display as dropdown' ) } + checked={ displayAsDropdown } onChange={ () => setAttributes( { - showLabel: ! showLabel, + displayAsDropdown: ! displayAsDropdown, } ) } /> + </ToolsPanelItem> + + { displayAsDropdown && ( + <ToolsPanelItem + label={ __( 'Show label' ) } + isShownByDefault + hasValue={ () => ! showLabel } + onDeselect={ () => + setAttributes( { showLabel: false } ) + } + > + <ToggleControl + __nextHasNoMarginBottom + label={ __( 'Show label' ) } + checked={ showLabel } + onChange={ () => + setAttributes( { + showLabel: ! showLabel, + } ) + } + /> + </ToolsPanelItem> ) } - <ToggleControl - __nextHasNoMarginBottom + + <ToolsPanelItem label={ __( 'Show post counts' ) } - checked={ showPostCounts } - onChange={ () => - setAttributes( { - showPostCounts: ! showPostCounts, - } ) + isShownByDefault + hasValue={ () => showPostCounts } + onDeselect={ () => + setAttributes( { showPostCounts: false } ) } - /> - <SelectControl - __next40pxDefaultSize - __nextHasNoMarginBottom + > + <ToggleControl + __nextHasNoMarginBottom + label={ __( 'Show post counts' ) } + checked={ showPostCounts } + onChange={ () => + setAttributes( { + showPostCounts: ! showPostCounts, + } ) + } + /> + </ToolsPanelItem> + + <ToolsPanelItem label={ __( 'Group by' ) } - options={ [ - { label: __( 'Year' ), value: 'yearly' }, - { label: __( 'Month' ), value: 'monthly' }, - { label: __( 'Week' ), value: 'weekly' }, - { label: __( 'Day' ), value: 'daily' }, - ] } - value={ type } - onChange={ ( value ) => - setAttributes( { type: value } ) + isShownByDefault + hasValue={ () => type !== 'monthly' } + onDeselect={ () => + setAttributes( { type: 'monthly' } ) } - /> - </PanelBody> + > + <SelectControl + __next40pxDefaultSize + __nextHasNoMarginBottom + label={ __( 'Group by' ) } + options={ [ + { label: __( 'Year' ), value: 'yearly' }, + { label: __( 'Month' ), value: 'monthly' }, + { label: __( 'Week' ), value: 'weekly' }, + { label: __( 'Day' ), value: 'daily' }, + ] } + value={ type } + onChange={ ( value ) => + setAttributes( { type: value } ) + } + /> + </ToolsPanelItem> + </ToolsPanel> </InspectorControls> <div { ...useBlockProps() }> <Disabled> diff --git a/packages/block-library/src/audio/test/__snapshots__/edit.native.js.snap b/packages/block-library/src/audio/test/__snapshots__/edit.native.js.snap index 4cf28f7063ad31..9cf88d804068af 100644 --- a/packages/block-library/src/audio/test/__snapshots__/edit.native.js.snap +++ b/packages/block-library/src/audio/test/__snapshots__/edit.native.js.snap @@ -89,7 +89,7 @@ exports[`Audio block renders audio block error state without crashing 1`] = ` <Svg height={16} style={{}} - viewBox="-2 -2 24 24" + viewBox="0 0 24 24" width={16} xmlns="http://www.w3.org/2000/svg" > diff --git a/packages/block-library/src/audio/test/transforms.native.js b/packages/block-library/src/audio/test/transforms.native.js index 0ed1a1f6306fbf..cf7b612fcecccb 100644 --- a/packages/block-library/src/audio/test/transforms.native.js +++ b/packages/block-library/src/audio/test/transforms.native.js @@ -15,8 +15,8 @@ const initialHtml = ` <figure class="wp-block-audio"><audio controls src="https://cldup.com/59IrU0WJtq.mp3"></audio></figure> <!-- /wp:audio -->`; -const tranformsWithInnerBlocks = [ 'Columns', 'Group' ]; -const blockTransforms = [ 'File', ...tranformsWithInnerBlocks ]; +const transformsWithInnerBlocks = [ 'Columns', 'Group' ]; +const blockTransforms = [ 'File', ...transformsWithInnerBlocks ]; setupCoreBlocks(); @@ -25,7 +25,8 @@ describe( `${ block } block transformations`, () => { const screen = await initializeEditor( { initialHtml } ); const newBlock = await transformBlock( screen, block, blockTransform, { isMediaBlock: false, - hasInnerBlocks: tranformsWithInnerBlocks.includes( blockTransform ), + hasInnerBlocks: + transformsWithInnerBlocks.includes( blockTransform ), } ); expect( newBlock ).toBeVisible(); expect( getEditorHtml() ).toMatchSnapshot(); diff --git a/packages/block-library/src/block-keyboard-shortcuts/index.js b/packages/block-library/src/block-keyboard-shortcuts/index.js index 6d9cde8364001f..2ce106b8ed99dc 100644 --- a/packages/block-library/src/block-keyboard-shortcuts/index.js +++ b/packages/block-library/src/block-keyboard-shortcuts/index.js @@ -90,22 +90,36 @@ function BlockKeyboardShortcuts() { }, } ); } ); - }, [] ); + }, [ registerShortcut ] ); useShortcut( 'core/block-editor/transform-heading-to-paragraph', ( event ) => handleTransformHeadingAndParagraph( event, 0 ) ); - - [ 1, 2, 3, 4, 5, 6 ].forEach( ( level ) => { - //the loop is based off on a constant therefore - //the hook will execute the same way every time - //eslint-disable-next-line react-hooks/rules-of-hooks - useShortcut( - `core/block-editor/transform-paragraph-to-heading-${ level }`, - ( event ) => handleTransformHeadingAndParagraph( event, level ) - ); - } ); + useShortcut( + 'core/block-editor/transform-paragraph-to-heading-1', + ( event ) => handleTransformHeadingAndParagraph( event, 1 ) + ); + useShortcut( + 'core/block-editor/transform-paragraph-to-heading-2', + ( event ) => handleTransformHeadingAndParagraph( event, 2 ) + ); + useShortcut( + 'core/block-editor/transform-paragraph-to-heading-3', + ( event ) => handleTransformHeadingAndParagraph( event, 3 ) + ); + useShortcut( + 'core/block-editor/transform-paragraph-to-heading-4', + ( event ) => handleTransformHeadingAndParagraph( event, 4 ) + ); + useShortcut( + 'core/block-editor/transform-paragraph-to-heading-5', + ( event ) => handleTransformHeadingAndParagraph( event, 5 ) + ); + useShortcut( + 'core/block-editor/transform-paragraph-to-heading-6', + ( event ) => handleTransformHeadingAndParagraph( event, 6 ) + ); return null; } diff --git a/packages/block-library/src/block/edit.js b/packages/block-library/src/block/edit.js index 104b07157cba74..3d4d07e52b386a 100644 --- a/packages/block-library/src/block/edit.js +++ b/packages/block-library/src/block/edit.js @@ -7,7 +7,7 @@ import clsx from 'clsx'; * WordPress dependencies */ import { useSelect, useDispatch } from '@wordpress/data'; -import { useRef, useMemo, useEffect } from '@wordpress/element'; +import { useRef, useMemo } from '@wordpress/element'; import { useEntityRecord, store as coreStore, @@ -37,12 +37,10 @@ import { getBlockBindingsSource } from '@wordpress/blocks'; /** * Internal dependencies */ -import { name as patternBlockName } from './index'; import { unlock } from '../lock-unlock'; const { useLayoutClasses } = unlock( blockEditorPrivateApis ); -const { isOverridableBlock, hasOverridableBlocks } = - unlock( patternsPrivateApis ); +const { hasOverridableBlocks } = unlock( patternsPrivateApis ); const fullAlignments = [ 'full', 'wide', 'left', 'right' ]; @@ -75,22 +73,6 @@ const useInferredLayout = ( blocks, parentLayout ) => { }, [ blocks, parentLayout ] ); }; -function setBlockEditMode( setEditMode, blocks, mode ) { - blocks.forEach( ( block ) => { - const editMode = - mode || - ( isOverridableBlock( block ) ? 'contentOnly' : 'disabled' ); - setEditMode( block.clientId, editMode ); - - setBlockEditMode( - setEditMode, - block.innerBlocks, - // Disable editing for nested patterns. - block.name === patternBlockName ? 'disabled' : mode - ); - } ); -} - function RecursionWarning() { const blockProps = useBlockProps(); return ( @@ -171,7 +153,6 @@ function ReusableBlockEdit( { name, attributes: { ref, content }, __unstableParentLayout: parentLayout, - clientId: patternClientId, setAttributes, } ) { const { record, hasResolved } = useEntityRecord( @@ -184,49 +165,24 @@ function ReusableBlockEdit( { } ); const isMissing = hasResolved && ! record; - const { setBlockEditingMode, __unstableMarkLastChangeAsPersistent } = + const { __unstableMarkLastChangeAsPersistent } = useDispatch( blockEditorStore ); - const { - innerBlocks, - onNavigateToEntityRecord, - editingMode, - hasPatternOverridesSource, - } = useSelect( + const { onNavigateToEntityRecord, hasPatternOverridesSource } = useSelect( ( select ) => { - const { getBlocks, getSettings, getBlockEditingMode } = - select( blockEditorStore ); + const { getSettings } = select( blockEditorStore ); // For editing link to the site editor if the theme and user permissions support it. return { - innerBlocks: getBlocks( patternClientId ), onNavigateToEntityRecord: getSettings().onNavigateToEntityRecord, - editingMode: getBlockEditingMode( patternClientId ), hasPatternOverridesSource: !! getBlockBindingsSource( 'core/pattern-overrides' ), }; }, - [ patternClientId ] + [] ); - // Sync the editing mode of the pattern block with the inner blocks. - useEffect( () => { - setBlockEditMode( - setBlockEditingMode, - innerBlocks, - // Disable editing if the pattern itself is disabled. - editingMode === 'disabled' || ! hasPatternOverridesSource - ? 'disabled' - : undefined - ); - }, [ - editingMode, - innerBlocks, - setBlockEditingMode, - hasPatternOverridesSource, - ] ); - const canOverrideBlocks = useMemo( () => hasPatternOverridesSource && hasOverridableBlocks( blocks ), [ hasPatternOverridesSource, blocks ] @@ -244,7 +200,6 @@ function ReusableBlockEdit( { } ); const innerBlocksProps = useInnerBlocksProps( blockProps, { - templateLock: 'all', layout, value: blocks, onInput: NOOP, diff --git a/packages/block-library/src/block/index.php b/packages/block-library/src/block/index.php index 8beef975fad6f3..e8075115cabda4 100644 --- a/packages/block-library/src/block/index.php +++ b/packages/block-library/src/block/index.php @@ -87,6 +87,26 @@ function render_block_core_block( $attributes ) { add_filter( 'render_block_context', $filter_block_context, 1 ); } + $ignored_hooked_blocks = get_post_meta( $attributes['ref'], '_wp_ignored_hooked_blocks', true ); + if ( ! empty( $ignored_hooked_blocks ) ) { + $ignored_hooked_blocks = json_decode( $ignored_hooked_blocks, true ); + $attributes['metadata'] = array( + 'ignoredHookedBlocks' => $ignored_hooked_blocks, + ); + } + + // Wrap in "Block" block so the Block Hooks algorithm can insert blocks + // that are hooked as first or last child of `core/block`. + $content = get_comment_delimited_block_content( + 'core/block', + $attributes, + $content + ); + // Apply Block Hooks. + $content = apply_block_hooks_to_content( $content, $reusable_block ); + // Remove block wrapper. + $content = remove_serialized_parent_block( $content ); + $content = do_blocks( $content ); unset( $seen_refs[ $attributes['ref'] ] ); diff --git a/packages/block-library/src/button/block.json b/packages/block-library/src/button/block.json index 2c1c05baa20dd3..6fcb7aca4c5923 100644 --- a/packages/block-library/src/button/block.json +++ b/packages/block-library/src/button/block.json @@ -85,6 +85,16 @@ } }, "typography": { + "__experimentalSkipSerialization": [ + "fontSize", + "lineHeight", + "fontFamily", + "fontWeight", + "fontStyle", + "textTransform", + "textDecoration", + "letterSpacing" + ], "fontSize": true, "lineHeight": true, "__experimentalFontFamily": true, @@ -122,7 +132,6 @@ "width": true } }, - "__experimentalSelector": ".wp-block-button .wp-block-button__link", "interactivity": { "clientNavigation": true } @@ -132,5 +141,11 @@ { "name": "outline", "label": "Outline" } ], "editorStyle": "wp-block-button-editor", - "style": "wp-block-button" + "style": "wp-block-button", + "selectors": { + "root": ".wp-block-button .wp-block-button__link", + "typography": { + "writingMode": ".wp-block-button" + } + } } diff --git a/packages/block-library/src/button/deprecated.js b/packages/block-library/src/button/deprecated.js index 8ab83e1b09518f..f478c39a0dc326 100644 --- a/packages/block-library/src/button/deprecated.js +++ b/packages/block-library/src/button/deprecated.js @@ -14,6 +14,8 @@ import { __experimentalGetBorderClassesAndStyles as getBorderClassesAndStyles, __experimentalGetColorClassesAndStyles as getColorClassesAndStyles, __experimentalGetSpacingClassesAndStyles as getSpacingClassesAndStyles, + __experimentalGetShadowClassesAndStyles as getShadowClassesAndStyles, + __experimentalGetElementClassName, } from '@wordpress/block-editor'; import { compose } from '@wordpress/compose'; @@ -132,6 +134,192 @@ const blockAttributes = { }, }; +const v12 = { + attributes: { + tagName: { + type: 'string', + enum: [ 'a', 'button' ], + default: 'a', + }, + type: { + type: 'string', + default: 'button', + }, + textAlign: { + type: 'string', + }, + url: { + type: 'string', + source: 'attribute', + selector: 'a', + attribute: 'href', + }, + title: { + type: 'string', + source: 'attribute', + selector: 'a,button', + attribute: 'title', + role: 'content', + }, + text: { + type: 'rich-text', + source: 'rich-text', + selector: 'a,button', + role: 'content', + }, + linkTarget: { + type: 'string', + source: 'attribute', + selector: 'a', + attribute: 'target', + role: 'content', + }, + rel: { + type: 'string', + source: 'attribute', + selector: 'a', + attribute: 'rel', + role: 'content', + }, + placeholder: { + type: 'string', + }, + backgroundColor: { + type: 'string', + }, + textColor: { + type: 'string', + }, + gradient: { + type: 'string', + }, + width: { + type: 'number', + }, + }, + supports: { + anchor: true, + align: true, + alignWide: false, + color: { + __experimentalSkipSerialization: true, + gradients: true, + __experimentalDefaultControls: { + background: true, + text: true, + }, + }, + typography: { + fontSize: true, + lineHeight: true, + __experimentalFontFamily: true, + __experimentalFontWeight: true, + __experimentalFontStyle: true, + __experimentalTextTransform: true, + __experimentalTextDecoration: true, + __experimentalLetterSpacing: true, + __experimentalWritingMode: true, + __experimentalDefaultControls: { + fontSize: true, + }, + }, + reusable: false, + shadow: { + __experimentalSkipSerialization: true, + }, + spacing: { + __experimentalSkipSerialization: true, + padding: [ 'horizontal', 'vertical' ], + __experimentalDefaultControls: { + padding: true, + }, + }, + __experimentalBorder: { + color: true, + radius: true, + style: true, + width: true, + __experimentalSkipSerialization: true, + __experimentalDefaultControls: { + color: true, + radius: true, + style: true, + width: true, + }, + }, + __experimentalSelector: '.wp-block-button__link', + interactivity: { + clientNavigation: true, + }, + }, + save( { attributes, className } ) { + const { + tagName, + type, + textAlign, + fontSize, + linkTarget, + rel, + style, + text, + title, + url, + width, + } = attributes; + + const TagName = tagName || 'a'; + const isButtonTag = 'button' === TagName; + const buttonType = type || 'button'; + const borderProps = getBorderClassesAndStyles( attributes ); + const colorProps = getColorClassesAndStyles( attributes ); + const spacingProps = getSpacingClassesAndStyles( attributes ); + const shadowProps = getShadowClassesAndStyles( attributes ); + const buttonClasses = clsx( + 'wp-block-button__link', + colorProps.className, + borderProps.className, + { + [ `has-text-align-${ textAlign }` ]: textAlign, + // For backwards compatibility add style that isn't provided via + // block support. + 'no-border-radius': style?.border?.radius === 0, + }, + __experimentalGetElementClassName( 'button' ) + ); + const buttonStyle = { + ...borderProps.style, + ...colorProps.style, + ...spacingProps.style, + ...shadowProps.style, + }; + + // The use of a `title` attribute here is soft-deprecated, but still applied + // if it had already been assigned, for the sake of backward-compatibility. + // A title will no longer be assigned for new or updated button block links. + + const wrapperClasses = clsx( className, { + [ `has-custom-width wp-block-button__width-${ width }` ]: width, + [ `has-custom-font-size` ]: fontSize || style?.typography?.fontSize, + } ); + + return ( + <div { ...useBlockProps.save( { className: wrapperClasses } ) }> + <RichText.Content + tagName={ TagName } + type={ isButtonTag ? buttonType : null } + className={ buttonClasses } + href={ isButtonTag ? null : url } + title={ title } + style={ buttonStyle } + value={ text } + target={ isButtonTag ? null : linkTarget } + rel={ isButtonTag ? null : rel } + /> + </div> + ); + }, +}; + const v11 = { attributes: { url: { @@ -399,6 +587,7 @@ const v10 = { }; const deprecated = [ + v12, v11, v10, { diff --git a/packages/block-library/src/button/edit.js b/packages/block-library/src/button/edit.js index 3539fd54f4eece..67e1218ca2f65d 100644 --- a/packages/block-library/src/button/edit.js +++ b/packages/block-library/src/button/edit.js @@ -9,19 +9,21 @@ import clsx from 'clsx'; import { NEW_TAB_TARGET, NOFOLLOW_REL } from './constants'; import { getUpdatedLinkAttributes } from './get-updated-link-attributes'; import removeAnchorTag from '../utils/remove-anchor-tag'; +import { useToolsPanelDropdownMenuProps } from '../utils/hooks'; /** * WordPress dependencies */ -import { __ } from '@wordpress/i18n'; +import { __, sprintf } from '@wordpress/i18n'; import { useEffect, useState, useRef, useMemo } from '@wordpress/element'; import { - Button, - ButtonGroup, - PanelBody, TextControl, ToolbarButton, Popover, + __experimentalToolsPanel as ToolsPanel, + __experimentalToolsPanelItem as ToolsPanelItem, + __experimentalToggleGroupControl as ToggleGroupControl, + __experimentalToggleGroupControlOption as ToggleGroupControlOption, } from '@wordpress/components'; import { AlignmentControl, @@ -29,14 +31,16 @@ import { InspectorControls, RichText, useBlockProps, + LinkControl, __experimentalUseBorderProps as useBorderProps, __experimentalUseColorProps as useColorProps, __experimentalGetSpacingClassesAndStyles as useSpacingProps, __experimentalGetShadowClassesAndStyles as useShadowProps, - __experimentalLinkControl as LinkControl, __experimentalGetElementClassName, store as blockEditorStore, useBlockEditingMode, + getTypographyClassesAndStyles as useTypographyProps, + useSettings, } from '@wordpress/block-editor'; import { displayShortcut, isKeyboardEvent, ENTER } from '@wordpress/keycodes'; import { link, linkOff } from '@wordpress/icons'; @@ -114,35 +118,47 @@ function useEnter( props ) { } function WidthPanel( { selectedWidth, setAttributes } ) { - function handleChange( newWidth ) { - // Check if we are toggling the width off - const width = selectedWidth === newWidth ? undefined : newWidth; - - // Update attributes. - setAttributes( { width } ); - } + const dropdownMenuProps = useToolsPanelDropdownMenuProps(); return ( - <PanelBody title={ __( 'Settings' ) }> - <ButtonGroup aria-label={ __( 'Button width' ) }> - { [ 25, 50, 75, 100 ].map( ( widthValue ) => { - return ( - <Button - key={ widthValue } - size="small" - variant={ - widthValue === selectedWidth - ? 'primary' - : undefined - } - onClick={ () => handleChange( widthValue ) } - > - { widthValue }% - </Button> - ); - } ) } - </ButtonGroup> - </PanelBody> + <ToolsPanel + label={ __( 'Settings' ) } + resetAll={ () => setAttributes( { width: undefined } ) } + dropdownMenuProps={ dropdownMenuProps } + > + <ToolsPanelItem + label={ __( 'Width' ) } + isShownByDefault + hasValue={ () => !! selectedWidth } + onDeselect={ () => setAttributes( { width: undefined } ) } + __nextHasNoMarginBottom + > + <ToggleGroupControl + label={ __( 'Width' ) } + value={ selectedWidth } + onChange={ ( newWidth ) => + setAttributes( { width: newWidth } ) + } + isBlock + __next40pxDefaultSize + __nextHasNoMarginBottom + > + { [ 25, 50, 75, 100 ].map( ( widthValue ) => { + return ( + <ToggleGroupControlOption + key={ widthValue } + value={ widthValue } + label={ sprintf( + /* translators: Percentage value. */ + __( '%d%%' ), + widthValue + ) } + /> + ); + } ) } + </ToggleGroupControl> + </ToolsPanelItem> + </ToolsPanel> ); } @@ -256,6 +272,19 @@ function ButtonEdit( props ) { [ context, isSelected, metadata?.bindings?.url ] ); + const [ fluidTypographySettings, layout ] = useSettings( + 'typography.fluid', + 'layout' + ); + const typographyProps = useTypographyProps( attributes, { + typography: { + fluid: fluidTypographySettings, + }, + layout: { + wideSize: layout?.wideSize, + }, + } ); + return ( <> <div @@ -263,7 +292,6 @@ function ButtonEdit( props ) { className={ clsx( blockProps.className, { [ `has-custom-width wp-block-button__width-${ width }` ]: width, - [ `has-custom-font-size` ]: blockProps.style.fontSize, } ) } > <RichText @@ -282,11 +310,14 @@ function ButtonEdit( props ) { 'wp-block-button__link', colorProps.className, borderProps.className, + typographyProps.className, { [ `has-text-align-${ textAlign }` ]: textAlign, // For backwards compatibility add style that isn't // provided via block support. 'no-border-radius': style?.border?.radius === 0, + [ `has-custom-font-size` ]: + blockProps.style.fontSize, }, __experimentalGetElementClassName( 'button' ) ) } @@ -295,6 +326,8 @@ function ButtonEdit( props ) { ...colorProps.style, ...spacingProps.style, ...shadowProps.style, + ...typographyProps.style, + writingMode: undefined, } } onReplace={ onReplace } onMerge={ mergeBlocks } diff --git a/packages/block-library/src/button/save.js b/packages/block-library/src/button/save.js index 8cb9da6fbfbc18..4255868d50fbc5 100644 --- a/packages/block-library/src/button/save.js +++ b/packages/block-library/src/button/save.js @@ -14,6 +14,7 @@ import { __experimentalGetSpacingClassesAndStyles as getSpacingClassesAndStyles, __experimentalGetShadowClassesAndStyles as getShadowClassesAndStyles, __experimentalGetElementClassName, + getTypographyClassesAndStyles, } from '@wordpress/block-editor'; export default function save( { attributes, className } ) { @@ -38,15 +39,18 @@ export default function save( { attributes, className } ) { const colorProps = getColorClassesAndStyles( attributes ); const spacingProps = getSpacingClassesAndStyles( attributes ); const shadowProps = getShadowClassesAndStyles( attributes ); + const typographyProps = getTypographyClassesAndStyles( attributes ); const buttonClasses = clsx( 'wp-block-button__link', colorProps.className, borderProps.className, + typographyProps.className, { [ `has-text-align-${ textAlign }` ]: textAlign, // For backwards compatibility add style that isn't provided via // block support. 'no-border-radius': style?.border?.radius === 0, + [ `has-custom-font-size` ]: fontSize || style?.typography?.fontSize, }, __experimentalGetElementClassName( 'button' ) ); @@ -55,6 +59,8 @@ export default function save( { attributes, className } ) { ...colorProps.style, ...spacingProps.style, ...shadowProps.style, + ...typographyProps.style, + writingMode: undefined, }; // The use of a `title` attribute here is soft-deprecated, but still applied @@ -63,7 +69,6 @@ export default function save( { attributes, className } ) { const wrapperClasses = clsx( className, { [ `has-custom-width wp-block-button__width-${ width }` ]: width, - [ `has-custom-font-size` ]: fontSize || style?.typography?.fontSize, } ); return ( diff --git a/packages/block-library/src/categories/edit.js b/packages/block-library/src/categories/edit.js index 8bd2769b0ec711..d8ea455b9ba1f7 100644 --- a/packages/block-library/src/categories/edit.js +++ b/packages/block-library/src/categories/edit.js @@ -7,12 +7,13 @@ import clsx from 'clsx'; * WordPress dependencies */ import { - PanelBody, Placeholder, SelectControl, Spinner, ToggleControl, VisuallyHidden, + __experimentalToolsPanel as ToolsPanel, + __experimentalToolsPanelItem as ToolsPanelItem, } from '@wordpress/components'; import { useInstanceId } from '@wordpress/compose'; import { @@ -25,6 +26,11 @@ import { __, sprintf } from '@wordpress/i18n'; import { pin } from '@wordpress/icons'; import { useEntityRecords } from '@wordpress/core-data'; +/** + * Internal dependencies + */ +import { useToolsPanelDropdownMenuProps } from '../utils/hooks'; + export default function CategoriesEdit( { attributes: { displayAsDropdown, @@ -180,72 +186,154 @@ export default function CategoriesEdit( { const blockProps = useBlockProps( { className: classes, } ); + const dropdownMenuProps = useToolsPanelDropdownMenuProps(); return ( <TagName { ...blockProps }> <InspectorControls> - <PanelBody title={ __( 'Settings' ) }> + <ToolsPanel + label={ __( 'Settings' ) } + resetAll={ () => { + setAttributes( { + taxonomy: 'category', + displayAsDropdown: false, + showHierarchy: false, + showPostCounts: false, + showOnlyTopLevel: false, + showEmpty: false, + showLabel: true, + } ); + } } + dropdownMenuProps={ dropdownMenuProps } + > { Array.isArray( taxonomies ) && ( - <SelectControl - __nextHasNoMarginBottom - __next40pxDefaultSize + <ToolsPanelItem + hasValue={ () => { + return taxonomySlug !== 'category'; + } } label={ __( 'Taxonomy' ) } - options={ taxonomies.map( ( t ) => ( { - label: t.name, - value: t.slug, - } ) ) } - value={ taxonomySlug } - onChange={ ( selectedTaxonomy ) => - setAttributes( { - taxonomy: selectedTaxonomy, - } ) - } - /> + onDeselect={ () => { + setAttributes( { taxonomy: 'category' } ); + } } + isShownByDefault + > + <SelectControl + __nextHasNoMarginBottom + __next40pxDefaultSize + label={ __( 'Taxonomy' ) } + options={ taxonomies.map( ( t ) => ( { + label: t.name, + value: t.slug, + } ) ) } + value={ taxonomySlug } + onChange={ ( selectedTaxonomy ) => + setAttributes( { + taxonomy: selectedTaxonomy, + } ) + } + /> + </ToolsPanelItem> ) } - <ToggleControl - __nextHasNoMarginBottom + <ToolsPanelItem + hasValue={ () => !! displayAsDropdown } label={ __( 'Display as dropdown' ) } - checked={ displayAsDropdown } - onChange={ toggleAttribute( 'displayAsDropdown' ) } - /> - { displayAsDropdown && ( + onDeselect={ () => + setAttributes( { displayAsDropdown: false } ) + } + isShownByDefault + > <ToggleControl __nextHasNoMarginBottom - className="wp-block-categories__indentation" - label={ __( 'Show label' ) } - checked={ showLabel } - onChange={ toggleAttribute( 'showLabel' ) } + label={ __( 'Display as dropdown' ) } + checked={ displayAsDropdown } + onChange={ toggleAttribute( 'displayAsDropdown' ) } /> + </ToolsPanelItem> + { displayAsDropdown && ( + <ToolsPanelItem + hasValue={ () => ! showLabel } + label={ __( 'Show label' ) } + onDeselect={ () => + setAttributes( { showLabel: true } ) + } + isShownByDefault + > + <ToggleControl + __nextHasNoMarginBottom + className="wp-block-categories__indentation" + label={ __( 'Show label' ) } + checked={ showLabel } + onChange={ toggleAttribute( 'showLabel' ) } + /> + </ToolsPanelItem> ) } - <ToggleControl - __nextHasNoMarginBottom + <ToolsPanelItem + hasValue={ () => !! showPostCounts } label={ __( 'Show post counts' ) } - checked={ showPostCounts } - onChange={ toggleAttribute( 'showPostCounts' ) } - /> - { isHierarchicalTaxonomy && ( + onDeselect={ () => + setAttributes( { showPostCounts: false } ) + } + isShownByDefault + > <ToggleControl __nextHasNoMarginBottom - label={ __( 'Show only top level terms' ) } - checked={ showOnlyTopLevel } - onChange={ toggleAttribute( 'showOnlyTopLevel' ) } + label={ __( 'Show post counts' ) } + checked={ showPostCounts } + onChange={ toggleAttribute( 'showPostCounts' ) } /> + </ToolsPanelItem> + { isHierarchicalTaxonomy && ( + <ToolsPanelItem + hasValue={ () => !! showOnlyTopLevel } + label={ __( 'Show only top level terms' ) } + onDeselect={ () => + setAttributes( { showOnlyTopLevel: false } ) + } + isShownByDefault + > + <ToggleControl + __nextHasNoMarginBottom + label={ __( 'Show only top level terms' ) } + checked={ showOnlyTopLevel } + onChange={ toggleAttribute( + 'showOnlyTopLevel' + ) } + /> + </ToolsPanelItem> ) } - <ToggleControl - __nextHasNoMarginBottom + <ToolsPanelItem + hasValue={ () => !! showEmpty } label={ __( 'Show empty terms' ) } - checked={ showEmpty } - onChange={ toggleAttribute( 'showEmpty' ) } - /> - { isHierarchicalTaxonomy && ! showOnlyTopLevel && ( + onDeselect={ () => + setAttributes( { showEmpty: false } ) + } + isShownByDefault + > <ToggleControl __nextHasNoMarginBottom - label={ __( 'Show hierarchy' ) } - checked={ showHierarchy } - onChange={ toggleAttribute( 'showHierarchy' ) } + label={ __( 'Show empty terms' ) } + checked={ showEmpty } + onChange={ toggleAttribute( 'showEmpty' ) } /> + </ToolsPanelItem> + { isHierarchicalTaxonomy && ! showOnlyTopLevel && ( + <ToolsPanelItem + hasValue={ () => !! showHierarchy } + label={ __( 'Show hierarchy' ) } + onDeselect={ () => + setAttributes( { showHierarchy: false } ) + } + isShownByDefault + > + <ToggleControl + __nextHasNoMarginBottom + label={ __( 'Show hierarchy' ) } + checked={ showHierarchy } + onChange={ toggleAttribute( 'showHierarchy' ) } + /> + </ToolsPanelItem> ) } - </PanelBody> + </ToolsPanel> </InspectorControls> { isResolving && ( <Placeholder icon={ pin } label={ __( 'Terms' ) }> diff --git a/packages/block-library/src/column/edit.js b/packages/block-library/src/column/edit.js index a0f3cdcf65393d..92a0b41b44ed52 100644 --- a/packages/block-library/src/column/edit.js +++ b/packages/block-library/src/column/edit.js @@ -18,31 +18,52 @@ import { } from '@wordpress/block-editor'; import { __experimentalUseCustomUnits as useCustomUnits, - PanelBody, __experimentalUnitControl as UnitControl, + __experimentalToolsPanel as ToolsPanel, + __experimentalToolsPanelItem as ToolsPanelItem, } from '@wordpress/components'; import { useSelect, useDispatch } from '@wordpress/data'; import { sprintf, __ } from '@wordpress/i18n'; +/** + * Internal dependencies + */ +import { useToolsPanelDropdownMenuProps } from '../utils/hooks'; + function ColumnInspectorControls( { width, setAttributes } ) { const [ availableUnits ] = useSettings( 'spacing.units' ); const units = useCustomUnits( { availableUnits: availableUnits || [ '%', 'px', 'em', 'rem', 'vw' ], } ); + const dropdownMenuProps = useToolsPanelDropdownMenuProps(); return ( - <PanelBody title={ __( 'Settings' ) }> - <UnitControl + <ToolsPanel + label={ __( 'Settings' ) } + resetAll={ () => { + setAttributes( { width: undefined } ); + } } + dropdownMenuProps={ dropdownMenuProps } + > + <ToolsPanelItem + hasValue={ () => width !== undefined } label={ __( 'Width' ) } - __unstableInputWidth="calc(50% - 8px)" - __next40pxDefaultSize - value={ width || '' } - onChange={ ( nextWidth ) => { - nextWidth = 0 > parseFloat( nextWidth ) ? '0' : nextWidth; - setAttributes( { width: nextWidth } ); - } } - units={ units } - /> - </PanelBody> + onDeselect={ () => setAttributes( { width: undefined } ) } + isShownByDefault + > + <UnitControl + label={ __( 'Width' ) } + __unstableInputWidth="calc(50% - 8px)" + __next40pxDefaultSize + value={ width || '' } + onChange={ ( nextWidth ) => { + nextWidth = + 0 > parseFloat( nextWidth ) ? '0' : nextWidth; + setAttributes( { width: nextWidth } ); + } } + units={ units } + /> + </ToolsPanelItem> + </ToolsPanel> ); } diff --git a/packages/block-library/src/columns/edit.js b/packages/block-library/src/columns/edit.js index f8cf0297302ccd..11a1b58bd213bb 100644 --- a/packages/block-library/src/columns/edit.js +++ b/packages/block-library/src/columns/edit.js @@ -9,9 +9,11 @@ import clsx from 'clsx'; import { __ } from '@wordpress/i18n'; import { Notice, - PanelBody, RangeControl, ToggleControl, + __experimentalToolsPanel as ToolsPanel, + __experimentalToolsPanelItem as ToolsPanelItem, + __experimentalVStack as VStack, } from '@wordpress/components'; import { @@ -39,6 +41,7 @@ import { getRedistributedColumnWidths, toWidthPrecision, } from './utils'; +import { useToolsPanelDropdownMenuProps } from '../utils/hooks'; const DEFAULT_BLOCK = { name: 'core/column', @@ -51,19 +54,15 @@ function ColumnInspectorControls( { } ) { const { count, canInsertColumnBlock, minCount } = useSelect( ( select ) => { - const { - canInsertBlockType, - canRemoveBlock, - getBlocks, - getBlockCount, - } = select( blockEditorStore ); - const innerBlocks = getBlocks( clientId ); + const { canInsertBlockType, canRemoveBlock, getBlockOrder } = + select( blockEditorStore ); + const blockOrder = getBlockOrder( clientId ); // Get the indexes of columns for which removal is prevented. // The highest index will be used to determine the minimum column count. - const preventRemovalBlockIndexes = innerBlocks.reduce( - ( acc, block, index ) => { - if ( ! canRemoveBlock( block.clientId ) ) { + const preventRemovalBlockIndexes = blockOrder.reduce( + ( acc, blockId, index ) => { + if ( ! canRemoveBlock( blockId ) ) { acc.push( index ); } return acc; @@ -72,7 +71,7 @@ function ColumnInspectorControls( { ); return { - count: getBlockCount( clientId ), + count: blockOrder.length, canInsertColumnBlock: canInsertBlockType( 'core/column', clientId @@ -148,41 +147,73 @@ function ColumnInspectorControls( { replaceInnerBlocks( clientId, innerBlocks ); } + const dropdownMenuProps = useToolsPanelDropdownMenuProps(); + return ( - <PanelBody title={ __( 'Settings' ) }> + <ToolsPanel + label={ __( 'Settings' ) } + resetAll={ () => { + updateColumns( count, minCount ); + setAttributes( { + isStackedOnMobile: true, + } ); + } } + dropdownMenuProps={ dropdownMenuProps } + > { canInsertColumnBlock && ( - <> - <RangeControl - __nextHasNoMarginBottom - __next40pxDefaultSize - label={ __( 'Columns' ) } - value={ count } - onChange={ ( value ) => - updateColumns( count, Math.max( minCount, value ) ) - } - min={ Math.max( 1, minCount ) } - max={ Math.max( 6, count ) } - /> - { count > 6 && ( - <Notice status="warning" isDismissible={ false }> - { __( - 'This column count exceeds the recommended amount and may cause visual breakage.' - ) } - </Notice> - ) } - </> + <ToolsPanelItem + label={ __( 'Columns' ) } + isShownByDefault + hasValue={ () => count } + onDeselect={ () => updateColumns( count, minCount ) } + > + <VStack spacing={ 4 }> + <RangeControl + __nextHasNoMarginBottom + __next40pxDefaultSize + label={ __( 'Columns' ) } + value={ count } + onChange={ ( value ) => + updateColumns( + count, + Math.max( minCount, value ) + ) + } + min={ Math.max( 1, minCount ) } + max={ Math.max( 6, count ) } + /> + { count > 6 && ( + <Notice status="warning" isDismissible={ false }> + { __( + 'This column count exceeds the recommended amount and may cause visual breakage.' + ) } + </Notice> + ) } + </VStack> + </ToolsPanelItem> ) } - <ToggleControl - __nextHasNoMarginBottom + <ToolsPanelItem label={ __( 'Stack on mobile' ) } - checked={ isStackedOnMobile } - onChange={ () => + isShownByDefault + hasValue={ () => isStackedOnMobile !== true } + onDeselect={ () => setAttributes( { - isStackedOnMobile: ! isStackedOnMobile, + isStackedOnMobile: true, } ) } - /> - </PanelBody> + > + <ToggleControl + __nextHasNoMarginBottom + label={ __( 'Stack on mobile' ) } + checked={ isStackedOnMobile } + onChange={ () => + setAttributes( { + isStackedOnMobile: ! isStackedOnMobile, + } ) + } + /> + </ToolsPanelItem> + </ToolsPanel> ); } @@ -211,7 +242,7 @@ function ColumnsEditContainer( { attributes, setAttributes, clientId } ) { /** * Update all child Column blocks with a new vertical alignment setting * based on whatever alignment is passed in. This allows change to parent - * to overide anything set on a individual column basis. + * to override anything set on a individual column basis. * * @param {string} newVerticalAlignment The vertical alignment setting. */ diff --git a/packages/block-library/src/columns/edit.native.js b/packages/block-library/src/columns/edit.native.js index 07655981edcada..abbc458307f5ca 100644 --- a/packages/block-library/src/columns/edit.native.js +++ b/packages/block-library/src/columns/edit.native.js @@ -287,7 +287,7 @@ const ColumnsEditContainerWrapper = withDispatch( /** * Update all child Column blocks with a new vertical alignment setting * based on whatever alignment is passed in. This allows change to parent - * to overide anything set on a individual column basis. + * to override anything set on a individual column basis. * * @param {string} verticalAlignment the vertical alignment setting */ diff --git a/packages/block-library/src/comment-template/edit.js b/packages/block-library/src/comment-template/edit.js index 50d83289e1ed92..038583e68c85cb 100644 --- a/packages/block-library/src/comment-template/edit.js +++ b/packages/block-library/src/comment-template/edit.js @@ -168,7 +168,7 @@ const CommentTemplatePreview = ( { }; // We have to hide the preview block if the `comment` props points to - // the curently active block! + // the currently active block! // Or, to put it differently, every preview block is visible unless it is the // currently active block - in this case we render its inner blocks. @@ -222,7 +222,7 @@ const CommentsList = ( { // "placeholder" and that the block is most likely being used in the // site editor. In this case, we have to set the commentId to `null` // because otherwise the (non-existent) comment with a negative ID - // would be reqested from the REST API. + // would be requested from the REST API. commentId: commentId < 0 ? null : commentId, } } > diff --git a/packages/block-library/src/comments-pagination-next/block.json b/packages/block-library/src/comments-pagination-next/block.json index 3f7ebe677328d5..22e20bfa8dbf2d 100644 --- a/packages/block-library/src/comments-pagination-next/block.json +++ b/packages/block-library/src/comments-pagination-next/block.json @@ -12,11 +12,6 @@ "type": "string" } }, - "example": { - "attributes": { - "label": "Comments Next Page" - } - }, "usesContext": [ "postId", "comments/paginationArrow" ], "supports": { "reusable": false, diff --git a/packages/block-library/src/comments-pagination-next/index.js b/packages/block-library/src/comments-pagination-next/index.js index 2df0e8da6aa99d..5e67bc851b1791 100644 --- a/packages/block-library/src/comments-pagination-next/index.js +++ b/packages/block-library/src/comments-pagination-next/index.js @@ -1,6 +1,7 @@ /** * WordPress dependencies */ +import { __ } from '@wordpress/i18n'; import { queryPaginationNext as icon } from '@wordpress/icons'; /** @@ -16,6 +17,11 @@ export { metadata, name }; export const settings = { icon, edit, + example: { + attributes: { + label: __( 'Newer Comments' ), + }, + }, }; export const init = () => initBlock( { name, metadata, settings } ); diff --git a/packages/block-library/src/comments-pagination-previous/block.json b/packages/block-library/src/comments-pagination-previous/block.json index eb5203af33c866..0871b000c569dd 100644 --- a/packages/block-library/src/comments-pagination-previous/block.json +++ b/packages/block-library/src/comments-pagination-previous/block.json @@ -12,11 +12,6 @@ "type": "string" } }, - "example": { - "attributes": { - "label": "Comments Previous Page" - } - }, "usesContext": [ "postId", "comments/paginationArrow" ], "supports": { "reusable": false, diff --git a/packages/block-library/src/comments-pagination-previous/index.js b/packages/block-library/src/comments-pagination-previous/index.js index 80e555ccc79d9b..975d4c0b6cbc02 100644 --- a/packages/block-library/src/comments-pagination-previous/index.js +++ b/packages/block-library/src/comments-pagination-previous/index.js @@ -1,6 +1,7 @@ /** * WordPress dependencies */ +import { __ } from '@wordpress/i18n'; import { queryPaginationPrevious as icon } from '@wordpress/icons'; /** @@ -16,6 +17,11 @@ export { metadata, name }; export const settings = { icon, edit, + example: { + attributes: { + label: __( 'Older Comments' ), + }, + }, }; export const init = () => initBlock( { name, metadata, settings } ); diff --git a/packages/block-library/src/comments-pagination/editor.scss b/packages/block-library/src/comments-pagination/editor.scss index a875c9e0ee21ce..3cd99c632ee833 100644 --- a/packages/block-library/src/comments-pagination/editor.scss +++ b/packages/block-library/src/comments-pagination/editor.scss @@ -26,6 +26,7 @@ $pagination-margin: 0.5em; margin-right: $pagination-margin; margin-bottom: $pagination-margin; + font-size: inherit; &:last-child { /*rtl:ignore*/ margin-right: 0; diff --git a/packages/block-library/src/comments-pagination/style.scss b/packages/block-library/src/comments-pagination/style.scss index c6b5d9a0a29e91..2fb6e3dd2d48f4 100644 --- a/packages/block-library/src/comments-pagination/style.scss +++ b/packages/block-library/src/comments-pagination/style.scss @@ -8,6 +8,7 @@ $pagination-margin: 0.5em; margin-right: $pagination-margin; margin-bottom: $pagination-margin; + font-size: inherit; &:last-child { /*rtl:ignore*/ margin-right: 0; diff --git a/packages/block-library/src/comments/edit/comments-inspector-controls.js b/packages/block-library/src/comments/edit/comments-inspector-controls.js index 1a33cb68ea38a6..fda1fb3cc2e4bd 100644 --- a/packages/block-library/src/comments/edit/comments-inspector-controls.js +++ b/packages/block-library/src/comments/edit/comments-inspector-controls.js @@ -5,18 +5,15 @@ import { SelectControl } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import { InspectorControls } from '@wordpress/block-editor'; +/** + * Internal dependencies + */ +import { htmlElementMessages } from '../../utils/messages'; + export default function CommentsInspectorControls( { attributes: { tagName }, setAttributes, } ) { - const htmlElementMessages = { - section: __( - "The <section> element should represent a standalone portion of the document that can't be better represented by another element." - ), - aside: __( - "The <aside> element should represent a portion of a document whose content is only indirectly related to the document's main content." - ), - }; return ( <InspectorControls> <InspectorControls group="advanced"> diff --git a/packages/block-library/src/comments/index.js b/packages/block-library/src/comments/index.js index 21db8b986d6e5e..b907bd41e3c6a2 100644 --- a/packages/block-library/src/comments/index.js +++ b/packages/block-library/src/comments/index.js @@ -17,6 +17,7 @@ export { metadata, name }; export const settings = { icon, + example: {}, edit, save, deprecated, diff --git a/packages/block-library/src/cover/deprecated.js b/packages/block-library/src/cover/deprecated.js index 6dfad9735457f6..6966c1971d7beb 100644 --- a/packages/block-library/src/cover/deprecated.js +++ b/packages/block-library/src/cover/deprecated.js @@ -885,7 +885,7 @@ const v11 = { migrate: migrateTag, }; -// Deprecation for blocks that renders fixed background as backgroud from the main block container. +// Deprecation for blocks that renders fixed background as background from the main block container. const v10 = { attributes: v8ToV11BlockAttributes, supports: v7toV11BlockSupports, diff --git a/packages/block-library/src/cover/edit.native.js b/packages/block-library/src/cover/edit.native.js index 99324545bf798e..7f73ec85a798e6 100644 --- a/packages/block-library/src/cover/edit.native.js +++ b/packages/block-library/src/cover/edit.native.js @@ -58,7 +58,7 @@ import { useCallback, useMemo, } from '@wordpress/element'; -import { cover as icon, replace, image, warning } from '@wordpress/icons'; +import { cover as icon, replace, image, cautionFilled } from '@wordpress/icons'; import { getProtocol } from '@wordpress/url'; // eslint-disable-next-line no-restricted-imports import { store as editPostStore } from '@wordpress/edit-post'; @@ -665,7 +665,10 @@ const Cover = ( { style={ styles.uploadFailedContainer } > <View style={ styles.uploadFailed }> - <Icon icon={ warning } { ...styles.uploadFailedIcon } /> + <Icon + icon={ cautionFilled } + { ...styles.uploadFailedIcon } + /> </View> </View> ) } diff --git a/packages/block-library/src/cover/edit/index.js b/packages/block-library/src/cover/edit/index.js index 1c86d953bc0bab..1eafe99e283eb4 100644 --- a/packages/block-library/src/cover/edit/index.js +++ b/packages/block-library/src/cover/edit/index.js @@ -114,13 +114,22 @@ function CoverEdit( { const { __unstableMarkNextChangeAsNotPersistent } = useDispatch( blockEditorStore ); - const media = useSelect( - ( select ) => - featuredImage && - select( coreStore ).getMedia( featuredImage, { context: 'view' } ), - [ featuredImage ] + const { media } = useSelect( + ( select ) => { + return { + media: + featuredImage && useFeaturedImage + ? select( coreStore ).getMedia( featuredImage, { + context: 'view', + } ) + : undefined, + }; + }, + [ featuredImage, useFeaturedImage ] ); - const mediaUrl = media?.source_url; + const mediaUrl = + media?.media_details?.sizes?.[ sizeSlug ]?.source_url ?? + media?.source_url; // User can change the featured image outside of the block, but we still // need to update the block when that happens. This effect should only @@ -201,7 +210,7 @@ function CoverEdit( { averageBackgroundColor ); - if ( backgroundType === IMAGE_BACKGROUND_TYPE && mediaAttributes.id ) { + if ( backgroundType === IMAGE_BACKGROUND_TYPE && mediaAttributes?.id ) { const { imageDefaultSize } = getSettings(); // Try to use the previous selected image size if it's available @@ -451,6 +460,7 @@ function CoverEdit( { toggleUseFeaturedImage={ toggleUseFeaturedImage } updateDimRatio={ onUpdateDimRatio } onClearMedia={ onClearMedia } + featuredImage={ media } /> ); diff --git a/packages/block-library/src/cover/edit/inspector-controls.js b/packages/block-library/src/cover/edit/inspector-controls.js index c0807869ee1a5c..bca844a9b4cedc 100644 --- a/packages/block-library/src/cover/edit/inspector-controls.js +++ b/packages/block-library/src/cover/edit/inspector-controls.js @@ -36,6 +36,7 @@ import { COVER_MIN_HEIGHT, mediaPosition } from '../shared'; import { unlock } from '../../lock-unlock'; import { useToolsPanelDropdownMenuProps } from '../../utils/hooks'; import { DEFAULT_MEDIA_SIZE_SLUG } from '../constants'; +import { htmlElementMessages } from '../../utils/messages'; const { cleanEmptyObject, ResolutionTool } = unlock( blockEditorPrivateApis ); @@ -96,6 +97,7 @@ export default function CoverInspectorControls( { coverRef, currentSettings, updateDimRatio, + featuredImage, } ) { const { useFeaturedImage, @@ -132,8 +134,12 @@ export default function CoverInspectorControls( { [ id, isImageBackground ] ); + const currentBackgroundImage = useFeaturedImage ? featuredImage : image; + function updateImage( newSizeSlug ) { - const newUrl = image?.media_details?.sizes?.[ newSizeSlug ]?.source_url; + const newUrl = + currentBackgroundImage?.media_details?.sizes?.[ newSizeSlug ] + ?.source_url; if ( ! newUrl ) { return null; } @@ -146,7 +152,9 @@ export default function CoverInspectorControls( { const imageSizeOptions = imageSizes ?.filter( - ( { slug } ) => image?.media_details?.sizes?.[ slug ]?.source_url + ( { slug } ) => + currentBackgroundImage?.media_details?.sizes?.[ slug ] + ?.source_url ) ?.map( ( { name, slug } ) => ( { value: slug, label: name } ) ); @@ -176,27 +184,6 @@ export default function CoverInspectorControls( { const colorGradientSettings = useMultipleOriginColorsAndGradients(); - const htmlElementMessages = { - header: __( - 'The <header> element should represent introductory content, typically a group of introductory or navigational aids.' - ), - main: __( - 'The <main> element should be used for the primary content of your document only.' - ), - section: __( - "The <section> element should represent a standalone portion of the document that can't be better represented by another element." - ), - article: __( - 'The <article> element should represent a self-contained, syndicatable portion of the document.' - ), - aside: __( - "The <aside> element should represent a portion of a document whose content is only indirectly related to the document's main content." - ), - footer: __( - 'The <footer> element should represent a footer for its nearest sectioning element (e.g.: <section>, <article>, <main> etc.).' - ), - }; - const dropdownMenuProps = useToolsPanelDropdownMenuProps(); return ( @@ -321,7 +308,7 @@ export default function CoverInspectorControls( { /> </ToolsPanelItem> ) } - { ! useFeaturedImage && !! imageSizeOptions?.length && ( + { !! imageSizeOptions?.length && ( <ResolutionTool value={ sizeSlug } onChange={ updateImage } diff --git a/packages/block-library/src/cover/index.js b/packages/block-library/src/cover/index.js index e6797a3b51dbe4..b5de174f708085 100644 --- a/packages/block-library/src/cover/index.js +++ b/packages/block-library/src/cover/index.js @@ -26,6 +26,14 @@ export const settings = { customOverlayColor: '#065174', dimRatio: 40, url: 'https://s.w.org/images/core/5.3/Windbuchencom.jpg', + style: { + typography: { + fontSize: 48, + }, + color: { + text: 'white', + }, + }, }, innerBlocks: [ { @@ -33,14 +41,6 @@ export const settings = { attributes: { content: __( '<strong>Snow Patrol</strong>' ), align: 'center', - style: { - typography: { - fontSize: 48, - }, - color: { - text: 'white', - }, - }, }, }, ], diff --git a/packages/block-library/src/cover/index.php b/packages/block-library/src/cover/index.php index 1ffe7ab3f4dbc6..630835a47947b3 100644 --- a/packages/block-library/src/cover/index.php +++ b/packages/block-library/src/cover/index.php @@ -35,12 +35,12 @@ function render_block_core_cover( $attributes, $content ) { $attr['style'] = 'object-position:' . $object_position . ';'; } - $image = get_the_post_thumbnail( null, 'post-thumbnail', $attr ); + $image = get_the_post_thumbnail( null, $attributes['sizeSlug'] ?? 'post-thumbnail', $attr ); } else { if ( in_the_loop() ) { update_post_thumbnail_cache(); } - $current_featured_image = get_the_post_thumbnail_url(); + $current_featured_image = get_the_post_thumbnail_url( null, $attributes['sizeSlug'] ?? null ); if ( ! $current_featured_image ) { return $content; } diff --git a/packages/block-library/src/cover/shared.js b/packages/block-library/src/cover/shared.js index 37390354a37d63..7628300cbf8cff 100644 --- a/packages/block-library/src/cover/shared.js +++ b/packages/block-library/src/cover/shared.js @@ -35,7 +35,7 @@ export function dimRatioToClass( ratio ) { } export function attributesFromMedia( media ) { - if ( ! media || ! media.url ) { + if ( ! media || ( ! media.url && ! media.src ) ) { return { url: undefined, id: undefined, @@ -52,23 +52,23 @@ export function attributesFromMedia( media ) { if ( media.media_type === IMAGE_BACKGROUND_TYPE ) { mediaType = IMAGE_BACKGROUND_TYPE; } else { - // only images and videos are accepted so if the media_type is not an image we can assume it is a video. + // Only images and videos are accepted so if the media_type is not an image we can assume it is a video. // Videos contain the media type of 'file' in the object returned from the rest api. mediaType = VIDEO_BACKGROUND_TYPE; } - } else { // For media selections originated from existing files in the media library. - if ( - media.type !== IMAGE_BACKGROUND_TYPE && - media.type !== VIDEO_BACKGROUND_TYPE - ) { - return; - } + } else if ( + media.type && + ( media.type === IMAGE_BACKGROUND_TYPE || + media.type === VIDEO_BACKGROUND_TYPE ) + ) { mediaType = media.type; + } else { + return; } return { - url: media.url, + url: media.url || media.src, id: media.id, alt: media?.alt, backgroundType: mediaType, diff --git a/packages/block-library/src/cover/test/__snapshots__/edit.native.js.snap b/packages/block-library/src/cover/test/__snapshots__/edit.native.js.snap index 6abcd0458752b0..647e401636ce61 100644 --- a/packages/block-library/src/cover/test/__snapshots__/edit.native.js.snap +++ b/packages/block-library/src/cover/test/__snapshots__/edit.native.js.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`color settings clears the selected overlay color and mantains the inner blocks 1`] = ` +exports[`color settings clears the selected overlay color and maintains the inner blocks 1`] = ` "<!-- wp:cover {"isUserOverlayColor":true,"isDark":false} --> <div class="wp-block-cover is-light"><span aria-hidden="true" class="wp-block-cover__background has-background-dim-100 has-background-dim"></span><div class="wp-block-cover__inner-container"><!-- wp:paragraph {"align":"center","placeholder":"Write title…"} --> <p class="has-text-align-center"></p> diff --git a/packages/block-library/src/cover/test/edit.js b/packages/block-library/src/cover/test/edit.js index 5c1a5b5e13e67d..0a18d2cf3f9f8e 100644 --- a/packages/block-library/src/cover/test/edit.js +++ b/packages/block-library/src/cover/test/edit.js @@ -48,7 +48,7 @@ async function setup( attributes, useCoreBlocks, customSettings ) { async function createAndSelectBlock() { await userEvent.click( screen.getByRole( 'option', { - name: 'Color: Black', + name: 'Black', } ) ); await userEvent.click( @@ -73,7 +73,7 @@ describe( 'Cover block', () => { test( 'can set overlay color using color picker on block placeholder', async () => { const { container } = await setup(); const colorPicker = screen.getByRole( 'option', { - name: 'Color: Black', + name: 'Black', } ); await userEvent.click( colorPicker ); const color = colorPicker.style.backgroundColor; @@ -97,7 +97,7 @@ describe( 'Cover block', () => { await userEvent.click( screen.getByRole( 'option', { - name: 'Color: Black', + name: 'Black', } ) ); @@ -200,9 +200,7 @@ describe( 'Cover block', () => { await selectBlock( 'Block: Cover' ); expect( - screen.getByRole( 'heading', { - name: 'Settings', - } ) + await screen.findByRole( 'heading', { name: 'Settings' } ) ).toBeInTheDocument(); } ); } ); @@ -216,7 +214,7 @@ describe( 'Cover block', () => { ); await selectBlock( 'Block: Cover' ); await userEvent.click( - screen.getByLabelText( 'Fixed background' ) + await screen.findByLabelText( 'Fixed background' ) ); expect( screen.getByLabelText( 'Block: Cover' ) ).toHaveClass( 'has-parallax' @@ -232,7 +230,7 @@ describe( 'Cover block', () => { ); await selectBlock( 'Block: Cover' ); await userEvent.click( - screen.getByLabelText( 'Repeated background' ) + await screen.findByLabelText( 'Repeated background' ) ); expect( screen.getByLabelText( 'Block: Cover' ) ).toHaveClass( 'is-repeated' @@ -245,7 +243,7 @@ describe( 'Cover block', () => { } ); await selectBlock( 'Block: Cover' ); - await userEvent.clear( screen.getByLabelText( 'Left' ) ); + await userEvent.clear( await screen.findByLabelText( 'Left' ) ); await userEvent.type( screen.getByLabelText( 'Left' ), '100' ); expect( @@ -262,7 +260,7 @@ describe( 'Cover block', () => { await selectBlock( 'Block: Cover' ); await userEvent.type( - screen.getByLabelText( 'Alternative text' ), + await screen.findByLabelText( 'Alternative text' ), 'Me' ); expect( screen.getByAltText( 'Me' ) ).toBeInTheDocument(); @@ -337,7 +335,7 @@ describe( 'Cover block', () => { describe( 'when colors are disabled', () => { test( 'does not render overlay control', async () => { await setup( undefined, true, disabledColorSettings ); - await createAndSelectBlock(); + await selectBlock( 'Block: Cover' ); await userEvent.click( screen.getByRole( 'tab', { name: 'Styles' } ) ); @@ -350,7 +348,7 @@ describe( 'Cover block', () => { } ); test( 'does not render opacity control', async () => { await setup( undefined, true, disabledColorSettings ); - await createAndSelectBlock(); + await selectBlock( 'Block: Cover' ); await userEvent.click( screen.getByRole( 'tab', { name: 'Styles' } ) ); @@ -392,7 +390,7 @@ describe( 'Cover block', () => { test( 'should toggle is-light class if background changed from light to dark', async () => { await setup(); const colorPicker = screen.getByRole( 'option', { - name: 'Color: White', + name: 'White', } ); await userEvent.click( colorPicker ); @@ -408,7 +406,7 @@ describe( 'Cover block', () => { ); await userEvent.click( screen.getByText( 'Overlay' ) ); const popupColorPicker = screen.getByRole( 'option', { - name: 'Color: Black', + name: 'Black', } ); await userEvent.click( popupColorPicker ); expect( coverBlock ).not.toHaveClass( 'is-light' ); @@ -416,7 +414,7 @@ describe( 'Cover block', () => { test( 'should remove is-light class if overlay color is removed', async () => { await setup(); const colorPicker = screen.getByRole( 'option', { - name: 'Color: White', + name: 'White', } ); await userEvent.click( colorPicker ); const coverBlock = screen.getByLabelText( 'Block: Cover' ); @@ -431,7 +429,7 @@ describe( 'Cover block', () => { // The default color is black, so clicking the black color option will remove the background color, // which should remove the isDark setting and assign the is-light class. const popupColorPicker = screen.getByRole( 'option', { - name: 'Color: White', + name: 'White', } ); await userEvent.click( popupColorPicker ); expect( coverBlock ).not.toHaveClass( 'is-light' ); diff --git a/packages/block-library/src/cover/test/edit.native.js b/packages/block-library/src/cover/test/edit.native.js index 1b8a7133926d9d..461a75f69e075d 100644 --- a/packages/block-library/src/cover/test/edit.native.js +++ b/packages/block-library/src/cover/test/edit.native.js @@ -101,7 +101,7 @@ const attributes = { }; beforeAll( () => { - // Mock Image.getSize to avoid failed attempt to size non-existant image. + // Mock Image.getSize to avoid failed attempt to size non-existent image. const getSizeSpy = jest.spyOn( Image, 'getSize' ); getSizeSpy.mockImplementation( ( _url, callback ) => callback( 300, 200 ) ); @@ -541,7 +541,7 @@ describe( 'color settings', () => { expect( getEditorHtml() ).toMatchSnapshot(); } ); - it( 'clears the selected overlay color and mantains the inner blocks', async () => { + it( 'clears the selected overlay color and maintains the inner blocks', async () => { const screen = await initializeEditor( { initialHtml: COVER_BLOCK_SOLID_COLOR_HTML, } ); diff --git a/packages/block-library/src/cover/test/transforms.native.js b/packages/block-library/src/cover/test/transforms.native.js index 4232570eb0779f..5a664d51f78ba8 100644 --- a/packages/block-library/src/cover/test/transforms.native.js +++ b/packages/block-library/src/cover/test/transforms.native.js @@ -23,16 +23,16 @@ const initialHtmlWithVideo = ` <!-- /wp:paragraph --></div></div> <!-- /wp:cover -->`; -const tranformsWithInnerBlocks = [ 'Columns', 'Group' ]; +const transformsWithInnerBlocks = [ 'Columns', 'Group' ]; const blockTransformsWithImage = [ 'Image', 'Media & Text', - ...tranformsWithInnerBlocks, + ...transformsWithInnerBlocks, ]; const blockTransformsWithVideo = [ 'Video', 'Media & Text', - ...tranformsWithInnerBlocks, + ...transformsWithInnerBlocks, ]; setupCoreBlocks(); @@ -52,7 +52,9 @@ describe( `${ block } block transformations`, () => { { isMediaBlock: true, hasInnerBlocks: - tranformsWithInnerBlocks.includes( blockTransform ), + transformsWithInnerBlocks.includes( + blockTransform + ), } ); expect( newBlock ).toBeVisible(); @@ -88,7 +90,9 @@ describe( `${ block } block transformations`, () => { { isMediaBlock: true, hasInnerBlocks: - tranformsWithInnerBlocks.includes( blockTransform ), + transformsWithInnerBlocks.includes( + blockTransform + ), } ); expect( newBlock ).toBeVisible(); diff --git a/packages/block-library/src/details/block.json b/packages/block-library/src/details/block.json index 0cd0040cdde1b9..e4fadc4a064f97 100644 --- a/packages/block-library/src/details/block.json +++ b/packages/block-library/src/details/block.json @@ -16,6 +16,15 @@ "type": "rich-text", "source": "rich-text", "selector": "summary" + }, + "name": { + "type": "string", + "source": "attribute", + "attribute": "name", + "selector": ".wp-block-details" + }, + "allowedBlocks": { + "type": "array" } }, "supports": { diff --git a/packages/block-library/src/details/edit.js b/packages/block-library/src/details/edit.js index 314556ba6d5919..b7e8f815e21c06 100644 --- a/packages/block-library/src/details/edit.js +++ b/packages/block-library/src/details/edit.js @@ -5,12 +5,21 @@ import { RichText, useBlockProps, useInnerBlocksProps, - store as blockEditorStore, InspectorControls, } from '@wordpress/block-editor'; -import { useSelect } from '@wordpress/data'; -import { PanelBody, ToggleControl } from '@wordpress/components'; +import { + TextControl, + ToggleControl, + __experimentalToolsPanel as ToolsPanel, + __experimentalToolsPanelItem as ToolsPanelItem, +} from '@wordpress/components'; import { __ } from '@wordpress/i18n'; +import { useState } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import { useToolsPanelDropdownMenuProps } from '../utils/hooks'; const TEMPLATE = [ [ @@ -21,49 +30,73 @@ const TEMPLATE = [ ], ]; -function DetailsEdit( { attributes, setAttributes, clientId } ) { - const { showContent, summary } = attributes; +function DetailsEdit( { attributes, setAttributes } ) { + const { name, showContent, summary, allowedBlocks } = attributes; const blockProps = useBlockProps(); const innerBlocksProps = useInnerBlocksProps( blockProps, { template: TEMPLATE, __experimentalCaptureToolbars: true, + allowedBlocks, } ); - - // Check if either the block or the inner blocks are selected. - const hasSelection = useSelect( - ( select ) => { - const { isBlockSelected, hasSelectedInnerBlock } = - select( blockEditorStore ); - /* Sets deep to true to also find blocks inside the details content block. */ - return ( - hasSelectedInnerBlock( clientId, true ) || - isBlockSelected( clientId ) - ); - }, - [ clientId ] - ); + const [ isOpen, setIsOpen ] = useState( showContent ); + const dropdownMenuProps = useToolsPanelDropdownMenuProps(); return ( <> <InspectorControls> - <PanelBody title={ __( 'Settings' ) }> - <ToggleControl - __nextHasNoMarginBottom + <ToolsPanel + label={ __( 'Settings' ) } + resetAll={ () => { + setAttributes( { + showContent: false, + } ); + } } + dropdownMenuProps={ dropdownMenuProps } + > + <ToolsPanelItem + isShownByDefault label={ __( 'Open by default' ) } - checked={ showContent } - onChange={ () => + hasValue={ () => showContent } + onDeselect={ () => { setAttributes( { - showContent: ! showContent, - } ) - } - /> - </PanelBody> + showContent: false, + } ); + } } + > + <ToggleControl + __nextHasNoMarginBottom + label={ __( 'Open by default' ) } + checked={ showContent } + onChange={ () => + setAttributes( { + showContent: ! showContent, + } ) + } + /> + </ToolsPanelItem> + </ToolsPanel> + </InspectorControls> + <InspectorControls group="advanced"> + <TextControl + __next40pxDefaultSize + __nextHasNoMarginBottom + label={ __( 'Name attribute' ) } + value={ name || '' } + onChange={ ( newName ) => + setAttributes( { name: newName } ) + } + help={ __( + 'Enables multiple Details blocks with the same name attribute to be connected, with only one open at a time.' + ) } + /> </InspectorControls> - <details - { ...innerBlocksProps } - open={ hasSelection || showContent } - > - <summary onClick={ ( event ) => event.preventDefault() }> + <details { ...innerBlocksProps } open={ isOpen }> + <summary + onClick={ ( event ) => { + event.preventDefault(); + setIsOpen( ! isOpen ); + } } + > <RichText identifier="summary" aria-label={ __( 'Write summary' ) } diff --git a/packages/block-library/src/details/index.js b/packages/block-library/src/details/index.js index 3ba5efb04e27d2..31ee2e0e00f139 100644 --- a/packages/block-library/src/details/index.js +++ b/packages/block-library/src/details/index.js @@ -2,7 +2,7 @@ * WordPress dependencies */ import { details as icon } from '@wordpress/icons'; -import { __ } from '@wordpress/i18n'; +import { __, sprintf } from '@wordpress/i18n'; /** * Internal dependencies @@ -34,6 +34,28 @@ export const settings = { }, ], }, + __experimentalLabel( attributes, { context } ) { + const { summary } = attributes; + + const customName = attributes?.metadata?.name; + const hasSummary = summary?.trim().length > 0; + + // In the list view, use the block's summary as the label. + // If the summary is empty, fall back to the default label. + if ( context === 'list-view' && ( customName || hasSummary ) ) { + return customName || summary; + } + + if ( context === 'accessibility' ) { + return ! hasSummary + ? __( 'Details. Empty.' ) + : sprintf( + /* translators: accessibility text; summary title. */ + __( 'Details. %s' ), + summary + ); + } + }, save, edit, transforms, diff --git a/packages/block-library/src/details/save.js b/packages/block-library/src/details/save.js index 0df5f63ce9410d..c7594d4d5aa3f4 100644 --- a/packages/block-library/src/details/save.js +++ b/packages/block-library/src/details/save.js @@ -4,12 +4,16 @@ import { RichText, useBlockProps, InnerBlocks } from '@wordpress/block-editor'; export default function save( { attributes } ) { - const { showContent } = attributes; + const { name, showContent } = attributes; const summary = attributes.summary ? attributes.summary : 'Details'; const blockProps = useBlockProps.save(); return ( - <details { ...blockProps } open={ showContent }> + <details + { ...blockProps } + name={ name || undefined } + open={ showContent } + > <summary> <RichText.Content value={ summary } /> </summary> diff --git a/packages/block-library/src/editor.scss b/packages/block-library/src/editor.scss index a16d5a6c2c69c7..336d99935f001f 100644 --- a/packages/block-library/src/editor.scss +++ b/packages/block-library/src/editor.scss @@ -49,7 +49,6 @@ @import "./template-part/editor.scss"; @import "./text-columns/editor.scss"; @import "./video/editor.scss"; -@import "./post-template/editor.scss"; @import "./query/editor.scss"; @import "./query-pagination/editor.scss"; @import "./query-pagination-numbers/editor.scss"; @@ -66,7 +65,7 @@ // Font sizes (not used now, kept because of backward compatibility). // // The reason we add the editor class wrapper here is -// to avoid enqueing the classes twice: here and in ./editor.scss +// to avoid enqueuing the classes twice: here and in ./editor.scss :where(.editor-styles-wrapper) .has-regular-font-size { font-size: 16px; } diff --git a/packages/block-library/src/embed/test/index.js b/packages/block-library/src/embed/test/index.js index 7cc3645611ea78..7c09d7656454d9 100644 --- a/packages/block-library/src/embed/test/index.js +++ b/packages/block-library/src/embed/test/index.js @@ -69,7 +69,7 @@ describe( 'utils', () => { expect( getClassNames( html, '', false ) ).toEqual( expected ); } ); - it( 'should preserve exsiting class names when removing responsive classes', () => { + it( 'should preserve existing class names when removing responsive classes', () => { const html = '<iframe height="9" width="16"></iframe>'; const expected = 'lovely'; expect( diff --git a/packages/block-library/src/embed/transforms.js b/packages/block-library/src/embed/transforms.js index cf29511fde7af4..82c46da1fa7f94 100644 --- a/packages/block-library/src/embed/transforms.js +++ b/packages/block-library/src/embed/transforms.js @@ -7,6 +7,7 @@ import { createBlock } from '@wordpress/blocks'; * Internal dependencies */ import metadata from './block.json'; +import { removeAspectRatioClasses } from './util'; const { name: EMBED_BLOCK } = metadata; @@ -33,13 +34,14 @@ const transforms = { type: 'block', blocks: [ 'core/paragraph' ], isMatch: ( { url } ) => !! url, - transform: ( { url, caption } ) => { + transform: ( { url, caption, className } ) => { let value = `<a href="${ url }">${ url }</a>`; if ( caption?.trim() ) { value += `<br />${ caption }`; } return createBlock( 'core/paragraph', { content: value, + className: removeAspectRatioClasses( className ), } ); }, }, diff --git a/packages/block-library/src/embed/util.js b/packages/block-library/src/embed/util.js index eae45cc397e7b7..a58f1a8efc5c30 100644 --- a/packages/block-library/src/embed/util.js +++ b/packages/block-library/src/embed/util.js @@ -42,7 +42,7 @@ export const getEmbedInfoByProvider = ( provider ) => * Returns true if any of the regular expressions match the URL. * * @param {string} url The URL to test. - * @param {Array} patterns The list of regular expressions to test agains. + * @param {Array} patterns The list of regular expressions to test against. * @return {boolean} True if any of the regular expressions match the URL. */ export const matchesPatterns = ( url, patterns = [] ) => diff --git a/packages/block-library/src/file/edit.js b/packages/block-library/src/file/edit.js index 937eb3d28eb192..cf2c9458176934 100644 --- a/packages/block-library/src/file/edit.js +++ b/packages/block-library/src/file/edit.js @@ -128,15 +128,21 @@ function FileEdit( { attributes, isSelected, setAttributes, clientId } ) { } const isPdf = newMedia.url.endsWith( '.pdf' ); + const pdfAttributes = { + displayPreview: isPdf + ? attributes.displayPreview ?? true + : undefined, + previewHeight: isPdf ? attributes.previewHeight ?? 600 : undefined, + }; + setAttributes( { href: newMedia.url, fileName: newMedia.title, textLinkHref: newMedia.url, id: newMedia.id, - displayPreview: isPdf ? true : undefined, - previewHeight: isPdf ? 600 : undefined, fileId: `wp-block-file--media-${ clientId }`, blob: undefined, + ...pdfAttributes, } ); setTemporaryURL(); } @@ -249,11 +255,12 @@ function FileEdit( { attributes, isSelected, setAttributes, clientId } ) { <div { ...blockProps }> { displayPreviewInEditor && ( <ResizableBox - size={ { height: previewHeight } } + size={ { height: previewHeight, width: '100%' } } minHeight={ MIN_PREVIEW_HEIGHT } maxHeight={ MAX_PREVIEW_HEIGHT } - minWidth="100%" - grid={ [ 10, 10 ] } + // The horizontal grid value must be 1 or else the width may snap during a + // resize even though only vertical resizing is enabled. + grid={ [ 1, 10 ] } enable={ { top: false, right: false, diff --git a/packages/block-library/src/file/test/__snapshots__/edit.native.js.snap b/packages/block-library/src/file/test/__snapshots__/edit.native.js.snap index 5ce876137ade00..0c9d88a2074019 100644 --- a/packages/block-library/src/file/test/__snapshots__/edit.native.js.snap +++ b/packages/block-library/src/file/test/__snapshots__/edit.native.js.snap @@ -132,7 +132,7 @@ exports[`File block renders file error state without crashing 1`] = ` <Svg height={24} style={{}} - viewBox="-2 -2 24 24" + viewBox="0 0 24 24" width={24} xmlns="http://www.w3.org/2000/svg" > diff --git a/packages/block-library/src/file/test/transforms.native.js b/packages/block-library/src/file/test/transforms.native.js index efaa507348c1cb..7535086e69e959 100644 --- a/packages/block-library/src/file/test/transforms.native.js +++ b/packages/block-library/src/file/test/transforms.native.js @@ -15,8 +15,8 @@ const initialHtml = ` <div class="wp-block-file"><a href="https://wordpress.org/latest.zip">WordPress.zip</a><a href="https://wordpress.org/latest.zip" class="wp-block-file__button wp-element-button" download>Download</a></div> <!-- /wp:file -->`; -const tranformsWithInnerBlocks = [ 'Columns', 'Group' ]; -const blockTransforms = [ ...tranformsWithInnerBlocks ]; +const transformsWithInnerBlocks = [ 'Columns', 'Group' ]; +const blockTransforms = [ ...transformsWithInnerBlocks ]; setupCoreBlocks(); @@ -25,7 +25,8 @@ describe( `${ block } block transformations`, () => { const screen = await initializeEditor( { initialHtml } ); const newBlock = await transformBlock( screen, block, blockTransform, { isMediaBlock: false, - hasInnerBlocks: tranformsWithInnerBlocks.includes( blockTransform ), + hasInnerBlocks: + transformsWithInnerBlocks.includes( blockTransform ), } ); expect( newBlock ).toBeVisible(); expect( getEditorHtml() ).toMatchSnapshot(); diff --git a/packages/block-library/src/file/transforms.js b/packages/block-library/src/file/transforms.js index c381a62f783866..0e129b9fe64baa 100644 --- a/packages/block-library/src/file/transforms.js +++ b/packages/block-library/src/file/transforms.js @@ -14,7 +14,7 @@ const transforms = { isMatch( files ) { return files.length > 0; }, - // We define a lower priorty (higher number) than the default of 10. This + // We define a lower priority (higher number) than the default of 10. This // ensures that the File block is only created as a fallback. priority: 15, transform: ( files ) => { diff --git a/packages/block-library/src/file/utils/index.js b/packages/block-library/src/file/utils/index.js index a60e9e131c4e45..8e8bb811dd038a 100644 --- a/packages/block-library/src/file/utils/index.js +++ b/packages/block-library/src/file/utils/index.js @@ -10,7 +10,7 @@ export const browserSupportsPdfs = () => { return false; } - // Android tablets are the noteable exception. + // Android tablets are the notable exception. if ( window.navigator.userAgent.indexOf( 'Android' ) > -1 ) { return false; } diff --git a/packages/block-library/src/form-input/edit.js b/packages/block-library/src/form-input/edit.js index 5f3713e83975f1..104124a13cda1f 100644 --- a/packages/block-library/src/form-input/edit.js +++ b/packages/block-library/src/form-input/edit.js @@ -77,7 +77,7 @@ function InputFieldBlock( { attributes, setAttributes, className } ) { } ); } } help={ __( - 'Affects the "name" atribute of the input element, and is used as a name for the form submission results.' + 'Affects the "name" attribute of the input element, and is used as a name for the form submission results.' ) } /> </InspectorControls> diff --git a/packages/block-library/src/form/block.json b/packages/block-library/src/form/block.json index fa5212822cc71e..20f3b89dc62b0c 100644 --- a/packages/block-library/src/form/block.json +++ b/packages/block-library/src/form/block.json @@ -64,6 +64,5 @@ } }, "__experimentalSelector": "form" - }, - "viewScript": "file:./view.min.js" + } } diff --git a/packages/block-library/src/form/index.php b/packages/block-library/src/form/index.php index c887d46ad86188..d2b4942d6a50b3 100644 --- a/packages/block-library/src/form/index.php +++ b/packages/block-library/src/form/index.php @@ -14,6 +14,7 @@ * @return string The content of the block being rendered. */ function render_block_core_form( $attributes, $content ) { + wp_enqueue_script_module( '@wordpress/block-library/form/view' ); $processed_content = new WP_HTML_Tag_Processor( $content ); $processed_content->next_tag( 'form' ); @@ -42,26 +43,6 @@ function render_block_core_form( $attributes, $content ) { ); } -/** - * Additional data to add to the view.js script for this block. - */ -function block_core_form_view_script() { - if ( ! gutenberg_is_experiment_enabled( 'gutenberg-form-blocks' ) ) { - return; - } - - wp_localize_script( - 'wp-block-form-view', - 'wpBlockFormSettings', - array( - 'nonce' => wp_create_nonce( 'wp-block-form' ), - 'ajaxUrl' => admin_url( 'admin-ajax.php' ), - 'action' => 'wp_block_form_email_submit', - ) - ); -} -add_action( 'wp_enqueue_scripts', 'block_core_form_view_script' ); - /** * Adds extra fields to the form. * diff --git a/packages/block-library/src/form/variations.js b/packages/block-library/src/form/variations.js index cf12cef2ea38f2..5eab3e247f88b3 100644 --- a/packages/block-library/src/form/variations.js +++ b/packages/block-library/src/form/variations.js @@ -61,7 +61,7 @@ const variations = [ }, { name: 'wp-privacy-form', - title: __( 'Experimental privacy request form' ), + title: __( 'Experimental Privacy Request Form' ), keywords: [ 'GDPR' ], description: __( 'A form to request data exports and/or deletion.' ), attributes: { diff --git a/packages/block-library/src/form/view.js b/packages/block-library/src/form/view.js index d162d66020f44b..43e5af99e21286 100644 --- a/packages/block-library/src/form/view.js +++ b/packages/block-library/src/form/view.js @@ -1,8 +1,21 @@ +let formSettings; +try { + formSettings = JSON.parse( + document.getElementById( + 'wp-script-module-data-@wordpress/block-library/form/view' + )?.textContent + ); +} catch {} + // eslint-disable-next-line eslint-comments/disable-enable-pair /* eslint-disable no-undef */ document.querySelectorAll( 'form.wp-block-form' ).forEach( function ( form ) { - // Bail If the form is not using the mailto: action. - if ( ! form.action || ! form.action.startsWith( 'mailto:' ) ) { + // Bail If the form settings not provided or the form is not using the mailto: action. + if ( + ! formSettings || + ! form.action || + ! form.action.startsWith( 'mailto:' ) + ) { return; } @@ -18,13 +31,13 @@ document.querySelectorAll( 'form.wp-block-form' ).forEach( function ( form ) { // Get the form data and merge it with the form action and nonce. const formData = Object.fromEntries( new FormData( form ).entries() ); formData.formAction = form.action; - formData._ajax_nonce = wpBlockFormSettings.nonce; - formData.action = wpBlockFormSettings.action; + formData._ajax_nonce = formSettings.nonce; + formData.action = formSettings.action; formData._wp_http_referer = window.location.href; formData.formAction = form.action; try { - const response = await fetch( wpBlockFormSettings.ajaxUrl, { + const response = await fetch( formSettings.ajaxUrl, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', diff --git a/packages/block-library/src/freeform/editor.scss b/packages/block-library/src/freeform/editor.scss index c2256ecd7a795f..2dd15359d8badd 100644 --- a/packages/block-library/src/freeform/editor.scss +++ b/packages/block-library/src/freeform/editor.scss @@ -240,12 +240,13 @@ div[data-type="core/freeform"] { &::before { - transition: border-color 0.1s linear, box-shadow 0.1s linear; - @include reduce-motion("transition"); border: $border-width solid $gray-300; - // Windows High Contrast mode will show this outline. outline: $border-width solid transparent; + + @media not (prefers-reduced-motion) { + transition: border-color 0.1s linear, box-shadow 0.1s linear; + } } &.is-selected::before { diff --git a/packages/block-library/src/freeform/modal.js b/packages/block-library/src/freeform/modal.js index 4ed4ef4d3a07ca..35652eb29948a3 100644 --- a/packages/block-library/src/freeform/modal.js +++ b/packages/block-library/src/freeform/modal.js @@ -25,7 +25,7 @@ function ModalAuxiliaryActions( { onClick, isModalFullScreen } ) { return ( <Button - size="small" + size="compact" onClick={ onClick } icon={ fullscreen } isPressed={ isModalFullScreen } diff --git a/packages/block-library/src/gallery/edit.js b/packages/block-library/src/gallery/edit.js index 01e725cc16cde6..1936c02c468189 100644 --- a/packages/block-library/src/gallery/edit.js +++ b/packages/block-library/src/gallery/edit.js @@ -80,7 +80,7 @@ const LINK_OPTIONS = [ }, { icon: fullscreen, - label: __( 'Expand on click' ), + label: __( 'Enlarge on click' ), value: LINK_DESTINATION_LIGHTBOX, noticeText: __( 'Lightbox effect' ), infoText: __( 'Scale images with a lightbox effect' ), diff --git a/packages/block-library/src/gallery/editor.scss b/packages/block-library/src/gallery/editor.scss index 61121f3dd866dc..62c0eff788746d 100644 --- a/packages/block-library/src/gallery/editor.scss +++ b/packages/block-library/src/gallery/editor.scss @@ -117,10 +117,6 @@ opacity: 0.3; } - .is-selected .block-library-gallery-item__inline-menu { - display: inline-flex; - } - .block-editor-media-placeholder { margin: 0; height: 100%; @@ -131,56 +127,6 @@ } } -.block-library-gallery-item__inline-menu { - display: none; - position: absolute; - top: -2px; - margin: $grid-unit-10; - z-index: z-index(".block-library-gallery-item__inline-menu"); - transition: box-shadow 0.2s ease-out; - @include reduce-motion("transition"); - border-radius: $radius-small; - background: $white; - border: $border-width solid $gray-900; - - &:hover { - box-shadow: $elevation-x-small; - } - - @include break-small() { - // Use smaller buttons to fit when there are many columns. - .columns-7 &, - .columns-8 & { - padding: $grid-unit-05 * 0.5; - } - } - - .components-button.has-icon { - &:not(:focus) { - border: none; - box-shadow: none; - } - - @include break-small() { - // Use smaller buttons to fit when there are many columns. - .columns-7 &, - .columns-8 & { - padding: 0; - width: inherit; - height: inherit; - } - } - } - - &.is-left { - left: -2px; - } - - &.is-right { - right: -2px; - } -} - .wp-block-gallery ul.blocks-gallery-grid { padding: 0; // Some themes give all <ul> default margin instead of padding. diff --git a/packages/block-library/src/gallery/test/transforms.native.js b/packages/block-library/src/gallery/test/transforms.native.js index d155c1bc01064d..c76a1dcc0a2441 100644 --- a/packages/block-library/src/gallery/test/transforms.native.js +++ b/packages/block-library/src/gallery/test/transforms.native.js @@ -25,8 +25,8 @@ const initialHtml = ` <!-- /wp:image --></figure> <!-- /wp:gallery -->`; -const tranformsWithInnerBlocks = [ 'Columns', 'Group' ]; -const blockTransforms = [ 'Image', ...tranformsWithInnerBlocks ]; +const transformsWithInnerBlocks = [ 'Columns', 'Group' ]; +const blockTransforms = [ 'Image', ...transformsWithInnerBlocks ]; setupCoreBlocks(); @@ -35,7 +35,8 @@ describe( `${ block } block transformations`, () => { const screen = await initializeEditor( { initialHtml } ); const newBlock = await transformBlock( screen, block, blockTransform, { isMediaBlock: true, - hasInnerBlocks: tranformsWithInnerBlocks.includes( blockTransform ), + hasInnerBlocks: + transformsWithInnerBlocks.includes( blockTransform ), } ); expect( newBlock ).toBeVisible(); expect( getEditorHtml() ).toMatchSnapshot(); diff --git a/packages/block-library/src/gallery/transforms.js b/packages/block-library/src/gallery/transforms.js index f040fdb12fb235..6dc2f526f44a01 100644 --- a/packages/block-library/src/gallery/transforms.js +++ b/packages/block-library/src/gallery/transforms.js @@ -25,7 +25,7 @@ const parseShortcodeIds = ( ids ) => { /** * Third party block plugins don't have an easy way to detect if the * innerBlocks version of the Gallery is running when they run a - * 3rdPartyBlock -> GalleryBlock transform so this tranform filter + * 3rdPartyBlock -> GalleryBlock transform so this transform filter * will handle this. Once the innerBlocks version is the default * in a core release, this could be deprecated and removed after * plugin authors have been given time to update transforms. @@ -189,7 +189,7 @@ const transforms = { // When created by drag and dropping multiple files on an insertion point. Because multiple // files must not be transformed to a gallery when dropped within a gallery there is another transform // within the image block to handle that case. Therefore this transform has to have priority 1 - // set so that it overrrides the image block transformation when mulitple images are dropped outside + // set so that it overrides the image block transformation when multiple images are dropped outside // of a gallery block. type: 'files', priority: 1, diff --git a/packages/block-library/src/gallery/use-get-new-images.js b/packages/block-library/src/gallery/use-get-new-images.js index 056cf1d2ff6ba1..b59c97a0ae4809 100644 --- a/packages/block-library/src/gallery/use-get-new-images.js +++ b/packages/block-library/src/gallery/use-get-new-images.js @@ -56,7 +56,7 @@ export default function useGetNewImages( images, imageData ) { currentImage.clientId === image.clientId ) && imageData?.find( ( img ) => img.id === image.id ) && - ! image.fromSavedConent + ! image.fromSavedContent ); if ( imagesUpdated || newImages?.length > 0 ) { diff --git a/packages/block-library/src/group/edit.js b/packages/block-library/src/group/edit.js index 352a6fbc77819e..c5588f3b73bb18 100644 --- a/packages/block-library/src/group/edit.js +++ b/packages/block-library/src/group/edit.js @@ -18,6 +18,7 @@ import { View } from '@wordpress/primitives'; * Internal dependencies */ import GroupPlaceHolder, { useShouldShowPlaceHolder } from './placeholder'; +import { htmlElementMessages } from '../utils/messages'; /** * Render inspector controls for the Group block. @@ -29,26 +30,6 @@ import GroupPlaceHolder, { useShouldShowPlaceHolder } from './placeholder'; * @return {JSX.Element} The control group. */ function GroupEditControls( { tagName, onSelectTagName } ) { - const htmlElementMessages = { - header: __( - 'The <header> element should represent introductory content, typically a group of introductory or navigational aids.' - ), - main: __( - 'The <main> element should be used for the primary content of your document only.' - ), - section: __( - "The <section> element should represent a standalone portion of the document that can't be better represented by another element." - ), - article: __( - 'The <article> element should represent a self-contained, syndicatable portion of the document.' - ), - aside: __( - "The <aside> element should represent a portion of a document whose content is only indirectly related to the document's main content." - ), - footer: __( - 'The <footer> element should represent a footer for its nearest sectioning element (e.g.: <section>, <article>, <main> etc.).' - ), - }; return ( <InspectorControls group="advanced"> <SelectControl diff --git a/packages/block-library/src/heading/block.json b/packages/block-library/src/heading/block.json index 2276bcbbb50172..2869ee85c55206 100644 --- a/packages/block-library/src/heading/block.json +++ b/packages/block-library/src/heading/block.json @@ -37,13 +37,7 @@ "color": true, "radius": true, "style": true, - "width": true, - "__experimentalDefaultControls": { - "color": true, - "radius": true, - "style": true, - "width": true - } + "width": true }, "color": { "gradients": true, diff --git a/packages/block-library/src/home-link/edit.js b/packages/block-library/src/home-link/edit.js index fa6b27a3e2128b..a4b975135b76f5 100644 --- a/packages/block-library/src/home-link/edit.js +++ b/packages/block-library/src/home-link/edit.js @@ -6,15 +6,10 @@ import clsx from 'clsx'; /** * WordPress dependencies */ -import { - RichText, - useBlockProps, - store as blockEditorStore, -} from '@wordpress/block-editor'; +import { RichText, useBlockProps } from '@wordpress/block-editor'; import { __ } from '@wordpress/i18n'; -import { useSelect, useDispatch } from '@wordpress/data'; +import { useSelect } from '@wordpress/data'; import { store as coreStore } from '@wordpress/core-data'; -import { useEffect } from '@wordpress/element'; const preventDefault = ( event ) => event.preventDefault(); @@ -24,8 +19,6 @@ export default function HomeEdit( { attributes, setAttributes, context } ) { return select( coreStore ).getEntityRecord( 'root', '__unstableBase' ) ?.home; }, [] ); - const { __unstableMarkNextChangeAsNotPersistent } = - useDispatch( blockEditorStore ); const { textColor, backgroundColor, style } = context; const blockProps = useBlockProps( { @@ -41,42 +34,25 @@ export default function HomeEdit( { attributes, setAttributes, context } ) { }, } ); - const { label } = attributes; - - useEffect( () => { - if ( label === undefined ) { - __unstableMarkNextChangeAsNotPersistent(); - setAttributes( { label: __( 'Home' ) } ); - } - }, [ label ] ); - return ( - <> - <div { ...blockProps }> - <a - className="wp-block-home-link__content wp-block-navigation-item__content" - href={ homeUrl } - onClick={ preventDefault } - > - <RichText - identifier="label" - className="wp-block-home-link__label" - value={ label } - onChange={ ( labelValue ) => { - setAttributes( { label: labelValue } ); - } } - aria-label={ __( 'Home link text' ) } - placeholder={ __( 'Add home link' ) } - withoutInteractiveFormatting - allowedFormats={ [ - 'core/bold', - 'core/italic', - 'core/image', - 'core/strikethrough', - ] } - /> - </a> - </div> - </> + <div { ...blockProps }> + <a + className="wp-block-home-link__content wp-block-navigation-item__content" + href={ homeUrl } + onClick={ preventDefault } + > + <RichText + identifier="label" + className="wp-block-home-link__label" + value={ attributes.label ?? __( 'Home' ) } + onChange={ ( labelValue ) => { + setAttributes( { label: labelValue } ); + } } + aria-label={ __( 'Home link text' ) } + placeholder={ __( 'Add home link' ) } + withoutInteractiveFormatting + /> + </a> + </div> ); } diff --git a/packages/block-library/src/home-link/index.php b/packages/block-library/src/home-link/index.php index fb7235834459d0..d61aa0bc235e22 100644 --- a/packages/block-library/src/home-link/index.php +++ b/packages/block-library/src/home-link/index.php @@ -137,9 +137,6 @@ function block_core_home_link_build_li_wrapper_attributes( $context ) { */ function render_block_core_home_link( $attributes, $content, $block ) { if ( empty( $attributes['label'] ) ) { - // Using a fallback for the label attribute allows rendering the block even if no attributes have been set, - // e.g. when using the block as a hooked block. - // Note that the fallback value needs to be kept in sync with the one set in `edit.js` (upon first loading the block in the editor). $attributes['label'] = __( 'Home' ); } $aria_current = ''; diff --git a/packages/block-library/src/image/block.json b/packages/block-library/src/image/block.json index 16e31217476026..26835df9e856cd 100644 --- a/packages/block-library/src/image/block.json +++ b/packages/block-library/src/image/block.json @@ -4,7 +4,14 @@ "name": "core/image", "title": "Image", "category": "media", - "usesContext": [ "allowResize", "imageCrop", "fixedHeight", "postId", "postType", "queryId" ], + "usesContext": [ + "allowResize", + "imageCrop", + "fixedHeight", + "postId", + "postType", + "queryId" + ], "description": "Insert an image to make a visual statement.", "keywords": [ "img", "photo", "picture" ], "textdomain": "default", diff --git a/packages/block-library/src/image/editor.scss b/packages/block-library/src/image/editor.scss index 35b05a063c2997..a7386205d9108a 100644 --- a/packages/block-library/src/image/editor.scss +++ b/packages/block-library/src/image/editor.scss @@ -32,7 +32,7 @@ figure.wp-block-image:not(.wp-block) { } } -// Shown while image is being uploded but cannot be previewed. +// Shown while image is being uploaded but cannot be previewed. .wp-block-image__placeholder { aspect-ratio: 4 / 3; diff --git a/packages/block-library/src/image/image.js b/packages/block-library/src/image/image.js index b4d269f9678221..c3783b50e00065 100644 --- a/packages/block-library/src/image/image.js +++ b/packages/block-library/src/image/image.js @@ -1126,7 +1126,7 @@ export default function Image( { id && clientId === selectedClientIds[ 0 ] && ( <MenuItem onClick={ setPostFeatureImage }> - { __( 'Set featured image' ) } + { __( 'Set as featured image' ) } </MenuItem> ) } diff --git a/packages/block-library/src/image/index.php b/packages/block-library/src/image/index.php index 1a5fae7ce9cbb7..697f67a927fc87 100644 --- a/packages/block-library/src/image/index.php +++ b/packages/block-library/src/image/index.php @@ -149,18 +149,14 @@ function block_core_image_render_lightbox( $block_content, $block ) { return $block_content; } - $alt = $p->get_attribute( 'alt' ); - $img_uploaded_src = $p->get_attribute( 'src' ); - $img_class_names = $p->get_attribute( 'class' ); - $img_styles = $p->get_attribute( 'style' ); - $img_width = 'none'; - $img_height = 'none'; - $aria_label = __( 'Enlarge image' ); - - if ( $alt ) { - /* translators: %s: Image alt text. */ - $aria_label = sprintf( __( 'Enlarge image: %s' ), $alt ); - } + $alt = $p->get_attribute( 'alt' ); + $img_uploaded_src = $p->get_attribute( 'src' ); + $img_class_names = $p->get_attribute( 'class' ); + $img_styles = $p->get_attribute( 'style' ); + $img_width = 'none'; + $img_height = 'none'; + $aria_label = __( 'Enlarge' ); + $dialog_aria_label = __( 'Enlarged image' ); if ( isset( $block['attrs']['id'] ) ) { $img_uploaded_src = wp_get_attachment_url( $block['attrs']['id'] ); @@ -190,7 +186,7 @@ function block_core_image_render_lightbox( $block_content, $block ) { 'targetWidth' => $img_width, 'targetHeight' => $img_height, 'scaleAttr' => $block['attrs']['scale'] ?? false, - 'ariaLabel' => $aria_label, + 'ariaLabel' => $dialog_aria_label, 'alt' => $alt, ), ), @@ -290,6 +286,7 @@ class="wp-lightbox-overlay zoom" data-wp-on-async--click="actions.hideLightbox" data-wp-on-async-window--resize="callbacks.setOverlayStyles" data-wp-on-async-window--scroll="actions.handleScroll" + data-wp-bind--style="state.overlayStyles" tabindex="-1" > <button type="button" aria-label="$close_button_label" style="fill: $close_button_color" class="close-button"> @@ -306,7 +303,6 @@ class="wp-lightbox-overlay zoom" </figure> </div> <div class="scrim" style="background-color: $background_color" aria-hidden="true"></div> - <style data-wp-text="state.overlayStyles"></style> </div> HTML; } diff --git a/packages/block-library/src/image/style.scss b/packages/block-library/src/image/style.scss index 1bb19bf29da691..117045f7dce627 100644 --- a/packages/block-library/src/image/style.scss +++ b/packages/block-library/src/image/style.scss @@ -1,6 +1,7 @@ .wp-block-image { - a { + > a, + > figure > a { display: inline-block; } @@ -10,7 +11,7 @@ vertical-align: bottom; box-sizing: border-box; - @media (prefers-reduced-motion: no-preference) { + @media not (prefers-reduced-motion) { &.hide { visibility: hidden; } @@ -42,8 +43,8 @@ text-align: center; } - &.alignfull a, - &.alignwide a { + &.alignfull > a, + &.alignwide > a { width: 100%; } @@ -166,7 +167,9 @@ text-align: center; padding: 0; border-radius: 4px; - transition: opacity 0.2s ease; + @media not (prefers-reduced-motion) { + transition: opacity 0.2s ease; + } &:focus-visible { outline: 3px auto rgb(90 90 90 / 25%); @@ -279,21 +282,29 @@ // or faster than the scrim to give a sense of depth. &.active { visibility: visible; - animation: both turn-on-visibility 0.25s; + @media not (prefers-reduced-motion) { + animation: both turn-on-visibility 0.25s; + } img { - animation: both turn-on-visibility 0.35s; + @media not (prefers-reduced-motion) { + animation: both turn-on-visibility 0.35s; + } } } &.show-closing-animation { &:not(.active) { - animation: both turn-off-visibility 0.35s; + @media not (prefers-reduced-motion) { + animation: both turn-off-visibility 0.35s; + } img { - animation: both turn-off-visibility 0.25s; + @media not (prefers-reduced-motion) { + animation: both turn-off-visibility 0.25s; + } } } } - @media (prefers-reduced-motion: no-preference) { + @media not (prefers-reduced-motion) { &.zoom { &.active { opacity: 1; diff --git a/packages/block-library/src/image/test/edit.native.js b/packages/block-library/src/image/test/edit.native.js index 5cf653321b2be0..16497b7bbcb54c 100644 --- a/packages/block-library/src/image/test/edit.native.js +++ b/packages/block-library/src/image/test/edit.native.js @@ -62,7 +62,7 @@ Clipboard.getString.mockImplementation( () => clipboardPromise ); beforeAll( () => { registerCoreBlocks(); - // Mock Image.getSize to avoid failed attempt to size non-existant image + // Mock Image.getSize to avoid failed attempt to size non-existent image const getSizeSpy = jest.spyOn( Image, 'getSize' ); getSizeSpy.mockImplementation( ( _url, callback ) => callback( 300, 200 ) ); } ); diff --git a/packages/block-library/src/image/test/transforms.native.js b/packages/block-library/src/image/test/transforms.native.js index f5b7bcdab97587..f32a06cb70cdd5 100644 --- a/packages/block-library/src/image/test/transforms.native.js +++ b/packages/block-library/src/image/test/transforms.native.js @@ -15,12 +15,12 @@ const initialHtml = ` <figure class="wp-block-image size-large is-style-default"><a href="https://cldup.com/cXyG__fTLN.jpg"><img src="https://cldup.com/cXyG__fTLN.jpg" alt="" class="wp-image-1"/></a><figcaption class="wp-element-caption">Mountain</figcaption></figure> <!-- /wp:image -->`; -const tranformsWithInnerBlocks = [ 'Gallery', 'Columns', 'Group' ]; +const transformsWithInnerBlocks = [ 'Gallery', 'Columns', 'Group' ]; const nonMediaTransforms = [ 'File' ]; const blockTransforms = [ 'Cover', 'Media & Text', - ...tranformsWithInnerBlocks, + ...transformsWithInnerBlocks, ...nonMediaTransforms, ]; @@ -31,7 +31,8 @@ describe( `${ block } block transformations`, () => { const screen = await initializeEditor( { initialHtml } ); const newBlock = await transformBlock( screen, block, blockTransform, { isMediaBlock: ! nonMediaTransforms.includes( blockTransform ), - hasInnerBlocks: tranformsWithInnerBlocks.includes( blockTransform ), + hasInnerBlocks: + transformsWithInnerBlocks.includes( blockTransform ), } ); expect( newBlock ).toBeVisible(); expect( getEditorHtml() ).toMatchSnapshot(); diff --git a/packages/block-library/src/image/transforms.js b/packages/block-library/src/image/transforms.js index 347d2408280170..0119009b2182c4 100644 --- a/packages/block-library/src/image/transforms.js +++ b/packages/block-library/src/image/transforms.js @@ -59,6 +59,7 @@ const schema = ( { phrasingContentSchema } ) => ( { ...imageSchema, a: { attributes: [ 'href', 'rel', 'target' ], + classes: [ '*' ], children: imageSchema, }, figcaption: { diff --git a/packages/block-library/src/image/view.js b/packages/block-library/src/image/view.js index 0bc0dfaacea1a2..3c9a729538813e 100644 --- a/packages/block-library/src/image/view.js +++ b/packages/block-library/src/image/view.js @@ -341,7 +341,6 @@ const { state, actions, callbacks } = store( // adding 1 pixel to the container width and height solves the problem, // though this can be removed if the issue is fixed in the future. state.overlayStyles = ` - :root { --wp--lightbox-initial-top-position: ${ screenPosY }px; --wp--lightbox-initial-left-position: ${ screenPosX }px; --wp--lightbox-container-width: ${ containerWidth + 1 }px; @@ -352,8 +351,7 @@ const { state, actions, callbacks } = store( --wp--lightbox-scrollbar-width: ${ window.innerWidth - document.documentElement.clientWidth }px; - } - `; + `; }, setButtonStyles() { const { imageId } = getContext(); diff --git a/packages/block-library/src/index.js b/packages/block-library/src/index.js index 56365c87a268fd..262f11de6ee22d 100644 --- a/packages/block-library/src/index.js +++ b/packages/block-library/src/index.js @@ -99,6 +99,7 @@ import * as queryPaginationNext from './query-pagination-next'; import * as queryPaginationNumbers from './query-pagination-numbers'; import * as queryPaginationPrevious from './query-pagination-previous'; import * as queryTitle from './query-title'; +import * as queryTotal from './query-total'; import * as quote from './quote'; import * as reusableBlock from './block'; import * as readMore from './read-more'; @@ -211,6 +212,7 @@ const getAllBlocks = () => { queryPaginationNumbers, queryPaginationPrevious, queryNoResults, + queryTotal, readMore, comments, commentAuthorName, diff --git a/packages/block-library/src/latest-comments/edit.js b/packages/block-library/src/latest-comments/edit.js index 85e66cf2e9dc60..432cd389efacc3 100644 --- a/packages/block-library/src/latest-comments/edit.js +++ b/packages/block-library/src/latest-comments/edit.js @@ -4,13 +4,19 @@ import { InspectorControls, useBlockProps } from '@wordpress/block-editor'; import { Disabled, - PanelBody, RangeControl, ToggleControl, + __experimentalToolsPanel as ToolsPanel, + __experimentalToolsPanelItem as ToolsPanelItem, } from '@wordpress/components'; import ServerSideRender from '@wordpress/server-side-render'; import { __ } from '@wordpress/i18n'; +/** + * Internal dependencies + */ +import { useToolsPanelDropdownMenuProps } from '../utils/hooks'; + /** * Minimum number of comments a user can show using this block. * @@ -36,49 +42,103 @@ export default function LatestComments( { attributes, setAttributes } ) { }, }; + const dropdownMenuProps = useToolsPanelDropdownMenuProps(); + return ( <div { ...useBlockProps() }> <InspectorControls> - <PanelBody title={ __( 'Settings' ) }> - <ToggleControl - __nextHasNoMarginBottom + <ToolsPanel + label={ __( 'Settings' ) } + resetAll={ () => { + setAttributes( { + commentsToShow: 5, + displayAvatar: true, + displayDate: true, + displayExcerpt: true, + } ); + } } + dropdownMenuProps={ dropdownMenuProps } + > + <ToolsPanelItem + hasValue={ () => ! displayAvatar } label={ __( 'Display avatar' ) } - checked={ displayAvatar } - onChange={ () => - setAttributes( { displayAvatar: ! displayAvatar } ) + onDeselect={ () => + setAttributes( { displayAvatar: true } ) } - /> - <ToggleControl - __nextHasNoMarginBottom + isShownByDefault + > + <ToggleControl + __nextHasNoMarginBottom + label={ __( 'Display avatar' ) } + checked={ displayAvatar } + onChange={ () => + setAttributes( { + displayAvatar: ! displayAvatar, + } ) + } + /> + </ToolsPanelItem> + + <ToolsPanelItem + hasValue={ () => ! displayDate } label={ __( 'Display date' ) } - checked={ displayDate } - onChange={ () => - setAttributes( { displayDate: ! displayDate } ) + onDeselect={ () => + setAttributes( { displayDate: true } ) } - /> - <ToggleControl - __nextHasNoMarginBottom + isShownByDefault + > + <ToggleControl + __nextHasNoMarginBottom + label={ __( 'Display date' ) } + checked={ displayDate } + onChange={ () => + setAttributes( { displayDate: ! displayDate } ) + } + /> + </ToolsPanelItem> + + <ToolsPanelItem + hasValue={ () => ! displayExcerpt } label={ __( 'Display excerpt' ) } - checked={ displayExcerpt } - onChange={ () => - setAttributes( { - displayExcerpt: ! displayExcerpt, - } ) + onDeselect={ () => + setAttributes( { displayExcerpt: true } ) } - /> - <RangeControl - __nextHasNoMarginBottom - __next40pxDefaultSize + isShownByDefault + > + <ToggleControl + __nextHasNoMarginBottom + label={ __( 'Display excerpt' ) } + checked={ displayExcerpt } + onChange={ () => + setAttributes( { + displayExcerpt: ! displayExcerpt, + } ) + } + /> + </ToolsPanelItem> + + <ToolsPanelItem + hasValue={ () => commentsToShow !== 5 } label={ __( 'Number of comments' ) } - value={ commentsToShow } - onChange={ ( value ) => - setAttributes( { commentsToShow: value } ) + onDeselect={ () => + setAttributes( { commentsToShow: 5 } ) } - min={ MIN_COMMENTS } - max={ MAX_COMMENTS } - required - /> - </PanelBody> + isShownByDefault + > + <RangeControl + __nextHasNoMarginBottom + __next40pxDefaultSize + label={ __( 'Number of comments' ) } + value={ commentsToShow } + onChange={ ( value ) => + setAttributes( { commentsToShow: value } ) + } + min={ MIN_COMMENTS } + max={ MAX_COMMENTS } + required + /> + </ToolsPanelItem> + </ToolsPanel> </InspectorControls> <Disabled> <ServerSideRender diff --git a/packages/block-library/src/latest-posts/block.json b/packages/block-library/src/latest-posts/block.json index bb8c2d24962f3f..58b1c6da81ca33 100644 --- a/packages/block-library/src/latest-posts/block.json +++ b/packages/block-library/src/latest-posts/block.json @@ -111,6 +111,18 @@ "fontSize": true } }, + "__experimentalBorder": { + "radius": true, + "color": true, + "width": true, + "style": true, + "__experimentalDefaultControls": { + "radius": true, + "color": true, + "width": true, + "style": true + } + }, "interactivity": { "clientNavigation": true } diff --git a/packages/block-library/src/latest-posts/constants.js b/packages/block-library/src/latest-posts/constants.js index 70c34448a3bff6..bcb367ce9d7448 100644 --- a/packages/block-library/src/latest-posts/constants.js +++ b/packages/block-library/src/latest-posts/constants.js @@ -1,3 +1,4 @@ export const MIN_EXCERPT_LENGTH = 10; export const MAX_EXCERPT_LENGTH = 100; export const MAX_POSTS_COLUMNS = 6; +export const DEFAULT_EXCERPT_LENGTH = 55; diff --git a/packages/block-library/src/latest-posts/edit.js b/packages/block-library/src/latest-posts/edit.js index 82f0661d04e40f..95c72ea538b0ef 100644 --- a/packages/block-library/src/latest-posts/edit.js +++ b/packages/block-library/src/latest-posts/edit.js @@ -17,6 +17,8 @@ import { ToolbarGroup, __experimentalToggleGroupControl as ToggleGroupControl, __experimentalToggleGroupControlOptionIcon as ToggleGroupControlOptionIcon, + __experimentalToolsPanel as ToolsPanel, + __experimentalToolsPanelItem as ToolsPanelItem, } from '@wordpress/components'; import { __, _x, sprintf } from '@wordpress/i18n'; import { dateI18n, format, getSettings } from '@wordpress/date'; @@ -49,7 +51,9 @@ import { MIN_EXCERPT_LENGTH, MAX_EXCERPT_LENGTH, MAX_POSTS_COLUMNS, + DEFAULT_EXCERPT_LENGTH, } from './constants'; +import { useToolsPanelDropdownMenuProps } from '../utils/hooks'; /** * Module Constants @@ -77,6 +81,8 @@ function getFeaturedImageDetails( post, size ) { export default function LatestPostsEdit( { attributes, setAttributes } ) { const instanceId = useInstanceId( LatestPostsEdit ); + const dropdownMenuProps = useToolsPanelDropdownMenuProps(); + const { postsToShow, order, @@ -227,68 +233,137 @@ export default function LatestPostsEdit( { attributes, setAttributes } ) { const hasPosts = !! latestPosts?.length; const inspectorControls = ( <InspectorControls> - <PanelBody title={ __( 'Post content' ) }> - <ToggleControl - __nextHasNoMarginBottom + <ToolsPanel + label={ __( 'Post content' ) } + resetAll={ () => + setAttributes( { + displayPostContent: false, + displayPostContentRadio: 'excerpt', + excerptLength: DEFAULT_EXCERPT_LENGTH, + } ) + } + dropdownMenuProps={ dropdownMenuProps } + > + <ToolsPanelItem + hasValue={ () => !! displayPostContent } label={ __( 'Post content' ) } - checked={ displayPostContent } - onChange={ ( value ) => - setAttributes( { displayPostContent: value } ) + onDeselect={ () => + setAttributes( { displayPostContent: false } ) } - /> + isShownByDefault + > + <ToggleControl + __nextHasNoMarginBottom + label={ __( 'Post content' ) } + checked={ displayPostContent } + onChange={ ( value ) => + setAttributes( { displayPostContent: value } ) + } + /> + </ToolsPanelItem> { displayPostContent && ( - <RadioControl + <ToolsPanelItem + hasValue={ () => displayPostContentRadio !== 'excerpt' } label={ __( 'Show' ) } - selected={ displayPostContentRadio } - options={ [ - { label: __( 'Excerpt' ), value: 'excerpt' }, - { - label: __( 'Full post' ), - value: 'full_post', - }, - ] } - onChange={ ( value ) => + onDeselect={ () => setAttributes( { - displayPostContentRadio: value, + displayPostContentRadio: 'excerpt', } ) } - /> + isShownByDefault + > + <RadioControl + label={ __( 'Show' ) } + selected={ displayPostContentRadio } + options={ [ + { label: __( 'Excerpt' ), value: 'excerpt' }, + { + label: __( 'Full post' ), + value: 'full_post', + }, + ] } + onChange={ ( value ) => + setAttributes( { + displayPostContentRadio: value, + } ) + } + /> + </ToolsPanelItem> ) } { displayPostContent && displayPostContentRadio === 'excerpt' && ( - <RangeControl - __nextHasNoMarginBottom - __next40pxDefaultSize + <ToolsPanelItem + hasValue={ () => + excerptLength !== DEFAULT_EXCERPT_LENGTH + } label={ __( 'Max number of words' ) } - value={ excerptLength } - onChange={ ( value ) => - setAttributes( { excerptLength: value } ) + onDeselect={ () => + setAttributes( { + excerptLength: DEFAULT_EXCERPT_LENGTH, + } ) } - min={ MIN_EXCERPT_LENGTH } - max={ MAX_EXCERPT_LENGTH } - /> + isShownByDefault + > + <RangeControl + __nextHasNoMarginBottom + __next40pxDefaultSize + label={ __( 'Max number of words' ) } + value={ excerptLength } + onChange={ ( value ) => + setAttributes( { excerptLength: value } ) + } + min={ MIN_EXCERPT_LENGTH } + max={ MAX_EXCERPT_LENGTH } + /> + </ToolsPanelItem> ) } - </PanelBody> + </ToolsPanel> - <PanelBody title={ __( 'Post meta' ) }> - <ToggleControl - __nextHasNoMarginBottom + <ToolsPanel + label={ __( 'Post meta' ) } + resetAll={ () => + setAttributes( { + displayAuthor: false, + displayPostDate: false, + } ) + } + dropdownMenuProps={ dropdownMenuProps } + > + <ToolsPanelItem + hasValue={ () => !! displayAuthor } label={ __( 'Display author name' ) } - checked={ displayAuthor } - onChange={ ( value ) => - setAttributes( { displayAuthor: value } ) + onDeselect={ () => + setAttributes( { displayAuthor: false } ) } - /> - <ToggleControl - __nextHasNoMarginBottom + isShownByDefault + > + <ToggleControl + __nextHasNoMarginBottom + label={ __( 'Display author name' ) } + checked={ displayAuthor } + onChange={ ( value ) => + setAttributes( { displayAuthor: value } ) + } + /> + </ToolsPanelItem> + <ToolsPanelItem + hasValue={ () => !! displayPostDate } label={ __( 'Display post date' ) } - checked={ displayPostDate } - onChange={ ( value ) => - setAttributes( { displayPostDate: value } ) + onDeselect={ () => + setAttributes( { displayPostDate: false } ) } - /> - </PanelBody> - + isShownByDefault + > + <ToggleControl + __nextHasNoMarginBottom + label={ __( 'Display post date' ) } + checked={ displayPostDate } + onChange={ ( value ) => + setAttributes( { displayPostDate: value } ) + } + /> + </ToolsPanelItem> + </ToolsPanel> <PanelBody title={ __( 'Featured image' ) }> <ToggleControl __nextHasNoMarginBottom diff --git a/packages/block-library/src/latest-posts/style.scss b/packages/block-library/src/latest-posts/style.scss index 2a01d177ef47c4..69a4746b5e751c 100644 --- a/packages/block-library/src/latest-posts/style.scss +++ b/packages/block-library/src/latest-posts/style.scss @@ -57,7 +57,8 @@ font-size: 0.8125em; } -.wp-block-latest-posts__post-excerpt { +.wp-block-latest-posts__post-excerpt, +.wp-block-latest-posts__post-full-content { margin-top: 0.5em; margin-bottom: 1em; } diff --git a/packages/block-library/src/list-item/block.json b/packages/block-library/src/list-item/block.json index 6eb30cfe6d0af0..1cdba86f19b2e8 100644 --- a/packages/block-library/src/list-item/block.json +++ b/packages/block-library/src/list-item/block.json @@ -2,7 +2,7 @@ "$schema": "https://schemas.wp.org/trunk/block.json", "apiVersion": 3, "name": "core/list-item", - "title": "List item", + "title": "List Item", "category": "text", "parent": [ "core/list" ], "allowedBlocks": [ "core/list" ], diff --git a/packages/block-library/src/list-item/edit.native.js b/packages/block-library/src/list-item/edit.native.js index 2367529242ded1..d1eb1a9441a0cb 100644 --- a/packages/block-library/src/list-item/edit.native.js +++ b/packages/block-library/src/list-item/edit.native.js @@ -54,7 +54,7 @@ export default function ListItemEdit( { getBlockParentsByBlockName, getBlockRootClientId, } = select( blockEditorStore ); - const currentIdentationLevel = getBlockParentsByBlockName( + const currentIndentationLevel = getBlockParentsByBlockName( clientId, 'core/list-item', true @@ -73,7 +73,7 @@ export default function ListItemEdit( { return { blockIndex: currentBlockIndex, hasInnerBlocks: blockWithInnerBlocks, - indentationLevel: currentIdentationLevel, + indentationLevel: currentIndentationLevel, numberOfListItems: totalListItems, ordered: isOrdered, reversed: isReversed, diff --git a/packages/block-library/src/list-item/hooks/use-merge.js b/packages/block-library/src/list-item/hooks/use-merge.js index 9ca4d5372ee6e8..3fe755868df37d 100644 --- a/packages/block-library/src/list-item/hooks/use-merge.js +++ b/packages/block-library/src/list-item/hooks/use-merge.js @@ -49,7 +49,7 @@ export default function useMerge( clientId, onMerge ) { * return the next list item of the parent list item if it exists. * * @param {string} id A list item client ID. - * @return {string?} The client ID of the next list item. + * @return {?string} The client ID of the next list item. */ function _getNextId( id ) { const next = getNextBlockClientId( id ); @@ -68,7 +68,7 @@ export default function useMerge( clientId, onMerge ) { * line, regardless of indentation level. * * @param {string} id The client ID of the current list item. - * @return {string?} The client ID of the next list item. + * @return {?string} The client ID of the next list item. */ function getNextId( id ) { const order = getBlockOrder( id ); diff --git a/packages/block-library/src/list/test/edit.native.js b/packages/block-library/src/list/test/edit.native.js index 2393f2820cfc2e..3084b11ecbd146 100644 --- a/packages/block-library/src/list/test/edit.native.js +++ b/packages/block-library/src/list/test/edit.native.js @@ -50,7 +50,7 @@ describe( 'List block', () => { await triggerBlockListLayout( listBlock ); // Get List item - const listItemBlock = await getBlock( screen, 'List item' ); + const listItemBlock = await getBlock( screen, 'List Item' ); fireEvent.press( listItemBlock ); expect( listItemBlock ).toBeVisible(); @@ -75,7 +75,7 @@ describe( 'List block', () => { // Select List Item block const [ listItemBlock ] = screen.getAllByLabelText( - /List item Block\. Row 1/ + /List Item Block\. Row 1/ ); fireEvent.press( listItemBlock ); @@ -124,7 +124,7 @@ describe( 'List block', () => { // Select List Item block const [ firstNestedLevelBlock ] = within( listBlock ).getAllByLabelText( - /List item Block\. Row 2/ + /List Item Block\. Row 2/ ); fireEvent.press( firstNestedLevelBlock ); await triggerBlockListLayout( firstNestedLevelBlock ); @@ -157,9 +157,9 @@ describe( 'List block', () => { fireEvent.press( listBlock ); await triggerBlockListLayout( listBlock ); - // Select Secont List Item block + // Select Second List Item block const [ listItemBlock ] = screen.getAllByLabelText( - /List item Block\. Row 2/ + /List Item Block\. Row 2/ ); fireEvent.press( listItemBlock ); @@ -169,7 +169,7 @@ describe( 'List block', () => { // Await recently indented list item layout const [ listItemBlock1 ] = screen.getAllByLabelText( - /List item Block\. Row 1/ + /List Item Block\. Row 1/ ); await triggerBlockListLayout( listItemBlock1 ); @@ -203,7 +203,7 @@ describe( 'List block', () => { // Select List Item block const [ firstNestedLevelBlock ] = within( listBlock ).getAllByLabelText( - /List item Block\. Row 1/ + /List Item Block\. Row 1/ ); fireEvent.press( firstNestedLevelBlock ); await triggerBlockListLayout( firstNestedLevelBlock ); @@ -217,7 +217,7 @@ describe( 'List block', () => { // Select nested List Item block const [ listItemBlock ] = within( innerBlockList ).getAllByLabelText( - /List item Block\. Row 1/ + /List Item Block\. Row 1/ ); fireEvent.press( listItemBlock ); @@ -500,7 +500,7 @@ describe( 'List block', () => { // Select List Item block const [ listItemBlock ] = within( listBlock ).getAllByLabelText( - /List item Block\. Row 1/ + /List Item Block\. Row 1/ ); fireEvent.press( listItemBlock ); @@ -560,7 +560,7 @@ describe( 'List block', () => { // Select List Item block const [ listItemBlock ] = within( listBlock ).getAllByLabelText( - /List item Block\. Row 1/ + /List Item Block\. Row 1/ ); fireEvent.press( listItemBlock ); diff --git a/packages/block-library/src/loginout/edit.js b/packages/block-library/src/loginout/edit.js index b6c2e9cf013041..9af634c87371cf 100644 --- a/packages/block-library/src/loginout/edit.js +++ b/packages/block-library/src/loginout/edit.js @@ -1,38 +1,74 @@ /** * WordPress dependencies */ -import { PanelBody, ToggleControl } from '@wordpress/components'; -import { __ } from '@wordpress/i18n'; import { InspectorControls, useBlockProps } from '@wordpress/block-editor'; +import { + ToggleControl, + __experimentalToolsPanel as ToolsPanel, + __experimentalToolsPanelItem as ToolsPanelItem, +} from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; +/** + * Internal dependencies + */ +import { useToolsPanelDropdownMenuProps } from '../utils/hooks'; export default function LoginOutEdit( { attributes, setAttributes } ) { const { displayLoginAsForm, redirectToCurrent } = attributes; + const dropdownMenuProps = useToolsPanelDropdownMenuProps(); return ( <> <InspectorControls> - <PanelBody title={ __( 'Settings' ) }> - <ToggleControl - __nextHasNoMarginBottom + <ToolsPanel + label={ __( 'Settings' ) } + resetAll={ () => { + setAttributes( { + displayLoginAsForm: false, + redirectToCurrent: true, + } ); + } } + dropdownMenuProps={ dropdownMenuProps } + > + <ToolsPanelItem label={ __( 'Display login as form' ) } - checked={ displayLoginAsForm } - onChange={ () => - setAttributes( { - displayLoginAsForm: ! displayLoginAsForm, - } ) + isShownByDefault + hasValue={ () => displayLoginAsForm } + onDeselect={ () => + setAttributes( { displayLoginAsForm: false } ) } - /> - <ToggleControl - __nextHasNoMarginBottom + > + <ToggleControl + __nextHasNoMarginBottom + label={ __( 'Display login as form' ) } + checked={ displayLoginAsForm } + onChange={ () => + setAttributes( { + displayLoginAsForm: ! displayLoginAsForm, + } ) + } + /> + </ToolsPanelItem> + <ToolsPanelItem label={ __( 'Redirect to current URL' ) } - checked={ redirectToCurrent } - onChange={ () => - setAttributes( { - redirectToCurrent: ! redirectToCurrent, - } ) + isShownByDefault + hasValue={ () => ! redirectToCurrent } + onDeselect={ () => + setAttributes( { redirectToCurrent: true } ) } - /> - </PanelBody> + > + <ToggleControl + __nextHasNoMarginBottom + label={ __( 'Redirect to current URL' ) } + checked={ redirectToCurrent } + onChange={ () => + setAttributes( { + redirectToCurrent: ! redirectToCurrent, + } ) + } + /> + </ToolsPanelItem> + </ToolsPanel> </InspectorControls> <div { ...useBlockProps( { diff --git a/packages/block-library/src/media-text/edit.js b/packages/block-library/src/media-text/edit.js index a946a499b26f21..820c7927303114 100644 --- a/packages/block-library/src/media-text/edit.js +++ b/packages/block-library/src/media-text/edit.js @@ -76,6 +76,7 @@ function attributesFromMedia( { mediaLink: undefined, href: undefined, focalPoint: undefined, + useFeaturedImage: false, } ); return; } @@ -128,10 +129,37 @@ function attributesFromMedia( { mediaLink: media.link || undefined, href: newHref, focalPoint: undefined, + useFeaturedImage: false, } ); }; } +function MediaTextResolutionTool( { image, value, onChange } ) { + const { imageSizes } = useSelect( ( select ) => { + const { getSettings } = select( blockEditorStore ); + return { + imageSizes: getSettings().imageSizes, + }; + }, [] ); + + if ( ! imageSizes?.length ) { + return null; + } + + const imageSizeOptions = imageSizes + .filter( ( { slug } ) => getImageSourceUrlBySizeSlug( image, slug ) ) + .map( ( { name, slug } ) => ( { value: slug, label: name } ) ); + + return ( + <ResolutionTool + value={ value } + defaultValue={ DEFAULT_MEDIA_SIZE_SLUG } + options={ imageSizeOptions } + onChange={ onChange } + /> + ); +} + function MediaTextEdit( { attributes, isSelected, @@ -152,12 +180,12 @@ function MediaTextEdit( { mediaType, mediaUrl, mediaWidth, + mediaSizeSlug, rel, verticalAlignment, allowedBlocks, useFeaturedImage, } = attributes; - const mediaSizeSlug = attributes.mediaSizeSlug || DEFAULT_MEDIA_SIZE_SLUG; const [ featuredImage ] = useEntityProp( 'postType', @@ -166,11 +194,32 @@ function MediaTextEdit( { postId ); - const featuredImageMedia = useSelect( - ( select ) => - featuredImage && - select( coreStore ).getMedia( featuredImage, { context: 'view' } ), - [ featuredImage ] + const { featuredImageMedia } = useSelect( + ( select ) => { + return { + featuredImageMedia: + featuredImage && useFeaturedImage + ? select( coreStore ).getMedia( featuredImage, { + context: 'view', + } ) + : undefined, + }; + }, + [ featuredImage, useFeaturedImage ] + ); + + const { image } = useSelect( + ( select ) => { + return { + image: + mediaId && isSelected + ? select( coreStore ).getMedia( mediaId, { + context: 'view', + } ) + : null, + }; + }, + [ isSelected, mediaId ] ); const featuredImageURL = useFeaturedImage @@ -197,22 +246,6 @@ function MediaTextEdit( { } ); }; - const { imageSizes, image } = useSelect( - ( select ) => { - const { getSettings } = select( blockEditorStore ); - return { - image: - mediaId && isSelected - ? select( coreStore ).getMedia( mediaId, { - context: 'view', - } ) - : null, - imageSizes: getSettings()?.imageSizes, - }; - }, - [ isSelected, mediaId ] - ); - const refMedia = useRef(); const imperativeFocalPointPreview = ( value ) => { const { style } = refMedia.current; @@ -260,10 +293,6 @@ function MediaTextEdit( { const onVerticalAlignmentChange = ( alignment ) => { setAttributes( { verticalAlignment: alignment } ); }; - - const imageSizeOptions = imageSizes - .filter( ( { slug } ) => getImageSourceUrlBySizeSlug( image, slug ) ) - .map( ( { name, slug } ) => ( { value: slug, label: name } ) ); const updateImage = ( newMediaSizeSlug ) => { const newUrl = getImageSourceUrlBySizeSlug( image, newMediaSizeSlug ); @@ -409,9 +438,9 @@ function MediaTextEdit( { </ToolsPanelItem> ) } { mediaType === 'image' && ! useFeaturedImage && ( - <ResolutionTool + <MediaTextResolutionTool + image={ image } value={ mediaSizeSlug } - options={ imageSizeOptions } onChange={ updateImage } /> ) } diff --git a/packages/block-library/src/media-text/edit.native.js b/packages/block-library/src/media-text/edit.native.js index a5ccb007bdf5c2..d2798fce52ce13 100644 --- a/packages/block-library/src/media-text/edit.native.js +++ b/packages/block-library/src/media-text/edit.native.js @@ -218,7 +218,7 @@ class MediaTextEdit extends Component { ? ( containerWidth * mediaWidth ) / 100 - styles.mediaAreaPadding.width : containerWidth; - const aligmentStyles = + const alignmentStyles = styles[ `is-vertically-aligned-${ verticalAlignment || 'center' }` ]; @@ -244,7 +244,7 @@ class MediaTextEdit extends Component { imageFill, focalPoint, isSelected, - aligmentStyles, + alignmentStyles, shouldStack, } } /> diff --git a/packages/block-library/src/media-text/media-container.native.js b/packages/block-library/src/media-text/media-container.native.js index 3bf4fbf25d8f2a..f37d9af3ed8be1 100644 --- a/packages/block-library/src/media-text/media-container.native.js +++ b/packages/block-library/src/media-text/media-container.native.js @@ -171,7 +171,7 @@ class MediaContainer extends Component { renderImage( params, openMediaOptions ) { const { isUploadInProgress } = this.state; const { - aligmentStyles, + alignmentStyles, focalPoint, imageFill, isMediaSelected, @@ -205,7 +205,7 @@ class MediaContainer extends Component { style={ [ imageFill && styles.imageCropped, styles.mediaImageContainer, - ! isUploadInProgress && aligmentStyles, + ! isUploadInProgress && alignmentStyles, ] } > <Image @@ -232,7 +232,7 @@ class MediaContainer extends Component { renderVideo( params ) { const { - aligmentStyles, + alignmentStyles, mediaUrl, isSelected, getStylesFromColorScheme, @@ -261,7 +261,7 @@ class MediaContainer extends Component { onPress={ this.onMediaPressed } disabled={ ! isSelected } > - <View style={ [ styles.videoContainer, aligmentStyles ] }> + <View style={ [ styles.videoContainer, alignmentStyles ] }> <View style={ [ styles.videoContent, diff --git a/packages/block-library/src/media-text/test/transforms.native.js b/packages/block-library/src/media-text/test/transforms.native.js index 10e3f08410d6e9..581aad8de32d4d 100644 --- a/packages/block-library/src/media-text/test/transforms.native.js +++ b/packages/block-library/src/media-text/test/transforms.native.js @@ -23,16 +23,16 @@ const initialHtmlWithVideo = ` <!-- /wp:paragraph --></div></div> <!-- /wp:media-text -->`; -const tranformsWithInnerBlocks = [ 'Columns', 'Group' ]; +const transformsWithInnerBlocks = [ 'Columns', 'Group' ]; const blockTransformsWithImage = [ 'Image', 'Cover', - ...tranformsWithInnerBlocks, + ...transformsWithInnerBlocks, ]; const blockTransformsWithVideo = [ 'Video', 'Cover', - ...tranformsWithInnerBlocks, + ...transformsWithInnerBlocks, ]; setupCoreBlocks(); @@ -52,7 +52,9 @@ describe( `${ block } block transformations`, () => { { isMediaBlock: true, hasInnerBlocks: - tranformsWithInnerBlocks.includes( blockTransform ), + transformsWithInnerBlocks.includes( + blockTransform + ), } ); expect( newBlock ).toBeVisible(); @@ -88,7 +90,9 @@ describe( `${ block } block transformations`, () => { { isMediaBlock: true, hasInnerBlocks: - tranformsWithInnerBlocks.includes( blockTransform ), + transformsWithInnerBlocks.includes( + blockTransform + ), } ); expect( newBlock ).toBeVisible(); diff --git a/packages/block-library/src/missing/test/edit.native.js b/packages/block-library/src/missing/test/edit.native.js index 47d0da572b7c88..eba1169ae643b7 100644 --- a/packages/block-library/src/missing/test/edit.native.js +++ b/packages/block-library/src/missing/test/edit.native.js @@ -10,7 +10,6 @@ import { Text } from 'react-native'; import { BottomSheet, Icon } from '@wordpress/components'; import { help, plugins } from '@wordpress/icons'; import { storeConfig } from '@wordpress/block-editor'; -jest.mock( '@wordpress/blocks' ); jest.mock( '@wordpress/block-editor/src/store/selectors' ); /** diff --git a/packages/block-library/src/more/edit.js b/packages/block-library/src/more/edit.js index bcad7ec1b83662..af903640b6b8dd 100644 --- a/packages/block-library/src/more/edit.js +++ b/packages/block-library/src/more/edit.js @@ -2,10 +2,18 @@ * WordPress dependencies */ import { __ } from '@wordpress/i18n'; -import { PanelBody, ToggleControl } from '@wordpress/components'; +import { + __experimentalToolsPanel as ToolsPanel, + __experimentalToolsPanelItem as ToolsPanelItem, + ToggleControl, +} from '@wordpress/components'; import { InspectorControls, useBlockProps } from '@wordpress/block-editor'; import { ENTER } from '@wordpress/keycodes'; import { getDefaultBlockName, createBlock } from '@wordpress/blocks'; +/** + * Internal dependencies + */ +import { useToolsPanelDropdownMenuProps } from '../utils/hooks'; const DEFAULT_TEXT = __( 'Read more' ); @@ -37,20 +45,39 @@ export default function MoreEdit( { width: `${ ( customText ? customText : DEFAULT_TEXT ).length + 1.2 }em`, }; + const dropdownMenuProps = useToolsPanelDropdownMenuProps(); + return ( <> <InspectorControls> - <PanelBody> - <ToggleControl - __nextHasNoMarginBottom - label={ __( - 'Hide the excerpt on the full content page' - ) } - checked={ !! noTeaser } - onChange={ toggleHideExcerpt } - help={ getHideExcerptHelp } - /> - </PanelBody> + <ToolsPanel + label={ __( 'Settings' ) } + resetAll={ () => { + setAttributes( { + noTeaser: false, + } ); + } } + dropdownMenuProps={ dropdownMenuProps } + > + <ToolsPanelItem + label={ __( 'Hide excerpt' ) } + isShownByDefault + hasValue={ () => noTeaser } + onDeselect={ () => + setAttributes( { noTeaser: false } ) + } + > + <ToggleControl + __nextHasNoMarginBottom + label={ __( + 'Hide the excerpt on the full content page' + ) } + checked={ !! noTeaser } + onChange={ toggleHideExcerpt } + help={ getHideExcerptHelp } + /> + </ToolsPanelItem> + </ToolsPanel> </InspectorControls> <div { ...useBlockProps() }> <input diff --git a/packages/block-library/src/navigation-link/edit.js b/packages/block-library/src/navigation-link/edit.js index 9e2e171975570f..8ff438dc20abef 100644 --- a/packages/block-library/src/navigation-link/edit.js +++ b/packages/block-library/src/navigation-link/edit.js @@ -9,7 +9,8 @@ import clsx from 'clsx'; import { createBlock } from '@wordpress/blocks'; import { useSelect, useDispatch } from '@wordpress/data'; import { - PanelBody, + __experimentalToolsPanel as ToolsPanel, + __experimentalToolsPanelItem as ToolsPanelItem, TextControl, TextareaControl, ToolbarButton, @@ -156,76 +157,115 @@ function getMissingText( type ) { /* * Warning, this duplicated in * packages/block-library/src/navigation-submenu/edit.js - * Consider reuseing this components for both blocks. + * Consider reusing this components for both blocks. */ function Controls( { attributes, setAttributes, setIsLabelFieldFocused } ) { const { label, url, description, title, rel } = attributes; return ( - <PanelBody title={ __( 'Settings' ) }> - <TextControl - __nextHasNoMarginBottom - __next40pxDefaultSize - value={ label ? stripHTML( label ) : '' } - onChange={ ( labelValue ) => { - setAttributes( { label: labelValue } ); - } } + <ToolsPanel label={ __( 'Settings' ) }> + <ToolsPanelItem + hasValue={ () => !! label } label={ __( 'Text' ) } - autoComplete="off" - onFocus={ () => setIsLabelFieldFocused( true ) } - onBlur={ () => setIsLabelFieldFocused( false ) } - /> - <TextControl - __nextHasNoMarginBottom - __next40pxDefaultSize - value={ url ? safeDecodeURI( url ) : '' } - onChange={ ( urlValue ) => { - updateAttributes( - { url: urlValue }, - setAttributes, - attributes - ); - } } + onDeselect={ () => setAttributes( { label: '' } ) } + isShownByDefault + > + <TextControl + __nextHasNoMarginBottom + __next40pxDefaultSize + label={ __( 'Text' ) } + value={ label ? stripHTML( label ) : '' } + onChange={ ( labelValue ) => { + setAttributes( { label: labelValue } ); + } } + autoComplete="off" + onFocus={ () => setIsLabelFieldFocused( true ) } + onBlur={ () => setIsLabelFieldFocused( false ) } + /> + </ToolsPanelItem> + + <ToolsPanelItem + hasValue={ () => !! url } label={ __( 'Link' ) } - autoComplete="off" - /> - <TextareaControl - __nextHasNoMarginBottom - value={ description || '' } - onChange={ ( descriptionValue ) => { - setAttributes( { description: descriptionValue } ); - } } + onDeselect={ () => setAttributes( { url: '' } ) } + isShownByDefault + > + <TextControl + __nextHasNoMarginBottom + __next40pxDefaultSize + label={ __( 'Link' ) } + value={ url ? safeDecodeURI( url ) : '' } + onChange={ ( urlValue ) => { + updateAttributes( + { url: urlValue }, + setAttributes, + attributes + ); + } } + autoComplete="off" + /> + </ToolsPanelItem> + + <ToolsPanelItem + hasValue={ () => !! description } label={ __( 'Description' ) } - help={ __( - 'The description will be displayed in the menu if the current theme supports it.' - ) } - /> - <TextControl - __nextHasNoMarginBottom - __next40pxDefaultSize - value={ title || '' } - onChange={ ( titleValue ) => { - setAttributes( { title: titleValue } ); - } } + onDeselect={ () => setAttributes( { description: '' } ) } + isShownByDefault + > + <TextareaControl + __nextHasNoMarginBottom + label={ __( 'Description' ) } + value={ description || '' } + onChange={ ( descriptionValue ) => { + setAttributes( { description: descriptionValue } ); + } } + help={ __( + 'The description will be displayed in the menu if the current theme supports it.' + ) } + /> + </ToolsPanelItem> + + <ToolsPanelItem + hasValue={ () => !! title } label={ __( 'Title attribute' ) } - autoComplete="off" - help={ __( - 'Additional information to help clarify the purpose of the link.' - ) } - /> - <TextControl - __nextHasNoMarginBottom - __next40pxDefaultSize - value={ rel || '' } - onChange={ ( relValue ) => { - setAttributes( { rel: relValue } ); - } } + onDeselect={ () => setAttributes( { title: '' } ) } + isShownByDefault + > + <TextControl + __nextHasNoMarginBottom + __next40pxDefaultSize + label={ __( 'Title attribute' ) } + value={ title || '' } + onChange={ ( titleValue ) => { + setAttributes( { title: titleValue } ); + } } + autoComplete="off" + help={ __( + 'Additional information to help clarify the purpose of the link.' + ) } + /> + </ToolsPanelItem> + + <ToolsPanelItem + hasValue={ () => !! rel } label={ __( 'Rel attribute' ) } - autoComplete="off" - help={ __( - 'The relationship of the linked URL as space-separated link types.' - ) } - /> - </PanelBody> + onDeselect={ () => setAttributes( { rel: '' } ) } + isShownByDefault + > + <TextControl + __nextHasNoMarginBottom + __next40pxDefaultSize + label={ __( 'Rel attribute' ) } + value={ rel || '' } + onChange={ ( relValue ) => { + setAttributes( { rel: relValue } ); + } } + autoComplete="off" + help={ __( + 'The relationship of the linked URL as space-separated link types.' + ) } + /> + </ToolsPanelItem> + </ToolsPanel> ); } @@ -527,12 +567,6 @@ export default function NavigationLinkEdit( { ) } placeholder={ itemLabelPlaceholder } withoutInteractiveFormatting - allowedFormats={ [ - 'core/bold', - 'core/italic', - 'core/image', - 'core/strikethrough', - ] } /> { description && ( <span className="wp-block-navigation-item__description"> diff --git a/packages/block-library/src/navigation-link/index.php b/packages/block-library/src/navigation-link/index.php index 5653e04fca88a3..81df2099dfc188 100644 --- a/packages/block-library/src/navigation-link/index.php +++ b/packages/block-library/src/navigation-link/index.php @@ -177,7 +177,22 @@ function render_block_core_navigation_link( $attributes, $content, $block ) { // Don't render the block's subtree if it is a draft or if the ID does not exist. if ( $is_post_type && $navigation_link_has_id ) { $post = get_post( $attributes['id'] ); - if ( ! $post || 'publish' !== $post->post_status ) { + /** + * Filter allowed post_status for navigation link block to render. + * + * @since 6.8.0 + * + * @param array $post_status + * @param array $attributes + * @param WP_Block $block + */ + $allowed_post_status = (array) apply_filters( + 'render_block_core_navigation_link_allowed_post_status', + array( 'publish' ), + $attributes, + $block + ); + if ( ! $post || ! in_array( $post->post_status, $allowed_post_status, true ) ) { return ''; } } diff --git a/packages/block-library/src/navigation-link/link-ui.js b/packages/block-library/src/navigation-link/link-ui.js index ee238c71ed28e0..47a8cb99ffdb24 100644 --- a/packages/block-library/src/navigation-link/link-ui.js +++ b/packages/block-library/src/navigation-link/link-ui.js @@ -10,7 +10,7 @@ import { } from '@wordpress/components'; import { __, sprintf, isRTL } from '@wordpress/i18n'; import { - __experimentalLinkControl as LinkControl, + LinkControl, store as blockEditorStore, privateApis as blockEditorPrivateApis, } from '@wordpress/block-editor'; @@ -78,7 +78,7 @@ export function getSuggestionsQuery( type, kind ) { } } -function LinkUIBlockInserter( { clientId, onBack, onSelectBlock } ) { +function LinkUIBlockInserter( { clientId, onBack } ) { const { rootBlockClientId } = useSelect( ( select ) => { const { getBlockRootClientId } = select( blockEditorStore ); @@ -96,7 +96,7 @@ function LinkUIBlockInserter( { clientId, onBack, onSelectBlock } ) { LinkControl, `link-ui-block-inserter__title` ); - const dialogDescritionId = useInstanceId( + const dialogDescriptionId = useInstanceId( LinkControl, `link-ui-block-inserter__description` ); @@ -110,13 +110,13 @@ function LinkUIBlockInserter( { clientId, onBack, onSelectBlock } ) { className="link-ui-block-inserter" role="dialog" aria-labelledby={ dialogTitleId } - aria-describedby={ dialogDescritionId } + aria-describedby={ dialogDescriptionId } ref={ focusOnMountRef } > <VisuallyHidden> <h2 id={ dialogTitleId }>{ __( 'Add block' ) }</h2> - <p id={ dialogDescritionId }> + <p id={ dialogDescriptionId }> { __( 'Choose a block to add to your Navigation.' ) } </p> </VisuallyHidden> @@ -140,7 +140,6 @@ function LinkUIBlockInserter( { clientId, onBack, onSelectBlock } ) { prioritizePatterns={ false } selectBlockOnInsert hasSearch={ false } - onSelect={ onSelectBlock } /> </div> ); @@ -198,15 +197,11 @@ function UnforwardedLinkUI( props, ref ) { LinkUI, `link-ui-link-control__title` ); - const dialogDescritionId = useInstanceId( + const dialogDescriptionId = useInstanceId( LinkUI, `link-ui-link-control__description` ); - // Selecting a block should close the popover and also remove the (previously) automatically inserted - // link block so that the newly selected block can be inserted in its place. - const { onClose: onSelectBlock } = props; - return ( <Popover ref={ ref } @@ -219,12 +214,12 @@ function UnforwardedLinkUI( props, ref ) { <div role="dialog" aria-labelledby={ dialogTitleId } - aria-describedby={ dialogDescritionId } + aria-describedby={ dialogDescriptionId } > <VisuallyHidden> <h2 id={ dialogTitleId }>{ __( 'Add link' ) }</h2> - <p id={ dialogDescritionId }> + <p id={ dialogDescriptionId }> { __( 'Search for and add a link to your Navigation.' ) } @@ -287,7 +282,6 @@ function UnforwardedLinkUI( props, ref ) { setAddingBlock( false ); setFocusAddBlockButton( true ); } } - onSelectBlock={ onSelectBlock } /> ) } </Popover> diff --git a/packages/block-library/src/navigation-submenu/edit.js b/packages/block-library/src/navigation-submenu/edit.js index acc9510d0d3d30..b5f40ffb676770 100644 --- a/packages/block-library/src/navigation-submenu/edit.js +++ b/packages/block-library/src/navigation-submenu/edit.js @@ -8,11 +8,12 @@ import clsx from 'clsx'; */ import { useSelect, useDispatch } from '@wordpress/data'; import { - PanelBody, TextControl, TextareaControl, ToolbarButton, ToolbarGroup, + __experimentalToolsPanel as ToolsPanel, + __experimentalToolsPanelItem as ToolsPanelItem, } from '@wordpress/components'; import { displayShortcut, isKeyboardEvent } from '@wordpress/keycodes'; import { __ } from '@wordpress/i18n'; @@ -43,6 +44,7 @@ import { getColors, getNavigationChildBlockProps, } from '../navigation/edit/utils'; +import { useToolsPanelDropdownMenuProps } from '../utils/hooks'; const ALLOWED_BLOCKS = [ 'core/navigation-link', @@ -152,6 +154,7 @@ export default function NavigationSubmenuEdit( { const isDraggingWithin = useIsDraggingWithin( listItemRef ); const itemLabelPlaceholder = __( 'Add text…' ); const ref = useRef(); + const dropdownMenuProps = useToolsPanelDropdownMenuProps(); const { parentCount, @@ -273,7 +276,7 @@ export default function NavigationSubmenuEdit( { // as it shares the CMD+K shortcut. // See https://github.com/WordPress/gutenberg/pull/59845. event.preventDefault(); - // If we don't stop propogation, this event bubbles up to the parent submenu item + // If we don't stop propagation, this event bubbles up to the parent submenu item event.stopPropagation(); setIsLinkOpen( true ); setOpenedBy( ref.current ); @@ -382,67 +385,120 @@ export default function NavigationSubmenuEdit( { </BlockControls> { /* Warning, this duplicated in packages/block-library/src/navigation-link/edit.js */ } <InspectorControls> - <PanelBody title={ __( 'Settings' ) }> - <TextControl - __nextHasNoMarginBottom - __next40pxDefaultSize - value={ label || '' } - onChange={ ( labelValue ) => { - setAttributes( { label: labelValue } ); - } } + <ToolsPanel + label={ __( 'Settings' ) } + resetAll={ () => { + setAttributes( { + label: '', + url: '', + description: '', + title: '', + rel: '', + } ); + } } + dropdownMenuProps={ dropdownMenuProps } + > + <ToolsPanelItem label={ __( 'Text' ) } - autoComplete="off" - /> - <TextControl - __nextHasNoMarginBottom - __next40pxDefaultSize - value={ url || '' } - onChange={ ( urlValue ) => { - setAttributes( { url: urlValue } ); - } } + isShownByDefault + hasValue={ () => !! label } + onDeselect={ () => setAttributes( { label: '' } ) } + > + <TextControl + __nextHasNoMarginBottom + __next40pxDefaultSize + value={ label || '' } + onChange={ ( labelValue ) => { + setAttributes( { label: labelValue } ); + } } + label={ __( 'Text' ) } + autoComplete="off" + /> + </ToolsPanelItem> + + <ToolsPanelItem label={ __( 'Link' ) } - autoComplete="off" - /> - <TextareaControl - __nextHasNoMarginBottom - value={ description || '' } - onChange={ ( descriptionValue ) => { - setAttributes( { - description: descriptionValue, - } ); - } } + isShownByDefault + hasValue={ () => !! url } + onDeselect={ () => setAttributes( { url: '' } ) } + > + <TextControl + __nextHasNoMarginBottom + __next40pxDefaultSize + value={ url || '' } + onChange={ ( urlValue ) => { + setAttributes( { url: urlValue } ); + } } + label={ __( 'Link' ) } + autoComplete="off" + /> + </ToolsPanelItem> + + <ToolsPanelItem label={ __( 'Description' ) } - help={ __( - 'The description will be displayed in the menu if the current theme supports it.' - ) } - /> - <TextControl - __nextHasNoMarginBottom - __next40pxDefaultSize - value={ title || '' } - onChange={ ( titleValue ) => { - setAttributes( { title: titleValue } ); - } } + isShownByDefault + hasValue={ () => !! description } + onDeselect={ () => + setAttributes( { description: '' } ) + } + > + <TextareaControl + __nextHasNoMarginBottom + value={ description || '' } + onChange={ ( descriptionValue ) => { + setAttributes( { + description: descriptionValue, + } ); + } } + label={ __( 'Description' ) } + help={ __( + 'The description will be displayed in the menu if the current theme supports it.' + ) } + /> + </ToolsPanelItem> + + <ToolsPanelItem label={ __( 'Title attribute' ) } - autoComplete="off" - help={ __( - 'Additional information to help clarify the purpose of the link.' - ) } - /> - <TextControl - __nextHasNoMarginBottom - __next40pxDefaultSize - value={ rel || '' } - onChange={ ( relValue ) => { - setAttributes( { rel: relValue } ); - } } + isShownByDefault + hasValue={ () => !! title } + onDeselect={ () => setAttributes( { title: '' } ) } + > + <TextControl + __nextHasNoMarginBottom + __next40pxDefaultSize + value={ title || '' } + onChange={ ( titleValue ) => { + setAttributes( { title: titleValue } ); + } } + label={ __( 'Title attribute' ) } + autoComplete="off" + help={ __( + 'Additional information to help clarify the purpose of the link.' + ) } + /> + </ToolsPanelItem> + + <ToolsPanelItem label={ __( 'Rel attribute' ) } - autoComplete="off" - help={ __( - 'The relationship of the linked URL as space-separated link types.' - ) } - /> - </PanelBody> + isShownByDefault + hasValue={ () => !! rel } + onDeselect={ () => setAttributes( { rel: '' } ) } + > + <TextControl + __nextHasNoMarginBottom + __next40pxDefaultSize + value={ rel || '' } + onChange={ ( relValue ) => { + setAttributes( { rel: relValue } ); + } } + label={ __( 'Rel attribute' ) } + autoComplete="off" + help={ __( + 'The relationship of the linked URL as space-separated link types.' + ) } + /> + </ToolsPanelItem> + </ToolsPanel> </InspectorControls> <div { ...blockProps }> { /* eslint-disable jsx-a11y/anchor-is-valid */ } @@ -461,12 +517,6 @@ export default function NavigationSubmenuEdit( { aria-label={ __( 'Navigation link text' ) } placeholder={ itemLabelPlaceholder } withoutInteractiveFormatting - allowedFormats={ [ - 'core/bold', - 'core/italic', - 'core/image', - 'core/strikethrough', - ] } onClick={ () => { if ( ! openSubmenusOnClick && ! url ) { setIsLinkOpen( true ); @@ -474,6 +524,11 @@ export default function NavigationSubmenuEdit( { } } } /> + { description && ( + <span className="wp-block-navigation-item__description"> + { description } + </span> + ) } { ! openSubmenusOnClick && isLinkOpen && ( <LinkUI clientId={ clientId } diff --git a/packages/block-library/src/navigation-submenu/index.php b/packages/block-library/src/navigation-submenu/index.php index 92b55e291606e8..0f560e2849fac2 100644 --- a/packages/block-library/src/navigation-submenu/index.php +++ b/packages/block-library/src/navigation-submenu/index.php @@ -159,7 +159,16 @@ function render_block_core_navigation_submenu( $attributes, $content, $block ) { $html .= '>'; // End appending HTML attributes to anchor tag. + $html .= '<span class="wp-block-navigation-item__label">'; $html .= $label; + $html .= '</span>'; + + // Add description if available. + if ( ! empty( $attributes['description'] ) ) { + $html .= '<span class="wp-block-navigation-item__description">'; + $html .= wp_kses_post( $attributes['description'] ); + $html .= '</span>'; + } $html .= '</a>'; // End anchor tag content. @@ -180,6 +189,13 @@ function render_block_core_navigation_submenu( $attributes, $content, $block ) { $html .= '</span>'; + // Add description if available. + if ( ! empty( $attributes['description'] ) ) { + $html .= '<span class="wp-block-navigation-item__description">'; + $html .= wp_kses_post( $attributes['description'] ); + $html .= '</span>'; + } + $html .= '</button>'; $html .= '<span class="wp-block-navigation__submenu-icon">' . block_core_navigation_submenu_render_submenu_icon() . '</span>'; @@ -222,7 +238,7 @@ function render_block_core_navigation_submenu( $attributes, $content, $block ) { if ( strpos( $inner_blocks_html, 'current-menu-item' ) ) { $tag_processor = new WP_HTML_Tag_Processor( $html ); - while ( $tag_processor->next_tag( array( 'class_name' => 'wp-block-navigation-item__content' ) ) ) { + while ( $tag_processor->next_tag( array( 'class_name' => 'wp-block-navigation-item' ) ) ) { $tag_processor->add_class( 'current-menu-ancestor' ); } $html = $tag_processor->get_updated_html(); diff --git a/packages/block-library/src/navigation/README.md b/packages/block-library/src/navigation/README.md index b59c4b40d85e73..cbd52a8f21f0ed 100644 --- a/packages/block-library/src/navigation/README.md +++ b/packages/block-library/src/navigation/README.md @@ -10,4 +10,5 @@ The structural CSS for the navigation block targets generic classnames across me - `.wp-block-navigation-item` is applied to every menu item. - `.wp-block-navigation-item__content` is applied to the link inside a menu item. - `.wp-block-navigation-item__label` is applied to the innermost container around the menu item text label. +- `.wp-block-navigation-item__description` is applied to the innermost container around the menu item description. - `.wp-block-navigation__submenu-icon` is applied to the submenu indicator (chevron). diff --git a/packages/block-library/src/navigation/edit/deleted-navigation-warning.js b/packages/block-library/src/navigation/edit/deleted-navigation-warning.js index 22d1e339c5c004..3d40f4d031ed34 100644 --- a/packages/block-library/src/navigation/edit/deleted-navigation-warning.js +++ b/packages/block-library/src/navigation/edit/deleted-navigation-warning.js @@ -4,9 +4,16 @@ import { Warning } from '@wordpress/block-editor'; import { Button, Notice } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; -import { createInterpolateElement } from '@wordpress/element'; +import { useState, createInterpolateElement } from '@wordpress/element'; function DeletedNavigationWarning( { onCreateNew, isNotice = false } ) { + const [ isButtonDisabled, setIsButtonDisabled ] = useState( false ); + + const handleButtonClick = () => { + setIsButtonDisabled( true ); + onCreateNew(); + }; + const message = createInterpolateElement( __( 'Navigation Menu has been deleted or is unavailable. <button>Create a new Menu?</button>' @@ -15,8 +22,10 @@ function DeletedNavigationWarning( { onCreateNew, isNotice = false } ) { button: ( <Button __next40pxDefaultSize - onClick={ onCreateNew } + onClick={ handleButtonClick } variant="link" + disabled={ isButtonDisabled } + accessibleWhenDisabled /> ), } diff --git a/packages/block-library/src/navigation/edit/index.js b/packages/block-library/src/navigation/edit/index.js index ae7dd60bd0c5ba..a9722c0b4f070e 100644 --- a/packages/block-library/src/navigation/edit/index.js +++ b/packages/block-library/src/navigation/edit/index.js @@ -142,24 +142,28 @@ function ColorTools( { label: __( 'Text' ), onColorChange: setTextColor, resetAllFilter: () => setTextColor(), + clearable: true, }, { colorValue: backgroundColor.color, label: __( 'Background' ), onColorChange: setBackgroundColor, resetAllFilter: () => setBackgroundColor(), + clearable: true, }, { colorValue: overlayTextColor.color, label: __( 'Submenu & overlay text' ), onColorChange: setOverlayTextColor, resetAllFilter: () => setOverlayTextColor(), + clearable: true, }, { colorValue: overlayBackgroundColor.color, label: __( 'Submenu & overlay background' ), onColorChange: setOverlayBackgroundColor, resetAllFilter: () => setOverlayBackgroundColor(), + clearable: true, }, ] } panelId={ clientId } diff --git a/packages/block-library/src/navigation/edit/navigation-menu-selector.js b/packages/block-library/src/navigation/edit/navigation-menu-selector.js index dceabf063b26e8..0efb597ff85324 100644 --- a/packages/block-library/src/navigation/edit/navigation-menu-selector.js +++ b/packages/block-library/src/navigation/edit/navigation-menu-selector.js @@ -61,7 +61,8 @@ function NavigationMenuSelector( { hasResolvedNavigationMenus, canUserCreateNavigationMenus, canSwitchNavigationMenu, - } = useNavigationMenu(); + isNavigationMenuMissing, + } = useNavigationMenu( currentMenuId ); const [ currentTitle ] = useEntityProp( 'postType', @@ -106,12 +107,18 @@ function NavigationMenuSelector( { const noBlockMenus = ! hasNavigationMenus && hasResolvedNavigationMenus; const menuUnavailable = hasResolvedNavigationMenus && currentMenuId === null; + const navMenuHasBeenDeleted = currentMenuId && isNavigationMenuMissing; let selectorLabel = ''; if ( isResolvingNavigationMenus ) { selectorLabel = __( 'Loading…' ); - } else if ( noMenuSelected || noBlockMenus || menuUnavailable ) { + } else if ( + noMenuSelected || + noBlockMenus || + menuUnavailable || + navMenuHasBeenDeleted + ) { // Note: classic Menus may be available. selectorLabel = __( 'Choose or create a Navigation Menu' ); } else { diff --git a/packages/block-library/src/navigation/index.js b/packages/block-library/src/navigation/index.js index d0405be794ffe8..36156e5f45bbaa 100644 --- a/packages/block-library/src/navigation/index.js +++ b/packages/block-library/src/navigation/index.js @@ -3,6 +3,9 @@ */ import { __ } from '@wordpress/i18n'; import { navigation as icon } from '@wordpress/icons'; +import { select } from '@wordpress/data'; +import { store as coreStore } from '@wordpress/core-data'; +import { decodeEntities } from '@wordpress/html-entities'; /** * Internal dependencies @@ -52,6 +55,23 @@ export const settings = { }, edit, save, + __experimentalLabel: ( { ref } ) => { + if ( ! ref ) { + return; + } + + const navigation = select( coreStore ).getEditedEntityRecord( + 'postType', + 'wp_navigation', + ref + ); + + if ( ! navigation?.title ) { + return; + } + + return decodeEntities( navigation.title ); + }, deprecated, }; diff --git a/packages/block-library/src/navigation/index.php b/packages/block-library/src/navigation/index.php index dd300eb12c6feb..43ca8331534275 100644 --- a/packages/block-library/src/navigation/index.php +++ b/packages/block-library/src/navigation/index.php @@ -539,8 +539,8 @@ private static function get_responsive_container_markup( $attributes, $inner_blo $inner_blocks_html, $toggle_aria_label_open, $toggle_aria_label_close, - esc_attr( implode( ' ', $responsive_container_classes ) ), - esc_attr( implode( ' ', $open_button_classes ) ), + esc_attr( trim( implode( ' ', $responsive_container_classes ) ) ), + esc_attr( trim( implode( ' ', $open_button_classes ) ) ), ( ! empty( $overlay_inline_styles ) ) ? "style=\"$overlay_inline_styles\"" : '', $toggle_button_content, $toggle_close_button_content, @@ -567,13 +567,14 @@ private static function get_nav_wrapper_attributes( $attributes, $inner_blocks ) $is_responsive_menu = static::is_responsive( $attributes ); $style = static::get_styles( $attributes ); $class = static::get_classes( $attributes ); - $wrapper_attributes = get_block_wrapper_attributes( - array( - 'class' => $class, - 'style' => $style, - 'aria-label' => $nav_menu_name, - ) + $extra_attributes = array( + 'class' => $class, + 'style' => $style, ); + if ( ! empty( $nav_menu_name ) ) { + $extra_attributes['aria-label'] = $nav_menu_name; + } + $wrapper_attributes = get_block_wrapper_attributes( $extra_attributes ); if ( $is_responsive_menu ) { $nav_element_directives = static::get_nav_element_directives( $is_interactive ); @@ -1436,20 +1437,6 @@ function block_core_navigation_get_most_recently_published_navigation() { return null; } -/** - * Accepts the serialized markup of a block and its inner blocks, and returns serialized markup of the inner blocks. - * - * @since 6.5.0 - * - * @param string $serialized_block The serialized markup of a block and its inner blocks. - * @return string - */ -function block_core_navigation_remove_serialized_parent_block( $serialized_block ) { - $start = strpos( $serialized_block, '-->' ) + strlen( '-->' ); - $end = strrpos( $serialized_block, '<!--' ); - return substr( $serialized_block, $start, $end - $start ); -} - /** * Mock a parsed block for the Navigation block given its inner blocks and the `wp_navigation` post object. * The `wp_navigation` post's `_wp_ignored_hooked_blocks` meta is queried to add the `metadata.ignoredHookedBlocks` attribute. @@ -1504,169 +1491,6 @@ function block_core_navigation_mock_parsed_block( $inner_blocks, $post ) { function block_core_navigation_insert_hooked_blocks( $inner_blocks, $post ) { $mock_navigation_block = block_core_navigation_mock_parsed_block( $inner_blocks, $post ); - if ( function_exists( 'apply_block_hooks_to_content' ) ) { - $mock_navigation_block_markup = serialize_block( $mock_navigation_block ); - return apply_block_hooks_to_content( $mock_navigation_block_markup, $post, 'insert_hooked_blocks' ); - } - - $hooked_blocks = get_hooked_blocks(); - $before_block_visitor = null; - $after_block_visitor = null; - - if ( ! empty( $hooked_blocks ) || has_filter( 'hooked_block_types' ) ) { - $before_block_visitor = make_before_block_visitor( $hooked_blocks, $post, 'insert_hooked_blocks' ); - $after_block_visitor = make_after_block_visitor( $hooked_blocks, $post, 'insert_hooked_blocks' ); - } - - return traverse_and_serialize_block( $mock_navigation_block, $before_block_visitor, $after_block_visitor ); -} - -/** - * Insert ignoredHookedBlocks meta into the Navigation block and its inner blocks. - * - * Given a Navigation block's inner blocks and its corresponding `wp_navigation` post object, - * this function inserts ignoredHookedBlocks meta into it, and returns the serialized inner blocks in a - * mock Navigation block wrapper. - * - * @since 6.5.0 - * - * @param array $inner_blocks Parsed inner blocks of a Navigation block. - * @param WP_Post $post `wp_navigation` post object corresponding to the block. - * @return string Serialized inner blocks in mock Navigation block wrapper, with hooked blocks inserted, if any. - */ -function block_core_navigation_set_ignored_hooked_blocks_metadata( $inner_blocks, $post ) { - $mock_navigation_block = block_core_navigation_mock_parsed_block( $inner_blocks, $post ); - $hooked_blocks = get_hooked_blocks(); - $before_block_visitor = null; - $after_block_visitor = null; - - if ( ! empty( $hooked_blocks ) || has_filter( 'hooked_block_types' ) ) { - $before_block_visitor = make_before_block_visitor( $hooked_blocks, $post, 'set_ignored_hooked_blocks_metadata' ); - $after_block_visitor = make_after_block_visitor( $hooked_blocks, $post, 'set_ignored_hooked_blocks_metadata' ); - } - - return traverse_and_serialize_block( $mock_navigation_block, $before_block_visitor, $after_block_visitor ); -} - -/** - * Updates the post meta with the list of ignored hooked blocks when the navigation is created or updated via the REST API. - * - * @access private - * @since 6.5.0 - * - * @param stdClass $post Post object. - * @return stdClass The updated post object. - */ -function block_core_navigation_update_ignore_hooked_blocks_meta( $post ) { - /* - * In this scenario the user has likely tried to create a navigation via the REST API. - * In which case we won't have a post ID to work with and store meta against. - */ - if ( empty( $post->ID ) ) { - return $post; - } - - /** - * Skip meta generation when consumers intentionally update specific Navigation fields - * and omit the content update. - */ - if ( ! isset( $post->post_content ) ) { - return $post; - } - - /* - * We run the Block Hooks mechanism to inject the `metadata.ignoredHookedBlocks` attribute into - * all anchor blocks. For the root level, we create a mock Navigation and extract them from there. - */ - $blocks = parse_blocks( $post->post_content ); - - /* - * Block Hooks logic requires a `WP_Post` object (rather than the `stdClass` with the updates that - * we're getting from the `rest_pre_insert_wp_navigation` filter) as its second argument (to be - * used as context for hooked blocks insertion). - * We thus have to look it up from the DB,based on `$post->ID`. - */ - $markup = block_core_navigation_set_ignored_hooked_blocks_metadata( $blocks, get_post( $post->ID ) ); - - $root_nav_block = parse_blocks( $markup )[0]; - $ignored_hooked_blocks = isset( $root_nav_block['attrs']['metadata']['ignoredHookedBlocks'] ) - ? $root_nav_block['attrs']['metadata']['ignoredHookedBlocks'] - : array(); - - if ( ! empty( $ignored_hooked_blocks ) ) { - $existing_ignored_hooked_blocks = get_post_meta( $post->ID, '_wp_ignored_hooked_blocks', true ); - if ( ! empty( $existing_ignored_hooked_blocks ) ) { - $existing_ignored_hooked_blocks = json_decode( $existing_ignored_hooked_blocks, true ); - $ignored_hooked_blocks = array_unique( array_merge( $ignored_hooked_blocks, $existing_ignored_hooked_blocks ) ); - } - update_post_meta( $post->ID, '_wp_ignored_hooked_blocks', json_encode( $ignored_hooked_blocks ) ); - } - - $post->post_content = block_core_navigation_remove_serialized_parent_block( $markup ); - return $post; -} - -/* - * Before adding our filter, we verify if it's already added in Core. - * However, during the build process, Gutenberg automatically prefixes our functions with "gutenberg_". - * Therefore, we concatenate the Core's function name to circumvent this prefix for our check. - */ -$rest_insert_wp_navigation_core_callback = 'block_core_navigation_' . 'update_ignore_hooked_blocks_meta'; // phpcs:ignore Generic.Strings.UnnecessaryStringConcat.Found - -/* - * Do not add the `block_core_navigation_update_ignore_hooked_blocks_meta` filter in the following cases: - * - If Core has added the `update_ignored_hooked_blocks_postmeta` filter already (WP >= 6.6); - * - or if the `$rest_insert_wp_navigation_core_callback` filter has already been added. - */ -if ( - ! has_filter( 'rest_pre_insert_wp_navigation', 'update_ignored_hooked_blocks_postmeta' ) && - ! has_filter( 'rest_pre_insert_wp_navigation', $rest_insert_wp_navigation_core_callback ) -) { - add_filter( 'rest_pre_insert_wp_navigation', 'block_core_navigation_update_ignore_hooked_blocks_meta' ); -} - -/** - * Hooks into the REST API response for the core/navigation block and adds the first and last inner blocks. - * - * @since 6.5.0 - * - * @param WP_REST_Response $response The response object. - * @param WP_Post $post Post object. - * @return WP_REST_Response The response object. - */ -function block_core_navigation_insert_hooked_blocks_into_rest_response( $response, $post ) { - if ( ! isset( $response->data['content']['raw'] ) || ! isset( $response->data['content']['rendered'] ) ) { - return $response; - } - $parsed_blocks = parse_blocks( $response->data['content']['raw'] ); - $content = block_core_navigation_insert_hooked_blocks( $parsed_blocks, $post ); - - // Remove mock Navigation block wrapper. - $content = block_core_navigation_remove_serialized_parent_block( $content ); - - $response->data['content']['raw'] = $content; - - /** This filter is documented in wp-includes/post-template.php */ - $response->data['content']['rendered'] = apply_filters( 'the_content', $content ); - - return $response; -} - -/* - * Before adding our filter, we verify if it's already added in Core. - * However, during the build process, Gutenberg automatically prefixes our functions with "gutenberg_". - * Therefore, we concatenate the Core's function name to circumvent this prefix for our check. - */ -$rest_prepare_wp_navigation_core_callback = 'block_core_navigation_' . 'insert_hooked_blocks_into_rest_response'; - -/* - * Do not add the `block_core_navigation_insert_hooked_blocks_into_rest_response` filter in the following cases: - * - If Core has added the `insert_hooked_blocks_into_rest_response` filter already (WP >= 6.6); - * - or if the `$rest_prepare_wp_navigation_core_callback` filter has already been added. - */ -if ( - ! has_filter( 'rest_prepare_wp_navigation', 'insert_hooked_blocks_into_rest_response' ) && - ! has_filter( 'rest_prepare_wp_navigation', $rest_prepare_wp_navigation_core_callback ) -) { - add_filter( 'rest_prepare_wp_navigation', 'block_core_navigation_insert_hooked_blocks_into_rest_response', 10, 3 ); + $mock_navigation_block_markup = serialize_block( $mock_navigation_block ); + return apply_block_hooks_to_content( $mock_navigation_block_markup, $post, 'insert_hooked_blocks' ); } diff --git a/packages/block-library/src/navigation/style.scss b/packages/block-library/src/navigation/style.scss index 3d7a8810ae9b71..ab93fa7da67ef4 100644 --- a/packages/block-library/src/navigation/style.scss +++ b/packages/block-library/src/navigation/style.scss @@ -166,7 +166,9 @@ $navigation-icon-size: 24px; // Hide until hover or focus within. opacity: 0; - transition: opacity 0.1s linear; + @media not (prefers-reduced-motion) { + transition: opacity 0.1s linear; + } visibility: hidden; // Don't take up space when the menu is collapsed. @@ -502,15 +504,16 @@ button.wp-block-navigation-item__content { background-color: inherit; // Animation. - animation: overlay-menu__fade-in-animation 0.1s ease-out; - animation-fill-mode: forwards; - @include reduce-motion("animation"); + @media not (prefers-reduced-motion) { + animation: overlay-menu__fade-in-animation 0.1s ease-out; + animation-fill-mode: forwards; + } // Try to inherit any root paddings set, so the X can align to a top-right aligned menu. padding-top: clamp(1rem, var(--wp--style--root--padding-top), 20rem); padding-right: clamp(1rem, var(--wp--style--root--padding-right), 20rem); padding-bottom: clamp(1rem, var(--wp--style--root--padding-bottom), 20rem); - padding-left: clamp(1rem, var(--wp--style--root--padding-left), 20em); + padding-left: clamp(1rem, var(--wp--style--root--padding-left), 20rem); // Allow modal to scroll. overflow: auto; diff --git a/packages/block-library/src/navigation/use-template-part-area-label.js b/packages/block-library/src/navigation/use-template-part-area-label.js index 48763a38ac62d1..7b4d514975e113 100644 --- a/packages/block-library/src/navigation/use-template-part-area-label.js +++ b/packages/block-library/src/navigation/use-template-part-area-label.js @@ -11,6 +11,7 @@ import { useSelect } from '@wordpress/data'; // TODO: this util should perhaps be refactored somewhere like core-data. import { createTemplatePartId } from '../template-part/edit/utils/create-template-part-id'; +import { getTemplatePartIcon } from '../template-part/edit/utils/get-template-part-icon'; export default function useTemplatePartAreaLabel( clientId ) { return useSelect( @@ -35,16 +36,15 @@ export default function useTemplatePartAreaLabel( clientId ) { return; } - // FIXME: @wordpress/block-library should not depend on @wordpress/editor. - // Blocks can be loaded into a *non-post* block editor. - // This code is lifted from this file: - // packages/block-library/src/template-part/edit/advanced-controls.js - /* eslint-disable @wordpress/data-no-store-string-literals */ - const definedAreas = - select( - 'core/editor' - ).__experimentalGetDefaultTemplatePartAreas(); - /* eslint-enable @wordpress/data-no-store-string-literals */ + const defaultTemplatePartAreas = + select( coreStore ).getEntityRecord( 'root', '__unstableBase' ) + ?.default_template_part_areas || []; + + const definedAreas = defaultTemplatePartAreas.map( ( item ) => ( { + ...item, + icon: getTemplatePartIcon( item.icon ), + } ) ); + const { getCurrentTheme, getEditedEntityRecord } = select( coreStore ); diff --git a/packages/block-library/src/page-list/block.json b/packages/block-library/src/page-list/block.json index b465e4ef5dc3d0..8802022382241a 100644 --- a/packages/block-library/src/page-list/block.json +++ b/packages/block-library/src/page-list/block.json @@ -51,6 +51,31 @@ }, "interactivity": { "clientNavigation": true + }, + "color": { + "text": true, + "background": true, + "link": true, + "gradients": true, + "__experimentalDefaultControls": { + "background": true, + "text": true, + "link": true + } + }, + "__experimentalBorder": { + "radius": true, + "color": true, + "width": true, + "style": true + }, + "spacing": { + "padding": true, + "margin": true, + "__experimentalDefaultControls": { + "padding": false, + "margin": false + } } }, "editorStyle": "wp-block-page-list-editor", diff --git a/packages/block-library/src/page-list/edit.js b/packages/block-library/src/page-list/edit.js index 31e400b8676717..458b8075749e5d 100644 --- a/packages/block-library/src/page-list/edit.js +++ b/packages/block-library/src/page-list/edit.js @@ -17,12 +17,13 @@ import { Warning, } from '@wordpress/block-editor'; import { - PanelBody, ToolbarButton, Spinner, Notice, ComboboxControl, Button, + __experimentalToolsPanel as ToolsPanel, + __experimentalToolsPanelItem as ToolsPanelItem, } from '@wordpress/components'; import { __, sprintf } from '@wordpress/i18n'; import { useMemo, useState, useEffect, useCallback } from '@wordpress/element'; @@ -37,6 +38,7 @@ import { convertDescription, ConvertToLinksModal, } from './convert-to-links-modal'; +import { useToolsPanelDropdownMenuProps } from '../utils/hooks'; // We only show the edit option when page count is <= MAX_PAGE_COUNT // Performance of Navigation Links is not good past this value. @@ -123,6 +125,7 @@ export default function PageListEdit( { const [ isOpen, setOpen ] = useState( false ); const openModal = useCallback( () => setOpen( true ), [] ); const closeModal = () => setOpen( false ); + const dropdownMenuProps = useToolsPanelDropdownMenuProps(); const { records: pages, hasResolved: hasResolvedPages } = useEntityRecords( 'postType', @@ -319,40 +322,60 @@ export default function PageListEdit( { return ( <> - <InspectorControls> - { pagesTree.length > 0 && ( - <PanelBody> - <ComboboxControl - __nextHasNoMarginBottom - __next40pxDefaultSize - className="editor-page-attributes__parent" - label={ __( 'Parent' ) } - value={ parentPageID } - options={ pagesTree } - onChange={ ( value ) => - setAttributes( { parentPageID: value ?? 0 } ) - } - help={ __( - 'Choose a page to show only its subpages.' - ) } - /> - </PanelBody> - ) } - { allowConvertToLinks && ( - <PanelBody title={ __( 'Edit this menu' ) }> - <p>{ convertDescription }</p> - <Button - __next40pxDefaultSize - variant="primary" - accessibleWhenDisabled - disabled={ ! hasResolvedPages } - onClick={ convertToNavigationLinks } - > - { __( 'Edit' ) } - </Button> - </PanelBody> - ) } - </InspectorControls> + { ( pagesTree.length > 0 || allowConvertToLinks ) && ( + <InspectorControls> + <ToolsPanel + label={ __( 'Settings' ) } + resetAll={ () => { + setAttributes( { parentPageID: 0 } ); + } } + dropdownMenuProps={ dropdownMenuProps } + > + { pagesTree.length > 0 && ( + <ToolsPanelItem + label={ __( 'Parent Page' ) } + hasValue={ () => parentPageID !== 0 } + onDeselect={ () => + setAttributes( { parentPageID: 0 } ) + } + isShownByDefault + > + <ComboboxControl + __nextHasNoMarginBottom + __next40pxDefaultSize + className="editor-page-attributes__parent" + label={ __( 'Parent' ) } + value={ parentPageID } + options={ pagesTree } + onChange={ ( value ) => + setAttributes( { + parentPageID: value ?? 0, + } ) + } + help={ __( + 'Choose a page to show only its subpages.' + ) } + /> + </ToolsPanelItem> + ) } + + { allowConvertToLinks && ( + <div style={ { gridColumn: '1 / -1' } }> + <p>{ convertDescription }</p> + <Button + __next40pxDefaultSize + variant="primary" + accessibleWhenDisabled + disabled={ ! hasResolvedPages } + onClick={ convertToNavigationLinks } + > + { __( 'Edit' ) } + </Button> + </div> + ) } + </ToolsPanel> + </InspectorControls> + ) } { allowConvertToLinks && ( <> <BlockControls group="other"> diff --git a/packages/block-library/src/page-list/style.scss b/packages/block-library/src/page-list/style.scss index d06a6142573bec..ab04d57e61b965 100644 --- a/packages/block-library/src/page-list/style.scss +++ b/packages/block-library/src/page-list/style.scss @@ -15,3 +15,7 @@ background-color: inherit; } } +.wp-block-page-list { + // This block has customizable padding, border-box makes that more predictable. + box-sizing: border-box; +} diff --git a/packages/block-library/src/paragraph/edit.js b/packages/block-library/src/paragraph/edit.js index b0dc5ab255af78..9e625a8bdd0041 100644 --- a/packages/block-library/src/paragraph/edit.js +++ b/packages/block-library/src/paragraph/edit.js @@ -21,8 +21,8 @@ import { useSettings, useBlockEditingMode, } from '@wordpress/block-editor'; +import { getBlockSupport } from '@wordpress/blocks'; import { formatLtr } from '@wordpress/icons'; - /** * Internal dependencies */ @@ -47,7 +47,7 @@ function hasDropCapDisabled( align ) { return align === ( isRTL() ? 'left' : 'right' ) || align === 'center'; } -function DropCapControl( { clientId, attributes, setAttributes } ) { +function DropCapControl( { clientId, attributes, setAttributes, name } ) { // Please do not add a useSelect call to the paragraph block unconditionally. // Every useSelect added to a (frequently used) block will degrade load // and type performance. By moving it within InspectorControls, the subscription is @@ -69,23 +69,32 @@ function DropCapControl( { clientId, attributes, setAttributes } ) { helpText = __( 'Show a large initial letter.' ); } + const isDropCapControlEnabledByDefault = getBlockSupport( + name, + 'typography.defaultControls.dropCap', + false + ); + return ( - <ToolsPanelItem - hasValue={ () => !! dropCap } - label={ __( 'Drop cap' ) } - onDeselect={ () => setAttributes( { dropCap: undefined } ) } - resetAllFilter={ () => ( { dropCap: undefined } ) } - panelId={ clientId } - > - <ToggleControl - __nextHasNoMarginBottom + <InspectorControls group="typography"> + <ToolsPanelItem + hasValue={ () => !! dropCap } label={ __( 'Drop cap' ) } - checked={ !! dropCap } - onChange={ () => setAttributes( { dropCap: ! dropCap } ) } - help={ helpText } - disabled={ hasDropCapDisabled( align ) } - /> - </ToolsPanelItem> + isShownByDefault={ isDropCapControlEnabledByDefault } + onDeselect={ () => setAttributes( { dropCap: undefined } ) } + resetAllFilter={ () => ( { dropCap: undefined } ) } + panelId={ clientId } + > + <ToggleControl + __nextHasNoMarginBottom + label={ __( 'Drop cap' ) } + checked={ !! dropCap } + onChange={ () => setAttributes( { dropCap: ! dropCap } ) } + help={ helpText } + disabled={ hasDropCapDisabled( align ) } + /> + </ToolsPanelItem> + </InspectorControls> ); } @@ -96,6 +105,8 @@ function ParagraphBlock( { onRemove, setAttributes, clientId, + isSelected: isSingleSelected, + name, } ) { const { align, content, direction, dropCap, placeholder } = attributes; const blockProps = useBlockProps( { @@ -131,13 +142,14 @@ function ParagraphBlock( { /> </BlockControls> ) } - <InspectorControls group="typography"> + { isSingleSelected && ( <DropCapControl + name={ name } clientId={ clientId } attributes={ attributes } setAttributes={ setAttributes } /> - </InspectorControls> + ) } <RichText identifier="content" tagName="p" diff --git a/packages/block-library/src/paragraph/editor.scss b/packages/block-library/src/paragraph/editor.scss index 369cc5cb1d63a9..ad4043bee610ad 100644 --- a/packages/block-library/src/paragraph/editor.scss +++ b/packages/block-library/src/paragraph/editor.scss @@ -22,3 +22,10 @@ .block-editor-block-list__block[data-type="core/paragraph"].has-text-align-left[style*="writing-mode: vertical-lr"] { rotate: 180deg; } + +// Hide the placeholder when the editor is in zoomed out mode. +.is-zoomed-out .block-editor-block-list__block[data-empty="true"] { + [data-rich-text-placeholder] { + opacity: 0; + } +} diff --git a/packages/block-library/src/paragraph/style.scss b/packages/block-library/src/paragraph/style.scss index 7bd8c77e85de83..59c73fffd9877e 100644 --- a/packages/block-library/src/paragraph/style.scss +++ b/packages/block-library/src/paragraph/style.scss @@ -44,7 +44,7 @@ p.has-drop-cap.has-background { } // Use :where to contain the specificity of this rule -// so it's easily overrideable by any theme that targets +// so it's easily overridable by any theme that targets // links using the a element. // For example, this is what global styles does. :where(p.has-text-color:not(.has-link-color)) a { diff --git a/packages/block-library/src/pattern/block.json b/packages/block-library/src/pattern/block.json index 13fc9d154b15a4..c30be8458e6360 100644 --- a/packages/block-library/src/pattern/block.json +++ b/packages/block-library/src/pattern/block.json @@ -2,7 +2,7 @@ "$schema": "https://schemas.wp.org/trunk/block.json", "apiVersion": 3, "name": "core/pattern", - "title": "Pattern placeholder", + "title": "Pattern Placeholder", "category": "theme", "description": "Show a block pattern.", "supports": { diff --git a/packages/block-library/src/pattern/index.php b/packages/block-library/src/pattern/index.php index 5595818a2271b7..870313eb5e86d8 100644 --- a/packages/block-library/src/pattern/index.php +++ b/packages/block-library/src/pattern/index.php @@ -58,13 +58,6 @@ function render_block_core_pattern( $attributes ) { $pattern = $registry->get_registered( $slug ); $content = $pattern['content']; - // Backward compatibility for handling Block Hooks and injecting the theme attribute in the Gutenberg plugin. - // This can be removed when the minimum supported WordPress is >= 6.4. - if ( defined( 'IS_GUTENBERG_PLUGIN' ) && IS_GUTENBERG_PLUGIN && ! function_exists( 'traverse_and_serialize_blocks' ) ) { - $blocks = parse_blocks( $content ); - $content = gutenberg_serialize_blocks( $blocks ); - } - $seen_refs[ $attributes['slug'] ] = true; $content = do_blocks( $content ); diff --git a/packages/block-library/src/post-author-name/block.json b/packages/block-library/src/post-author-name/block.json index 68d2c49bd91056..23211f0bf5bf46 100644 --- a/packages/block-library/src/post-author-name/block.json +++ b/packages/block-library/src/post-author-name/block.json @@ -12,11 +12,13 @@ }, "isLink": { "type": "boolean", - "default": false + "default": false, + "role": "content" }, "linkTarget": { "type": "string", - "default": "_self" + "default": "_self", + "role": "content" } }, "usesContext": [ "postType", "postId" ], diff --git a/packages/block-library/src/post-author-name/edit.js b/packages/block-library/src/post-author-name/edit.js index b4afb9a9799498..8c0281edb5df8a 100644 --- a/packages/block-library/src/post-author-name/edit.js +++ b/packages/block-library/src/post-author-name/edit.js @@ -13,18 +13,28 @@ import { useBlockProps, } from '@wordpress/block-editor'; import { useSelect } from '@wordpress/data'; -import { __ } from '@wordpress/i18n'; +import { __, sprintf } from '@wordpress/i18n'; import { store as coreStore } from '@wordpress/core-data'; -import { PanelBody, ToggleControl } from '@wordpress/components'; +import { + ToggleControl, + __experimentalToolsPanel as ToolsPanel, + __experimentalToolsPanelItem as ToolsPanelItem, +} from '@wordpress/components'; + +/** + * Internal dependencies + */ +import { useToolsPanelDropdownMenuProps } from '../utils/hooks'; function PostAuthorNameEdit( { context: { postType, postId }, attributes: { textAlign, isLink, linkTarget }, setAttributes, } ) { - const { authorName } = useSelect( + const { authorName, supportsAuthor } = useSelect( ( select ) => { - const { getEditedEntityRecord, getUser } = select( coreStore ); + const { getEditedEntityRecord, getUser, getPostType } = + select( coreStore ); const _authorId = getEditedEntityRecord( 'postType', postType, @@ -33,6 +43,8 @@ function PostAuthorNameEdit( { return { authorName: _authorId ? getUser( _authorId ) : null, + supportsAuthor: + getPostType( postType )?.supports?.author ?? false, }; }, [ postType, postId ] @@ -58,6 +70,8 @@ function PostAuthorNameEdit( { displayName ); + const dropdownMenuProps = useToolsPanelDropdownMenuProps(); + return ( <> <BlockControls group="block"> @@ -69,28 +83,65 @@ function PostAuthorNameEdit( { /> </BlockControls> <InspectorControls> - <PanelBody title={ __( 'Settings' ) }> - <ToggleControl - __nextHasNoMarginBottom + <ToolsPanel + label={ __( 'Settings' ) } + resetAll={ () => { + setAttributes( { + isLink: false, + linkTarget: '_self', + } ); + } } + dropdownMenuProps={ dropdownMenuProps } + > + <ToolsPanelItem label={ __( 'Link to author archive' ) } - onChange={ () => setAttributes( { isLink: ! isLink } ) } - checked={ isLink } - /> - { isLink && ( + isShownByDefault + hasValue={ () => isLink } + onDeselect={ () => setAttributes( { isLink: false } ) } + > <ToggleControl __nextHasNoMarginBottom - label={ __( 'Open in new tab' ) } - onChange={ ( value ) => - setAttributes( { - linkTarget: value ? '_blank' : '_self', - } ) + label={ __( 'Link to author archive' ) } + onChange={ () => + setAttributes( { isLink: ! isLink } ) } - checked={ linkTarget === '_blank' } + checked={ isLink } /> + </ToolsPanelItem> + { isLink && ( + <ToolsPanelItem + label={ __( 'Open in new tab' ) } + isShownByDefault + hasValue={ () => linkTarget !== '_self' } + onDeselect={ () => + setAttributes( { linkTarget: '_self' } ) + } + > + <ToggleControl + __nextHasNoMarginBottom + label={ __( 'Open in new tab' ) } + onChange={ ( value ) => + setAttributes( { + linkTarget: value ? '_blank' : '_self', + } ) + } + checked={ linkTarget === '_blank' } + /> + </ToolsPanelItem> ) } - </PanelBody> + </ToolsPanel> </InspectorControls> - <div { ...blockProps }> { displayAuthor } </div> + <div { ...blockProps }> + { supportsAuthor + ? displayAuthor + : sprintf( + // translators: %s: Name of the post type e.g: "post". + __( + 'This post type (%s) does not support the author.' + ), + postType + ) } + </div> </> ); } diff --git a/packages/block-library/src/post-author-name/index.php b/packages/block-library/src/post-author-name/index.php index effc83962a3547..243d78ca70129e 100644 --- a/packages/block-library/src/post-author-name/index.php +++ b/packages/block-library/src/post-author-name/index.php @@ -26,6 +26,10 @@ function render_block_core_post_author_name( $attributes, $content, $block ) { return ''; } + if ( ! post_type_supports( $block->context['postType'], 'author' ) ) { + return ''; + } + $author_name = get_the_author_meta( 'display_name', $author_id ); if ( isset( $attributes['isLink'] ) && $attributes['isLink'] ) { $author_name = sprintf( '<a href="%1$s" target="%2$s" class="wp-block-post-author-name__link">%3$s</a>', get_author_posts_url( $author_id ), esc_attr( $attributes['linkTarget'] ), $author_name ); diff --git a/packages/block-library/src/post-author/block.json b/packages/block-library/src/post-author/block.json index d66498c8ee3df9..c7f2f01550a613 100644 --- a/packages/block-library/src/post-author/block.json +++ b/packages/block-library/src/post-author/block.json @@ -26,11 +26,13 @@ }, "isLink": { "type": "boolean", - "default": false + "default": false, + "role": "content" }, "linkTarget": { "type": "string", - "default": "_self" + "default": "_self", + "role": "content" } }, "usesContext": [ "postType", "postId", "queryId" ], diff --git a/packages/block-library/src/post-author/edit.js b/packages/block-library/src/post-author/edit.js index 6186b0d052e8aa..dd2b3aa617548d 100644 --- a/packages/block-library/src/post-author/edit.js +++ b/packages/block-library/src/post-author/edit.js @@ -21,7 +21,7 @@ import { __experimentalVStack as VStack, } from '@wordpress/components'; import { useSelect, useDispatch } from '@wordpress/data'; -import { __ } from '@wordpress/i18n'; +import { __, sprintf } from '@wordpress/i18n'; import { store as coreStore } from '@wordpress/core-data'; const minimumUsersForCombobox = 25; @@ -38,9 +38,9 @@ function PostAuthorEdit( { setAttributes, } ) { const isDescendentOfQueryLoop = Number.isFinite( queryId ); - const { authorId, authorDetails, authors } = useSelect( + const { authorId, authorDetails, authors, supportsAuthor } = useSelect( ( select ) => { - const { getEditedEntityRecord, getUser, getUsers } = + const { getEditedEntityRecord, getUser, getUsers, getPostType } = select( coreStore ); const _authorId = getEditedEntityRecord( 'postType', @@ -52,6 +52,8 @@ function PostAuthorEdit( { authorId: _authorId, authorDetails: _authorId ? getUser( _authorId ) : null, authors: getUsers( AUTHORS_QUERY ), + supportsAuthor: + getPostType( postType )?.supports?.author ?? false, }; }, [ postType, postId ] @@ -97,6 +99,18 @@ function PostAuthorEdit( { const showAuthorControl = !! postId && ! isDescendentOfQueryLoop && authorOptions.length > 0; + if ( ! supportsAuthor ) { + return ( + <div { ...blockProps }> + { sprintf( + // translators: %s: Name of the post type e.g: "post". + __( 'This post type (%s) does not support the author.' ), + postType + ) } + </div> + ); + } + return ( <> <InspectorControls> diff --git a/packages/block-library/src/post-author/index.php b/packages/block-library/src/post-author/index.php index faf894d997d732..2d01de508b94af 100644 --- a/packages/block-library/src/post-author/index.php +++ b/packages/block-library/src/post-author/index.php @@ -26,6 +26,10 @@ function render_block_core_post_author( $attributes, $content, $block ) { return ''; } + if ( ! post_type_supports( $block->context['postType'], 'author' ) ) { + return ''; + } + $avatar = ! empty( $attributes['avatarSize'] ) ? get_avatar( $author_id, $attributes['avatarSize'] diff --git a/packages/block-library/src/post-comments-form/block.json b/packages/block-library/src/post-comments-form/block.json index af893ccb67a082..4b6b333b75cfab 100644 --- a/packages/block-library/src/post-comments-form/block.json +++ b/packages/block-library/src/post-comments-form/block.json @@ -56,5 +56,10 @@ "wp-block-post-comments-form", "wp-block-buttons", "wp-block-button" - ] + ], + "example": { + "attributes": { + "textAlign": "center" + } + } } diff --git a/packages/block-library/src/post-comments-link/block.json b/packages/block-library/src/post-comments-link/block.json index 67831b1d15c5d5..cb0f3c8fbdae03 100644 --- a/packages/block-library/src/post-comments-link/block.json +++ b/packages/block-library/src/post-comments-link/block.json @@ -42,6 +42,19 @@ }, "interactivity": { "clientNavigation": true + }, + "__experimentalBorder": { + "radius": true, + "color": true, + "width": true, + "style": true, + "__experimentalDefaultControls": { + "radius": true, + "color": true, + "width": true, + "style": true + } } - } + }, + "style": "wp-block-post-comments-link" } diff --git a/packages/block-library/src/post-comments-link/style.scss b/packages/block-library/src/post-comments-link/style.scss new file mode 100644 index 00000000000000..110179d3ee1df9 --- /dev/null +++ b/packages/block-library/src/post-comments-link/style.scss @@ -0,0 +1,4 @@ +.wp-block-post-comments-link { + // This block has customizable padding, border-box makes that more predictable. + box-sizing: border-box; +} diff --git a/packages/block-library/src/post-content/block.json b/packages/block-library/src/post-content/block.json index ed9c47154b2f8e..1348cb296af083 100644 --- a/packages/block-library/src/post-content/block.json +++ b/packages/block-library/src/post-content/block.json @@ -35,6 +35,7 @@ }, "color": { "gradients": true, + "heading": true, "link": true, "__experimentalDefaultControls": { "background": false, @@ -69,4 +70,4 @@ }, "style": "wp-block-post-content", "editorStyle": "wp-block-post-content-editor" -} \ No newline at end of file +} diff --git a/packages/block-library/src/post-content/index.php b/packages/block-library/src/post-content/index.php index 25be880cc47887..e0a06b7217eebe 100644 --- a/packages/block-library/src/post-content/index.php +++ b/packages/block-library/src/post-content/index.php @@ -46,10 +46,33 @@ function render_block_core_post_content( $attributes, $content, $block ) { $content .= wp_link_pages( array( 'echo' => 0 ) ); } + $ignored_hooked_blocks = get_post_meta( $post_id, '_wp_ignored_hooked_blocks', true ); + if ( ! empty( $ignored_hooked_blocks ) ) { + $ignored_hooked_blocks = json_decode( $ignored_hooked_blocks, true ); + $attributes['metadata'] = array( + 'ignoredHookedBlocks' => $ignored_hooked_blocks, + ); + } + + // Wrap in Post Content block so the Block Hooks algorithm can insert blocks + // that are hooked as first or last child of `core/post-content`. + $content = get_comment_delimited_block_content( + 'core/post-content', + $attributes, + $content + ); + + // We need to remove the `core/post-content` block wrapper after the Block Hooks algorithm, + // but before `do_blocks` runs, as it would otherwise attempt to render the same block again -- + // thus recursing infinitely. + add_filter( 'the_content', 'remove_serialized_parent_block', 8 ); + /** This filter is documented in wp-includes/post-template.php */ $content = apply_filters( 'the_content', str_replace( ']]>', ']]&gt;', $content ) ); unset( $seen_ids[ $post_id ] ); + remove_filter( 'the_content', 'remove_serialized_parent_block', 8 ); + if ( empty( $content ) ) { return ''; } diff --git a/packages/block-library/src/post-date/block.json b/packages/block-library/src/post-date/block.json index 470bddae53bdfc..dadc0d2f489fee 100644 --- a/packages/block-library/src/post-date/block.json +++ b/packages/block-library/src/post-date/block.json @@ -15,7 +15,8 @@ }, "isLink": { "type": "boolean", - "default": false + "default": false, + "role": "content" }, "displayType": { "type": "string", diff --git a/packages/block-library/src/post-date/edit.js b/packages/block-library/src/post-date/edit.js index 5057466c6af453..0599e860904652 100644 --- a/packages/block-library/src/post-date/edit.js +++ b/packages/block-library/src/post-date/edit.js @@ -26,13 +26,19 @@ import { ToolbarGroup, ToolbarButton, ToggleControl, - PanelBody, + __experimentalToolsPanel as ToolsPanel, + __experimentalToolsPanelItem as ToolsPanelItem, } from '@wordpress/components'; import { __, _x, sprintf } from '@wordpress/i18n'; import { edit } from '@wordpress/icons'; import { DOWN } from '@wordpress/keycodes'; import { useSelect } from '@wordpress/data'; +/** + * Internal dependencies + */ +import { useToolsPanelDropdownMenuProps } from '../utils/hooks'; + export default function PostDateEdit( { attributes: { textAlign, format, isLink, displayType }, context: { postId, postType: postTypeSlug, queryId }, @@ -44,6 +50,7 @@ export default function PostDateEdit( { [ `wp-block-post-date__modified-date` ]: displayType === 'modified', } ), } ); + const dropdownMenuProps = useToolsPanelDropdownMenuProps(); // Use internal state instead of a ref to make sure that the component // re-renders when the popover's anchor updates. @@ -160,16 +167,35 @@ export default function PostDateEdit( { </BlockControls> <InspectorControls> - <PanelBody title={ __( 'Settings' ) }> - <DateFormatPicker - format={ format } - defaultFormat={ siteFormat } - onChange={ ( nextFormat ) => - setAttributes( { format: nextFormat } ) + <ToolsPanel + label={ __( 'Settings' ) } + resetAll={ () => { + setAttributes( { + format: undefined, + isLink: false, + displayType: 'date', + } ); + } } + dropdownMenuProps={ dropdownMenuProps } + > + <ToolsPanelItem + hasValue={ () => !! format } + label={ __( 'Date Format' ) } + onDeselect={ () => + setAttributes( { format: undefined } ) } - /> - <ToggleControl - __nextHasNoMarginBottom + isShownByDefault + > + <DateFormatPicker + format={ format } + defaultFormat={ siteFormat } + onChange={ ( nextFormat ) => + setAttributes( { format: nextFormat } ) + } + /> + </ToolsPanelItem> + <ToolsPanelItem + hasValue={ () => isLink !== false } label={ postType?.labels.singular_name ? sprintf( @@ -179,23 +205,49 @@ export default function PostDateEdit( { ) : __( 'Link to post' ) } - onChange={ () => setAttributes( { isLink: ! isLink } ) } - checked={ isLink } - /> - <ToggleControl - __nextHasNoMarginBottom + onDeselect={ () => setAttributes( { isLink: false } ) } + isShownByDefault + > + <ToggleControl + __nextHasNoMarginBottom + label={ + postType?.labels.singular_name + ? sprintf( + // translators: %s: Name of the post type e.g: "post". + __( 'Link to %s' ), + postType.labels.singular_name.toLowerCase() + ) + : __( 'Link to post' ) + } + onChange={ () => + setAttributes( { isLink: ! isLink } ) + } + checked={ isLink } + /> + </ToolsPanelItem> + <ToolsPanelItem + hasValue={ () => displayType !== 'date' } label={ __( 'Display last modified date' ) } - onChange={ ( value ) => - setAttributes( { - displayType: value ? 'modified' : 'date', - } ) + onDeselect={ () => + setAttributes( { displayType: 'date' } ) } - checked={ displayType === 'modified' } - help={ __( - 'Only shows if the post has been modified' - ) } - /> - </PanelBody> + isShownByDefault + > + <ToggleControl + __nextHasNoMarginBottom + label={ __( 'Display last modified date' ) } + onChange={ ( value ) => + setAttributes( { + displayType: value ? 'modified' : 'date', + } ) + } + checked={ displayType === 'modified' } + help={ __( + 'Only shows if the post has been modified' + ) } + /> + </ToolsPanelItem> + </ToolsPanel> </InspectorControls> <div { ...blockProps }>{ postDate }</div> diff --git a/packages/block-library/src/post-excerpt/edit.js b/packages/block-library/src/post-excerpt/edit.js index 05aaf543b59196..bc94c2599e60ec 100644 --- a/packages/block-library/src/post-excerpt/edit.js +++ b/packages/block-library/src/post-excerpt/edit.js @@ -16,14 +16,22 @@ import { Warning, useBlockProps, } from '@wordpress/block-editor'; -import { PanelBody, ToggleControl, RangeControl } from '@wordpress/components'; +import { + ToggleControl, + RangeControl, + __experimentalToolsPanel as ToolsPanel, + __experimentalToolsPanelItem as ToolsPanelItem, +} from '@wordpress/components'; import { __, _x } from '@wordpress/i18n'; import { useSelect } from '@wordpress/data'; /** * Internal dependencies */ -import { useCanEditEntity } from '../utils/hooks'; +import { + useCanEditEntity, + useToolsPanelDropdownMenuProps, +} from '../utils/hooks'; const ELLIPSIS = '…'; @@ -41,6 +49,8 @@ export default function PostExcerptEditor( { { rendered: renderedExcerpt, protected: isProtected } = {}, ] = useEntityProp( 'postType', postType, 'excerpt', postId ); + const dropdownMenuProps = useToolsPanelDropdownMenuProps(); + /** * Check if the post type supports excerpts. * Add an exception and return early for the "page" post type, @@ -219,29 +229,56 @@ export default function PostExcerptEditor( { /> </BlockControls> <InspectorControls> - <PanelBody title={ __( 'Settings' ) }> - <ToggleControl - __nextHasNoMarginBottom + <ToolsPanel + label={ __( 'Settings' ) } + resetAll={ () => { + setAttributes( { + showMoreOnNewLine: true, + excerptLength: 55, + } ); + } } + dropdownMenuProps={ dropdownMenuProps } + > + <ToolsPanelItem + hasValue={ () => showMoreOnNewLine !== true } label={ __( 'Show link on new line' ) } - checked={ showMoreOnNewLine } - onChange={ ( newShowMoreOnNewLine ) => - setAttributes( { - showMoreOnNewLine: newShowMoreOnNewLine, - } ) + onDeselect={ () => + setAttributes( { showMoreOnNewLine: true } ) } - /> - <RangeControl - __next40pxDefaultSize - __nextHasNoMarginBottom + isShownByDefault + > + <ToggleControl + __nextHasNoMarginBottom + label={ __( 'Show link on new line' ) } + checked={ showMoreOnNewLine } + onChange={ ( newShowMoreOnNewLine ) => + setAttributes( { + showMoreOnNewLine: newShowMoreOnNewLine, + } ) + } + /> + </ToolsPanelItem> + <ToolsPanelItem + hasValue={ () => excerptLength !== 55 } label={ __( 'Max number of words' ) } - value={ excerptLength } - onChange={ ( value ) => { - setAttributes( { excerptLength: value } ); - } } - min="10" - max="100" - /> - </PanelBody> + onDeselect={ () => + setAttributes( { excerptLength: 55 } ) + } + isShownByDefault + > + <RangeControl + __next40pxDefaultSize + __nextHasNoMarginBottom + label={ __( 'Max number of words' ) } + value={ excerptLength } + onChange={ ( value ) => { + setAttributes( { excerptLength: value } ); + } } + min="10" + max="100" + /> + </ToolsPanelItem> + </ToolsPanel> </InspectorControls> <div { ...blockProps }> { excerptContent } diff --git a/packages/block-library/src/post-featured-image/block.json b/packages/block-library/src/post-featured-image/block.json index 8b431ffc625790..3cd144caa0cf42 100644 --- a/packages/block-library/src/post-featured-image/block.json +++ b/packages/block-library/src/post-featured-image/block.json @@ -9,7 +9,8 @@ "attributes": { "isLink": { "type": "boolean", - "default": false + "default": false, + "role": "content" }, "aspectRatio": { "type": "string" @@ -30,11 +31,13 @@ "rel": { "type": "string", "attribute": "rel", - "default": "" + "default": "", + "role": "content" }, "linkTarget": { "type": "string", - "default": "_self" + "default": "_self", + "role": "content" }, "overlayColor": { "type": "string" diff --git a/packages/block-library/src/post-featured-image/dimension-controls.js b/packages/block-library/src/post-featured-image/dimension-controls.js index 5a3e40a126bf8d..9a71a96b2db846 100644 --- a/packages/block-library/src/post-featured-image/dimension-controls.js +++ b/packages/block-library/src/post-featured-image/dimension-controls.js @@ -12,10 +12,18 @@ import { } from '@wordpress/components'; import { useSettings, + privateApis as blockEditorPrivateApis, store as blockEditorStore, } from '@wordpress/block-editor'; import { useSelect } from '@wordpress/data'; +/** + * Internal dependencies + */ +import { unlock } from '../lock-unlock'; + +const { ResolutionTool } = unlock( blockEditorPrivateApis ); + const SCALE_OPTIONS = ( <> <ToggleGroupControlOption @@ -223,30 +231,19 @@ const DimensionControls = ( { </ToolsPanelItem> ) } { !! imageSizeOptions.length && ( - <ToolsPanelItem - hasValue={ () => !! sizeSlug } - label={ __( 'Resolution' ) } - onDeselect={ () => - setAttributes( { sizeSlug: undefined } ) + <ResolutionTool + panelId={ clientId } + value={ sizeSlug } + defaultValue={ DEFAULT_SIZE } + options={ imageSizeOptions } + onChange={ ( nextSizeSlug ) => + setAttributes( { sizeSlug: nextSizeSlug } ) } + isShownByDefault={ false } resetAllFilter={ () => ( { - sizeSlug: undefined, + sizeSlug: DEFAULT_SIZE, } ) } - isShownByDefault={ false } - panelId={ clientId } - > - <SelectControl - __next40pxDefaultSize - __nextHasNoMarginBottom - label={ __( 'Resolution' ) } - value={ sizeSlug || DEFAULT_SIZE } - options={ imageSizeOptions } - onChange={ ( nextSizeSlug ) => - setAttributes( { sizeSlug: nextSizeSlug } ) - } - help={ __( 'Select the size of the source image.' ) } - /> - </ToolsPanelItem> + /> ) } </> ); diff --git a/packages/block-library/src/post-featured-image/edit.js b/packages/block-library/src/post-featured-image/edit.js index 95441a5a55cfd0..6afe2c29e25045 100644 --- a/packages/block-library/src/post-featured-image/edit.js +++ b/packages/block-library/src/post-featured-image/edit.js @@ -11,11 +11,12 @@ import { useEntityProp, store as coreStore } from '@wordpress/core-data'; import { useSelect, useDispatch } from '@wordpress/data'; import { ToggleControl, - PanelBody, Placeholder, Button, Spinner, TextControl, + __experimentalToolsPanel as ToolsPanel, + __experimentalToolsPanelItem as ToolsPanelItem, } from '@wordpress/components'; import { InspectorControls, @@ -38,6 +39,7 @@ import { store as noticesStore } from '@wordpress/notices'; import DimensionControls from './dimension-controls'; import OverlayControls from './overlay-controls'; import Overlay from './overlay'; +import { useToolsPanelDropdownMenuProps } from '../utils/hooks'; const ALLOWED_MEDIA_TYPES = [ 'image' ]; @@ -47,11 +49,6 @@ function getMediaSourceUrlBySizeSlug( media, slug ) { ); } -const disabledClickProps = { - onClick: ( event ) => event.preventDefault(), - 'aria-disabled': true, -}; - export default function PostFeaturedImageEdit( { clientId, attributes, @@ -183,6 +180,8 @@ export default function PostFeaturedImageEdit( { setTemporaryURL(); }; + const dropdownMenuProps = useToolsPanelDropdownMenuProps(); + const controls = blockEditingMode === 'default' && ( <> <InspectorControls group="color"> @@ -201,9 +200,18 @@ export default function PostFeaturedImageEdit( { /> </InspectorControls> <InspectorControls> - <PanelBody title={ __( 'Settings' ) }> - <ToggleControl - __nextHasNoMarginBottom + <ToolsPanel + label={ __( 'Settings' ) } + resetAll={ () => { + setAttributes( { + isLink: false, + linkTarget: '_self', + rel: '', + } ); + } } + dropdownMenuProps={ dropdownMenuProps } + > + <ToolsPanelItem label={ postType?.labels.singular_name ? sprintf( @@ -213,11 +221,42 @@ export default function PostFeaturedImageEdit( { ) : __( 'Link to post' ) } - onChange={ () => setAttributes( { isLink: ! isLink } ) } - checked={ isLink } - /> + isShownByDefault + hasValue={ () => !! isLink } + onDeselect={ () => + setAttributes( { + isLink: false, + } ) + } + > + <ToggleControl + __nextHasNoMarginBottom + label={ + postType?.labels.singular_name + ? sprintf( + // translators: %s: Name of the post type e.g: "post". + __( 'Link to %s' ), + postType.labels.singular_name + ) + : __( 'Link to post' ) + } + onChange={ () => + setAttributes( { isLink: ! isLink } ) + } + checked={ isLink } + /> + </ToolsPanelItem> { isLink && ( - <> + <ToolsPanelItem + label={ __( 'Open in new tab' ) } + isShownByDefault + hasValue={ () => '_self' !== linkTarget } + onDeselect={ () => + setAttributes( { + linkTarget: '_self', + } ) + } + > <ToggleControl __nextHasNoMarginBottom label={ __( 'Open in new tab' ) } @@ -228,6 +267,19 @@ export default function PostFeaturedImageEdit( { } checked={ linkTarget === '_blank' } /> + </ToolsPanelItem> + ) } + { isLink && ( + <ToolsPanelItem + label={ __( 'Link rel' ) } + isShownByDefault + hasValue={ () => !! rel } + onDeselect={ () => + setAttributes( { + rel: '', + } ) + } + > <TextControl __next40pxDefaultSize __nextHasNoMarginBottom @@ -237,9 +289,9 @@ export default function PostFeaturedImageEdit( { setAttributes( { rel: newRel } ) } /> - </> + </ToolsPanelItem> ) } - </PanelBody> + </ToolsPanel> </InspectorControls> </> ); @@ -261,11 +313,7 @@ export default function PostFeaturedImageEdit( { { controls } <div { ...blockProps }> { !! isLink ? ( - <a - href={ postPermalink } - target={ linkTarget } - { ...disabledClickProps } - > + <a href={ postPermalink } target={ linkTarget }> { placeholder() } </a> ) : ( @@ -373,11 +421,7 @@ export default function PostFeaturedImageEdit( { <figure { ...blockProps }> { /* If the featured image is linked, wrap in an <a /> tag to trigger any inherited link element styles */ } { !! isLink ? ( - <a - href={ postPermalink } - target={ linkTarget } - { ...disabledClickProps } - > + <a href={ postPermalink } target={ linkTarget }> { image } </a> ) : ( diff --git a/packages/block-library/src/post-featured-image/editor.scss b/packages/block-library/src/post-featured-image/editor.scss index 5fab62c571b1cf..3295fe14e62ac2 100644 --- a/packages/block-library/src/post-featured-image/editor.scss +++ b/packages/block-library/src/post-featured-image/editor.scss @@ -100,9 +100,9 @@ // When the Post Featured Image block is linked, // it's wrapped with a disabled <a /> tag. - // Restore cursor style so it doesn't appear 'clickable'. + // Ensure that the link is not clickable. > a { - cursor: default; + pointer-events: none; } // When the Post Featured Image block is linked, diff --git a/packages/block-library/src/post-featured-image/overlay-controls.js b/packages/block-library/src/post-featured-image/overlay-controls.js index 3dabb60f77fb18..8a38fe3e1acab1 100644 --- a/packages/block-library/src/post-featured-image/overlay-controls.js +++ b/packages/block-library/src/post-featured-image/overlay-controls.js @@ -47,6 +47,7 @@ const Overlay = ( { gradient: undefined, customGradient: undefined, } ), + clearable: true, }, ] } panelId={ clientId } diff --git a/packages/block-library/src/post-navigation-link/block.json b/packages/block-library/src/post-navigation-link/block.json index 5f1b295119822a..ce733759846fee 100644 --- a/packages/block-library/src/post-navigation-link/block.json +++ b/packages/block-library/src/post-navigation-link/block.json @@ -34,12 +34,6 @@ "default": "" } }, - "example": { - "attributes": { - "label": "Next post", - "arrow": "arrow" - } - }, "usesContext": [ "postType" ], "supports": { "reusable": false, diff --git a/packages/block-library/src/post-navigation-link/index.js b/packages/block-library/src/post-navigation-link/index.js index e85e594990adba..4bcb1999067053 100644 --- a/packages/block-library/src/post-navigation-link/index.js +++ b/packages/block-library/src/post-navigation-link/index.js @@ -1,3 +1,8 @@ +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; + /** * Internal dependencies */ @@ -12,6 +17,12 @@ export { metadata, name }; export const settings = { edit, variations, + example: { + attributes: { + label: __( 'Next post' ), + arrow: 'arrow', + }, + }, }; export const init = () => initBlock( { name, metadata, settings } ); diff --git a/packages/block-library/src/post-navigation-link/variations.js b/packages/block-library/src/post-navigation-link/variations.js index 4f52b21338af1e..125f10942cd15e 100644 --- a/packages/block-library/src/post-navigation-link/variations.js +++ b/packages/block-library/src/post-navigation-link/variations.js @@ -8,7 +8,7 @@ const variations = [ { isDefault: true, name: 'post-next', - title: __( 'Next post' ), + title: __( 'Next Post' ), description: __( 'Displays the post link that follows the current post.' ), @@ -17,14 +17,14 @@ const variations = [ scope: [ 'inserter', 'transform' ], example: { attributes: { - label: 'Next post', + label: __( 'Next post' ), arrow: 'arrow', }, }, }, { name: 'post-previous', - title: __( 'Previous post' ), + title: __( 'Previous Post' ), description: __( 'Displays the post link that precedes the current post.' ), @@ -33,7 +33,7 @@ const variations = [ scope: [ 'inserter', 'transform' ], example: { attributes: { - label: 'Previous post', + label: __( 'Previous post' ), arrow: 'arrow', }, }, diff --git a/packages/block-library/src/post-template/block.json b/packages/block-library/src/post-template/block.json index da576a83312a45..d379a46d3142f8 100644 --- a/packages/block-library/src/post-template/block.json +++ b/packages/block-library/src/post-template/block.json @@ -4,7 +4,7 @@ "name": "core/post-template", "title": "Post Template", "category": "theme", - "parent": [ "core/query" ], + "ancestor": [ "core/query" ], "description": "Contains the block elements used to render a post, like the title, date, featured image, content or excerpt, and more.", "textdomain": "default", "usesContext": [ @@ -43,15 +43,25 @@ } }, "spacing": { + "margin": true, + "padding": true, "blockGap": { "__experimentalDefault": "1.25em" }, "__experimentalDefaultControls": { - "blockGap": true + "blockGap": true, + "padding": false, + "margin": false } }, "interactivity": { "clientNavigation": true + }, + "__experimentalBorder": { + "radius": true, + "color": true, + "width": true, + "style": true } }, "style": "wp-block-post-template", diff --git a/packages/block-library/src/post-template/editor.scss b/packages/block-library/src/post-template/editor.scss deleted file mode 100644 index 7b426b0f3d37a5..00000000000000 --- a/packages/block-library/src/post-template/editor.scss +++ /dev/null @@ -1,7 +0,0 @@ -.editor-styles-wrapper { - ul.wp-block-post-template { - padding-left: 0; - margin-left: 0; - list-style: none; - } -} diff --git a/packages/block-library/src/post-template/style.scss b/packages/block-library/src/post-template/style.scss index 806aadc77470eb..e6896f2db024a8 100644 --- a/packages/block-library/src/post-template/style.scss +++ b/packages/block-library/src/post-template/style.scss @@ -4,6 +4,8 @@ max-width: 100%; list-style: none; padding: 0; + // This block has customizable padding, border-box makes that more predictable. + box-sizing: border-box; // These rules no longer apply but should be kept for backwards compatibility. &.is-flex-container { diff --git a/packages/block-library/src/post-terms/edit.js b/packages/block-library/src/post-terms/edit.js index 13d5b61ad13d6b..802e5ab5898b47 100644 --- a/packages/block-library/src/post-terms/edit.js +++ b/packages/block-library/src/post-terms/edit.js @@ -122,6 +122,7 @@ export default function PostTermsEdit( { key={ postTerm.id } href={ postTerm.link } onClick={ ( event ) => event.preventDefault() } + rel="tag" > { decodeEntities( postTerm.name ) } </a> diff --git a/packages/block-library/src/post-time-to-read/block.json b/packages/block-library/src/post-time-to-read/block.json index ce0ab2be6c02f3..a9f09895556c74 100644 --- a/packages/block-library/src/post-time-to-read/block.json +++ b/packages/block-library/src/post-time-to-read/block.json @@ -3,7 +3,7 @@ "apiVersion": 3, "__experimental": true, "name": "core/post-time-to-read", - "title": "Time To Read", + "title": "Time to Read", "category": "theme", "description": "Show minutes required to finish reading the post.", "textdomain": "default", diff --git a/packages/block-library/src/post-title/block.json b/packages/block-library/src/post-title/block.json index ecb5053d6cd39e..5587d71b148d0c 100644 --- a/packages/block-library/src/post-title/block.json +++ b/packages/block-library/src/post-title/block.json @@ -20,16 +20,19 @@ }, "isLink": { "type": "boolean", - "default": false + "default": false, + "role": "content" }, "rel": { "type": "string", "attribute": "rel", - "default": "" + "default": "", + "role": "content" }, "linkTarget": { "type": "string", - "default": "_self" + "default": "_self", + "role": "content" } }, "example": { diff --git a/packages/block-library/src/pullquote/style.scss b/packages/block-library/src/pullquote/style.scss index b9e28b7cdcfaa6..ff5fe6068dfac3 100644 --- a/packages/block-library/src/pullquote/style.scss +++ b/packages/block-library/src/pullquote/style.scss @@ -72,4 +72,5 @@ .wp-block-pullquote cite { color: inherit; + display: block; } diff --git a/packages/block-library/src/query-no-results/block.json b/packages/block-library/src/query-no-results/block.json index 2f656594afa306..44d2ceef987e2f 100644 --- a/packages/block-library/src/query-no-results/block.json +++ b/packages/block-library/src/query-no-results/block.json @@ -2,22 +2,12 @@ "$schema": "https://schemas.wp.org/trunk/block.json", "apiVersion": 3, "name": "core/query-no-results", - "title": "No results", + "title": "No Results", "category": "theme", "description": "Contains the block elements used to render content when no query results are found.", - "parent": [ "core/query" ], + "ancestor": [ "core/query" ], "textdomain": "default", "usesContext": [ "queryId", "query" ], - "example": { - "innerBlocks": [ - { - "name": "core/paragraph", - "attributes": { - "content": "No posts were found." - } - } - ] - }, "supports": { "align": true, "reusable": false, diff --git a/packages/block-library/src/query-no-results/index.js b/packages/block-library/src/query-no-results/index.js index 1c56638cdfdba8..fab5993148470e 100644 --- a/packages/block-library/src/query-no-results/index.js +++ b/packages/block-library/src/query-no-results/index.js @@ -1,6 +1,7 @@ /** * WordPress dependencies */ +import { __ } from '@wordpress/i18n'; import { loop as icon } from '@wordpress/icons'; /** @@ -18,6 +19,16 @@ export const settings = { icon, edit, save, + example: { + innerBlocks: [ + { + name: 'core/paragraph', + attributes: { + content: __( 'No posts were found.' ), + }, + }, + ], + }, }; export const init = () => initBlock( { name, metadata, settings } ); diff --git a/packages/block-library/src/query-pagination-numbers/edit.js b/packages/block-library/src/query-pagination-numbers/edit.js index b8d8c160cc874d..a03ed8419bb086 100644 --- a/packages/block-library/src/query-pagination-numbers/edit.js +++ b/packages/block-library/src/query-pagination-numbers/edit.js @@ -3,7 +3,16 @@ */ import { __ } from '@wordpress/i18n'; import { InspectorControls, useBlockProps } from '@wordpress/block-editor'; -import { PanelBody, RangeControl } from '@wordpress/components'; +import { + __experimentalToolsPanel as ToolsPanel, + __experimentalToolsPanelItem as ToolsPanelItem, + RangeControl, +} from '@wordpress/components'; + +/** + * Internal dependencies + */ +import { useToolsPanelDropdownMenuProps } from '../utils/hooks'; const createPaginationItem = ( content, Tag = 'a', extraClass = '' ) => ( <Tag key={ content } className={ `page-numbers ${ extraClass }` }> @@ -46,28 +55,41 @@ export default function QueryPaginationNumbersEdit( { const paginationNumbers = previewPaginationNumbers( parseInt( midSize, 10 ) ); + const dropdownMenuProps = useToolsPanelDropdownMenuProps(); + return ( <> <InspectorControls> - <PanelBody title={ __( 'Settings' ) }> - <RangeControl - __next40pxDefaultSize - __nextHasNoMarginBottom + <ToolsPanel + label={ __( 'Settings' ) } + resetAll={ () => setAttributes( { midSize: 2 } ) } + dropdownMenuProps={ dropdownMenuProps } + > + <ToolsPanelItem label={ __( 'Number of links' ) } - help={ __( - 'Specify how many links can appear before and after the current page number. Links to the first, current and last page are always visible.' - ) } - value={ midSize } - onChange={ ( value ) => { - setAttributes( { - midSize: parseInt( value, 10 ), - } ); - } } - min={ 0 } - max={ 5 } - withInputField={ false } - /> - </PanelBody> + hasValue={ () => midSize !== 2 } + onDeselect={ () => setAttributes( { midSize: 2 } ) } + isShownByDefault + > + <RangeControl + __next40pxDefaultSize + __nextHasNoMarginBottom + label={ __( 'Number of links' ) } + help={ __( + 'Specify how many links can appear before and after the current page number. Links to the first, current and last page are always visible.' + ) } + value={ midSize } + onChange={ ( value ) => { + setAttributes( { + midSize: parseInt( value, 10 ), + } ); + } } + min={ 0 } + max={ 5 } + withInputField={ false } + /> + </ToolsPanelItem> + </ToolsPanel> </InspectorControls> <div { ...useBlockProps() }>{ paginationNumbers }</div> </> diff --git a/packages/block-library/src/query-pagination-previous/index.php b/packages/block-library/src/query-pagination-previous/index.php index 1592f0a10cbff5..20b59109874d9e 100644 --- a/packages/block-library/src/query-pagination-previous/index.php +++ b/packages/block-library/src/query-pagination-previous/index.php @@ -19,14 +19,14 @@ function render_block_core_query_pagination_previous( $attributes, $content, $block ) { $page_key = isset( $block->context['queryId'] ) ? 'query-' . $block->context['queryId'] . '-page' : 'query-page'; $enhanced_pagination = isset( $block->context['enhancedPagination'] ) && $block->context['enhancedPagination']; + $max_page = isset( $block->context['query']['pages'] ) ? (int) $block->context['query']['pages'] : 0; $page = empty( $_GET[ $page_key ] ) ? 1 : (int) $_GET[ $page_key ]; - - $wrapper_attributes = get_block_wrapper_attributes(); - $show_label = isset( $block->context['showLabel'] ) ? (bool) $block->context['showLabel'] : true; - $default_label = __( 'Previous Page' ); - $label_text = isset( $attributes['label'] ) && ! empty( $attributes['label'] ) ? esc_html( $attributes['label'] ) : $default_label; - $label = $show_label ? $label_text : ''; - $pagination_arrow = get_query_pagination_arrow( $block, false ); + $wrapper_attributes = get_block_wrapper_attributes(); + $show_label = isset( $block->context['showLabel'] ) ? (bool) $block->context['showLabel'] : true; + $default_label = __( 'Previous Page' ); + $label_text = isset( $attributes['label'] ) && ! empty( $attributes['label'] ) ? esc_html( $attributes['label'] ) : $default_label; + $label = $show_label ? $label_text : ''; + $pagination_arrow = get_query_pagination_arrow( $block, false ); if ( ! $label ) { $wrapper_attributes .= ' aria-label="' . $label_text . '"'; } @@ -44,13 +44,20 @@ function render_block_core_query_pagination_previous( $attributes, $content, $bl add_filter( 'previous_posts_link_attributes', $filter_link_attributes ); $content = get_previous_posts_link( $label ); remove_filter( 'previous_posts_link_attributes', $filter_link_attributes ); - } elseif ( 1 !== $page ) { - $content = sprintf( - '<a href="%1$s" %2$s>%3$s</a>', - esc_url( add_query_arg( $page_key, $page - 1 ) ), - $wrapper_attributes, - $label - ); + } else { + $block_query = new WP_Query( build_query_vars_from_query_block( $block, $page ) ); + $block_max_pages = $block_query->max_num_pages; + $total = ! $max_page || $max_page > $block_max_pages ? $block_max_pages : $max_page; + wp_reset_postdata(); + + if ( 1 < $page && $page <= $total ) { + $content = sprintf( + '<a href="%1$s" %2$s>%3$s</a>', + esc_url( add_query_arg( $page_key, $page - 1 ) ), + $wrapper_attributes, + $label + ); + } } if ( $enhanced_pagination && isset( $content ) ) { diff --git a/packages/block-library/src/query-pagination/edit.js b/packages/block-library/src/query-pagination/edit.js index e051c2e67e7e5a..8ca0705058be28 100644 --- a/packages/block-library/src/query-pagination/edit.js +++ b/packages/block-library/src/query-pagination/edit.js @@ -8,8 +8,11 @@ import { useInnerBlocksProps, store as blockEditorStore, } from '@wordpress/block-editor'; -import { useSelect } from '@wordpress/data'; -import { PanelBody } from '@wordpress/components'; +import { useDispatch, useSelect } from '@wordpress/data'; +import { + __experimentalToolsPanel as ToolsPanel, + __experimentalToolsPanelItem as ToolsPanelItem, +} from '@wordpress/components'; import { useEffect } from '@wordpress/element'; /** @@ -17,6 +20,7 @@ import { useEffect } from '@wordpress/element'; */ import { QueryPaginationArrowControls } from './query-pagination-arrow-controls'; import { QueryPaginationLabelControl } from './query-pagination-label-control'; +import { useToolsPanelDropdownMenuProps } from '../utils/hooks'; const TEMPLATE = [ [ 'core/query-pagination-previous' ], @@ -46,36 +50,74 @@ export default function QueryPaginationEdit( { }, [ clientId ] ); + const { __unstableMarkNextChangeAsNotPersistent } = + useDispatch( blockEditorStore ); + const dropdownMenuProps = useToolsPanelDropdownMenuProps(); const blockProps = useBlockProps(); const innerBlocksProps = useInnerBlocksProps( blockProps, { template: TEMPLATE, } ); + // Always show label text if paginationArrow is set to 'none'. useEffect( () => { if ( paginationArrow === 'none' && ! showLabel ) { + __unstableMarkNextChangeAsNotPersistent(); setAttributes( { showLabel: true } ); } - }, [ paginationArrow, setAttributes, showLabel ] ); + }, [ + paginationArrow, + setAttributes, + showLabel, + __unstableMarkNextChangeAsNotPersistent, + ] ); + return ( <> { hasNextPreviousBlocks && ( <InspectorControls> - <PanelBody title={ __( 'Settings' ) }> - <QueryPaginationArrowControls - value={ paginationArrow } - onChange={ ( value ) => { - setAttributes( { paginationArrow: value } ); - } } - /> - { paginationArrow !== 'none' && ( - <QueryPaginationLabelControl - value={ showLabel } + <ToolsPanel + label={ __( 'Settings' ) } + resetAll={ () => { + setAttributes( { + paginationArrow: 'none', + showLabel: true, + } ); + } } + dropdownMenuProps={ dropdownMenuProps } + > + <ToolsPanelItem + hasValue={ () => paginationArrow !== 'none' } + label={ __( 'Pagination arrow' ) } + onDeselect={ () => + setAttributes( { paginationArrow: 'none' } ) + } + isShownByDefault + > + <QueryPaginationArrowControls + value={ paginationArrow } onChange={ ( value ) => { - setAttributes( { showLabel: value } ); + setAttributes( { paginationArrow: value } ); } } /> + </ToolsPanelItem> + { paginationArrow !== 'none' && ( + <ToolsPanelItem + hasValue={ () => ! showLabel } + label={ __( 'Show text' ) } + onDeselect={ () => + setAttributes( { showLabel: true } ) + } + isShownByDefault + > + <QueryPaginationLabelControl + value={ showLabel } + onChange={ ( value ) => { + setAttributes( { showLabel: value } ); + } } + /> + </ToolsPanelItem> ) } - </PanelBody> + </ToolsPanel> </InspectorControls> ) } <nav { ...innerBlocksProps } /> diff --git a/packages/block-library/src/query-pagination/query-pagination-label-control.js b/packages/block-library/src/query-pagination/query-pagination-label-control.js index 9ff80a663adeb5..16766c19bef086 100644 --- a/packages/block-library/src/query-pagination/query-pagination-label-control.js +++ b/packages/block-library/src/query-pagination/query-pagination-label-control.js @@ -9,9 +9,7 @@ export function QueryPaginationLabelControl( { value, onChange } ) { <ToggleControl __nextHasNoMarginBottom label={ __( 'Show label text' ) } - help={ __( - 'Toggle off to hide the label text, e.g. "Next Page".' - ) } + help={ __( 'Make label text visible, e.g. "Next Page".' ) } onChange={ onChange } checked={ value === true } /> diff --git a/packages/block-library/src/query-title/edit.js b/packages/block-library/src/query-title/edit.js index 21d23081837cdf..0da6da47ef6147 100644 --- a/packages/block-library/src/query-title/edit.js +++ b/packages/block-library/src/query-title/edit.js @@ -14,13 +14,18 @@ import { Warning, HeadingLevelDropdown, } from '@wordpress/block-editor'; -import { ToggleControl, PanelBody } from '@wordpress/components'; +import { + ToggleControl, + __experimentalToolsPanel as ToolsPanel, + __experimentalToolsPanelItem as ToolsPanelItem, +} from '@wordpress/components'; import { __, _x, sprintf } from '@wordpress/i18n'; /** * Internal dependencies */ import { useArchiveLabel } from './use-archive-label'; +import { useToolsPanelDropdownMenuProps } from '../utils/hooks'; const SUPPORTED_TYPES = [ 'archive', 'search' ]; @@ -36,6 +41,7 @@ export default function QueryTitleEdit( { setAttributes, } ) { const { archiveTypeLabel, archiveNameLabel } = useArchiveLabel(); + const dropdownMenuProps = useToolsPanelDropdownMenuProps(); const TagName = `h${ level }`; const blockProps = useBlockProps( { @@ -89,16 +95,35 @@ export default function QueryTitleEdit( { titleElement = ( <> <InspectorControls> - <PanelBody title={ __( 'Settings' ) }> - <ToggleControl - __nextHasNoMarginBottom + <ToolsPanel + label={ __( 'Settings' ) } + resetAll={ () => + setAttributes( { + showPrefix: true, + } ) + } + dropdownMenuProps={ dropdownMenuProps } + > + <ToolsPanelItem + hasValue={ () => ! showPrefix } label={ __( 'Show archive type in title' ) } - onChange={ () => - setAttributes( { showPrefix: ! showPrefix } ) + onDeselect={ () => + setAttributes( { showPrefix: true } ) } - checked={ showPrefix } - /> - </PanelBody> + isShownByDefault + > + <ToggleControl + __nextHasNoMarginBottom + label={ __( 'Show archive type in title' ) } + onChange={ () => + setAttributes( { + showPrefix: ! showPrefix, + } ) + } + checked={ showPrefix } + /> + </ToolsPanelItem> + </ToolsPanel> </InspectorControls> <TagName { ...blockProps }>{ title }</TagName> </> @@ -109,18 +134,35 @@ export default function QueryTitleEdit( { titleElement = ( <> <InspectorControls> - <PanelBody title={ __( 'Settings' ) }> - <ToggleControl - __nextHasNoMarginBottom + <ToolsPanel + label={ __( 'Settings' ) } + resetAll={ () => + setAttributes( { + showSearchTerm: true, + } ) + } + dropdownMenuProps={ dropdownMenuProps } + > + <ToolsPanelItem + hasValue={ () => ! showSearchTerm } label={ __( 'Show search term in title' ) } - onChange={ () => - setAttributes( { - showSearchTerm: ! showSearchTerm, - } ) + onDeselect={ () => + setAttributes( { showSearchTerm: true } ) } - checked={ showSearchTerm } - /> - </PanelBody> + isShownByDefault + > + <ToggleControl + __nextHasNoMarginBottom + label={ __( 'Show search term in title' ) } + onChange={ () => + setAttributes( { + showSearchTerm: ! showSearchTerm, + } ) + } + checked={ showSearchTerm } + /> + </ToolsPanelItem> + </ToolsPanel> </InspectorControls> <TagName { ...blockProps }> diff --git a/packages/block-library/src/query-total/block.json b/packages/block-library/src/query-total/block.json new file mode 100644 index 00000000000000..d52c3dd5ebab1a --- /dev/null +++ b/packages/block-library/src/query-total/block.json @@ -0,0 +1,58 @@ +{ + "$schema": "https://schemas.wp.org/trunk/block.json", + "apiVersion": 3, + "name": "core/query-total", + "title": "Query Total", + "category": "theme", + "ancestor": [ "core/query" ], + "description": "Display the total number of results in a query.", + "textdomain": "default", + "attributes": { + "displayType": { + "type": "string", + "default": "total-results" + } + }, + "usesContext": [ "queryId", "query" ], + "supports": { + "align": [ "wide", "full" ], + "html": false, + "spacing": { + "margin": true, + "padding": true + }, + "color": { + "gradients": true, + "text": true, + "__experimentalDefaultControls": { + "background": true + } + }, + "typography": { + "fontSize": true, + "lineHeight": true, + "__experimentalFontFamily": true, + "__experimentalFontWeight": true, + "__experimentalFontStyle": true, + "__experimentalTextTransform": true, + "__experimentalTextDecoration": true, + "__experimentalLetterSpacing": true, + "__experimentalDefaultControls": { + "fontSize": true + } + }, + "__experimentalBorder": { + "radius": true, + "color": true, + "width": true, + "style": true, + "__experimentalDefaultControls": { + "radius": true, + "color": true, + "width": true, + "style": true + } + } + }, + "style": "wp-block-query-total" +} diff --git a/packages/block-library/src/query-total/edit.js b/packages/block-library/src/query-total/edit.js new file mode 100644 index 00000000000000..d91a1990715727 --- /dev/null +++ b/packages/block-library/src/query-total/edit.js @@ -0,0 +1,81 @@ +/** + * WordPress dependencies + */ +import { useBlockProps, BlockControls } from '@wordpress/block-editor'; +import { ToolbarGroup, ToolbarDropdownMenu } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import { resultsFound, displayingResults } from './icons'; + +export default function QueryTotalEdit( { attributes, setAttributes } ) { + const { displayType } = attributes; + + // Block properties and classes. + const blockProps = useBlockProps(); + + const getButtonPositionIcon = () => { + switch ( displayType ) { + case 'total-results': + return resultsFound; + case 'range-display': + return displayingResults; + } + }; + + const buttonPositionControls = [ + { + role: 'menuitemradio', + title: __( 'Total results' ), + isActive: displayType === 'total-results', + icon: resultsFound, + onClick: () => { + setAttributes( { displayType: 'total-results' } ); + }, + }, + { + role: 'menuitemradio', + title: __( 'Range display' ), + isActive: displayType === 'range-display', + icon: displayingResults, + onClick: () => { + setAttributes( { displayType: 'range-display' } ); + }, + }, + ]; + + // Controls for the block. + const controls = ( + <BlockControls> + <ToolbarGroup> + <ToolbarDropdownMenu + icon={ getButtonPositionIcon() } + label={ __( 'Change display type' ) } + controls={ buttonPositionControls } + /> + </ToolbarGroup> + </BlockControls> + ); + + // Render output based on the selected display type. + const renderDisplay = () => { + if ( displayType === 'total-results' ) { + return <>{ __( '12 results found' ) }</>; + } + + if ( displayType === 'range-display' ) { + return <>{ __( 'Displaying 1 – 10 of 12' ) }</>; + } + + return null; + }; + + return ( + <div { ...blockProps }> + { controls } + { renderDisplay() } + </div> + ); +} diff --git a/packages/block-library/src/query-total/icons.js b/packages/block-library/src/query-total/icons.js new file mode 100644 index 00000000000000..8b285b99b1ade9 --- /dev/null +++ b/packages/block-library/src/query-total/icons.js @@ -0,0 +1,43 @@ +/** + * WordPress dependencies + */ +import { SVG, Path } from '@wordpress/components'; + +export const resultsFound = ( + <SVG + xmlns="http://www.w3.org/2000/svg" + viewBox="0 0 24 24" + width="24" + height="24" + aria-hidden="true" + focusable="false" + > + <Path d="M4 11h4v2H4v-2zm6 0h6v2h-6v-2zm8 0h2v2h-2v-2z" /> + </SVG> +); + +export const displayingResults = ( + <SVG + xmlns="http://www.w3.org/2000/svg" + viewBox="0 0 24 24" + width="24" + height="24" + aria-hidden="true" + focusable="false" + > + <Path d="M4 13h2v-2H4v2zm4 0h10v-2H8v2zm12 0h2v-2h-2v2z" /> + </SVG> +); + +export const queryTotal = ( + <SVG + xmlns="http://www.w3.org/2000/svg" + viewBox="0 0 24 24" + width="24" + height="24" + aria-hidden="true" + focusable="false" + > + <Path d="M18 4H6c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2Zm.5 14c0 .3-.2.5-.5.5H6c-.3 0-.5-.2-.5-.5V6c0-.3.2-.5.5-.5h12c.3 0 .5.2.5.5v12Zm-7-6-4.1 5h8.8v-3h-1.5v1.5h-4.2l2.9-3.5-2.9-3.5h4.2V10h1.5V7H7.4l4.1 5Z" /> + </SVG> +); diff --git a/packages/block-library/src/query-total/index.js b/packages/block-library/src/query-total/index.js new file mode 100644 index 00000000000000..79508972291565 --- /dev/null +++ b/packages/block-library/src/query-total/index.js @@ -0,0 +1,18 @@ +/** + * Internal dependencies + */ +import metadata from './block.json'; +import edit from './edit'; +import initBlock from '../utils/init-block'; +import { queryTotal } from './icons'; + +/* Block settings */ +const { name } = metadata; +export { metadata, name }; + +export const settings = { + icon: queryTotal, + edit, +}; + +export const init = () => initBlock( { name, metadata, settings } ); diff --git a/packages/block-library/src/query-total/index.php b/packages/block-library/src/query-total/index.php new file mode 100644 index 00000000000000..ff2ac486727b92 --- /dev/null +++ b/packages/block-library/src/query-total/index.php @@ -0,0 +1,88 @@ +<?php +/** + * Server-side rendering of the `core/query-total` block. + * + * @package WordPress + */ + +/** + * Renders the `query-total` block on the server. + * + * @since 6.8.0 + * + * @param array $attributes Block attributes. + * @param string $content Block default content. + * @param WP_Block $block Block instance. + * + * @return string The rendered block content. + */ +function render_block_core_query_total( $attributes, $content, $block ) { + global $wp_query; + $wrapper_attributes = get_block_wrapper_attributes(); + if ( isset( $block->context['query']['inherit'] ) && $block->context['query']['inherit'] ) { + $query_to_use = $wp_query; + $current_page = max( 1, get_query_var( 'paged', 1 ) ); + } else { + $page_key = isset( $block->context['queryId'] ) ? 'query-' . $block->context['queryId'] . '-page' : 'query-page'; + $current_page = isset( $_GET[ $page_key ] ) ? (int) $_GET[ $page_key ] : 1; + $query_to_use = new WP_Query( build_query_vars_from_query_block( $block, $current_page ) ); + } + + $max_rows = $query_to_use->found_posts; + $posts_per_page = $query_to_use->get( 'posts_per_page' ); + + // Calculate the range of posts being displayed. + $start = ( $current_page - 1 ) * $posts_per_page + 1; + $end = min( $start + $posts_per_page - 1, $max_rows ); + + // Prepare the display based on the `displayType` attribute. + $output = ''; + switch ( $attributes['displayType'] ) { + case 'range-display': + if ( $start === $end ) { + $output = sprintf( + /* translators: 1: Start index of posts, 2: Total number of posts */ + __( 'Displaying %1$s of %2$s' ), + $start, + $max_rows + ); + } else { + $output = sprintf( + /* translators: 1: Start index of posts, 2: End index of posts, 3: Total number of posts */ + __( 'Displaying %1$s – %2$s of %3$s' ), + $start, + $end, + $max_rows + ); + } + + break; + + case 'total-results': + default: + // translators: %d: number of results. + $output = sprintf( _n( '%d result found', '%d results found', $max_rows ), $max_rows ); + break; + } + + return sprintf( + '<div %1$s>%2$s</div>', + $wrapper_attributes, + $output + ); +} + +/** + * Registers the `query-total` block. + * + * @since 6.8.0 + */ +function register_block_core_query_total() { + register_block_type_from_metadata( + __DIR__ . '/query-total', + array( + 'render_callback' => 'render_block_core_query_total', + ) + ); +} +add_action( 'init', 'register_block_core_query_total' ); diff --git a/packages/block-library/src/query-total/init.js b/packages/block-library/src/query-total/init.js new file mode 100644 index 00000000000000..79f0492c2cb2f8 --- /dev/null +++ b/packages/block-library/src/query-total/init.js @@ -0,0 +1,6 @@ +/** + * Internal dependencies + */ +import { init } from './'; + +export default init(); diff --git a/packages/block-library/src/query-total/style.scss b/packages/block-library/src/query-total/style.scss new file mode 100644 index 00000000000000..c6a2bc131cfaf9 --- /dev/null +++ b/packages/block-library/src/query-total/style.scss @@ -0,0 +1,4 @@ +.wp-block-query-total { + // This block has customizable padding, border-box makes that more predictable. + box-sizing: border-box; +} diff --git a/packages/block-library/src/query/edit/index.js b/packages/block-library/src/query/edit/index.js index 9a54b5976fe4bc..60fc2ebd6170b3 100644 --- a/packages/block-library/src/query/edit/index.js +++ b/packages/block-library/src/query/edit/index.js @@ -10,7 +10,7 @@ import { store as blockEditorStore } from '@wordpress/block-editor'; */ import QueryContent from './query-content'; import QueryPlaceholder from './query-placeholder'; -import PatternSelectionModal from './pattern-selection-modal'; +import { PatternSelectionModal } from './pattern-selection'; const QueryEdit = ( props ) => { const { clientId, attributes } = props; diff --git a/packages/block-library/src/query/edit/inspector-controls/author-control.js b/packages/block-library/src/query/edit/inspector-controls/author-control.js index b27322837e4b9e..4379dfe3e46cb6 100644 --- a/packages/block-library/src/query/edit/inspector-controls/author-control.js +++ b/packages/block-library/src/query/edit/inspector-controls/author-control.js @@ -30,7 +30,7 @@ function AuthorControl( { value, onChange } ) { const authorsInfo = getEntitiesInfo( authorsList ); /** * We need to normalize the value because the block operates on a - * comma(`,`) separated string value and `FormTokenFiels` needs an + * comma(`,`) separated string value and `FormTokenField` needs an * array. */ const normalizedValue = ! value ? [] : value.toString().split( ',' ); diff --git a/packages/block-library/src/query/edit/inspector-controls/create-new-post-link.js b/packages/block-library/src/query/edit/inspector-controls/create-new-post-link.js deleted file mode 100644 index 58d58a6089aacd..00000000000000 --- a/packages/block-library/src/query/edit/inspector-controls/create-new-post-link.js +++ /dev/null @@ -1,32 +0,0 @@ -/** - * WordPress dependencies - */ -import { createInterpolateElement } from '@wordpress/element'; -import { addQueryArgs } from '@wordpress/url'; -import { store as coreStore } from '@wordpress/core-data'; -import { useSelect } from '@wordpress/data'; - -const CreateNewPostLink = ( { postType } ) => { - const newPostUrl = addQueryArgs( 'post-new.php', { - post_type: postType, - } ); - - const addNewItemLabel = useSelect( - ( select ) => { - const { getPostType } = select( coreStore ); - return getPostType( postType )?.labels?.add_new_item; - }, - [ postType ] - ); - return ( - <div className="wp-block-query__create-new-link"> - { createInterpolateElement( - '<a>' + addNewItemLabel + '</a>', - // eslint-disable-next-line jsx-a11y/anchor-has-content - { a: <a href={ newPostUrl } /> } - ) } - </div> - ); -}; - -export default CreateNewPostLink; diff --git a/packages/block-library/src/query/edit/inspector-controls/enhanced-pagination-control.js b/packages/block-library/src/query/edit/inspector-controls/enhanced-pagination-control.js index 9d47d67e61d781..4d0b1a9fd9da37 100644 --- a/packages/block-library/src/query/edit/inspector-controls/enhanced-pagination-control.js +++ b/packages/block-library/src/query/edit/inspector-controls/enhanced-pagination-control.js @@ -18,15 +18,13 @@ export default function EnhancedPaginationControl( { const fullPageClientSideNavigation = window.__experimentalFullPageClientSideNavigation; - let help = __( 'Browsing between pages requires a full page reload.' ); + let help = __( + 'Reload the full page—instead of just the posts list—when visitors navigate between pages.' + ); if ( fullPageClientSideNavigation ) { help = __( 'Experimental full-page client-side navigation setting enabled.' ); - } else if ( enhancedPagination ) { - help = __( - 'Reload the full page—instead of just the posts list—when visitors navigate between pages.' - ); } else if ( hasUnsupportedBlocks ) { help = __( 'Enhancement disabled because there are non-compatible blocks inside the Query block.' diff --git a/packages/block-library/src/query/edit/inspector-controls/index.js b/packages/block-library/src/query/edit/inspector-controls/index.js index 7d5745e190c9a7..3470846a14795b 100644 --- a/packages/block-library/src/query/edit/inspector-controls/index.js +++ b/packages/block-library/src/query/edit/inspector-controls/index.js @@ -2,7 +2,6 @@ * WordPress dependencies */ import { - PanelBody, TextControl, SelectControl, RangeControl, @@ -15,7 +14,6 @@ import { import { useSelect } from '@wordpress/data'; import { store as coreStore } from '@wordpress/core-data'; import { __ } from '@wordpress/i18n'; -import { privateApis as blockEditorPrivateApis } from '@wordpress/block-editor'; import { debounce } from '@wordpress/compose'; import { useEffect, useState, useCallback } from '@wordpress/element'; @@ -28,11 +26,9 @@ import ParentControl from './parent-control'; import { TaxonomyControls } from './taxonomy-controls'; import FormatControls from './format-controls'; import StickyControl from './sticky-control'; -import CreateNewPostLink from './create-new-post-link'; import PerPageControl from './per-page-control'; import OffsetControl from './offset-controls'; import PagesControl from './pages-control'; -import { unlock } from '../../../lock-unlock'; import { usePostTypes, useIsPostTypeHierarchical, @@ -42,8 +38,6 @@ import { } from '../../utils'; import { useToolsPanelDropdownMenuProps } from '../../../utils/hooks'; -const { BlockInfo } = unlock( blockEditorPrivateApis ); - export default function QueryInspectorControls( props ) { const { attributes, setQuery, setDisplayLayout, isSingular } = props; const { query, displayLayout } = attributes; @@ -191,116 +185,172 @@ export default function QueryInspectorControls( props ) { return ( <> - { !! postType && ( - <BlockInfo> - <CreateNewPostLink postType={ postType } /> - </BlockInfo> - ) } { showSettingsPanel && ( - <PanelBody title={ __( 'Settings' ) }> + <ToolsPanel + label={ __( 'Settings' ) } + resetAll={ () => { + setQuery( { + postType: 'post', + order: 'desc', + orderBy: 'date', + sticky: '', + inherit: true, + } ); + } } + dropdownMenuProps={ dropdownMenuProps } + > { showInheritControl && ( - <ToggleGroupControl - __next40pxDefaultSize - __nextHasNoMarginBottom + <ToolsPanelItem + hasValue={ () => ! inherit } label={ __( 'Query type' ) } - isBlock - onChange={ ( value ) => { - setQuery( { inherit: value === 'default' } ); - } } - help={ - inherit - ? __( - 'Display a list of posts or custom post types based on the current template.' - ) - : __( - 'Display a list of posts or custom post types based on specific criteria.' - ) - } - value={ !! inherit ? 'default' : 'custom' } + onDeselect={ () => setQuery( { inherit: true } ) } + isShownByDefault > - <ToggleGroupControlOption - value="default" - label={ __( 'Default' ) } - /> - <ToggleGroupControlOption - value="custom" - label={ __( 'Custom' ) } - /> - </ToggleGroupControl> - ) } - { showPostTypeControl && - ( postTypesSelectOptions.length > 2 ? ( - <SelectControl - __nextHasNoMarginBottom - __next40pxDefaultSize - options={ postTypesSelectOptions } - value={ postType } - label={ postTypeControlLabel } - onChange={ onPostTypeChange } - help={ postTypeControlHelp } - /> - ) : ( <ToggleGroupControl - __nextHasNoMarginBottom __next40pxDefaultSize + __nextHasNoMarginBottom + label={ __( 'Query type' ) } isBlock - value={ postType } - label={ postTypeControlLabel } - onChange={ onPostTypeChange } - help={ postTypeControlHelp } + onChange={ ( value ) => { + setQuery( { + inherit: value === 'default', + } ); + } } + help={ + inherit + ? __( + 'Display a list of posts or custom post types based on the current template.' + ) + : __( + 'Display a list of posts or custom post types based on specific criteria.' + ) + } + value={ !! inherit ? 'default' : 'custom' } > - { postTypesSelectOptions.map( ( option ) => ( - <ToggleGroupControlOption - key={ option.value } - value={ option.value } - label={ option.label } - /> - ) ) } + <ToggleGroupControlOption + value="default" + label={ __( 'Default' ) } + /> + <ToggleGroupControlOption + value="custom" + label={ __( 'Custom' ) } + /> </ToggleGroupControl> - ) ) } + </ToolsPanelItem> + ) } - { showColumnsControl && ( - <> - <RangeControl - __nextHasNoMarginBottom - __next40pxDefaultSize - label={ __( 'Columns' ) } - value={ displayLayout.columns } - onChange={ ( value ) => - setDisplayLayout( { - columns: value, - } ) - } - min={ 2 } - max={ Math.max( 6, displayLayout.columns ) } - /> - { displayLayout.columns > 6 && ( - <Notice - status="warning" - isDismissible={ false } + { showPostTypeControl && ( + <ToolsPanelItem + hasValue={ () => postType !== 'post' } + label={ postTypeControlLabel } + onDeselect={ () => onPostTypeChange( 'post' ) } + isShownByDefault + > + { postTypesSelectOptions.length > 2 ? ( + <SelectControl + __nextHasNoMarginBottom + __next40pxDefaultSize + options={ postTypesSelectOptions } + value={ postType } + label={ postTypeControlLabel } + onChange={ onPostTypeChange } + help={ postTypeControlHelp } + /> + ) : ( + <ToggleGroupControl + __nextHasNoMarginBottom + __next40pxDefaultSize + isBlock + value={ postType } + label={ postTypeControlLabel } + onChange={ onPostTypeChange } + help={ postTypeControlHelp } > - { __( - 'This column count exceeds the recommended amount and may cause visual breakage.' + { postTypesSelectOptions.map( + ( option ) => ( + <ToggleGroupControlOption + key={ option.value } + value={ option.value } + label={ option.label } + /> + ) ) } - </Notice> + </ToggleGroupControl> ) } - </> + </ToolsPanelItem> + ) } + + { showColumnsControl && ( + <ToolsPanelItem + hasValue={ () => displayLayout?.columns !== 2 } + label={ __( 'Columns' ) } + onDeselect={ () => + setDisplayLayout( { columns: 2 } ) + } + isShownByDefault + > + <> + <RangeControl + __nextHasNoMarginBottom + __next40pxDefaultSize + label={ __( 'Columns' ) } + value={ displayLayout.columns } + onChange={ ( value ) => + setDisplayLayout( { + columns: value, + } ) + } + min={ 2 } + max={ Math.max( 6, displayLayout.columns ) } + /> + { displayLayout.columns > 6 && ( + <Notice + status="warning" + isDismissible={ false } + > + { __( + 'This column count exceeds the recommended amount and may cause visual breakage.' + ) } + </Notice> + ) } + </> + </ToolsPanelItem> ) } + { showOrderControl && ( - <OrderControl - { ...{ order, orderBy } } - onChange={ setQuery } - /> + <ToolsPanelItem + hasValue={ () => + order !== 'desc' || orderBy !== 'date' + } + label={ __( 'Order by' ) } + onDeselect={ () => + setQuery( { order: 'desc', orderBy: 'date' } ) + } + isShownByDefault + > + <OrderControl + { ...{ order, orderBy } } + onChange={ setQuery } + /> + </ToolsPanelItem> ) } + { showStickyControl && ( - <StickyControl - value={ sticky } - onChange={ ( value ) => - setQuery( { sticky: value } ) - } - /> + <ToolsPanelItem + hasValue={ () => !! sticky } + label={ __( 'Sticky posts' ) } + onDeselect={ () => setQuery( { sticky: '' } ) } + isShownByDefault + > + <StickyControl + value={ sticky } + onChange={ ( value ) => + setQuery( { sticky: value } ) + } + /> + </ToolsPanelItem> ) } - </PanelBody> + </ToolsPanel> ) } { ! inherit && showDisplayPanel && ( <ToolsPanel diff --git a/packages/block-library/src/query/edit/pattern-selection-modal.js b/packages/block-library/src/query/edit/pattern-selection.js similarity index 67% rename from packages/block-library/src/query/edit/pattern-selection-modal.js rename to packages/block-library/src/query/edit/pattern-selection.js index e4cc8d0c851c23..0c5d95d15206f2 100644 --- a/packages/block-library/src/query/edit/pattern-selection-modal.js +++ b/packages/block-library/src/query/edit/pattern-selection.js @@ -21,48 +21,67 @@ import { } from '../utils'; import { searchPatterns } from '../../utils/search-patterns'; -export default function PatternSelectionModal( { +export function PatternSelectionModal( { clientId, attributes, setIsPatternSelectionModalOpen, +} ) { + return ( + <Modal + overlayClassName="block-library-query-pattern__selection-modal" + title={ __( 'Choose a pattern' ) } + onRequestClose={ () => setIsPatternSelectionModalOpen( false ) } + isFullScreen + > + <PatternSelection clientId={ clientId } attributes={ attributes } /> + </Modal> + ); +} + +export function useBlockPatterns( clientId, attributes ) { + const blockNameForPatterns = useBlockNameForPatterns( + clientId, + attributes + ); + return usePatterns( clientId, blockNameForPatterns ); +} + +export default function PatternSelection( { + clientId, + attributes, + showTitlesAsTooltip = false, + showSearch = true, } ) { const [ searchValue, setSearchValue ] = useState( '' ); const { replaceBlock, selectBlock } = useDispatch( blockEditorStore ); - const onBlockPatternSelect = ( pattern, blocks ) => { - const { newBlocks, queryClientIds } = getTransformedBlocksFromPattern( - blocks, - attributes - ); - replaceBlock( clientId, newBlocks ); - if ( queryClientIds[ 0 ] ) { - selectBlock( queryClientIds[ 0 ] ); - } - }; - // When we preview Query Loop blocks we should prefer the current - // block's postType, which is passed through block context. + const blockPatterns = useBlockPatterns( clientId, attributes ); + /* + * When we preview Query Loop blocks we should prefer the current + * block's postType, which is passed through block context. + */ const blockPreviewContext = useMemo( () => ( { previewPostType: attributes.query.postType, } ), [ attributes.query.postType ] ); - const blockNameForPatterns = useBlockNameForPatterns( - clientId, - attributes - ); - const blockPatterns = usePatterns( clientId, blockNameForPatterns ); const filteredBlockPatterns = useMemo( () => { return searchPatterns( blockPatterns, searchValue ); }, [ blockPatterns, searchValue ] ); + const onBlockPatternSelect = ( pattern, blocks ) => { + const { newBlocks, queryClientIds } = getTransformedBlocksFromPattern( + blocks, + attributes + ); + replaceBlock( clientId, newBlocks ); + if ( queryClientIds[ 0 ] ) { + selectBlock( queryClientIds[ 0 ] ); + } + }; return ( - <Modal - overlayClassName="block-library-query-pattern__selection-modal" - title={ __( 'Choose a pattern' ) } - onRequestClose={ () => setIsPatternSelectionModalOpen( false ) } - isFullScreen - > - <div className="block-library-query-pattern__selection-content"> + <div className="block-library-query-pattern__selection-content"> + { showSearch && ( <div className="block-library-query-pattern__selection-search"> <SearchControl __nextHasNoMarginBottom @@ -72,13 +91,14 @@ export default function PatternSelectionModal( { placeholder={ __( 'Search' ) } /> </div> - <BlockContextProvider value={ blockPreviewContext }> - <BlockPatternsList - blockPatterns={ filteredBlockPatterns } - onClickPattern={ onBlockPatternSelect } - /> - </BlockContextProvider> - </div> - </Modal> + ) } + <BlockContextProvider value={ blockPreviewContext }> + <BlockPatternsList + blockPatterns={ filteredBlockPatterns } + onClickPattern={ onBlockPatternSelect } + showTitlesAsTooltip={ showTitlesAsTooltip } + /> + </BlockContextProvider> + </div> ); } diff --git a/packages/block-library/src/query/edit/query-content.js b/packages/block-library/src/query/edit/query-content.js index 17eea9337823c5..459ce2af018c2a 100644 --- a/packages/block-library/src/query/edit/query-content.js +++ b/packages/block-library/src/query/edit/query-content.js @@ -19,10 +19,11 @@ import { store as coreStore } from '@wordpress/core-data'; * Internal dependencies */ import EnhancedPaginationControl from './inspector-controls/enhanced-pagination-control'; -import QueryToolbar from './query-toolbar'; import QueryInspectorControls from './inspector-controls'; import EnhancedPaginationModal from './enhanced-pagination-modal'; import { getQueryContextFromTemplate } from '../utils'; +import QueryToolbar from './query-toolbar'; +import { htmlElementMessages } from '../../utils/messages'; const DEFAULTS_POSTS_PER_PAGE = 3; @@ -30,10 +31,9 @@ const TEMPLATE = [ [ 'core/post-template' ] ]; export default function QueryContent( { attributes, setAttributes, - openPatternSelectionModal, - name, clientId, context, + name, } ) { const { queryId, @@ -133,17 +133,6 @@ export default function QueryContent( { setAttributes( { displayLayout: { ...displayLayout, ...newDisplayLayout }, } ); - const htmlElementMessages = { - main: __( - 'The <main> element should be used for the primary content of your document only.' - ), - section: __( - "The <section> element should represent a standalone portion of the document that can't be better represented by another element." - ), - aside: __( - "The <aside> element should represent a portion of a document whose content is only indirectly related to the document's main content." - ), - }; return ( <> @@ -154,6 +143,7 @@ export default function QueryContent( { /> <InspectorControls> <QueryInspectorControls + name={ name } attributes={ attributes } setQuery={ updateQuery } setDisplayLayout={ updateDisplayLayout } @@ -163,13 +153,7 @@ export default function QueryContent( { /> </InspectorControls> <BlockControls> - <QueryToolbar - name={ name } - clientId={ clientId } - attributes={ attributes } - setQuery={ updateQuery } - openPatternSelectionModal={ openPatternSelectionModal } - /> + <QueryToolbar attributes={ attributes } clientId={ clientId } /> </BlockControls> <InspectorControls group="advanced"> <SelectControl diff --git a/packages/block-library/src/query/edit/query-placeholder.js b/packages/block-library/src/query/edit/query-placeholder.js index 631eb64de07157..c3a01a0447f55e 100644 --- a/packages/block-library/src/query/edit/query-placeholder.js +++ b/packages/block-library/src/query/edit/query-placeholder.js @@ -18,7 +18,8 @@ import { __ } from '@wordpress/i18n'; /** * Internal dependencies */ -import { useScopedBlockVariations, useBlockNameForPatterns } from '../utils'; +import { useScopedBlockVariations } from '../utils'; +import { useBlockPatterns } from './pattern-selection'; export default function QueryPlaceholder( { attributes, @@ -28,31 +29,21 @@ export default function QueryPlaceholder( { } ) { const [ isStartingBlank, setIsStartingBlank ] = useState( false ); const blockProps = useBlockProps(); - const blockNameForPatterns = useBlockNameForPatterns( - clientId, - attributes - ); - const { blockType, activeBlockVariation, hasPatterns } = useSelect( + const { blockType, activeBlockVariation } = useSelect( ( select ) => { const { getActiveBlockVariation, getBlockType } = select( blocksStore ); - const { getBlockRootClientId, getPatternsByBlockTypes } = - select( blockEditorStore ); - const rootClientId = getBlockRootClientId( clientId ); return { blockType: getBlockType( name ), activeBlockVariation: getActiveBlockVariation( name, attributes ), - hasPatterns: !! getPatternsByBlockTypes( - blockNameForPatterns, - rootClientId - ).length, }; }, - [ name, blockNameForPatterns, clientId, attributes ] + [ name, attributes ] ); + const hasPatterns = !! useBlockPatterns( clientId, attributes ).length; const icon = activeBlockVariation?.icon?.src || activeBlockVariation?.icon || diff --git a/packages/block-library/src/query/edit/query-toolbar.js b/packages/block-library/src/query/edit/query-toolbar.js index cc2d62a54d529f..25e087ebe1559c 100644 --- a/packages/block-library/src/query/edit/query-toolbar.js +++ b/packages/block-library/src/query/edit/query-toolbar.js @@ -1,30 +1,51 @@ /** * WordPress dependencies */ -import { ToolbarGroup, ToolbarButton } from '@wordpress/components'; +import { + ToolbarGroup, + ToolbarButton, + Dropdown, + __experimentalDropdownContentWrapper as DropdownContentWrapper, +} from '@wordpress/components'; import { __ } from '@wordpress/i18n'; /** * Internal dependencies */ -import { usePatterns } from '../utils'; +import PatternSelection, { useBlockPatterns } from './pattern-selection'; -export default function QueryToolbar( { - openPatternSelectionModal, - name, - clientId, -} ) { - const hasPatterns = !! usePatterns( clientId, name ).length; +export default function QueryToolbar( { clientId, attributes } ) { + const hasPatterns = useBlockPatterns( clientId, attributes ).length; + if ( ! hasPatterns ) { + return null; + } return ( - <> - { hasPatterns && ( - <ToolbarGroup className="wp-block-template-part__block-control-group"> - <ToolbarButton onClick={ openPatternSelectionModal }> - { __( 'Replace' ) } - </ToolbarButton> - </ToolbarGroup> - ) } - </> + <ToolbarGroup className="wp-block-template-part__block-control-group"> + <DropdownContentWrapper> + <Dropdown + contentClassName="block-editor-block-settings-menu__popover" + focusOnMount="firstElement" + expandOnMobile + renderToggle={ ( { isOpen, onToggle } ) => ( + <ToolbarButton + aria-haspopup="true" + aria-expanded={ isOpen } + onClick={ onToggle } + > + { __( 'Change design' ) } + </ToolbarButton> + ) } + renderContent={ () => ( + <PatternSelection + clientId={ clientId } + attributes={ attributes } + showSearch={ false } + showTitlesAsTooltip + /> + ) } + /> + </DropdownContentWrapper> + </ToolbarGroup> ); } diff --git a/packages/block-library/src/query/editor.scss b/packages/block-library/src/query/editor.scss index b86eae0cbf65a8..ab6b361ed9e98e 100644 --- a/packages/block-library/src/query/editor.scss +++ b/packages/block-library/src/query/editor.scss @@ -1,28 +1,3 @@ -.block-library-query-toolbar__popover .components-popover__content { - min-width: 230px; - - .block-library-query-toolbar__popover-number-control { - margin-bottom: $grid-unit-10; - } -} - -.wp-block-query__create-new-link { - padding: 0 $grid-unit-20 $grid-unit-20 52px; -} - -.block-library-query__pattern-selection-content .block-editor-block-patterns-list { - display: grid; - grid-template-columns: 1fr 1fr 1fr; - grid-gap: $grid-unit-10; - - .block-editor-block-patterns-list__list-item { - margin-bottom: 0; - .block-editor-block-preview__container { - max-height: 250px; - } - } -} - .block-library-query-pattern__selection-modal { .block-editor-block-patterns-list { @@ -55,6 +30,26 @@ } } -.wp-block-query__enhanced-pagination-notice { - margin: 0; +.block-editor-block-settings-menu__popover { + &.is-expanded { + overflow-y: scroll; + } + .block-library-query-pattern__selection-content { + height: 100%; + } + .block-editor-block-patterns-list { + display: grid; + grid-template-columns: 1fr; + @include break-small() { + grid-template-columns: 1fr 1fr; + } + grid-gap: $grid-unit-15; + min-width: $break-zoomed-in; + @include break-small() { + min-width: $break-mobile; + } + } + .block-editor-block-patterns-list__list-item { + margin-bottom: 0; + } } diff --git a/packages/block-library/src/query/index.php b/packages/block-library/src/query/index.php index 043f351e11d7f1..6b544cd99ae8c7 100644 --- a/packages/block-library/src/query/index.php +++ b/packages/block-library/src/query/index.php @@ -79,7 +79,7 @@ function register_block_core_query() { * @since 6.4.0 * * @param array $parsed_block The block being rendered. - * @return string Returns the parsed block, unmodified. + * @return array Returns the parsed block, unmodified. */ function block_core_query_disable_enhanced_pagination( $parsed_block ) { static $enhanced_query_stack = array(); diff --git a/packages/block-library/src/query/utils.js b/packages/block-library/src/query/utils.js index fc22ca46d471c0..9aabf05bae37cd 100644 --- a/packages/block-library/src/query/utils.js +++ b/packages/block-library/src/query/utils.js @@ -81,7 +81,7 @@ export const getValueFromObjectPath = ( object, path ) => { * * @param {Object[]} entities The array of entities. * @param {string} path The path to map a `name` property from the entity. - * @return {IHasNameAndId[]} An array of enitities that now implement the `IHasNameAndId` interface. + * @return {IHasNameAndId[]} An array of entities that now implement the `IHasNameAndId` interface. */ export const mapToIHasNameAndId = ( entities, path ) => { return ( entities || [] ).map( ( entity ) => ( { @@ -272,32 +272,31 @@ export const getTransformedBlocksFromPattern = ( * @return {string} The block name to be used in the patterns suggestions. */ export function useBlockNameForPatterns( clientId, attributes ) { - const activeVariationName = useSelect( - ( select ) => - select( blocksStore ).getActiveBlockVariation( - 'core/query', - attributes - )?.name, - [ attributes ] - ); - const blockName = `core/query/${ activeVariationName }`; - const hasActiveVariationPatterns = useSelect( + return useSelect( ( select ) => { + const activeVariationName = select( + blocksStore + ).getActiveBlockVariation( 'core/query', attributes )?.name; + if ( ! activeVariationName ) { - return false; + return 'core/query'; } + const { getBlockRootClientId, getPatternsByBlockTypes } = select( blockEditorStore ); + const rootClientId = getBlockRootClientId( clientId ); const activePatterns = getPatternsByBlockTypes( - blockName, + `core/query/${ activeVariationName }`, rootClientId ); - return activePatterns.length > 0; + + return activePatterns.length > 0 + ? `core/query/${ activeVariationName }` + : 'core/query'; }, - [ clientId, activeVariationName, blockName ] + [ clientId, attributes ] ); - return hasActiveVariationPatterns ? blockName : 'core/query'; } /** diff --git a/packages/block-library/src/quote/transforms.js b/packages/block-library/src/quote/transforms.js index c960759691bf16..7ddead03d6b2b5 100644 --- a/packages/block-library/src/quote/transforms.js +++ b/packages/block-library/src/quote/transforms.js @@ -67,7 +67,7 @@ const transforms = { isMultiBlock: true, blocks: [ '*' ], isMatch: ( {}, blocks ) => { - // When a single block is selected make the tranformation + // When a single block is selected make the transformation // available only to specific blocks that make sense. if ( blocks.length === 1 ) { return [ diff --git a/packages/block-library/src/read-more/edit.js b/packages/block-library/src/read-more/edit.js index 8486dd6e50141a..8482d40d037916 100644 --- a/packages/block-library/src/read-more/edit.js +++ b/packages/block-library/src/read-more/edit.js @@ -6,31 +6,55 @@ import { RichText, useBlockProps, } from '@wordpress/block-editor'; -import { ToggleControl, PanelBody } from '@wordpress/components'; +import { + ToggleControl, + __experimentalToolsPanel as ToolsPanel, + __experimentalToolsPanelItem as ToolsPanelItem, +} from '@wordpress/components'; import { createBlock, getDefaultBlockName } from '@wordpress/blocks'; import { __ } from '@wordpress/i18n'; +/** + * Internal dependencies + */ +import { useToolsPanelDropdownMenuProps } from '../utils/hooks'; + export default function ReadMore( { attributes: { content, linkTarget }, setAttributes, insertBlocksAfter, } ) { const blockProps = useBlockProps(); + const dropdownMenuProps = useToolsPanelDropdownMenuProps(); + return ( <> <InspectorControls> - <PanelBody title={ __( 'Settings' ) }> - <ToggleControl - __nextHasNoMarginBottom + <ToolsPanel + label={ __( 'Settings' ) } + resetAll={ () => setAttributes( { linkTarget: '_self' } ) } + dropdownMenuProps={ dropdownMenuProps } + > + <ToolsPanelItem label={ __( 'Open in new tab' ) } - onChange={ ( value ) => - setAttributes( { - linkTarget: value ? '_blank' : '_self', - } ) + isShownByDefault + hasValue={ () => linkTarget !== '_self' } + onDeselect={ () => + setAttributes( { linkTarget: '_self' } ) } - checked={ linkTarget === '_blank' } - /> - </PanelBody> + > + <ToggleControl + __nextHasNoMarginBottom + label={ __( 'Open in new tab' ) } + onChange={ ( value ) => + setAttributes( { + linkTarget: value ? '_blank' : '_self', + } ) + } + checked={ linkTarget === '_blank' } + /> + </ToolsPanelItem> + </ToolsPanel> </InspectorControls> <RichText identifier="content" diff --git a/packages/block-library/src/read-more/index.js b/packages/block-library/src/read-more/index.js index 497cd77f429e62..f982f35151b4b8 100644 --- a/packages/block-library/src/read-more/index.js +++ b/packages/block-library/src/read-more/index.js @@ -1,6 +1,7 @@ /** * WordPress dependencies */ +import { __ } from '@wordpress/i18n'; import { link as icon } from '@wordpress/icons'; /** @@ -16,6 +17,11 @@ export { metadata, name }; export const settings = { icon, edit, + example: { + attributes: { + content: __( 'Read more' ), + }, + }, }; export const init = () => initBlock( { name, metadata, settings } ); diff --git a/packages/block-library/src/rss/block.json b/packages/block-library/src/rss/block.json index 36d70e7b7ccb98..844104b7d8113d 100644 --- a/packages/block-library/src/rss/block.json +++ b/packages/block-library/src/rss/block.json @@ -46,6 +46,12 @@ "html": false, "interactivity": { "clientNavigation": true + }, + "color": { + "background": true, + "text": true, + "gradients": true, + "link": true } }, "editorStyle": "wp-block-rss-editor", diff --git a/packages/block-library/src/rss/edit.js b/packages/block-library/src/rss/edit.js index b67cb4f9193df1..39564da79b16e5 100644 --- a/packages/block-library/src/rss/edit.js +++ b/packages/block-library/src/rss/edit.js @@ -77,6 +77,7 @@ export default function RSSEdit( { attributes, setAttributes } ) { <InputControl __next40pxDefaultSize label={ label } + type="url" hideLabelFromVision placeholder={ __( 'Enter URL here…' ) } value={ feedURL } diff --git a/packages/block-library/src/search/edit.js b/packages/block-library/src/search/edit.js index f193c04e2493aa..16eb62daa6abbf 100644 --- a/packages/block-library/src/search/edit.js +++ b/packages/block-library/src/search/edit.js @@ -25,16 +25,17 @@ import { ToolbarGroup, ToolbarButton, ResizableBox, - PanelBody, - __experimentalVStack as VStack, __experimentalUseCustomUnits as useCustomUnits, __experimentalUnitControl as UnitControl, __experimentalToggleGroupControl as ToggleGroupControl, __experimentalToggleGroupControlOption as ToggleGroupControlOption, + __experimentalToolsPanel as ToolsPanel, + __experimentalToolsPanelItem as ToolsPanelItem, + __experimentalVStack as VStack, } from '@wordpress/components'; import { useInstanceId } from '@wordpress/compose'; import { Icon, search } from '@wordpress/icons'; -import { __ } from '@wordpress/i18n'; +import { __, sprintf } from '@wordpress/i18n'; import { __unstableStripHTML as stripHTML } from '@wordpress/dom'; /** @@ -54,6 +55,7 @@ import { MIN_WIDTH, isPercentageUnit, } from './utils.js'; +import { useToolsPanelDropdownMenuProps } from '../utils/hooks'; // Used to calculate border radius adjustment to avoid "fat" corners when // button is placed inside wrapper. @@ -370,6 +372,7 @@ export default function SearchEdit( { </> ); }; + const dropdownMenuProps = useToolsPanelDropdownMenuProps(); const controls = ( <> @@ -408,75 +411,101 @@ export default function SearchEdit( { </BlockControls> <InspectorControls> - <PanelBody title={ __( 'Settings' ) }> - <VStack - className="wp-block-search__inspector-controls" - spacing={ 4 } + <ToolsPanel + label={ __( 'Settings' ) } + resetAll={ () => { + setAttributes( { + width: undefined, + widthUnit: undefined, + } ); + } } + dropdownMenuProps={ dropdownMenuProps } + > + <ToolsPanelItem + hasValue={ () => !! width } + label={ __( 'Width' ) } + onDeselect={ () => { + setAttributes( { + width: undefined, + widthUnit: undefined, + } ); + } } + isShownByDefault > - <UnitControl - __next40pxDefaultSize - label={ __( 'Width' ) } - id={ unitControlInputId } // unused, kept for backwards compatibility - min={ - isPercentageUnit( widthUnit ) ? 0 : MIN_WIDTH - } - max={ - isPercentageUnit( widthUnit ) ? 100 : undefined - } - step={ 1 } - onChange={ ( newWidth ) => { - const parsedNewWidth = - newWidth === '' - ? undefined - : parseInt( newWidth, 10 ); - setAttributes( { - width: parsedNewWidth, - } ); - } } - onUnitChange={ ( newUnit ) => { - setAttributes( { - width: - '%' === newUnit - ? PC_WIDTH_DEFAULT - : PX_WIDTH_DEFAULT, - widthUnit: newUnit, - } ); - } } - __unstableInputWidth="80px" - value={ `${ width }${ widthUnit }` } - units={ units } - /> - <ToggleGroupControl - label={ __( 'Percentage Width' ) } - value={ - PERCENTAGE_WIDTHS.includes( width ) && - widthUnit === '%' - ? width - : undefined - } - hideLabelFromVision - onChange={ ( newWidth ) => { - setAttributes( { - width: newWidth, - widthUnit: '%', - } ); - } } - isBlock - __next40pxDefaultSize - __nextHasNoMarginBottom - > - { PERCENTAGE_WIDTHS.map( ( widthValue ) => { - return ( - <ToggleGroupControlOption - key={ widthValue } - value={ widthValue } - label={ `${ widthValue }%` } - /> - ); - } ) } - </ToggleGroupControl> - </VStack> - </PanelBody> + <VStack> + <UnitControl + __next40pxDefaultSize + label={ __( 'Width' ) } + id={ unitControlInputId } // Unused, kept for backwards compatibility + min={ + isPercentageUnit( widthUnit ) + ? 0 + : MIN_WIDTH + } + max={ + isPercentageUnit( widthUnit ) + ? 100 + : undefined + } + step={ 1 } + onChange={ ( newWidth ) => { + const parsedNewWidth = + newWidth === '' + ? undefined + : parseInt( newWidth, 10 ); + setAttributes( { + width: parsedNewWidth, + } ); + } } + onUnitChange={ ( newUnit ) => { + setAttributes( { + width: + '%' === newUnit + ? PC_WIDTH_DEFAULT + : PX_WIDTH_DEFAULT, + widthUnit: newUnit, + } ); + } } + __unstableInputWidth="80px" + value={ `${ width }${ widthUnit }` } + units={ units } + /> + <ToggleGroupControl + label={ __( 'Percentage Width' ) } + value={ + PERCENTAGE_WIDTHS.includes( width ) && + widthUnit === '%' + ? width + : undefined + } + hideLabelFromVision + onChange={ ( newWidth ) => { + setAttributes( { + width: newWidth, + widthUnit: '%', + } ); + } } + isBlock + __next40pxDefaultSize + __nextHasNoMarginBottom + > + { PERCENTAGE_WIDTHS.map( ( widthValue ) => { + return ( + <ToggleGroupControlOption + key={ widthValue } + value={ widthValue } + label={ sprintf( + /* translators: Percentage value. */ + __( '%d%%' ), + widthValue + ) } + /> + ); + } ) } + </ToggleGroupControl> + </VStack> + </ToolsPanelItem> + </ToolsPanel> </InspectorControls> </> ); diff --git a/packages/block-library/src/search/index.php b/packages/block-library/src/search/index.php index b74daf548a8025..87e12f5d33d079 100644 --- a/packages/block-library/src/search/index.php +++ b/packages/block-library/src/search/index.php @@ -177,9 +177,9 @@ function render_block_core_search( $attributes ) { ) ); $form_directives = ' - data-wp-interactive="core/search"' - . $form_context . - 'data-wp-class--wp-block-search__searchfield-hidden="!context.isSearchInputVisible" + data-wp-interactive="core/search" + ' . $form_context . ' + data-wp-class--wp-block-search__searchfield-hidden="!context.isSearchInputVisible" data-wp-on-async--keydown="actions.handleSearchKeydown" data-wp-on-async--focusout="actions.handleSearchFocusout" '; diff --git a/packages/block-library/src/search/style.scss b/packages/block-library/src/search/style.scss index eb8e3051bada85..e61fdaea0e6434 100644 --- a/packages/block-library/src/search/style.scss +++ b/packages/block-library/src/search/style.scss @@ -58,11 +58,9 @@ $button-spacing-y: math.div($grid-unit-15, 2); // 6px // Prevent unintended text wrapping. flex-shrink: 0; max-width: 100%; - } - - // Ensure minimum input field width in small viewports. - .wp-block-search__button[aria-expanded="true"] { - max-width: calc(100% - 100px); + box-sizing: border-box; + display: flex; + justify-content: center; } .wp-block-search__inside-wrapper { diff --git a/packages/block-library/src/separator/block.json b/packages/block-library/src/separator/block.json index 484627aaa1612a..926d978b7e4d59 100644 --- a/packages/block-library/src/separator/block.json +++ b/packages/block-library/src/separator/block.json @@ -11,6 +11,11 @@ "opacity": { "type": "string", "default": "alpha-channel" + }, + "tagName": { + "type": "string", + "enum": [ "hr", "div" ], + "default": "hr" } }, "supports": { diff --git a/packages/block-library/src/separator/deprecated.js b/packages/block-library/src/separator/deprecated.js index 5174310e186ff0..7ce442813e2f78 100644 --- a/packages/block-library/src/separator/deprecated.js +++ b/packages/block-library/src/separator/deprecated.js @@ -49,6 +49,7 @@ const v1 = { style: customColor ? { color: { background: customColor } } : undefined, + tagName: 'hr', }; }, }; diff --git a/packages/block-library/src/separator/deprecated.scss b/packages/block-library/src/separator/deprecated.scss index b133ad12437042..4977122f5a5033 100644 --- a/packages/block-library/src/separator/deprecated.scss +++ b/packages/block-library/src/separator/deprecated.scss @@ -1,5 +1,5 @@ .wp-block-separator { - // V1 version of the block expects a default opactiy of 0.4 to be set. + // V1 version of the block expects a default opacity of 0.4 to be set. &.has-css-opacity { opacity: 0.4; } diff --git a/packages/block-library/src/separator/edit.js b/packages/block-library/src/separator/edit.js index 0d0af336713c15..ca94ef41f1d8ad 100644 --- a/packages/block-library/src/separator/edit.js +++ b/packages/block-library/src/separator/edit.js @@ -6,20 +6,23 @@ import clsx from 'clsx'; /** * WordPress dependencies */ -import { HorizontalRule } from '@wordpress/components'; +import { HorizontalRule, SelectControl } from '@wordpress/components'; import { useBlockProps, getColorClassName, __experimentalUseColorProps as useColorProps, + InspectorControls, } from '@wordpress/block-editor'; +import { __ } from '@wordpress/i18n'; /** * Internal dependencies */ import useDeprecatedOpacity from './use-deprecated-opacity'; +import { htmlElementMessages } from '../utils/messages'; export default function SeparatorEdit( { attributes, setAttributes } ) { - const { backgroundColor, opacity, style } = attributes; + const { backgroundColor, opacity, style, tagName } = attributes; const colorProps = useColorProps( attributes ); const currentColor = colorProps?.style?.backgroundColor; const hasCustomColor = !! style?.color?.background; @@ -44,10 +47,27 @@ export default function SeparatorEdit( { attributes, setAttributes } ) { color: currentColor, backgroundColor: currentColor, }; + const Wrapper = tagName === 'hr' ? HorizontalRule : tagName; return ( <> - <HorizontalRule + <InspectorControls group="advanced"> + <SelectControl + __nextHasNoMarginBottom + __next40pxDefaultSize + label={ __( 'HTML element' ) } + options={ [ + { label: __( 'Default (<hr>)' ), value: 'hr' }, + { label: '<div>', value: 'div' }, + ] } + value={ tagName } + onChange={ ( value ) => + setAttributes( { tagName: value } ) + } + help={ htmlElementMessages[ tagName ] } + /> + </InspectorControls> + <Wrapper { ...useBlockProps( { className, style: hasCustomColor ? styles : undefined, diff --git a/packages/block-library/src/separator/save.js b/packages/block-library/src/separator/save.js index 12d2282eb1d3e3..06a54b7519ac17 100644 --- a/packages/block-library/src/separator/save.js +++ b/packages/block-library/src/separator/save.js @@ -13,7 +13,7 @@ import { } from '@wordpress/block-editor'; export default function separatorSave( { attributes } ) { - const { backgroundColor, style, opacity } = attributes; + const { backgroundColor, style, opacity, tagName: Tag } = attributes; const customColor = style?.color?.background; const colorProps = getColorClassesAndStyles( attributes ); // The hr support changing color using border-color, since border-color @@ -37,5 +37,5 @@ export default function separatorSave( { attributes } ) { backgroundColor: colorProps?.style?.backgroundColor, color: colorClass ? undefined : customColor, }; - return <hr { ...useBlockProps.save( { className, style: styles } ) } />; + return <Tag { ...useBlockProps.save( { className, style: styles } ) } />; } diff --git a/packages/block-library/src/separator/test/edit.js b/packages/block-library/src/separator/test/edit.js index a37a0ebbe6dd67..33719b24925cd8 100644 --- a/packages/block-library/src/separator/test/edit.js +++ b/packages/block-library/src/separator/test/edit.js @@ -23,6 +23,7 @@ const defaultAttributes = { opacity: 'alpha-channel', style: {}, className: '', + tagName: 'hr', }; const defaultProps = { attributes: defaultAttributes, diff --git a/packages/block-library/src/site-logo/block.json b/packages/block-library/src/site-logo/block.json index 3bdbdc1b809ab1..1f5b3a5525e3ec 100644 --- a/packages/block-library/src/site-logo/block.json +++ b/packages/block-library/src/site-logo/block.json @@ -12,11 +12,13 @@ }, "isLink": { "type": "boolean", - "default": true + "default": true, + "role": "content" }, "linkTarget": { "type": "string", - "default": "_self" + "default": "_self", + "role": "content" }, "shouldSyncIcon": { "type": "boolean" diff --git a/packages/block-library/src/site-logo/edit.js b/packages/block-library/src/site-logo/edit.js index 36c217c1bf0c79..d2d2327736fd72 100644 --- a/packages/block-library/src/site-logo/edit.js +++ b/packages/block-library/src/site-logo/edit.js @@ -33,8 +33,6 @@ import { BlockControls, InspectorControls, MediaPlaceholder, - MediaUpload, - MediaUploadCheck, MediaReplaceFlow, useBlockProps, store as blockEditorStore, @@ -347,29 +345,24 @@ const SiteLogo = ( { // This is a light wrapper around MediaReplaceFlow because the block has two // different MediaReplaceFlows, one for the inspector and one for the toolbar. -function SiteLogoReplaceFlow( { - mediaURL, - onRemoveLogo, - ...mediaReplaceProps -} ) { +function SiteLogoReplaceFlow( { mediaURL, ...mediaReplaceProps } ) { return ( <MediaReplaceFlow { ...mediaReplaceProps } mediaURL={ mediaURL } allowedTypes={ ALLOWED_MEDIA_TYPES } accept={ ACCEPT_MEDIA_STRING } - onReset={ onRemoveLogo } /> ); } -const InspectorLogoPreview = ( { mediaItemData = {}, itemGroupProps } ) => { +const InspectorLogoPreview = ( { media, itemGroupProps } ) => { const { alt_text: alt, source_url: logoUrl, slug: logoSlug, media_details: logoMediaDetails, - } = mediaItemData; + } = media ?? {}; const logoLabel = logoMediaDetails?.sizes?.full?.file || logoSlug; return ( <ItemGroup { ...itemGroupProps } as="span"> @@ -506,6 +499,11 @@ export default function LogoEdit( { }; const onFilesDrop = ( filesList ) => { + if ( filesList?.length > 1 ) { + onUploadError( __( 'Only one image can be used as a site logo.' ) ); + return; + } + getSettings().mediaUpload( { allowedTypes: ALLOWED_MEDIA_TYPES, filesList, @@ -517,7 +515,6 @@ export default function LogoEdit( { onInitialSelectLogo( image ); }, onError: onUploadError, - onRemoveLogo, } ); }; @@ -526,7 +523,7 @@ export default function LogoEdit( { name: ! logoUrl ? __( 'Choose logo' ) : __( 'Replace' ), onSelect: onSelectLogo, onError: onUploadError, - onRemoveLogo, + onReset: onRemoveLogo, }; const controls = canUserEdit && ( <BlockControls group="other"> @@ -599,50 +596,40 @@ export default function LogoEdit( { <InspectorControls> <PanelBody title={ __( 'Media' ) }> <div className="block-library-site-logo__inspector-media-replace-container"> - { ! canUserEdit && !! logoUrl && ( + { ! canUserEdit ? ( <InspectorLogoPreview - mediaItemData={ mediaItemData } + media={ mediaItemData } itemGroupProps={ { isBordered: true, className: 'block-library-site-logo__inspector-readonly-logo-preview', } } /> - ) } - { canUserEdit && !! logoUrl && ( - <SiteLogoReplaceFlow - { ...mediaReplaceFlowProps } - name={ - <InspectorLogoPreview - mediaItemData={ mediaItemData } - /> - } - popoverProps={ {} } - /> - ) } - { canUserEdit && ! logoUrl && ( - <MediaUploadCheck> - <MediaUpload - onSelect={ onInitialSelectLogo } - allowedTypes={ ALLOWED_MEDIA_TYPES } - render={ ( { open } ) => ( - <div className="block-library-site-logo__inspector-upload-container"> - <Button - __next40pxDefaultSize - onClick={ open } - variant="secondary" - > - { isLoading ? ( - <Spinner /> - ) : ( - __( 'Choose logo' ) - ) } - </Button> - <DropZone onFilesDrop={ onFilesDrop } /> - </div> + ) : ( + <> + <SiteLogoReplaceFlow + { ...mediaReplaceFlowProps } + name={ + !! logoUrl ? ( + <InspectorLogoPreview + media={ mediaItemData } + /> + ) : ( + __( 'Choose logo' ) + ) + } + renderToggle={ ( props ) => ( + <Button { ...props } __next40pxDefaultSize> + { temporaryURL ? ( + <Spinner /> + ) : ( + props.children + ) } + </Button> ) } /> - </MediaUploadCheck> + <DropZone onFilesDrop={ onFilesDrop } /> + </> ) } </div> </PanelBody> diff --git a/packages/block-library/src/site-logo/editor.scss b/packages/block-library/src/site-logo/editor.scss index 2f9d1917079bec..da6d8d0830411a 100644 --- a/packages/block-library/src/site-logo/editor.scss +++ b/packages/block-library/src/site-logo/editor.scss @@ -108,17 +108,16 @@ } } -.block-library-site-logo__inspector-upload-container { +.block-library-site-logo__inspector-media-replace-container { + // Ensure the dropzone is positioned to the size of the item. position: relative; + // Since there is no option to skip rendering the drag'n'drop icon in drop // zone, we hide it for now. .components-drop-zone__content-icon { display: none; } -} -.block-library-site-logo__inspector-upload-container, -.block-library-site-logo__inspector-media-replace-container { button.components-button { color: $gray-900; box-shadow: inset 0 0 0 1px $gray-400; @@ -144,9 +143,7 @@ text-align: start; text-align-last: center; } -} -.block-library-site-logo__inspector-media-replace-container { .components-dropdown { display: block; } diff --git a/packages/block-library/src/site-title/block.json b/packages/block-library/src/site-title/block.json index c75b1bc229beb9..8edf6b945f9ce2 100644 --- a/packages/block-library/src/site-title/block.json +++ b/packages/block-library/src/site-title/block.json @@ -20,11 +20,13 @@ }, "isLink": { "type": "boolean", - "default": true + "default": true, + "role": "content" }, "linkTarget": { "type": "string", - "default": "_self" + "default": "_self", + "role": "content" } }, "example": { diff --git a/packages/block-library/src/site-title/edit.js b/packages/block-library/src/site-title/edit.js index 82e3c1d7f7bb40..0e3e96bd87cb3d 100644 --- a/packages/block-library/src/site-title/edit.js +++ b/packages/block-library/src/site-title/edit.js @@ -17,10 +17,19 @@ import { useBlockProps, HeadingLevelDropdown, } from '@wordpress/block-editor'; -import { ToggleControl, PanelBody } from '@wordpress/components'; +import { + ToggleControl, + __experimentalToolsPanel as ToolsPanel, + __experimentalToolsPanelItem as ToolsPanelItem, +} from '@wordpress/components'; import { createBlock, getDefaultBlockName } from '@wordpress/blocks'; import { decodeEntities } from '@wordpress/html-entities'; +/** + * Internal dependencies + */ +import { useToolsPanelDropdownMenuProps } from '../utils/hooks'; + export default function SiteTitleEdit( { attributes, setAttributes, @@ -43,6 +52,7 @@ export default function SiteTitleEdit( { }; }, [] ); const { editEntityRecord } = useDispatch( coreStore ); + const dropdownMenuProps = useToolsPanelDropdownMenuProps(); function setTitle( newTitle ) { editEntityRecord( 'root', 'site', undefined, { @@ -109,26 +119,53 @@ export default function SiteTitleEdit( { /> </BlockControls> <InspectorControls> - <PanelBody title={ __( 'Settings' ) }> - <ToggleControl - __nextHasNoMarginBottom + <ToolsPanel + label={ __( 'Settings' ) } + resetAll={ () => { + setAttributes( { + isLink: true, + linkTarget: '_self', + } ); + } } + dropdownMenuProps={ dropdownMenuProps } + > + <ToolsPanelItem + hasValue={ () => ! isLink } label={ __( 'Make title link to home' ) } - onChange={ () => setAttributes( { isLink: ! isLink } ) } - checked={ isLink } - /> - { isLink && ( + onDeselect={ () => setAttributes( { isLink: true } ) } + isShownByDefault + > <ToggleControl __nextHasNoMarginBottom - label={ __( 'Open in new tab' ) } - onChange={ ( value ) => - setAttributes( { - linkTarget: value ? '_blank' : '_self', - } ) + label={ __( 'Make title link to home' ) } + onChange={ () => + setAttributes( { isLink: ! isLink } ) } - checked={ linkTarget === '_blank' } + checked={ isLink } /> + </ToolsPanelItem> + { isLink && ( + <ToolsPanelItem + hasValue={ () => linkTarget !== '_self' } + label={ __( 'Open in new tab' ) } + onDeselect={ () => + setAttributes( { linkTarget: '_self' } ) + } + isShownByDefault + > + <ToggleControl + __nextHasNoMarginBottom + label={ __( 'Open in new tab' ) } + onChange={ ( value ) => + setAttributes( { + linkTarget: value ? '_blank' : '_self', + } ) + } + checked={ linkTarget === '_blank' } + /> + </ToolsPanelItem> ) } - </PanelBody> + </ToolsPanel> </InspectorControls> { siteTitleContent } </> diff --git a/packages/block-library/src/social-link/block.json b/packages/block-library/src/social-link/block.json index 37e8376f22ff09..667fd74b208f29 100644 --- a/packages/block-library/src/social-link/block.json +++ b/packages/block-library/src/social-link/block.json @@ -9,13 +9,15 @@ "textdomain": "default", "attributes": { "url": { - "type": "string" + "type": "string", + "role": "content" }, "service": { "type": "string" }, "label": { - "type": "string" + "type": "string", + "role": "content" }, "rel": { "type": "string" diff --git a/packages/block-library/src/social-link/edit.js b/packages/block-library/src/social-link/edit.js index 47af181bfe17e8..4cd24505fd552a 100644 --- a/packages/block-library/src/social-link/edit.js +++ b/packages/block-library/src/social-link/edit.js @@ -6,24 +6,29 @@ import clsx from 'clsx'; /** * WordPress dependencies */ -import { DELETE, BACKSPACE } from '@wordpress/keycodes'; +import { DELETE, BACKSPACE, ENTER } from '@wordpress/keycodes'; import { useDispatch } from '@wordpress/data'; import { + BlockControls, InspectorControls, URLPopover, URLInput, + useBlockEditingMode, useBlockProps, store as blockEditorStore, } from '@wordpress/block-editor'; -import { useState } from '@wordpress/element'; +import { useState, useRef } from '@wordpress/element'; import { Button, - PanelBody, - PanelRow, + Dropdown, TextControl, + ToolbarButton, + __experimentalToolsPanel as ToolsPanel, + __experimentalToolsPanelItem as ToolsPanelItem, __experimentalInputControlSuffixWrapper as InputControlSuffixWrapper, } from '@wordpress/components'; +import { useMergeRefs } from '@wordpress/compose'; import { __ } from '@wordpress/i18n'; import { keyboardReturn } from '@wordpress/icons'; @@ -31,6 +36,7 @@ import { keyboardReturn } from '@wordpress/icons'; * Internal dependencies */ import { getIconBySite, getNameBySite } from './social-list'; +import { useToolsPanelDropdownMenuProps } from '../utils/hooks'; const SocialLinkURLPopover = ( { url, @@ -104,6 +110,7 @@ const SocialLinkEdit = ( { clientId, } ) => { const { url, service, label = '', rel } = attributes; + const dropdownMenuProps = useToolsPanelDropdownMenuProps(); const { showLabels, iconColor, @@ -112,16 +119,24 @@ const SocialLinkEdit = ( { iconBackgroundColorValue, } = context; const [ showURLPopover, setPopover ] = useState( false ); - const classes = clsx( 'wp-social-link', 'wp-social-link-' + service, { - 'wp-social-link__is-incomplete': ! url, - [ `has-${ iconColor }-color` ]: iconColor, - [ `has-${ iconBackgroundColor }-background-color` ]: - iconBackgroundColor, - } ); + const wrapperClasses = clsx( + 'wp-social-link', + // Manually adding this class for backwards compatibility of CSS when moving the + // blockProps from the li to the button: https://github.com/WordPress/gutenberg/pull/64883 + 'wp-block-social-link', + 'wp-social-link-' + service, + { + 'wp-social-link__is-incomplete': ! url, + [ `has-${ iconColor }-color` ]: iconColor, + [ `has-${ iconBackgroundColor }-background-color` ]: + iconBackgroundColor, + } + ); // Use internal state instead of a ref to make sure that the component // re-renders when the popover's anchor updates. const [ popoverAnchor, setPopoverAnchor ] = useState( null ); + const isContentOnlyMode = useBlockEditingMode() === 'contentOnly'; const IconComponent = getIconBySite( service ); const socialLinkName = getNameBySite( service ); @@ -131,19 +146,72 @@ const SocialLinkEdit = ( { // spaces. The PHP render callback fallbacks to the social name as well. const socialLinkText = label.trim() === '' ? socialLinkName : label; + const ref = useRef(); const blockProps = useBlockProps( { - className: classes, - style: { - color: iconColorValue, - backgroundColor: iconBackgroundColorValue, + className: 'wp-block-social-link-anchor', + ref: useMergeRefs( [ setPopoverAnchor, ref ] ), + onClick: () => setPopover( true ), + onKeyDown: ( event ) => { + if ( event.keyCode === ENTER ) { + event.preventDefault(); + setPopover( true ); + } }, } ); return ( <> + { isContentOnlyMode && showLabels && ( + // Add an extra control to modify the label attribute when content only mode is active. + // With content only mode active, the inspector is hidden, so users need another way + // to edit this attribute. + <BlockControls group="other"> + <Dropdown + popoverProps={ { position: 'bottom right' } } + renderToggle={ ( { isOpen, onToggle } ) => ( + <ToolbarButton + onClick={ onToggle } + aria-haspopup="true" + aria-expanded={ isOpen } + > + { __( 'Text' ) } + </ToolbarButton> + ) } + renderContent={ () => ( + <TextControl + __next40pxDefaultSize + __nextHasNoMarginBottom + className="wp-block-social-link__toolbar_content_text" + label={ __( 'Text' ) } + help={ __( + 'Provide a text label or use the default.' + ) } + value={ label } + onChange={ ( value ) => + setAttributes( { label: value } ) + } + placeholder={ socialLinkName } + /> + ) } + /> + </BlockControls> + ) } <InspectorControls> - <PanelBody title={ __( 'Settings' ) }> - <PanelRow> + <ToolsPanel + label={ __( 'Settings' ) } + resetAll={ () => { + setAttributes( { label: undefined } ); + } } + dropdownMenuProps={ dropdownMenuProps } + > + <ToolsPanelItem + isShownByDefault + label={ __( 'Text' ) } + hasValue={ () => !! label } + onDeselect={ () => { + setAttributes( { label: undefined } ); + } } + > <TextControl __next40pxDefaultSize __nextHasNoMarginBottom @@ -157,8 +225,8 @@ const SocialLinkEdit = ( { } placeholder={ socialLinkName } /> - </PanelRow> - </PanelBody> + </ToolsPanelItem> + </ToolsPanel> </InspectorControls> <InspectorControls group="advanced"> <TextControl @@ -169,13 +237,27 @@ const SocialLinkEdit = ( { onChange={ ( value ) => setAttributes( { rel: value } ) } /> </InspectorControls> - <li { ...blockProps }> - <button - className="wp-block-social-link-anchor" - ref={ setPopoverAnchor } - onClick={ () => setPopover( true ) } - aria-haspopup="dialog" - > + { /* + * Because the `<ul>` element has a role=document, the `<li>` is + * not semantically correct, so adding role=presentation is cleaner. + * https://github.com/WordPress/gutenberg/pull/64883#issuecomment-2472874551 + */ } + <li + role="presentation" + className={ wrapperClasses } + style={ { + color: iconColorValue, + backgroundColor: iconBackgroundColorValue, + } } + > + { /* + * Disable reason: The `button` ARIA role is redundant but + * blockProps has a role of `document` automatically applied + * which breaks the semantics of this button since it removes + * the information about the popover. + */ + /* eslint-disable jsx-a11y/no-redundant-roles */ } + <button aria-haspopup="dialog" { ...blockProps } role="button"> <IconComponent /> <span className={ clsx( 'wp-block-social-link-label', { @@ -185,6 +267,7 @@ const SocialLinkEdit = ( { { socialLinkText } </span> </button> + { /* eslint-enable jsx-a11y/no-redundant-roles */ } { isSelected && showURLPopover && ( <SocialLinkURLPopover url={ url } diff --git a/packages/block-library/src/social-link/editor.scss b/packages/block-library/src/social-link/editor.scss index bd93ad9f055fcd..d61cf7c1a7cf56 100644 --- a/packages/block-library/src/social-link/editor.scss +++ b/packages/block-library/src/social-link/editor.scss @@ -14,6 +14,8 @@ font-size: inherit; color: currentColor; height: auto; + font-weight: inherit; + font-family: inherit; // This rule ensures social link buttons display correctly in template parts. opacity: 1; @@ -21,13 +23,10 @@ // This rule is duplicated from the style.scss and needs to be the same as there. padding: 0.25em; - // Focus styles replicate the `@wordpress/components` button component. - &:focus:not(:disabled) { - border-radius: 2px; - box-shadow: 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: 3px solid transparent; + // Override the shared `.wp-block-social-link` class used on both the li and button + // due to backwards compatibility from moving the blockProps from the li to the button. + &:hover { + transform: none; } } @@ -39,3 +38,8 @@ :root :where(.wp-block-social-links.is-style-logos-only .wp-social-link button) { padding: 0; } + +.wp-block-social-link__toolbar_content_text { + // Corresponds to the size of the text control input in the block inspector. + width: 250px; +} diff --git a/packages/block-library/src/social-link/index.php b/packages/block-library/src/social-link/index.php index da28034f5a55d2..f241daff2a11a8 100644 --- a/packages/block-library/src/social-link/index.php +++ b/packages/block-library/src/social-link/index.php @@ -42,9 +42,9 @@ function render_block_core_social_link( $attributes, $content, $block ) { /** * Prepend URL with https:// if it doesn't appear to contain a scheme - * and it's not a relative link starting with //. + * and it's not a relative link or a fragment. */ - if ( ! parse_url( $url, PHP_URL_SCHEME ) && ! str_starts_with( $url, '//' ) ) { + if ( ! parse_url( $url, PHP_URL_SCHEME ) && ! str_starts_with( $url, '//' ) && ! str_starts_with( $url, '#' ) ) { $url = 'https://' . $url; } diff --git a/packages/block-library/src/social-links/edit.js b/packages/block-library/src/social-links/edit.js index 068b34a3a70a4e..0b8b5c04deffba 100644 --- a/packages/block-library/src/social-links/edit.js +++ b/packages/block-library/src/social-links/edit.js @@ -22,14 +22,20 @@ import { import { MenuGroup, MenuItem, - PanelBody, ToggleControl, ToolbarDropdownMenu, + __experimentalToolsPanel as ToolsPanel, + __experimentalToolsPanelItem as ToolsPanelItem, } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import { check } from '@wordpress/icons'; import { useSelect } from '@wordpress/data'; +/** + * Internal dependencies + */ +import { useToolsPanelDropdownMenuProps } from '../utils/hooks'; + const sizeOptions = [ { name: __( 'Small' ), value: 'has-small-icon-size' }, { name: __( 'Normal' ), value: 'has-normal-icon-size' }, @@ -68,6 +74,8 @@ export function SocialLinksEdit( props ) { const logosOnly = attributes.className?.includes( 'is-style-logos-only' ); + const dropdownMenuProps = useToolsPanelDropdownMenuProps(); + // Remove icon background color when logos only style is selected or // restore it when any other style is selected. const backgroundBackupRef = useRef( {} ); @@ -198,24 +206,53 @@ export function SocialLinksEdit( props ) { </ToolbarDropdownMenu> </BlockControls> <InspectorControls> - <PanelBody title={ __( 'Settings' ) }> - <ToggleControl - __nextHasNoMarginBottom + <ToolsPanel + label={ __( 'Settings' ) } + resetAll={ () => { + setAttributes( { + openInNewTab: false, + showLabels: false, + } ); + } } + dropdownMenuProps={ dropdownMenuProps } + > + <ToolsPanelItem + isShownByDefault label={ __( 'Open links in new tab' ) } - checked={ openInNewTab } - onChange={ () => - setAttributes( { openInNewTab: ! openInNewTab } ) + hasValue={ () => !! openInNewTab } + onDeselect={ () => + setAttributes( { openInNewTab: false } ) } - /> - <ToggleControl - __nextHasNoMarginBottom + > + <ToggleControl + __nextHasNoMarginBottom + label={ __( 'Open links in new tab' ) } + checked={ openInNewTab } + onChange={ () => + setAttributes( { + openInNewTab: ! openInNewTab, + } ) + } + /> + </ToolsPanelItem> + <ToolsPanelItem + isShownByDefault label={ __( 'Show text' ) } - checked={ showLabels } - onChange={ () => - setAttributes( { showLabels: ! showLabels } ) + hasValue={ () => !! showLabels } + onDeselect={ () => + setAttributes( { showLabels: false } ) } - /> - </PanelBody> + > + <ToggleControl + __nextHasNoMarginBottom + label={ __( 'Show text' ) } + checked={ showLabels } + onChange={ () => + setAttributes( { showLabels: ! showLabels } ) + } + /> + </ToolsPanelItem> + </ToolsPanel> </InspectorControls> { colorGradientSettings.hasColorsOrGradients && ( <InspectorControls group="color"> @@ -232,6 +269,7 @@ export function SocialLinksEdit( props ) { isShownByDefault: true, resetAllFilter, enableAlpha: true, + clearable: true, }, ] } panelId={ clientId } diff --git a/packages/block-library/src/social-links/style.scss b/packages/block-library/src/social-links/style.scss index 955c0434feea22..9df3a7d5dde6da 100644 --- a/packages/block-library/src/social-links/style.scss +++ b/packages/block-library/src/social-links/style.scss @@ -70,8 +70,9 @@ .wp-block-social-link { display: block; border-radius: 9999px; // This makes it pill-shaped instead of oval, in cases where the image fed is not perfectly sized. - transition: transform 0.1s ease; - @include reduce-motion("transition"); + @media not (prefers-reduced-motion) { + transition: transform 0.1s ease; + } // Dimensions. height: auto; @@ -80,7 +81,6 @@ align-items: center; display: flex; line-height: 0; - transition: transform 0.1s ease; } &:hover { diff --git a/packages/block-library/src/spacer/controls.js b/packages/block-library/src/spacer/controls.js index 1e899e15aff0de..b5f73a259419d2 100644 --- a/packages/block-library/src/spacer/controls.js +++ b/packages/block-library/src/spacer/controls.js @@ -10,10 +10,11 @@ import { privateApis as blockEditorPrivateApis, } from '@wordpress/block-editor'; import { - PanelBody, __experimentalUseCustomUnits as useCustomUnits, __experimentalUnitControl as UnitControl, __experimentalParseQuantityAndUnitFromRawValue as parseQuantityAndUnitFromRawValue, + __experimentalToolsPanel as ToolsPanel, + __experimentalToolsPanelItem as ToolsPanelItem, } from '@wordpress/components'; import { useInstanceId } from '@wordpress/compose'; import { View } from '@wordpress/primitives'; @@ -23,6 +24,7 @@ import { View } from '@wordpress/primitives'; */ import { unlock } from '../lock-unlock'; import { MIN_SPACER_SIZE } from './constants'; +import { useToolsPanelDropdownMenuProps } from '../utils/hooks'; const { useSpacingSizes } = unlock( blockEditorPrivateApis ); @@ -92,30 +94,59 @@ export default function SpacerControls( { width, isResizing, } ) { + const dropdownMenuProps = useToolsPanelDropdownMenuProps(); + return ( <InspectorControls> - <PanelBody title={ __( 'Settings' ) }> + <ToolsPanel + label={ __( 'Settings' ) } + resetAll={ () => { + setAttributes( { + width: undefined, + height: '100px', + } ); + } } + dropdownMenuProps={ dropdownMenuProps } + > { orientation === 'horizontal' && ( - <DimensionInput + <ToolsPanelItem label={ __( 'Width' ) } - value={ width } - onChange={ ( nextWidth ) => - setAttributes( { width: nextWidth } ) + isShownByDefault + hasValue={ () => width !== undefined } + onDeselect={ () => + setAttributes( { width: undefined } ) } - isResizing={ isResizing } - /> + > + <DimensionInput + label={ __( 'Width' ) } + value={ width } + onChange={ ( nextWidth ) => + setAttributes( { width: nextWidth } ) + } + isResizing={ isResizing } + /> + </ToolsPanelItem> ) } { orientation !== 'horizontal' && ( - <DimensionInput + <ToolsPanelItem label={ __( 'Height' ) } - value={ height } - onChange={ ( nextHeight ) => - setAttributes( { height: nextHeight } ) + isShownByDefault + hasValue={ () => height !== '100px' } + onDeselect={ () => + setAttributes( { height: '100px' } ) } - isResizing={ isResizing } - /> + > + <DimensionInput + label={ __( 'Height' ) } + value={ height } + onChange={ ( nextHeight ) => + setAttributes( { height: nextHeight } ) + } + isResizing={ isResizing } + /> + </ToolsPanelItem> ) } - </PanelBody> + </ToolsPanel> </InspectorControls> ); } diff --git a/packages/block-library/src/style.scss b/packages/block-library/src/style.scss index a8819c2084dc2e..c61049c23151b9 100644 --- a/packages/block-library/src/style.scss +++ b/packages/block-library/src/style.scss @@ -37,6 +37,7 @@ @import "./post-author-biography/style.scss"; @import "./post-comments-form/style.scss"; @import "./post-content/style.scss"; +@import "./post-comments-link/style.scss"; @import "./post-date/style.scss"; @import "./post-excerpt/style.scss"; @import "./post-featured-image/style.scss"; @@ -50,6 +51,7 @@ @import "./post-template/style.scss"; @import "./query-pagination/style.scss"; @import "./query-title/style.scss"; +@import "./query-total/style.scss"; @import "./quote/style.scss"; @import "./read-more/style.scss"; @import "./rss/style.scss"; diff --git a/packages/block-library/src/table-of-contents/block.json b/packages/block-library/src/table-of-contents/block.json index 5eb6e729d3f03e..68266166080bbd 100644 --- a/packages/block-library/src/table-of-contents/block.json +++ b/packages/block-library/src/table-of-contents/block.json @@ -62,57 +62,5 @@ } } }, - "example": { - "innerBlocks": [ - { - "name": "core/heading", - "attributes": { - "level": 2, - "content": "Heading" - } - }, - { - "name": "core/heading", - "attributes": { - "level": 3, - "content": "Subheading" - } - }, - { - "name": "core/heading", - "attributes": { - "level": 2, - "content": "Heading" - } - }, - { - "name": "core/heading", - "attributes": { - "level": 3, - "content": "Subheading" - } - } - ], - "attributes": { - "headings": [ - { - "content": "Heading", - "level": 2 - }, - { - "content": "Subheading", - "level": 3 - }, - { - "content": "Heading", - "level": 2 - }, - { - "content": "Subheading", - "level": 3 - } - ] - } - }, "style": "wp-block-table-of-contents" } diff --git a/packages/block-library/src/table-of-contents/edit.js b/packages/block-library/src/table-of-contents/edit.js index c95b89200cb88c..1b1da0f3d78baa 100644 --- a/packages/block-library/src/table-of-contents/edit.js +++ b/packages/block-library/src/table-of-contents/edit.js @@ -10,11 +10,12 @@ import { } from '@wordpress/block-editor'; import { createBlock } from '@wordpress/blocks'; import { - PanelBody, Placeholder, ToggleControl, ToolbarButton, ToolbarGroup, + __experimentalToolsPanel as ToolsPanel, + __experimentalToolsPanelItem as ToolsPanelItem, } from '@wordpress/components'; import { useDispatch, useSelect } from '@wordpress/data'; import { renderToString } from '@wordpress/element'; @@ -29,6 +30,7 @@ import { tableOfContents as icon } from '@wordpress/icons'; import TableOfContentsList from './list'; import { linearToNestedHeadingList } from './utils'; import { useObserveHeadings } from './hooks'; +import { useToolsPanelDropdownMenuProps } from '../utils/hooks'; /** @typedef {import('./utils').HeadingData} HeadingData */ @@ -79,7 +81,7 @@ export default function TableOfContentsEdit( { ); const { replaceBlocks } = useDispatch( blockEditorStore ); - + const dropdownMenuProps = useToolsPanelDropdownMenuProps(); const headingTree = linearToNestedHeadingList( headings ); const toolbarControls = canInsertList && ( @@ -108,25 +110,42 @@ export default function TableOfContentsEdit( { const inspectorControls = ( <InspectorControls> - <PanelBody title={ __( 'Settings' ) }> - <ToggleControl - __nextHasNoMarginBottom + <ToolsPanel + label={ __( 'Settings' ) } + resetAll={ () => { + setAttributes( { + onlyIncludeCurrentPage: false, + } ); + } } + dropdownMenuProps={ dropdownMenuProps } + > + <ToolsPanelItem + hasValue={ () => !! onlyIncludeCurrentPage } label={ __( 'Only include current page' ) } - checked={ onlyIncludeCurrentPage } - onChange={ ( value ) => - setAttributes( { onlyIncludeCurrentPage: value } ) - } - help={ - onlyIncludeCurrentPage - ? __( - 'Only including headings from the current page (if the post is paginated).' - ) - : __( - 'Toggle to only include headings from the current page (if the post is paginated).' - ) + onDeselect={ () => + setAttributes( { onlyIncludeCurrentPage: false } ) } - /> - </PanelBody> + isShownByDefault + > + <ToggleControl + __nextHasNoMarginBottom + label={ __( 'Only include current page' ) } + checked={ onlyIncludeCurrentPage } + onChange={ ( value ) => + setAttributes( { onlyIncludeCurrentPage: value } ) + } + help={ + onlyIncludeCurrentPage + ? __( + 'Only including headings from the current page (if the post is paginated).' + ) + : __( + 'Include headings from all pages (if the post is paginated).' + ) + } + /> + </ToolsPanelItem> + </ToolsPanel> </InspectorControls> ); diff --git a/packages/block-library/src/table-of-contents/index.js b/packages/block-library/src/table-of-contents/index.js index 408538a7dcadbd..ff1b658966f19f 100644 --- a/packages/block-library/src/table-of-contents/index.js +++ b/packages/block-library/src/table-of-contents/index.js @@ -1,6 +1,7 @@ /** * WordPress dependencies */ +import { __ } from '@wordpress/i18n'; import { tableOfContents as icon } from '@wordpress/icons'; /** @@ -19,6 +20,58 @@ export const settings = { icon, edit, save, + example: { + innerBlocks: [ + { + name: 'core/heading', + attributes: { + level: 2, + content: __( 'Heading' ), + }, + }, + { + name: 'core/heading', + attributes: { + level: 3, + content: __( 'Subheading' ), + }, + }, + { + name: 'core/heading', + attributes: { + level: 2, + content: __( 'Heading' ), + }, + }, + { + name: 'core/heading', + attributes: { + level: 3, + content: __( 'Subheading' ), + }, + }, + ], + attributes: { + headings: [ + { + content: __( 'Heading' ), + level: 2, + }, + { + content: __( 'Subheading' ), + level: 3, + }, + { + content: __( 'Heading' ), + level: 2, + }, + { + content: __( 'Subheading' ), + level: 3, + }, + ], + }, + }, }; export const init = () => initBlock( { name, metadata, settings } ); diff --git a/packages/block-library/src/table/block.json b/packages/block-library/src/table/block.json index 11dd5b5f323e3b..2f0ea753f6f8de 100644 --- a/packages/block-library/src/table/block.json +++ b/packages/block-library/src/table/block.json @@ -195,11 +195,14 @@ "width": true } }, - "__experimentalSelector": ".wp-block-table > table", "interactivity": { "clientNavigation": true } }, + "selectors": { + "root": ".wp-block-table > table", + "spacing": ".wp-block-table" + }, "styles": [ { "name": "regular", diff --git a/packages/block-library/src/table/edit.js b/packages/block-library/src/table/edit.js index f1cb3fa5d8b8ae..a6c8be3c4a4899 100644 --- a/packages/block-library/src/table/edit.js +++ b/packages/block-library/src/table/edit.js @@ -20,12 +20,13 @@ import { import { __ } from '@wordpress/i18n'; import { Button, - PanelBody, Placeholder, TextControl, ToggleControl, ToolbarDropdownMenu, __experimentalHasSplitBorders as hasSplitBorders, + __experimentalToolsPanel as ToolsPanel, + __experimentalToolsPanelItem as ToolsPanelItem, } from '@wordpress/components'; import { alignLeft, @@ -56,6 +57,7 @@ import { isEmptyTableSection, } from './state'; import { Caption } from '../utils/caption'; +import { useToolsPanelDropdownMenuProps } from '../utils/hooks'; const ALIGNMENT_CONTROLS = [ { @@ -108,6 +110,8 @@ function TableEdit( { const tableRef = useRef(); const [ hasTableCreated, setHasTableCreated ] = useState( false ); + const dropdownMenuProps = useToolsPanelDropdownMenuProps(); + /** * Updates the initial column count used for table creation. * @@ -473,33 +477,67 @@ function TableEdit( { </> ) } <InspectorControls> - <PanelBody - title={ __( 'Settings' ) } - className="blocks-table-settings" + <ToolsPanel + label={ __( 'Settings' ) } + resetAll={ () => { + setAttributes( { + hasFixedLayout: true, + head: [], + foot: [], + } ); + } } + dropdownMenuProps={ dropdownMenuProps } > - <ToggleControl - __nextHasNoMarginBottom + <ToolsPanelItem + hasValue={ () => hasFixedLayout !== true } label={ __( 'Fixed width table cells' ) } - checked={ !! hasFixedLayout } - onChange={ onChangeFixedLayout } - /> + onDeselect={ () => + setAttributes( { hasFixedLayout: true } ) + } + isShownByDefault + > + <ToggleControl + __nextHasNoMarginBottom + label={ __( 'Fixed width table cells' ) } + checked={ !! hasFixedLayout } + onChange={ onChangeFixedLayout } + /> + </ToolsPanelItem> { ! isEmpty && ( <> - <ToggleControl - __nextHasNoMarginBottom + <ToolsPanelItem + hasValue={ () => head && head.length } label={ __( 'Header section' ) } - checked={ !! ( head && head.length ) } - onChange={ onToggleHeaderSection } - /> - <ToggleControl - __nextHasNoMarginBottom + onDeselect={ () => + setAttributes( { head: [] } ) + } + isShownByDefault + > + <ToggleControl + __nextHasNoMarginBottom + label={ __( 'Header section' ) } + checked={ !! ( head && head.length ) } + onChange={ onToggleHeaderSection } + /> + </ToolsPanelItem> + <ToolsPanelItem + hasValue={ () => foot && foot.length } label={ __( 'Footer section' ) } - checked={ !! ( foot && foot.length ) } - onChange={ onToggleFooterSection } - /> + onDeselect={ () => + setAttributes( { foot: [] } ) + } + isShownByDefault + > + <ToggleControl + __nextHasNoMarginBottom + label={ __( 'Footer section' ) } + checked={ !! ( foot && foot.length ) } + onChange={ onToggleFooterSection } + /> + </ToolsPanelItem> </> ) } - </PanelBody> + </ToolsPanel> </InspectorControls> { ! isEmpty && ( <table diff --git a/packages/block-library/src/tag-cloud/edit.js b/packages/block-library/src/tag-cloud/edit.js index eeb568e7a89ef1..7e544d2474f049 100644 --- a/packages/block-library/src/tag-cloud/edit.js +++ b/packages/block-library/src/tag-cloud/edit.js @@ -4,14 +4,14 @@ import { Flex, FlexItem, - PanelBody, ToggleControl, SelectControl, RangeControl, __experimentalUnitControl as UnitControl, __experimentalUseCustomUnits as useCustomUnits, __experimentalParseQuantityAndUnitFromRawValue as parseQuantityAndUnitFromRawValue, - __experimentalVStack as VStack, + __experimentalToolsPanel as ToolsPanel, + __experimentalToolsPanelItem as ToolsPanelItem, Disabled, } from '@wordpress/components'; import { useSelect } from '@wordpress/data'; @@ -24,6 +24,11 @@ import { import ServerSideRender from '@wordpress/server-side-render'; import { store as coreStore } from '@wordpress/core-data'; +/** + * Internal dependencies + */ +import { useToolsPanelDropdownMenuProps } from '../utils/hooks'; + /** * Minimum number of tags a user can show using this block. * @@ -51,6 +56,7 @@ function TagCloudEdit( { attributes, setAttributes } ) { } = attributes; const [ availableUnits ] = useSettings( 'spacing.units' ); + const dropdownMenuProps = useToolsPanelDropdownMenuProps(); // The `pt` unit is used as the default value and is therefore // always considered an available unit. @@ -118,10 +124,26 @@ function TagCloudEdit( { attributes, setAttributes } ) { const inspectorControls = ( <InspectorControls> - <PanelBody title={ __( 'Settings' ) }> - <VStack - spacing={ 4 } - className="wp-block-tag-cloud__inspector-settings" + <ToolsPanel + label={ __( 'Settings' ) } + resetAll={ () => { + setAttributes( { + taxonomy: 'post_tag', + showTagCounts: false, + numberOfTags: 45, + smallestFontSize: '8pt', + largestFontSize: '22pt', + } ); + } } + dropdownMenuProps={ dropdownMenuProps } + > + <ToolsPanelItem + hasValue={ () => taxonomy !== 'post_tag' } + label={ __( 'Taxonomy' ) } + onDeselect={ () => + setAttributes( { taxonomy: 'post_tag' } ) + } + isShownByDefault > <SelectControl __nextHasNoMarginBottom @@ -133,6 +155,20 @@ function TagCloudEdit( { attributes, setAttributes } ) { setAttributes( { taxonomy: selectedTaxonomy } ) } /> + </ToolsPanelItem> + <ToolsPanelItem + hasValue={ () => + smallestFontSize !== '8pt' || largestFontSize !== '22pt' + } + label={ __( 'Font size' ) } + onDeselect={ () => + setAttributes( { + smallestFontSize: '8pt', + largestFontSize: '22pt', + } ) + } + isShownByDefault + > <Flex gap={ 4 }> <FlexItem isBlock> <UnitControl @@ -167,6 +203,13 @@ function TagCloudEdit( { attributes, setAttributes } ) { /> </FlexItem> </Flex> + </ToolsPanelItem> + <ToolsPanelItem + hasValue={ () => numberOfTags !== 45 } + label={ __( 'Number of tags' ) } + onDeselect={ () => setAttributes( { numberOfTags: 45 } ) } + isShownByDefault + > <RangeControl __nextHasNoMarginBottom __next40pxDefaultSize @@ -179,6 +222,15 @@ function TagCloudEdit( { attributes, setAttributes } ) { max={ MAX_TAGS } required /> + </ToolsPanelItem> + <ToolsPanelItem + hasValue={ () => showTagCounts !== false } + label={ __( 'Show tag counts' ) } + onDeselect={ () => + setAttributes( { showTagCounts: false } ) + } + isShownByDefault + > <ToggleControl __nextHasNoMarginBottom label={ __( 'Show tag counts' ) } @@ -187,8 +239,8 @@ function TagCloudEdit( { attributes, setAttributes } ) { setAttributes( { showTagCounts: ! showTagCounts } ) } /> - </VStack> - </PanelBody> + </ToolsPanelItem> + </ToolsPanel> </InspectorControls> ); diff --git a/packages/block-library/src/tag-cloud/editor.scss b/packages/block-library/src/tag-cloud/editor.scss index e85129e22f1aca..d00a450174f2fd 100644 --- a/packages/block-library/src/tag-cloud/editor.scss +++ b/packages/block-library/src/tag-cloud/editor.scss @@ -9,11 +9,3 @@ border: none; border-radius: inherit; } - -.wp-block-tag-cloud__inspector-settings { - .components-base-control, - .components-base-control:last-child { - // Cancel out extra margins added by block inspector - margin-bottom: 0; - } -} diff --git a/packages/block-library/src/template-part/edit/advanced-controls.js b/packages/block-library/src/template-part/edit/advanced-controls.js index 3c319a7ec0fe73..f63c01b71be02c 100644 --- a/packages/block-library/src/template-part/edit/advanced-controls.js +++ b/packages/block-library/src/template-part/edit/advanced-controls.js @@ -1,7 +1,7 @@ /** * WordPress dependencies */ -import { useEntityProp } from '@wordpress/core-data'; +import { useEntityProp, store as coreStore } from '@wordpress/core-data'; import { SelectControl, TextControl } from '@wordpress/components'; import { sprintf, __ } from '@wordpress/i18n'; import { useSelect } from '@wordpress/data'; @@ -10,27 +10,7 @@ import { useSelect } from '@wordpress/data'; * Internal dependencies */ import { TemplatePartImportControls } from './import-controls'; - -const htmlElementMessages = { - header: __( - 'The <header> element should represent introductory content, typically a group of introductory or navigational aids.' - ), - main: __( - 'The <main> element should be used for the primary content of your document only.' - ), - section: __( - "The <section> element should represent a standalone portion of the document that can't be better represented by another element." - ), - article: __( - 'The <article> element should represent a self-contained, syndicatable portion of the document.' - ), - aside: __( - "The <aside> element should represent a portion of a document whose content is only indirectly related to the document's main content." - ), - footer: __( - 'The <footer> element should represent a footer for its nearest sectioning element (e.g.: <section>, <article>, <main> etc.).' - ), -}; +import { htmlElementMessages } from '../../utils/messages'; export function TemplatePartAdvancedControls( { tagName, @@ -54,19 +34,19 @@ export function TemplatePartAdvancedControls( { templatePartId ); - const definedAreas = useSelect( ( select ) => { - // FIXME: @wordpress/block-library should not depend on @wordpress/editor. - // Blocks can be loaded into a *non-post* block editor. - /* eslint-disable-next-line @wordpress/data-no-store-string-literals */ - return select( - 'core/editor' - ).__experimentalGetDefaultTemplatePartAreas(); - }, [] ); + const defaultTemplatePartAreas = useSelect( + ( select ) => + select( coreStore ).getEntityRecord( 'root', '__unstableBase' ) + ?.default_template_part_areas || [], + [] + ); - const areaOptions = definedAreas.map( ( { label, area: _area } ) => ( { - label, - value: _area, - } ) ); + const areaOptions = defaultTemplatePartAreas.map( + ( { label, area: _area } ) => ( { + label, + value: _area, + } ) + ); return ( <> diff --git a/packages/block-library/src/template-part/edit/utils/get-template-part-icon.js b/packages/block-library/src/template-part/edit/utils/get-template-part-icon.js new file mode 100644 index 00000000000000..bb13a8840c9458 --- /dev/null +++ b/packages/block-library/src/template-part/edit/utils/get-template-part-icon.js @@ -0,0 +1,20 @@ +/** + * WordPress dependencies + */ +import { + header as headerIcon, + footer as footerIcon, + sidebar as sidebarIcon, + symbolFilled as symbolFilledIcon, +} from '@wordpress/icons'; + +export const getTemplatePartIcon = ( iconName ) => { + if ( 'header' === iconName ) { + return headerIcon; + } else if ( 'footer' === iconName ) { + return footerIcon; + } else if ( 'sidebar' === iconName ) { + return sidebarIcon; + } + return symbolFilledIcon; +}; diff --git a/packages/block-library/src/template-part/edit/utils/hooks.js b/packages/block-library/src/template-part/edit/utils/hooks.js index 39daa4080c8160..c71327db0290c4 100644 --- a/packages/block-library/src/template-part/edit/utils/hooks.js +++ b/packages/block-library/src/template-part/edit/utils/hooks.js @@ -136,14 +136,9 @@ export function useCreateTemplatePartFromBlocks( area, setAttributes ) { export function useTemplatePartArea( area ) { return useSelect( ( select ) => { - // FIXME: @wordpress/block-library should not depend on @wordpress/editor. - // Blocks can be loaded into a *non-post* block editor. - /* eslint-disable @wordpress/data-no-store-string-literals */ const definedAreas = - select( - 'core/editor' - ).__experimentalGetDefaultTemplatePartAreas(); - /* eslint-enable @wordpress/data-no-store-string-literals */ + select( coreStore ).getEntityRecord( 'root', '__unstableBase' ) + ?.default_template_part_areas || []; const selectedArea = definedAreas.find( ( definedArea ) => definedArea.area === area diff --git a/packages/block-library/src/template-part/variations.js b/packages/block-library/src/template-part/variations.js index acd8af13508ba9..6f62057bec33dd 100644 --- a/packages/block-library/src/template-part/variations.js +++ b/packages/block-library/src/template-part/variations.js @@ -3,23 +3,11 @@ */ import { store as coreDataStore } from '@wordpress/core-data'; import { select } from '@wordpress/data'; -import { - header as headerIcon, - footer as footerIcon, - sidebar as sidebarIcon, - symbolFilled as symbolFilledIcon, -} from '@wordpress/icons'; -function getTemplatePartIcon( iconName ) { - if ( 'header' === iconName ) { - return headerIcon; - } else if ( 'footer' === iconName ) { - return footerIcon; - } else if ( 'sidebar' === iconName ) { - return sidebarIcon; - } - return symbolFilledIcon; -} +/** + * Internal dependencies + */ +import { getTemplatePartIcon } from './edit/utils/get-template-part-icon'; export function enhanceTemplatePartVariations( settings, name ) { if ( name !== 'core/template-part' ) { diff --git a/packages/block-library/src/utils/messages.js b/packages/block-library/src/utils/messages.js new file mode 100644 index 00000000000000..564bb32ee951eb --- /dev/null +++ b/packages/block-library/src/utils/messages.js @@ -0,0 +1,31 @@ +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; + +export const htmlElementMessages = { + article: __( + 'The <article> element should represent a self-contained, syndicatable portion of the document.' + ), + aside: __( + "The <aside> element should represent a portion of a document whose content is only indirectly related to the document's main content." + ), + div: __( + 'The <div> element should only be used if the block is a design element with no semantic meaning.' + ), + footer: __( + 'The <footer> element should represent a footer for its nearest sectioning element (e.g.: <section>, <article>, <main> etc.).' + ), + header: __( + 'The <header> element should represent introductory content, typically a group of introductory or navigational aids.' + ), + main: __( + 'The <main> element should be used for the primary content of your document only.' + ), + nav: __( + 'The <nav> element should be used to identify groups of links that are intended to be used for website or page content navigation.' + ), + section: __( + "The <section> element should represent a standalone portion of the document that can't be better represented by another element." + ), +}; diff --git a/packages/block-library/src/video/edit-common-settings.js b/packages/block-library/src/video/edit-common-settings.js index 9394bfaf5c6145..96d59d7e2f7269 100644 --- a/packages/block-library/src/video/edit-common-settings.js +++ b/packages/block-library/src/video/edit-common-settings.js @@ -2,7 +2,11 @@ * WordPress dependencies */ import { __, _x } from '@wordpress/i18n'; -import { ToggleControl, SelectControl } from '@wordpress/components'; +import { + ToggleControl, + SelectControl, + __experimentalToolsPanelItem as ToolsPanelItem, +} from '@wordpress/components'; import { useMemo, useCallback, Platform } from '@wordpress/element'; const options = [ @@ -47,50 +51,104 @@ const VideoSettings = ( { setAttributes, attributes } ) => { return ( <> - <ToggleControl - __nextHasNoMarginBottom + <ToolsPanelItem label={ __( 'Autoplay' ) } - onChange={ toggleFactory.autoplay } - checked={ !! autoplay } - help={ getAutoplayHelp } - /> - <ToggleControl - __nextHasNoMarginBottom + isShownByDefault + hasValue={ () => !! autoplay } + onDeselect={ () => { + setAttributes( { autoplay: false } ); + } } + > + <ToggleControl + __nextHasNoMarginBottom + label={ __( 'Autoplay' ) } + onChange={ toggleFactory.autoplay } + checked={ !! autoplay } + help={ getAutoplayHelp } + /> + </ToolsPanelItem> + <ToolsPanelItem label={ __( 'Loop' ) } - onChange={ toggleFactory.loop } - checked={ !! loop } - /> - <ToggleControl - __nextHasNoMarginBottom + isShownByDefault + hasValue={ () => !! loop } + onDeselect={ () => { + setAttributes( { loop: false } ); + } } + > + <ToggleControl + __nextHasNoMarginBottom + label={ __( 'Loop' ) } + onChange={ toggleFactory.loop } + checked={ !! loop } + /> + </ToolsPanelItem> + <ToolsPanelItem label={ __( 'Muted' ) } - onChange={ toggleFactory.muted } - checked={ !! muted } - /> - <ToggleControl - __nextHasNoMarginBottom + isShownByDefault + hasValue={ () => !! muted } + onDeselect={ () => { + setAttributes( { muted: false } ); + } } + > + <ToggleControl + __nextHasNoMarginBottom + label={ __( 'Muted' ) } + onChange={ toggleFactory.muted } + checked={ !! muted } + /> + </ToolsPanelItem> + <ToolsPanelItem label={ __( 'Playback controls' ) } - onChange={ toggleFactory.controls } - checked={ !! controls } - /> - <ToggleControl - __nextHasNoMarginBottom - /* translators: Setting to play videos within the webpage on mobile browsers rather than opening in a fullscreen player. */ + isShownByDefault + hasValue={ () => ! controls } + onDeselect={ () => { + setAttributes( { controls: true } ); + } } + > + <ToggleControl + __nextHasNoMarginBottom + label={ __( 'Playback controls' ) } + onChange={ toggleFactory.controls } + checked={ !! controls } + /> + </ToolsPanelItem> + <ToolsPanelItem label={ __( 'Play inline' ) } - onChange={ toggleFactory.playsInline } - checked={ !! playsInline } - help={ __( - 'When enabled, videos will play directly within the webpage on mobile browsers, instead of opening in a fullscreen player.' - ) } - /> - <SelectControl - __next40pxDefaultSize - __nextHasNoMarginBottom + isShownByDefault + hasValue={ () => !! playsInline } + onDeselect={ () => { + setAttributes( { playsInline: false } ); + } } + > + <ToggleControl + __nextHasNoMarginBottom + /* translators: Setting to play videos within the webpage on mobile browsers rather than opening in a fullscreen player. */ + label={ __( 'Play inline' ) } + onChange={ toggleFactory.playsInline } + checked={ !! playsInline } + help={ __( + 'When enabled, videos will play directly within the webpage on mobile browsers, instead of opening in a fullscreen player.' + ) } + /> + </ToolsPanelItem> + <ToolsPanelItem label={ __( 'Preload' ) } - value={ preload } - onChange={ onChangePreload } - options={ options } - hideCancelButton - /> + isShownByDefault + hasValue={ () => preload !== 'metadata' } + onDeselect={ () => { + setAttributes( { preload: 'metadata' } ); + } } + > + <SelectControl + __next40pxDefaultSize + __nextHasNoMarginBottom + label={ __( 'Preload' ) } + value={ preload } + onChange={ onChangePreload } + options={ options } + hideCancelButton + /> + </ToolsPanelItem> </> ); }; diff --git a/packages/block-library/src/video/edit.js b/packages/block-library/src/video/edit.js index 32221919c7ea20..95ecab25f95985 100644 --- a/packages/block-library/src/video/edit.js +++ b/packages/block-library/src/video/edit.js @@ -8,25 +8,21 @@ import clsx from 'clsx'; */ import { isBlobURL } from '@wordpress/blob'; import { - BaseControl, - Button, Disabled, - PanelBody, Spinner, Placeholder, + __experimentalToolsPanel as ToolsPanel, } from '@wordpress/components'; import { BlockControls, BlockIcon, InspectorControls, MediaPlaceholder, - MediaUpload, - MediaUploadCheck, MediaReplaceFlow, useBlockProps, } from '@wordpress/block-editor'; import { useRef, useEffect, useState } from '@wordpress/element'; -import { __, sprintf } from '@wordpress/i18n'; +import { __ } from '@wordpress/i18n'; import { useInstanceId } from '@wordpress/compose'; import { useDispatch } from '@wordpress/data'; import { video as icon } from '@wordpress/icons'; @@ -35,15 +31,18 @@ import { store as noticesStore } from '@wordpress/notices'; /** * Internal dependencies */ +import PosterImage from './poster-image'; import { createUpgradedEmbedBlock } from '../embed/util'; -import { useUploadMediaFromBlobURL } from '../utils/hooks'; +import { + useUploadMediaFromBlobURL, + useToolsPanelDropdownMenuProps, +} from '../utils/hooks'; import VideoCommonSettings from './edit-common-settings'; import TracksEditor from './tracks-editor'; import Tracks from './tracks'; import { Caption } from '../utils/caption'; const ALLOWED_MEDIA_TYPES = [ 'video' ]; -const VIDEO_POSTER_ALLOWED_MEDIA_TYPES = [ 'image' ]; function VideoEdit( { isSelected: isSingleSelected, @@ -55,9 +54,9 @@ function VideoEdit( { } ) { const instanceId = useInstanceId( VideoEdit ); const videoPlayer = useRef(); - const posterImageButton = useRef(); const { id, controls, poster, src, tracks } = attributes; const [ temporaryURL, setTemporaryURL ] = useState( attributes.blob ); + const dropdownMenuProps = useToolsPanelDropdownMenuProps(); useUploadMediaFromBlobURL( { url: temporaryURL, @@ -174,19 +173,6 @@ function VideoEdit( { ); } - function onSelectPoster( image ) { - setAttributes( { poster: image.url } ); - } - - function onRemovePoster() { - setAttributes( { poster: undefined } ); - - // Move focus back to the Media Upload button. - posterImageButton.current.focus(); - } - - const videoPosterDescription = `video-block__poster-image-description-${ instanceId }`; - return ( <> { isSingleSelected && ( @@ -214,63 +200,31 @@ function VideoEdit( { </> ) } <InspectorControls> - <PanelBody title={ __( 'Settings' ) }> + <ToolsPanel + label={ __( 'Settings' ) } + resetAll={ () => { + setAttributes( { + autoplay: false, + controls: true, + loop: false, + muted: false, + playsInline: false, + preload: 'metadata', + poster: '', + } ); + } } + dropdownMenuProps={ dropdownMenuProps } + > <VideoCommonSettings setAttributes={ setAttributes } attributes={ attributes } /> - <MediaUploadCheck> - <div className="editor-video-poster-control"> - <BaseControl.VisualLabel> - { __( 'Poster image' ) } - </BaseControl.VisualLabel> - <MediaUpload - title={ __( 'Select poster image' ) } - onSelect={ onSelectPoster } - allowedTypes={ - VIDEO_POSTER_ALLOWED_MEDIA_TYPES - } - render={ ( { open } ) => ( - <Button - __next40pxDefaultSize - variant="primary" - onClick={ open } - ref={ posterImageButton } - aria-describedby={ - videoPosterDescription - } - > - { ! poster - ? __( 'Select' ) - : __( 'Replace' ) } - </Button> - ) } - /> - <p id={ videoPosterDescription } hidden> - { poster - ? sprintf( - /* translators: %s: poster image URL. */ - __( - 'The current poster image url is %s' - ), - poster - ) - : __( - 'There is no poster image currently selected' - ) } - </p> - { !! poster && ( - <Button - __next40pxDefaultSize - onClick={ onRemovePoster } - variant="tertiary" - > - { __( 'Remove' ) } - </Button> - ) } - </div> - </MediaUploadCheck> - </PanelBody> + <PosterImage + poster={ poster } + setAttributes={ setAttributes } + instanceId={ instanceId } + /> + </ToolsPanel> </InspectorControls> <figure { ...blockProps }> { /* diff --git a/packages/block-library/src/video/edit.native.js b/packages/block-library/src/video/edit.native.js index f6960f0888617d..a323d516ff553d 100644 --- a/packages/block-library/src/video/edit.native.js +++ b/packages/block-library/src/video/edit.native.js @@ -72,7 +72,7 @@ class VideoEdit extends Component { this.finishMediaUploadWithFailure.bind( this ); this.updateMediaProgress = this.updateMediaProgress.bind( this ); this.onVideoPressed = this.onVideoPressed.bind( this ); - this.onVideoContanerLayout = this.onVideoContanerLayout.bind( this ); + this.onVideoContainerLayout = this.onVideoContainerLayout.bind( this ); this.onFocusCaption = this.onFocusCaption.bind( this ); } @@ -179,7 +179,7 @@ class VideoEdit extends Component { } } - onVideoContanerLayout( event ) { + onVideoContainerLayout( event ) { const { width } = event.nativeEvent.layout; const height = width / VIDEO_ASPECT_RATIO; if ( height !== this.state.videoContainerHeight ) { @@ -321,7 +321,7 @@ class VideoEdit extends Component { return ( <View - onLayout={ this.onVideoContanerLayout } + onLayout={ this.onVideoContainerLayout } style={ containerStyle } > { showVideo && ( diff --git a/packages/block-library/src/video/editor.scss b/packages/block-library/src/video/editor.scss index dd50ecc6c066fd..03e1c116cccb78 100644 --- a/packages/block-library/src/video/editor.scss +++ b/packages/block-library/src/video/editor.scss @@ -37,6 +37,7 @@ max-width: 240px; } +.block-library-video-tracks-editor__tracks-informative-message-title, .block-library-video-tracks-editor__single-track-editor-edit-track-label { margin-top: $grid-unit-05; color: $gray-700; @@ -56,3 +57,11 @@ padding: 0; } } + +.block-library-video-tracks-editor__tracks-informative-message { + padding: $grid-unit-10; + + &-description { + margin-bottom: 0; + } +} diff --git a/packages/block-library/src/video/poster-image.js b/packages/block-library/src/video/poster-image.js new file mode 100644 index 00000000000000..cde95f974d8e69 --- /dev/null +++ b/packages/block-library/src/video/poster-image.js @@ -0,0 +1,86 @@ +/** + * WordPress dependencies + */ +import { MediaUpload, MediaUploadCheck } from '@wordpress/block-editor'; +import { + Button, + BaseControl, + __experimentalToolsPanelItem as ToolsPanelItem, +} from '@wordpress/components'; +import { __, sprintf } from '@wordpress/i18n'; +import { useRef } from '@wordpress/element'; + +function PosterImage( { poster, setAttributes, instanceId } ) { + const posterImageButton = useRef(); + const VIDEO_POSTER_ALLOWED_MEDIA_TYPES = [ 'image' ]; + + const videoPosterDescription = `video-block__poster-image-description-${ instanceId }`; + + function onSelectPoster( image ) { + setAttributes( { poster: image.url } ); + } + + function onRemovePoster() { + setAttributes( { poster: undefined } ); + + // Move focus back to the Media Upload button. + posterImageButton.current.focus(); + } + + return ( + <ToolsPanelItem + label={ __( 'Poster image' ) } + isShownByDefault + hasValue={ () => !! poster } + onDeselect={ () => { + setAttributes( { poster: '' } ); + } } + > + <MediaUploadCheck> + <div className="editor-video-poster-control"> + <BaseControl.VisualLabel> + { __( 'Poster image' ) } + </BaseControl.VisualLabel> + <MediaUpload + title={ __( 'Select poster image' ) } + onSelect={ onSelectPoster } + allowedTypes={ VIDEO_POSTER_ALLOWED_MEDIA_TYPES } + render={ ( { open } ) => ( + <Button + __next40pxDefaultSize + variant="primary" + onClick={ open } + ref={ posterImageButton } + aria-describedby={ videoPosterDescription } + > + { ! poster ? __( 'Select' ) : __( 'Replace' ) } + </Button> + ) } + /> + <p id={ videoPosterDescription } hidden> + { poster + ? sprintf( + /* translators: %s: poster image URL. */ + __( 'The current poster image url is %s' ), + poster + ) + : __( + 'There is no poster image currently selected' + ) } + </p> + { !! poster && ( + <Button + __next40pxDefaultSize + onClick={ onRemovePoster } + variant="tertiary" + > + { __( 'Remove' ) } + </Button> + ) } + </div> + </MediaUploadCheck> + </ToolsPanelItem> + ); +} + +export default PosterImage; diff --git a/packages/block-library/src/video/test/transforms.native.js b/packages/block-library/src/video/test/transforms.native.js index 1655c04e2eb219..94caa24950c346 100644 --- a/packages/block-library/src/video/test/transforms.native.js +++ b/packages/block-library/src/video/test/transforms.native.js @@ -15,12 +15,12 @@ const initialHtml = ` <figure class="wp-block-video"><video controls src="https://i.cloudup.com/YtZFJbuQCE.mov"></video><figcaption class="wp-element-caption">Cloudup video</figcaption></figure> <!-- /wp:video -->`; -const tranformsWithInnerBlocks = [ 'Columns', 'Group' ]; +const transformsWithInnerBlocks = [ 'Columns', 'Group' ]; const nonMediaTransforms = [ 'File' ]; const blockTransforms = [ 'Cover', 'Media & Text', - ...tranformsWithInnerBlocks, + ...transformsWithInnerBlocks, ...nonMediaTransforms, ]; @@ -31,7 +31,8 @@ describe( `${ block } block transforms`, () => { const screen = await initializeEditor( { initialHtml } ); const newBlock = await transformBlock( screen, block, blockTransform, { isMediaBlock: ! nonMediaTransforms.includes( blockTransform ), - hasInnerBlocks: tranformsWithInnerBlocks.includes( blockTransform ), + hasInnerBlocks: + transformsWithInnerBlocks.includes( blockTransform ), } ); expect( newBlock ).toBeVisible(); expect( getEditorHtml() ).toMatchSnapshot(); diff --git a/packages/block-library/src/video/tracks-editor.js b/packages/block-library/src/video/tracks-editor.js index e23d1c93378a69..a0152885f55671 100644 --- a/packages/block-library/src/video/tracks-editor.js +++ b/packages/block-library/src/video/tracks-editor.js @@ -24,7 +24,7 @@ import { } from '@wordpress/block-editor'; import { upload, media } from '@wordpress/icons'; import { useSelect } from '@wordpress/data'; -import { useState } from '@wordpress/element'; +import { useState, useRef, useEffect } from '@wordpress/element'; import { getFilename } from '@wordpress/url'; const ALLOWED_TYPES = [ 'text/vtt' ]; @@ -40,39 +40,29 @@ const KIND_OPTIONS = [ ]; function TrackList( { tracks, onEditPress } ) { - let content; - if ( tracks.length === 0 ) { - content = ( - <p className="block-library-video-tracks-editor__tracks-informative-message"> - { __( - 'Tracks can be subtitles, captions, chapters, or descriptions. They help make your content more accessible to a wider range of users.' - ) } - </p> - ); - } else { - content = tracks.map( ( track, index ) => { - return ( - <HStack - key={ index } - className="block-library-video-tracks-editor__track-list-track" + const content = tracks.map( ( track, index ) => { + return ( + <HStack + key={ index } + className="block-library-video-tracks-editor__track-list-track" + > + <span>{ track.label }</span> + <Button + __next40pxDefaultSize + variant="tertiary" + onClick={ () => onEditPress( index ) } + aria-label={ sprintf( + /* translators: %s: Label of the video text track e.g: "French subtitles". */ + _x( 'Edit %s', 'text tracks' ), + track.label + ) } > - <span>{ track.label } </span> - <Button - __next40pxDefaultSize - variant="tertiary" - onClick={ () => onEditPress( index ) } - aria-label={ sprintf( - /* translators: %s: Label of the video text track e.g: "French subtitles" */ - _x( 'Edit %s', 'text tracks' ), - track.label - ) } - > - { __( 'Edit' ) } - </Button> - </HStack> - ); - } ); - } + { __( 'Edit' ) } + </Button> + </HStack> + ); + } ); + return ( <MenuGroup label={ __( 'Text tracks' ) } @@ -87,105 +77,100 @@ function SingleTrackEditor( { track, onChange, onClose, onRemove } ) { const { src = '', label = '', srcLang = '', kind = DEFAULT_KIND } = track; const fileName = src.startsWith( 'blob:' ) ? '' : getFilename( src ) || ''; return ( - <NavigableMenu> - <VStack - className="block-library-video-tracks-editor__single-track-editor" - spacing="4" - > - <span className="block-library-video-tracks-editor__single-track-editor-edit-track-label"> - { __( 'Edit track' ) } - </span> - <span> - { __( 'File' ) }: <b>{ fileName }</b> - </span> - <Grid columns={ 2 } gap={ 4 }> - <TextControl - __next40pxDefaultSize - __nextHasNoMarginBottom - /* eslint-disable jsx-a11y/no-autofocus */ - autoFocus - /* eslint-enable jsx-a11y/no-autofocus */ - onChange={ ( newLabel ) => - onChange( { - ...track, - label: newLabel, - } ) - } - label={ __( 'Label' ) } - value={ label } - help={ __( 'Title of track' ) } - /> - <TextControl + <VStack + className="block-library-video-tracks-editor__single-track-editor" + spacing="4" + > + <span className="block-library-video-tracks-editor__single-track-editor-edit-track-label"> + { __( 'Edit track' ) } + </span> + <span> + { __( 'File' ) }: <b>{ fileName }</b> + </span> + <Grid columns={ 2 } gap={ 4 }> + <TextControl + __next40pxDefaultSize + __nextHasNoMarginBottom + onChange={ ( newLabel ) => + onChange( { + ...track, + label: newLabel, + } ) + } + label={ __( 'Label' ) } + value={ label } + help={ __( 'Title of track' ) } + /> + <TextControl + __next40pxDefaultSize + __nextHasNoMarginBottom + onChange={ ( newSrcLang ) => + onChange( { + ...track, + srcLang: newSrcLang, + } ) + } + label={ __( 'Source language' ) } + value={ srcLang } + help={ __( 'Language tag (en, fr, etc.)' ) } + /> + </Grid> + <VStack spacing="8"> + <SelectControl + __next40pxDefaultSize + __nextHasNoMarginBottom + className="block-library-video-tracks-editor__single-track-editor-kind-select" + options={ KIND_OPTIONS } + value={ kind } + label={ __( 'Kind' ) } + onChange={ ( newKind ) => { + onChange( { + ...track, + kind: newKind, + } ); + } } + /> + <HStack className="block-library-video-tracks-editor__single-track-editor-buttons-container"> + <Button __next40pxDefaultSize - __nextHasNoMarginBottom - onChange={ ( newSrcLang ) => - onChange( { - ...track, - srcLang: newSrcLang, - } ) - } - label={ __( 'Source language' ) } - value={ srcLang } - help={ __( 'Language tag (en, fr, etc.)' ) } - /> - </Grid> - <VStack spacing="8"> - <SelectControl + isDestructive + variant="link" + onClick={ onRemove } + > + { __( 'Remove track' ) } + </Button> + <Button __next40pxDefaultSize - __nextHasNoMarginBottom - className="block-library-video-tracks-editor__single-track-editor-kind-select" - options={ KIND_OPTIONS } - value={ kind } - label={ __( 'Kind' ) } - onChange={ ( newKind ) => { - onChange( { - ...track, - kind: newKind, - } ); + variant="primary" + onClick={ () => { + const changes = {}; + let hasChanges = false; + if ( label === '' ) { + changes.label = __( 'English' ); + hasChanges = true; + } + if ( srcLang === '' ) { + changes.srcLang = 'en'; + hasChanges = true; + } + if ( track.kind === undefined ) { + changes.kind = DEFAULT_KIND; + hasChanges = true; + } + if ( hasChanges ) { + onChange( { + ...track, + ...changes, + } ); + } + onClose(); } } - /> - <HStack className="block-library-video-tracks-editor__single-track-editor-buttons-container"> - <Button - __next40pxDefaultSize - variant="secondary" - onClick={ () => { - const changes = {}; - let hasChanges = false; - if ( label === '' ) { - changes.label = __( 'English' ); - hasChanges = true; - } - if ( srcLang === '' ) { - changes.srcLang = 'en'; - hasChanges = true; - } - if ( track.kind === undefined ) { - changes.kind = DEFAULT_KIND; - hasChanges = true; - } - if ( hasChanges ) { - onChange( { - ...track, - ...changes, - } ); - } - onClose(); - } } - > - { __( 'Close' ) } - </Button> - <Button - __next40pxDefaultSize - isDestructive - variant="link" - onClick={ onRemove } - > - { __( 'Remove track' ) } - </Button> - </HStack> - </VStack> + > + { __( 'Apply' ) } + </Button> + </HStack> </VStack> - </NavigableMenu> + </VStack> ); } @@ -194,6 +179,11 @@ export default function TracksEditor( { tracks = [], onChange } ) { return select( blockEditorStore ).getSettings().mediaUpload; }, [] ); const [ trackBeingEdited, setTrackBeingEdited ] = useState( null ); + const dropdownPopoverRef = useRef(); + + useEffect( () => { + dropdownPopoverRef.current?.focus(); + }, [ trackBeingEdited ] ); if ( ! mediaUpload ) { return null; @@ -201,17 +191,32 @@ export default function TracksEditor( { tracks = [], onChange } ) { return ( <Dropdown contentClassName="block-library-video-tracks-editor" - renderToggle={ ( { isOpen, onToggle } ) => ( - <ToolbarGroup> - <ToolbarButton - aria-expanded={ isOpen } - aria-haspopup="true" - onClick={ onToggle } - > - { __( 'Text tracks' ) } - </ToolbarButton> - </ToolbarGroup> - ) } + focusOnMount + popoverProps={ { + ref: dropdownPopoverRef, + } } + renderToggle={ ( { isOpen, onToggle } ) => { + const handleOnToggle = () => { + if ( ! isOpen ) { + // When the Popover opens make sure the initial view is + // always the track list rather than the edit track UI. + setTrackBeingEdited( null ); + } + onToggle(); + }; + + return ( + <ToolbarGroup> + <ToolbarButton + aria-expanded={ isOpen } + aria-haspopup="true" + onClick={ handleOnToggle } + > + { __( 'Text tracks' ) } + </ToolbarButton> + </ToolbarGroup> + ); + } } renderContent={ () => { if ( trackBeingEdited !== null ) { return ( @@ -235,8 +240,21 @@ export default function TracksEditor( { tracks = [], onChange } ) { /> ); } + return ( <> + { tracks.length === 0 && ( + <div className="block-library-video-tracks-editor__tracks-informative-message"> + <h2 className="block-library-video-tracks-editor__tracks-informative-message-title"> + { __( 'Text tracks' ) } + </h2> + <p className="block-library-video-tracks-editor__tracks-informative-message-description"> + { __( + 'Tracks can be subtitles, captions, chapters, or descriptions. They help make your content more accessible to a wider range of users.' + ) } + </p> + </div> + ) } <NavigableMenu> <TrackList tracks={ tracks } @@ -305,7 +323,7 @@ export default function TracksEditor( { tracks = [], onChange } ) { openFileDialog(); } } > - { __( 'Upload' ) } + { _x( 'Upload', 'verb' ) } </MenuItem> ); } } diff --git a/packages/block-library/tsconfig.json b/packages/block-library/tsconfig.json index 2a2cb1653d4285..a8423ee4a27093 100644 --- a/packages/block-library/tsconfig.json +++ b/packages/block-library/tsconfig.json @@ -2,8 +2,6 @@ "$schema": "https://json.schemastore.org/tsconfig.json", "extends": "../../tsconfig.base.json", "compilerOptions": { - "rootDir": "src", - "declarationDir": "build-types", "types": [ "gutenberg-env" ], "strictNullChecks": true }, diff --git a/packages/block-serialization-default-parser/CHANGELOG.md b/packages/block-serialization-default-parser/CHANGELOG.md index 5911f22795d463..e35805be07d06c 100644 --- a/packages/block-serialization-default-parser/CHANGELOG.md +++ b/packages/block-serialization-default-parser/CHANGELOG.md @@ -2,6 +2,16 @@ ## Unreleased +## 5.16.0 (2025-01-15) + +## 5.15.0 (2025-01-02) + +## 5.14.0 (2024-12-11) + +## 5.13.0 (2024-11-27) + +## 5.12.0 (2024-11-16) + ## 5.11.0 (2024-10-30) ## 5.10.0 (2024-10-16) diff --git a/packages/block-serialization-default-parser/package.json b/packages/block-serialization-default-parser/package.json index 010132ac971db1..4fc3bff8afb64c 100644 --- a/packages/block-serialization-default-parser/package.json +++ b/packages/block-serialization-default-parser/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/block-serialization-default-parser", - "version": "5.11.0", + "version": "5.16.0", "description": "Block serialization specification parser for WordPress posts.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/block-serialization-default-parser/tsconfig.json b/packages/block-serialization-default-parser/tsconfig.json index 6e33d8ff82d47e..7ff060ab6ce105 100644 --- a/packages/block-serialization-default-parser/tsconfig.json +++ b/packages/block-serialization-default-parser/tsconfig.json @@ -1,9 +1,4 @@ { "$schema": "https://json.schemastore.org/tsconfig.json", - "extends": "../../tsconfig.base.json", - "compilerOptions": { - "rootDir": "src", - "declarationDir": "build-types" - }, - "include": [ "src/**/*" ] + "extends": "../../tsconfig.base.json" } diff --git a/packages/block-serialization-spec-parser/CHANGELOG.md b/packages/block-serialization-spec-parser/CHANGELOG.md index 1833dff1e52c83..285ee69d78310a 100644 --- a/packages/block-serialization-spec-parser/CHANGELOG.md +++ b/packages/block-serialization-spec-parser/CHANGELOG.md @@ -2,6 +2,16 @@ ## Unreleased +## 5.16.0 (2025-01-15) + +## 5.15.0 (2025-01-02) + +## 5.14.0 (2024-12-11) + +## 5.13.0 (2024-11-27) + +## 5.12.0 (2024-11-16) + ## 5.11.0 (2024-10-30) ## 5.10.0 (2024-10-16) diff --git a/packages/block-serialization-spec-parser/package.json b/packages/block-serialization-spec-parser/package.json index d430d2afeebf5a..e9ac344ff34a10 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": "5.11.0", + "version": "5.16.0", "description": "Block serialization specification parser for WordPress posts.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/blocks/CHANGELOG.md b/packages/blocks/CHANGELOG.md index 54fc6f77a7427d..bf63c0089bb8e5 100644 --- a/packages/blocks/CHANGELOG.md +++ b/packages/blocks/CHANGELOG.md @@ -2,6 +2,16 @@ ## Unreleased +## 14.5.0 (2025-01-15) + +## 14.4.0 (2025-01-02) + +## 14.3.0 (2024-12-11) + +## 14.2.0 (2024-11-27) + +## 14.1.0 (2024-11-16) + ## 14.0.0 (2024-10-30) ### Breaking changes diff --git a/packages/blocks/README.md b/packages/blocks/README.md index 3e5a88a2b92c1b..f4805e1c60b381 100644 --- a/packages/blocks/README.md +++ b/packages/blocks/README.md @@ -503,6 +503,10 @@ _Returns_ - `Array|string`: A list of blocks or a string, depending on `handlerMode`. +### privateApis + +Undocumented declaration. + ### rawHandler Converts an HTML string to known blocks. diff --git a/packages/blocks/package.json b/packages/blocks/package.json index 42890e9b7d56bc..5940cfbeb46647 100644 --- a/packages/blocks/package.json +++ b/packages/blocks/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/blocks", - "version": "14.0.0", + "version": "14.5.0", "description": "Block API for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -31,21 +31,21 @@ ], "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/autop": "*", - "@wordpress/blob": "*", - "@wordpress/block-serialization-default-parser": "*", - "@wordpress/data": "*", - "@wordpress/deprecated": "*", - "@wordpress/dom": "*", - "@wordpress/element": "*", - "@wordpress/hooks": "*", - "@wordpress/html-entities": "*", - "@wordpress/i18n": "*", - "@wordpress/is-shallow-equal": "*", - "@wordpress/private-apis": "*", - "@wordpress/rich-text": "*", - "@wordpress/shortcode": "*", - "@wordpress/warning": "*", + "@wordpress/autop": "file:../autop", + "@wordpress/blob": "file:../blob", + "@wordpress/block-serialization-default-parser": "file:../block-serialization-default-parser", + "@wordpress/data": "file:../data", + "@wordpress/deprecated": "file:../deprecated", + "@wordpress/dom": "file:../dom", + "@wordpress/element": "file:../element", + "@wordpress/hooks": "file:../hooks", + "@wordpress/html-entities": "file:../html-entities", + "@wordpress/i18n": "file:../i18n", + "@wordpress/is-shallow-equal": "file:../is-shallow-equal", + "@wordpress/private-apis": "file:../private-apis", + "@wordpress/rich-text": "file:../rich-text", + "@wordpress/shortcode": "file:../shortcode", + "@wordpress/warning": "file:../warning", "change-case": "^4.1.2", "colord": "^2.7.0", "fast-deep-equal": "^3.1.3", diff --git a/packages/blocks/src/api/constants.js b/packages/blocks/src/api/constants.js index 85a359af754db8..620dfcbb8599c0 100644 --- a/packages/blocks/src/api/constants.js +++ b/packages/blocks/src/api/constants.js @@ -183,7 +183,7 @@ export const __EXPERIMENTAL_STYLE_PROPERTY = { }, fontFamily: { value: [ 'typography', 'fontFamily' ], - support: [ 'typography', 'fontFamily' ], + support: [ 'typography', '__experimentalFontFamily' ], useEngine: true, }, fontSize: { @@ -193,12 +193,12 @@ export const __EXPERIMENTAL_STYLE_PROPERTY = { }, fontStyle: { value: [ 'typography', 'fontStyle' ], - support: [ 'typography', 'fontStyle' ], + support: [ 'typography', '__experimentalFontStyle' ], useEngine: true, }, fontWeight: { value: [ 'typography', 'fontWeight' ], - support: [ 'typography', 'fontWeight' ], + support: [ 'typography', '__experimentalFontWeight' ], useEngine: true, }, lineHeight: { @@ -240,17 +240,17 @@ export const __EXPERIMENTAL_STYLE_PROPERTY = { }, textDecoration: { value: [ 'typography', 'textDecoration' ], - support: [ 'typography', 'textDecoration' ], + support: [ 'typography', '__experimentalTextDecoration' ], useEngine: true, }, textTransform: { value: [ 'typography', 'textTransform' ], - support: [ 'typography', 'textTransform' ], + support: [ 'typography', '__experimentalTextTransform' ], useEngine: true, }, letterSpacing: { value: [ 'typography', 'letterSpacing' ], - support: [ 'typography', 'letterSpacing' ], + support: [ 'typography', '__experimentalLetterSpacing' ], useEngine: true, }, writingMode: { @@ -297,12 +297,3 @@ export const __EXPERIMENTAL_PATHS_WITH_OVERRIDE = { 'typography.fontSizes': true, 'spacing.spacingSizes': true, }; - -export const TYPOGRAPHY_SUPPORTS_EXPERIMENTAL_TO_STABLE = { - __experimentalFontFamily: 'fontFamily', - __experimentalFontStyle: 'fontStyle', - __experimentalFontWeight: 'fontWeight', - __experimentalLetterSpacing: 'letterSpacing', - __experimentalTextDecoration: 'textDecoration', - __experimentalTextTransform: 'textTransform', -}; diff --git a/packages/blocks/src/api/factory.js b/packages/blocks/src/api/factory.js index 25bf64ca65dc90..5eacf96fb1e5b5 100644 --- a/packages/blocks/src/api/factory.js +++ b/packages/blocks/src/api/factory.js @@ -17,6 +17,7 @@ import { getGroupingBlockName, } from './registration'; import { + isBlockRegistered, normalizeBlockType, __experimentalSanitizeBlockAttributes, } from './utils'; @@ -31,6 +32,14 @@ import { * @return {Object} Block object. */ export function createBlock( name, attributes = {}, innerBlocks = [] ) { + if ( ! isBlockRegistered( name ) ) { + return createBlock( 'core/missing', { + originalName: name, + originalContent: '', + originalUndelimitedContent: '', + } ); + } + const sanitizedAttributes = __experimentalSanitizeBlockAttributes( name, attributes @@ -94,15 +103,22 @@ export function __experimentalCloneSanitizedBlock( mergeAttributes = {}, newInnerBlocks ) { + const { name } = block; + + if ( ! isBlockRegistered( name ) ) { + return createBlock( 'core/missing', { + originalName: name, + originalContent: '', + originalUndelimitedContent: '', + } ); + } + const clientId = uuid(); - const sanitizedAttributes = __experimentalSanitizeBlockAttributes( - block.name, - { - ...block.attributes, - ...mergeAttributes, - } - ); + const sanitizedAttributes = __experimentalSanitizeBlockAttributes( name, { + ...block.attributes, + ...mergeAttributes, + } ); return { ...block, @@ -583,20 +599,11 @@ export function switchToBlockType( blocks, name ) { * * @return {Object} block. */ -export const getBlockFromExample = ( name, example ) => { - try { - return createBlock( - name, - example.attributes, - ( example.innerBlocks ?? [] ).map( ( innerBlock ) => - getBlockFromExample( innerBlock.name, innerBlock ) - ) - ); - } catch { - return createBlock( 'core/missing', { - originalName: name, - originalContent: '', - originalUndelimitedContent: '', - } ); - } -}; +export const getBlockFromExample = ( name, example ) => + createBlock( + name, + example.attributes, + ( example.innerBlocks ?? [] ).map( ( innerBlock ) => + getBlockFromExample( innerBlock.name, innerBlock ) + ) + ); diff --git a/packages/blocks/src/api/index.js b/packages/blocks/src/api/index.js index 3ace68be87393c..fbfe16384fa7e5 100644 --- a/packages/blocks/src/api/index.js +++ b/packages/blocks/src/api/index.js @@ -1,3 +1,9 @@ +/** + * Internal dependencies + */ +import { lock } from '../lock-unlock'; +import { isContentBlock } from './utils'; + // The blocktype is the most important concept within the block API. It defines // all aspects of the block configuration and its interfaces, including `edit` // and `save`. The transforms specification allows converting one blocktype to @@ -23,7 +29,7 @@ export { // // This has multiple practical implications: when parsing, we can safely dispose // of any block boundary found within a block from the innerHTML property when -// transfering to state. Not doing so would have a compounding effect on memory +// transferring to state. Not doing so would have a compounding effect on memory // and uncertainty over the source of truth. This can be illustrated in how, // given a tree of `n` nested blocks, the entry node would have to contain the // actual content of each block while each subsequent block node in the state @@ -42,7 +48,7 @@ export { // While block transformations account for a specific surface of the API, there // are also raw transformations which handle arbitrary sources not made out of -// blocks but producing block basaed on various heursitics. This includes +// blocks but producing block basaed on various heuristics. This includes // pasting rich text or HTML data. export { pasteHandler, @@ -169,3 +175,6 @@ export { __EXPERIMENTAL_ELEMENTS, __EXPERIMENTAL_PATHS_WITH_OVERRIDE, } from './constants'; + +export const privateApis = {}; +lock( privateApis, { isContentBlock } ); diff --git a/packages/blocks/src/api/parser/index.js b/packages/blocks/src/api/parser/index.js index 14a88f602987ab..7230932f0a5fb8 100644 --- a/packages/blocks/src/api/parser/index.js +++ b/packages/blocks/src/api/parser/index.js @@ -204,7 +204,7 @@ export function parseRawBlock( rawBlock, options ) { // Try finding the type for known block name. let blockType = getBlockType( normalizedBlock.blockName ); - // If not blockType is found for the specified name, fallback to the "unregistedBlockType". + // If not blockType is found for the specified name, fallback to the "unregisteredBlockType". if ( ! blockType ) { normalizedBlock = createMissingBlockType( normalizedBlock ); blockType = getBlockType( normalizedBlock.blockName ); diff --git a/packages/blocks/src/api/parser/test/apply-block-deprecated-versions.js b/packages/blocks/src/api/parser/test/apply-block-deprecated-versions.js index bea5a4ea30bfdd..0d451c142e9569 100644 --- a/packages/blocks/src/api/parser/test/apply-block-deprecated-versions.js +++ b/packages/blocks/src/api/parser/test/apply-block-deprecated-versions.js @@ -275,7 +275,7 @@ describe( 'applyBlockDeprecatedVersions', () => { }; // When the block was created, it was given the new default value for the fruit attribute of 'Oranges'. - // This is because unchanged default values are not saved to the comment delimeter attributes. + // This is because unchanged default values are not saved to the comment delimiter attributes. // Validation failed because this block was saved when the old default was 'Bananas' as reflected by the originalContent. const block = deepFreeze( { name: 'core/test-block', diff --git a/packages/blocks/src/api/raw-handling/html-formatting-remover.js b/packages/blocks/src/api/raw-handling/html-formatting-remover.js index 73a8eb5c2e8f68..0e76c8833e8eea 100644 --- a/packages/blocks/src/api/raw-handling/html-formatting-remover.js +++ b/packages/blocks/src/api/raw-handling/html-formatting-remover.js @@ -61,7 +61,7 @@ export default function htmlFormattingRemover( node ) { } // Remove the trailing space if the text element is at the end of a block, - // is succeded by a line break element, or has a space in the next text + // is succeeded by a line break element, or has a space in the next text // node. if ( newData[ newData.length - 1 ] === ' ' ) { const nextSibling = getSibling( node, 'next' ); diff --git a/packages/blocks/src/api/raw-handling/test/html-formatting-remover.js b/packages/blocks/src/api/raw-handling/test/html-formatting-remover.js index 64df51a4f781b6..d8619f5e60143f 100644 --- a/packages/blocks/src/api/raw-handling/test/html-formatting-remover.js +++ b/packages/blocks/src/api/raw-handling/test/html-formatting-remover.js @@ -120,7 +120,7 @@ describe( 'HTMLFormattingRemover', () => { expect( doc.body.innerHTML ).toEqual( input ); } ); - it( 'should not remove white space if next elemnt has none', () => { + it( 'should not remove white space if next element has none', () => { const input = `<div><strong>a </strong>b</div>`; const output = '<div><strong>a </strong>b</div>'; expect( deepFilterHTML( input, [ filter ] ) ).toEqual( output ); diff --git a/packages/blocks/src/api/raw-handling/utils.js b/packages/blocks/src/api/raw-handling/utils.js index 3f4fe32a1af248..abf586532b9372 100644 --- a/packages/blocks/src/api/raw-handling/utils.js +++ b/packages/blocks/src/api/raw-handling/utils.js @@ -100,7 +100,7 @@ export function getBlockContentSchemaFromTransforms( transforms, context ) { /** * Gets the block content schema, which is extracted and merged from all - * registered blocks with raw transfroms. + * registered blocks with raw transforms. * * @param {string} context Set to "paste" when in paste context, where the * schema is more strict. diff --git a/packages/blocks/src/api/registration.js b/packages/blocks/src/api/registration.js index 2f4bab2b5f2589..2886632e2ab0e4 100644 --- a/packages/blocks/src/api/registration.js +++ b/packages/blocks/src/api/registration.js @@ -866,7 +866,7 @@ export const registerBlockBindingsSource = ( source ) => { } if ( label && existingSource?.label && label !== existingSource?.label ) { - warning( 'Block bindings "' + name + '" source label was overriden.' ); + warning( 'Block bindings "' + name + '" source label was overridden.' ); } // Check the `usesContext` property is correct. diff --git a/packages/blocks/src/api/templates.js b/packages/blocks/src/api/templates.js index 71231121362a49..6f7e13f27ebe80 100644 --- a/packages/blocks/src/api/templates.js +++ b/packages/blocks/src/api/templates.js @@ -109,23 +109,12 @@ export function synchronizeBlocksWithTemplate( blocks = [], template ) { attributes ); - let [ blockName, blockAttributes ] = + const [ blockName, blockAttributes ] = convertLegacyBlockNameAndAttributes( name, normalizedAttributes ); - // If a Block is undefined at this point, use the core/missing block as - // a placeholder for a better user experience. - if ( undefined === getBlockType( blockName ) ) { - blockAttributes = { - originalName: name, - originalContent: '', - originalUndelimitedContent: '', - }; - blockName = 'core/missing'; - } - return createBlock( blockName, blockAttributes, diff --git a/packages/blocks/src/api/test/registration.js b/packages/blocks/src/api/test/registration.js index 5941415e61fe55..3826f58c2e94bc 100644 --- a/packages/blocks/src/api/test/registration.js +++ b/packages/blocks/src/api/test/registration.js @@ -1568,7 +1568,7 @@ describe( 'blocks', () => { label: 'Client label', } ); expect( console ).toHaveWarnedWith( - 'Block bindings "core/testing" source label was overriden.' + 'Block bindings "core/testing" source label was overridden.' ); const source = getBlockBindingsSource( 'core/testing' ); unregisterBlockBindingsSource( 'core/testing' ); diff --git a/packages/blocks/src/api/test/serializer.js b/packages/blocks/src/api/test/serializer.js index 3c1cbd6d1e74ff..854e035b27d43a 100644 --- a/packages/blocks/src/api/test/serializer.js +++ b/packages/blocks/src/api/test/serializer.js @@ -149,7 +149,7 @@ describe( 'block serializer', () => { expect( attributes ).toEqual( { fruit: 'bananas' } ); } ); - it( 'should ingore local attributes', () => { + it( 'should ignore local attributes', () => { const attributes = getCommentAttributes( { attributes: { diff --git a/packages/blocks/src/api/test/utils.js b/packages/blocks/src/api/test/utils.js index ad76e89aafe5f0..b1906b65b4208f 100644 --- a/packages/blocks/src/api/test/utils.js +++ b/packages/blocks/src/api/test/utils.js @@ -12,8 +12,10 @@ import { isUnmodifiedDefaultBlock, getAccessibleBlockLabel, getBlockLabel, + isBlockRegistered, __experimentalSanitizeBlockAttributes, getBlockAttributesNamesByRole, + isContentBlock, } from '../utils'; const noop = () => {}; @@ -212,6 +214,20 @@ describe( 'getAccessibleBlockLabel', () => { } ); } ); +describe( 'isBlockRegistered', () => { + it( 'returns true if the block is registered', () => { + registerBlockType( 'core/test-block', { title: 'Test block' } ); + expect( isBlockRegistered( 'core/test-block' ) ).toBe( true ); + unregisterBlockType( 'core/test-block' ); + } ); + + it( 'returns false if the block is not registered', () => { + expect( isBlockRegistered( 'core/not-registered-test-block' ) ).toBe( + false + ); + } ); +} ); + describe( 'sanitizeBlockAttributes', () => { afterEach( () => { getBlockTypes().forEach( ( block ) => { @@ -382,3 +398,40 @@ describe( 'getBlockAttributesNamesByRole', () => { ).toEqual( [] ); } ); } ); + +describe( 'isContentBlock', () => { + it( 'returns true if the block has a content role attribute', () => { + registerBlockType( 'core/test-content-block', { + attributes: { + content: { + type: 'string', + role: 'content', + }, + align: { + type: 'string', + }, + }, + save: noop, + category: 'text', + title: 'test content block', + } ); + expect( isContentBlock( 'core/test-content-block' ) ).toBe( true ); + } ); + + it( 'returns false if the block does not have a content role attribute', () => { + registerBlockType( 'core/test-non-content-block', { + attributes: { + content: { + type: 'string', + }, + align: { + type: 'string', + }, + }, + save: noop, + category: 'text', + title: 'test non-content block', + } ); + expect( isContentBlock( 'core/test-non-content-block' ) ).toBe( false ); + } ); +} ); diff --git a/packages/blocks/src/api/utils.js b/packages/blocks/src/api/utils.js index 20f0f6a85ed091..ad94d9d5c9e0c1 100644 --- a/packages/blocks/src/api/utils.js +++ b/packages/blocks/src/api/utils.js @@ -266,6 +266,17 @@ export function getDefault( attributeSchema ) { } } +/** + * Check if a block is registered. + * + * @param {string} name The block's name. + * + * @return {boolean} Whether the block is registered. + */ +export function isBlockRegistered( name ) { + return getBlockType( name ) !== undefined; +} + /** * Ensure attributes contains only values defined by block type, and merge * default values for missing attributes. @@ -370,6 +381,30 @@ export const __experimentalGetBlockAttributesNamesByRole = ( ...args ) => { return getBlockAttributesNamesByRole( ...args ); }; +/** + * Checks if a block is a content block by examining its attributes. + * A block is considered a content block if it has at least one attribute + * with a role of 'content'. + * + * @param {string} name The name of the block to check. + * @return {boolean} Whether the block is a content block. + */ +export function isContentBlock( name ) { + const attributes = getBlockType( name )?.attributes; + + if ( ! attributes ) { + return false; + } + + return !! Object.keys( attributes )?.some( ( attributeKey ) => { + const attribute = attributes[ attributeKey ]; + return ( + attribute?.role === 'content' || + attribute?.__experimentalRole === 'content' + ); + } ); +} + /** * Return a new object with the specified keys omitted. * diff --git a/packages/blocks/src/api/validation/index.js b/packages/blocks/src/api/validation/index.js index 29b8a087718334..d5ac569e15ff09 100644 --- a/packages/blocks/src/api/validation/index.js +++ b/packages/blocks/src/api/validation/index.js @@ -163,7 +163,7 @@ const TEXT_NORMALIZATIONS = [ identity, getTextWithCollapsedWhitespace ]; * "The ampersand must be followed by one of the names given in the named * character references section, using the same case." * - * Tested aginst "12.5 Named character references": + * Tested against "12.5 Named character references": * * ``` * const references = Array.from( document.querySelectorAll( @@ -222,7 +222,7 @@ export function isValidCharacterReference( text ) { } /** - * Subsitute EntityParser class for `simple-html-tokenizer` which uses the + * Substitute EntityParser class for `simple-html-tokenizer` which uses the * implementation of `decodeEntities` from `html-entities`, in order to avoid * bundling a massive named character reference. * diff --git a/packages/blocks/src/store/private-actions.js b/packages/blocks/src/store/private-actions.js index bfefe56773d77a..33f29a93c34832 100644 --- a/packages/blocks/src/store/private-actions.js +++ b/packages/blocks/src/store/private-actions.js @@ -7,7 +7,7 @@ import { processBlockType } from './process-block-type'; /** * Add bootstrapped block type metadata to the store. These metadata usually come from - * the `block.json` file and are either statically boostrapped from the server, or + * the `block.json` file and are either statically bootstrapped from the server, or * passed as the `metadata` parameter to the `registerBlockType` function. * * @param {string} name Block name. diff --git a/packages/blocks/src/store/process-block-type.js b/packages/blocks/src/store/process-block-type.js index 98fb044f8faadf..bc7b1a0e10e774 100644 --- a/packages/blocks/src/store/process-block-type.js +++ b/packages/blocks/src/store/process-block-type.js @@ -15,11 +15,7 @@ import warning from '@wordpress/warning'; * Internal dependencies */ import { isValidIcon, normalizeIconObject, omit } from '../api/utils'; -import { - BLOCK_ICON_DEFAULT, - DEPRECATED_ENTRY_KEYS, - TYPOGRAPHY_SUPPORTS_EXPERIMENTAL_TO_STABLE, -} from '../api/constants'; +import { BLOCK_ICON_DEFAULT, DEPRECATED_ENTRY_KEYS } from '../api/constants'; /** @typedef {import('../api/registration').WPBlockType} WPBlockType */ @@ -66,39 +62,6 @@ function mergeBlockVariations( return result; } -function stabilizeSupports( rawSupports ) { - if ( ! rawSupports ) { - return rawSupports; - } - - // Create a new object to avoid mutating the original. This ensures that - // custom block plugins that rely on immutable supports are not affected. - // See: https://github.com/WordPress/gutenberg/pull/66849#issuecomment-2463614281 - const newSupports = {}; - for ( const [ key, value ] of Object.entries( rawSupports ) ) { - if ( - key === 'typography' && - typeof value === 'object' && - value !== null - ) { - newSupports.typography = Object.fromEntries( - Object.entries( value ).map( - ( [ typographyKey, typographyValue ] ) => [ - TYPOGRAPHY_SUPPORTS_EXPERIMENTAL_TO_STABLE[ - typographyKey - ] || typographyKey, - typographyValue, - ] - ) - ); - } else { - newSupports[ key ] = value; - } - } - - return newSupports; -} - /** * Takes the unprocessed block type settings, merges them with block type metadata * and applies all the existing filters for the registered block type. @@ -139,9 +102,6 @@ export const processBlockType = ), }; - // Stabilize any experimental supports before applying filters. - blockType.supports = stabilizeSupports( blockType.supports ); - const settings = applyFilters( 'blocks.registerBlockType', blockType, @@ -149,10 +109,6 @@ export const processBlockType = null ); - // Re-stabilize any experimental supports after applying filters. - // This ensures that any supports updated by filters are also stabilized. - blockType.supports = stabilizeSupports( blockType.supports ); - if ( settings.description && typeof settings.description !== 'string' @@ -163,40 +119,29 @@ export const processBlockType = } if ( settings.deprecated ) { - settings.deprecated = settings.deprecated.map( ( deprecation ) => { - // Stabilize any experimental supports before applying filters. - let filteredDeprecation = { - ...deprecation, - supports: stabilizeSupports( deprecation.supports ), - }; - - filteredDeprecation = // Only keep valid deprecation keys. - applyFilters( - 'blocks.registerBlockType', - // Merge deprecation keys with pre-filter settings - // so that filters that depend on specific keys being - // present don't fail. - { - // Omit deprecation keys here so that deprecations - // can opt out of specific keys like "supports". - ...omit( blockType, DEPRECATED_ENTRY_KEYS ), - ...filteredDeprecation, - }, - blockType.name, - filteredDeprecation - ); - // Re-stabilize any experimental supports after applying filters. - // This ensures that any supports updated by filters are also stabilized. - filteredDeprecation.supports = stabilizeSupports( - filteredDeprecation.supports - ); - - return Object.fromEntries( - Object.entries( filteredDeprecation ).filter( ( [ key ] ) => + settings.deprecated = settings.deprecated.map( ( deprecation ) => + Object.fromEntries( + Object.entries( + // Only keep valid deprecation keys. + applyFilters( + 'blocks.registerBlockType', + // Merge deprecation keys with pre-filter settings + // so that filters that depend on specific keys being + // present don't fail. + { + // Omit deprecation keys here so that deprecations + // can opt out of specific keys like "supports". + ...omit( blockType, DEPRECATED_ENTRY_KEYS ), + ...deprecation, + }, + blockType.name, + deprecation + ) + ).filter( ( [ key ] ) => DEPRECATED_ENTRY_KEYS.includes( key ) ) - ); - } ); + ) + ); } if ( ! isPlainObject( settings ) ) { diff --git a/packages/blocks/src/store/selectors.js b/packages/blocks/src/store/selectors.js index 79e88073ba20de..e20938fd84e2b4 100644 --- a/packages/blocks/src/store/selectors.js +++ b/packages/blocks/src/store/selectors.js @@ -102,7 +102,7 @@ export const getBlockTypes = createSelector( * }; * ``` * - * @return {Object?} Block Type. + * @return {?Object} Block Type. */ export function getBlockType( state, name ) { return state.blockTypes[ name ]; @@ -437,7 +437,7 @@ export function getCollections( state ) { * }; * ``` * - * @return {string?} Default block name. + * @return {?string} Default block name. */ export function getDefaultBlockName( state ) { return state.defaultBlockName; @@ -473,7 +473,7 @@ export function getDefaultBlockName( state ) { * }; * ``` * - * @return {string?} Name of the block for handling non-block content. + * @return {?string} Name of the block for handling non-block content. */ export function getFreeformFallbackBlockName( state ) { return state.freeformFallbackBlockName; @@ -509,7 +509,7 @@ export function getFreeformFallbackBlockName( state ) { * }; * ``` * - * @return {string?} Name of the block for handling unregistered blocks. + * @return {?string} Name of the block for handling unregistered blocks. */ export function getUnregisteredFallbackBlockName( state ) { return state.unregisteredFallbackBlockName; @@ -545,7 +545,7 @@ export function getUnregisteredFallbackBlockName( state ) { * }; * ``` * - * @return {string?} Name of the block for handling the grouping of blocks. + * @return {?string} Name of the block for handling the grouping of blocks. */ export function getGroupingBlockName( state ) { return state.groupingBlockName; diff --git a/packages/blocks/src/store/test/private-selectors.js b/packages/blocks/src/store/test/private-selectors.js index 2c173b96b0bcb1..ada2bd7c8cbcfe 100644 --- a/packages/blocks/src/store/test/private-selectors.js +++ b/packages/blocks/src/store/test/private-selectors.js @@ -127,12 +127,12 @@ describe( 'private selectors', () => { name: 'core/example-block', supports: { typography: { - fontFamily: true, - fontStyle: true, - fontWeight: true, - textDecoration: true, - textTransform: true, - letterSpacing: true, + __experimentalFontFamily: true, + __experimentalFontStyle: true, + __experimentalFontWeight: true, + __experimentalTextDecoration: true, + __experimentalTextTransform: true, + __experimentalLetterSpacing: true, fontSize: true, lineHeight: true, }, diff --git a/packages/blocks/src/store/test/process-block-type.js b/packages/blocks/src/store/test/process-block-type.js deleted file mode 100644 index c82d08cc45d651..00000000000000 --- a/packages/blocks/src/store/test/process-block-type.js +++ /dev/null @@ -1,310 +0,0 @@ -/** - * WordPress dependencies - */ -import { addFilter, removeFilter } from '@wordpress/hooks'; - -/** - * Internal dependencies - */ -import { processBlockType } from '../process-block-type'; - -describe( 'processBlockType', () => { - const baseBlockSettings = { - apiVersion: 3, - attributes: {}, - edit: () => null, - name: 'test/block', - save: () => null, - title: 'Test Block', - }; - - const select = { - getBootstrappedBlockType: () => null, - }; - - afterEach( () => { - removeFilter( 'blocks.registerBlockType', 'test/filterSupports' ); - } ); - - it( 'should return the block type with stabilized typography supports', () => { - const blockSettings = { - ...baseBlockSettings, - supports: { - typography: { - fontSize: true, - lineHeight: true, - __experimentalFontFamily: true, - __experimentalFontStyle: true, - __experimentalFontWeight: true, - __experimentalLetterSpacing: true, - __experimentalTextTransform: true, - __experimentalTextDecoration: true, - __experimentalWritingMode: true, - __experimentalDefaultControls: { - fontSize: true, - fontAppearance: true, - textTransform: true, - }, - }, - }, - }; - - const processedBlockType = processBlockType( - 'test/block', - blockSettings - )( { select } ); - - expect( processedBlockType.supports.typography ).toEqual( { - fontSize: true, - lineHeight: true, - fontFamily: true, - fontStyle: true, - fontWeight: true, - letterSpacing: true, - textTransform: true, - textDecoration: true, - __experimentalWritingMode: true, - __experimentalDefaultControls: { - fontSize: true, - fontAppearance: true, - textTransform: true, - }, - } ); - } ); - - it( 'should return the block type with stable typography supports', () => { - const blockSettings = { - ...baseBlockSettings, - supports: { - typography: { - fontSize: true, - lineHeight: true, - fontFamily: true, - fontStyle: true, - fontWeight: true, - letterSpacing: true, - textTransform: true, - textDecoration: true, - __experimentalWritingMode: true, - __experimentalDefaultControls: { - fontSize: true, - fontAppearance: true, - textTransform: true, - }, - }, - }, - }; - - const processedBlockType = processBlockType( - 'test/block', - blockSettings - )( { select } ); - - expect( processedBlockType.supports.typography ).toEqual( { - fontSize: true, - lineHeight: true, - fontFamily: true, - fontStyle: true, - fontWeight: true, - letterSpacing: true, - textTransform: true, - textDecoration: true, - __experimentalWritingMode: true, - __experimentalDefaultControls: { - fontSize: true, - fontAppearance: true, - textTransform: true, - }, - } ); - } ); - - it( 'should reapply transformations after supports are filtered', () => { - const blockSettings = { - ...baseBlockSettings, - supports: { - typography: { - fontSize: true, - lineHeight: true, - __experimentalFontFamily: true, - __experimentalFontStyle: true, - __experimentalFontWeight: true, - __experimentalLetterSpacing: true, - __experimentalTextTransform: true, - __experimentalTextDecoration: true, - __experimentalWritingMode: true, - __experimentalDefaultControls: { - fontSize: true, - fontAppearance: true, - textTransform: true, - }, - }, - }, - }; - - addFilter( - 'blocks.registerBlockType', - 'test/filterSupports', - ( settings, name ) => { - if ( name === 'test/block' && settings.supports.typography ) { - settings.supports.typography.__experimentalFontFamily = false; - settings.supports.typography.__experimentalFontStyle = false; - settings.supports.typography.__experimentalFontWeight = false; - } - return settings; - } - ); - - const processedBlockType = processBlockType( - 'test/block', - blockSettings - )( { select } ); - - expect( processedBlockType.supports.typography ).toEqual( { - fontSize: true, - lineHeight: true, - fontFamily: false, - fontStyle: false, - fontWeight: false, - letterSpacing: true, - textTransform: true, - textDecoration: true, - __experimentalWritingMode: true, - __experimentalDefaultControls: { - fontSize: true, - fontAppearance: true, - textTransform: true, - }, - } ); - } ); - - it( 'should stabilize experimental typography supports within block deprecations', () => { - const blockSettings = { - ...baseBlockSettings, - supports: { - typography: { - fontSize: true, - lineHeight: true, - fontFamily: true, - fontStyle: true, - fontWeight: true, - letterSpacing: true, - textTransform: true, - textDecoration: true, - __experimentalWritingMode: true, - __experimentalDefaultControls: { - fontSize: true, - fontAppearance: true, - textTransform: true, - }, - }, - }, - deprecated: [ - { - supports: { - typography: { - __experimentalFontFamily: true, - __experimentalFontStyle: true, - __experimentalFontWeight: true, - __experimentalLetterSpacing: true, - __experimentalTextTransform: true, - __experimentalTextDecoration: true, - __experimentalWritingMode: true, - }, - }, - }, - ], - }; - - // Freeze the deprecated block object and its supports so that the original is not mutated. - // This ensures the test covers a regression where the original object was mutated. - // See: https://github.com/WordPress/gutenberg/pull/63401#discussion_r1832394335. - Object.freeze( blockSettings.deprecated[ 0 ] ); - Object.freeze( blockSettings.deprecated[ 0 ].supports ); - - const processedBlockType = processBlockType( - 'test/block', - blockSettings - )( { select } ); - - expect( - processedBlockType.deprecated[ 0 ].supports.typography - ).toEqual( { - fontFamily: true, - fontStyle: true, - fontWeight: true, - letterSpacing: true, - textTransform: true, - textDecoration: true, - __experimentalWritingMode: true, - } ); - } ); - - it( 'should reapply transformations after supports are filtered within block deprecations', () => { - const blockSettings = { - ...baseBlockSettings, - supports: { - typography: { - fontSize: true, - lineHeight: true, - fontFamily: true, - fontStyle: true, - fontWeight: true, - letterSpacing: true, - textTransform: true, - textDecoration: true, - __experimentalWritingMode: true, - __experimentalDefaultControls: { - fontSize: true, - fontAppearance: true, - textTransform: true, - }, - }, - }, - deprecated: [ - { - supports: { - typography: { - __experimentalFontFamily: true, - __experimentalFontStyle: true, - __experimentalFontWeight: true, - __experimentalLetterSpacing: true, - __experimentalTextTransform: true, - __experimentalTextDecoration: true, - __experimentalWritingMode: true, - }, - }, - }, - ], - }; - - addFilter( - 'blocks.registerBlockType', - 'test/filterSupports', - ( settings, name ) => { - if ( name === 'test/block' && settings.supports.typography ) { - settings.supports.typography.__experimentalFontFamily = false; - settings.supports.typography.__experimentalFontStyle = false; - settings.supports.typography.__experimentalFontWeight = false; - } - return settings; - } - ); - - const processedBlockType = processBlockType( - 'test/block', - blockSettings - )( { select } ); - - expect( - processedBlockType.deprecated[ 0 ].supports.typography - ).toEqual( { - fontFamily: false, - fontStyle: false, - fontWeight: false, - letterSpacing: true, - textTransform: true, - textDecoration: true, - __experimentalWritingMode: true, - } ); - } ); -} ); diff --git a/packages/browserslist-config/CHANGELOG.md b/packages/browserslist-config/CHANGELOG.md index 1ef626d40ef832..82159a48650348 100644 --- a/packages/browserslist-config/CHANGELOG.md +++ b/packages/browserslist-config/CHANGELOG.md @@ -2,6 +2,16 @@ ## Unreleased +## 6.16.0 (2025-01-15) + +## 6.15.0 (2025-01-02) + +## 6.14.0 (2024-12-11) + +## 6.13.0 (2024-11-27) + +## 6.12.0 (2024-11-16) + ## 6.11.0 (2024-10-30) ## 6.10.0 (2024-10-16) diff --git a/packages/browserslist-config/package.json b/packages/browserslist-config/package.json index 65bc5231f0651b..dc16330ceb4e70 100644 --- a/packages/browserslist-config/package.json +++ b/packages/browserslist-config/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/browserslist-config", - "version": "6.11.0", + "version": "6.16.0", "description": "WordPress Browserslist shared configuration.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/commands/CHANGELOG.md b/packages/commands/CHANGELOG.md index 2ec9f745b774fd..b0d765c04c3064 100644 --- a/packages/commands/CHANGELOG.md +++ b/packages/commands/CHANGELOG.md @@ -2,6 +2,16 @@ ## Unreleased +## 1.16.0 (2025-01-15) + +## 1.15.0 (2025-01-02) + +## 1.14.0 (2024-12-11) + +## 1.13.0 (2024-11-27) + +## 1.12.0 (2024-11-16) + ## 1.11.0 (2024-10-30) ## 1.10.0 (2024-10-16) diff --git a/packages/commands/package.json b/packages/commands/package.json index cebf3237a00d75..ce9df5b78d7dd8 100644 --- a/packages/commands/package.json +++ b/packages/commands/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/commands", - "version": "1.11.0", + "version": "1.16.0", "description": "Handles the commands menu.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -29,13 +29,13 @@ "wpScript": true, "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/components": "*", - "@wordpress/data": "*", - "@wordpress/element": "*", - "@wordpress/i18n": "*", - "@wordpress/icons": "*", - "@wordpress/keyboard-shortcuts": "*", - "@wordpress/private-apis": "*", + "@wordpress/components": "file:../components", + "@wordpress/data": "file:../data", + "@wordpress/element": "file:../element", + "@wordpress/i18n": "file:../i18n", + "@wordpress/icons": "file:../icons", + "@wordpress/keyboard-shortcuts": "file:../keyboard-shortcuts", + "@wordpress/private-apis": "file:../private-apis", "clsx": "^2.1.1", "cmdk": "^1.0.0" }, diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index 2fddf51173e832..db1778ee625b18 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -2,6 +2,153 @@ ## Unreleased +## 29.2.0 (2025-01-15) + +### Internal + +- `Components`: Standardize reduced motion handling using media queries ([#68421](https://github.com/WordPress/gutenberg/pull/68421)). + +### Bug Fixes + +- `CircularOptionPicker`, `ColorPalette`: Fix usage of tooltip in the Circular option picker. ([#68602](https://github.com/WordPress/gutenberg/pull/68602)). +- `InputControl`: Ensures email and url inputs have consistent LTR alignment in RTL languages ([#68188](https://github.com/WordPress/gutenberg/pull/68188)). + +## 29.1.0 (2025-01-02) + +### Enhancements + +- `BoxControl`: Add presets support ([#67688](https://github.com/WordPress/gutenberg/pull/67688)). +- `Navigation`: Upsize back buttons ([#68157](https://github.com/WordPress/gutenberg/pull/68157)). +- `Heading`: Fix text contrast for dark mode ([#68349](https://github.com/WordPress/gutenberg/pull/68349)). +- `Text`: Fix text contrast for dark mode ([#68349](https://github.com/WordPress/gutenberg/pull/68349)). +- `Heading`: Revert text contrast fix for dark mode with optimizeReadabilityFor ([#68472](https://github.com/WordPress/gutenberg/pull/68472)). +- `Text`: Revert text contrast fix for dark mode with optimizeReadabilityFor ([#68472](https://github.com/WordPress/gutenberg/pull/68472)). + +### Deprecations + +- `TreeSelect`: Deprecate 36px default size ([#67855](https://github.com/WordPress/gutenberg/pull/67855)). +- `SelectControl`: Deprecate 36px default size ([#66898](https://github.com/WordPress/gutenberg/pull/66898)). +- `InputControl`: Deprecate 36px default size ([#66897](https://github.com/WordPress/gutenberg/pull/66897)). +- `RadioGroup`: Log deprecation warning ([#68067](https://github.com/WordPress/gutenberg/pull/68067)). +- Soft deprecate `ButtonGroup` component. Use `ToggleGroupControl` instead ([#65429](https://github.com/WordPress/gutenberg/pull/65429)). +- `Navigation`: Log deprecation warning for removal in WP 7.1. Use `Navigator` instead ([#68158](https://github.com/WordPress/gutenberg/pull/68158)). + +### Bug Fixes + +- `BoxControl`: Better respect for the `min` prop in the Range Slider ([#67819](https://github.com/WordPress/gutenberg/pull/67819)). +- `FontSizePicker`: Add `display:contents` rule to fix overflowing text in the custom size select. ([#68280](https://github.com/WordPress/gutenberg/pull/68280)). +- `BoxControl`: Fix aria-valuetext value ([#68362](https://github.com/WordPress/gutenberg/pull/68362)). + +### Experimental + +- Add new `Badge` component ([#66555](https://github.com/WordPress/gutenberg/pull/66555)). +- `Menu`: refactor to more granular sub-components ([#67422](https://github.com/WordPress/gutenberg/pull/67422)). +- `Badge`: Support text truncation ([#68107](https://github.com/WordPress/gutenberg/pull/68107)). + +### Internal + +- `SlotFill`: rewrite the non-portal version to use `observableMap` ([#67400](https://github.com/WordPress/gutenberg/pull/67400)). +- `DatePicker`: Prepare day buttons for 40px default size ([#68156](https://github.com/WordPress/gutenberg/pull/68156)). +- `SlotFill`: register slots in a layout effect ([#68176](https://github.com/WordPress/gutenberg/pull/68176)). + +## 29.0.0 (2024-12-11) + +### Breaking Changes + +- Removed the unused `__unstableMotionContext` export ([#67623](https://github.com/WordPress/gutenberg/pull/67623)). + +### Enhancements + +- `GradientPicker`: Add `enableAlpha` prop ([#66974](https://github.com/WordPress/gutenberg/pull/66974)) +- `CustomGradientPicker`: Add `enableAlpha` prop ([#66974](https://github.com/WordPress/gutenberg/pull/66974)) +- `Menu`: Replace hardcoded white color with theme-ready variable ([#67649](https://github.com/WordPress/gutenberg/pull/67649)). +- `Navigation` (deprecated): Replace hardcoded white color with theme-ready variable ([#67649](https://github.com/WordPress/gutenberg/pull/67649)). +- `ToggleGroupControl`: Replace hardcoded white color with theme-ready variable ([#67649](https://github.com/WordPress/gutenberg/pull/67649)). +- `RangeControl`: Update the design of the range control marks ([#67611](https://github.com/WordPress/gutenberg/pull/67611)). +- `BorderBoxControl`: Reduce gap value when unlinked ([#67049](https://github.com/WordPress/gutenberg/pull/67049)). +- `DropdownMenu`: Increase option height to 40px ([#67435](https://github.com/WordPress/gutenberg/pull/67435)). +- `MenuItem`: Increase height to 40px ([#67435](https://github.com/WordPress/gutenberg/pull/67435)). +- `MenuItemsChoice`: Increase option height to 40px ([#67435](https://github.com/WordPress/gutenberg/pull/67435)). +- `Navigation`: Fix active item hover color ([#67732](https://github.com/WordPress/gutenberg/pull/67732)). +- `Button`: Adjust `secondary` variant hover style. ([#67325](https://github.com/WordPress/gutenberg/pull/67325)). + +### Deprecations + +- `BoxControl`: Passive deprecate `onMouseOver`/`onMouseOut`. Pass to the `inputProps` prop instead ([#67332](https://github.com/WordPress/gutenberg/pull/67332)). +- `BoxControl`: Deprecate 36px default size ([#66704](https://github.com/WordPress/gutenberg/pull/66704)). +- `CustomSelectControl`: Deprecate 36px default size ([#67441](https://github.com/WordPress/gutenberg/pull/67441)). +- `NumberControl`: Deprecate 36px default size ([#66730](https://github.com/WordPress/gutenberg/pull/66730)). +- `UnitControl`: Deprecate 36px default size ([#66791](https://github.com/WordPress/gutenberg/pull/66791)). +- `FormFileUpload`: Deprecate 36px default size ([#67438](https://github.com/WordPress/gutenberg/pull/67438)). +- `FormTokenField`: Deprecate 36px default size ([#67454](https://github.com/WordPress/gutenberg/pull/67454)). + +### Experimental + +- `Menu`: throw when subcomponents are not rendered inside top level `Menu` ([#67411](https://github.com/WordPress/gutenberg/pull/67411)). + +### Internal + +- Upgraded `@ariakit/react` (v0.4.13) and `@ariakit/test` (v0.4.5) ([#65907](https://github.com/WordPress/gutenberg/pull/65907)). +- Upgraded `@ariakit/react` (v0.4.15) and `@ariakit/test` (v0.4.7) ([#67404](https://github.com/WordPress/gutenberg/pull/67404)). +- `ToolbarButton`: Set size to "compact" ([#67440](https://github.com/WordPress/gutenberg/pull/67440)). +- `SlotFill`: remove manual rerenders from the portal `Fill` component ([#67471](https://github.com/WordPress/gutenberg/pull/67471)). +- `BoxControl`: Refactor internals to unify the different combinations of sides ([#67626](https://github.com/WordPress/gutenberg/pull/67626)). + +### Bug Fixes + +- `ResizableBox`: Make drag handles focusable ([#67305](https://github.com/WordPress/gutenberg/pull/67305)). +- `CustomSelectControl`: Update correctly when `showSelectedHint` is enabled ([#67733](https://github.com/WordPress/gutenberg/pull/67733)). +- `CustomSelectControl`: Use `useStoreState` to get `currentValue` and avoid stale values ([#67815](https://github.com/WordPress/gutenberg/pull/67815)). + +## 28.13.0 (2024-11-27) + +### Deprecations + +- `DimensionControl`: Deprecate 36px default size ([#66705](https://github.com/WordPress/gutenberg/pull/66705)). +- `TextControl`: Deprecate 36px default size ([#66745](https://github.com/WordPress/gutenberg/pull/66745). +- `FontSizePicker`: Deprecate 36px default size ([#66920](https://github.com/WordPress/gutenberg/pull/66920)). +- `ComboboxControl`: Deprecate 36px default size ([#66900](https://github.com/WordPress/gutenberg/pull/66900)). +- `ToggleGroupControl`: Deprecate 36px default size ([#66747](https://github.com/WordPress/gutenberg/pull/66747)). +- `RangeControl`: Deprecate 36px default size ([#66721](https://github.com/WordPress/gutenberg/pull/66721)). + +### Bug Fixes + +- `Modal`: Modal: Increase size of the Close button ([#66792](https://github.com/WordPress/gutenberg/pull/66792)). +- `ToggleGroupControl`: Fix active background for `0` value ([#66855](https://github.com/WordPress/gutenberg/pull/66855)). +- `SlotFill`: Fix a bug where a stale value of `fillProps` could be used ([#67000](https://github.com/WordPress/gutenberg/pull/67000)). +- `ColorPalette`: Disable `Clear` button if there's no color value. ([#67108](https://github.com/WordPress/gutenberg/pull/67108)). +- `GradientPicker`: Disable `Clear` button if there's no value. ([#67108](https://github.com/WordPress/gutenberg/pull/67108)). +- `DuotonePicker`: Disable `Clear` button if there's no value. ([#67108](https://github.com/WordPress/gutenberg/pull/67108)). +- `ColorPicker`: Add accessible label for copy button ([#67094](https://github.com/WordPress/gutenberg/pull/67094)). +- `FormFileUpload`: Prevent HEIC and HEIF files from being uploaded on Safari ([#67139](https://github.com/WordPress/gutenberg/pull/67139)). +- `Composite.Hover`: Restore functionality ([#67212](https://github.com/WordPress/gutenberg/pull/67212)). +- `Composite.Typeahead`: Restore functionality ([#67212](https://github.com/WordPress/gutenberg/pull/67212)). +- `Dashicons`: Remove non-existent icons from type ([#67235](https://github.com/WordPress/gutenberg/pull/67235)). + +### Enhancements + +- `ColorPicker`: Update sizes of color format select and copy button ([#67093](https://github.com/WordPress/gutenberg/pull/67093)). +- `ComboboxControl`: Update reset button size ([#67215](https://github.com/WordPress/gutenberg/pull/67215)). +- `Autocomplete`: Increase option height ([#67214](https://github.com/WordPress/gutenberg/pull/67214)). +- `DropZone`: Add `isEligible` prop to allow customizing whether the drop zone should activate ([#67317](https://github.com/WordPress/gutenberg/pull/67317)). +- `CircularOptionPicker`: Update `Button` sizes to be ready for 40px default size ([#67285](https://github.com/WordPress/gutenberg/pull/67285)). +- `DuotonePicker`: Simplify Button styles ([#66641](https://github.com/WordPress/gutenberg/pull/66641)). + +### Experimental + +- `SlotFill`: Remove registration API methods from return value of `__experimentalUseSlot` ([#67070](https://github.com/WordPress/gutenberg/pull/67070)). +- `SlotFill`: Remove the `createPrivateSlotFill` private API ([#67238](https://github.com/WordPress/gutenberg/pull/67238)). + +### Internal + +- `SlotFill`: fix dependencies of `Fill` registration effects ([#67071](https://github.com/WordPress/gutenberg/pull/67071)). +- `SlotFill`: rewrite the `Slot` component from class component to functional ([#67153](https://github.com/WordPress/gutenberg/pull/67153)). +- `Menu.ItemHelpText`: Fix text wrapping to prevent unintended word breaks ([#67011](https://github.com/WordPress/gutenberg/pull/67011)). +- `BorderBoxControl`: Suppress redundant warnings for deprecated 36px size ([#67213](https://github.com/WordPress/gutenberg/pull/67213)). +- `CustomGradientPicker`: Prepare `Button`s for 40px default size ([#67286](https://github.com/WordPress/gutenberg/pull/67286)). + +## 28.12.0 (2024-11-16) + ### Deprecations - `Radio`: Deprecate 36px default size ([#66572](https://github.com/WordPress/gutenberg/pull/66572)). @@ -9,7 +156,6 @@ ### Bug Fixes - `Popover`: Fix missing label of the headerTitle Close button ([#66813](https://github.com/WordPress/gutenberg/pull/66813)). -- `ToggleGroupControl`: Fix active background for `0` value ([#66855](https://github.com/WordPress/gutenberg/pull/66855)). ### Enhancements @@ -46,7 +192,7 @@ - `Tabs`: remove internal custom logic ([#66097](https://github.com/WordPress/gutenberg/pull/66097)). - `Tabs`: add props to control active tab item ([#66223](https://github.com/WordPress/gutenberg/pull/66223)). -- `Tabs`: restore vertical alignent for tabs content ([#66215](https://github.com/WordPress/gutenberg/pull/66215)). +- `Tabs`: restore vertical alignment for tabs content ([#66215](https://github.com/WordPress/gutenberg/pull/66215)). - `Tabs`: fix indicator animation ([#66198](https://github.com/WordPress/gutenberg/pull/66198)). - `Tabs`: update indicator more reactively ([#66207](https://github.com/WordPress/gutenberg/pull/66207)). - `Tabs` and `TabPanel`: Fix arrow key navigation in RTL ([#66201](https://github.com/WordPress/gutenberg/pull/66201)). @@ -382,7 +528,7 @@ - `Tabs`: Vertical Tabs should be 40px min height. ([#63446](https://github.com/WordPress/gutenberg/pull/63446)). - `ColorPicker`: Use `minimal` variant for `SelectControl` ([#63676](https://github.com/WordPress/gutenberg/pull/63676)). - `Tabs`: keep full opacity of focus ring and remove hover styles on disabled tabs ([#63754](https://github.com/WordPress/gutenberg/pull/63754)). -- `Placeholder`: Remove unnecssary `placeholder-style` Sass mixin ([#63885](https://github.com/WordPress/gutenberg/pull/63885)). +- `Placeholder`: Remove unnecessary `placeholder-style` Sass mixin ([#63885](https://github.com/WordPress/gutenberg/pull/63885)). ### Documentation @@ -1449,7 +1595,7 @@ - `TabPanel`: support manual tab activation ([#46004](https://github.com/WordPress/gutenberg/pull/46004)). - `TabPanel`: support disabled prop for tab buttons ([#46471](https://github.com/WordPress/gutenberg/pull/46471)). -- `BaseControl`: Add `useBaseControlProps` hook to help generate id-releated props ([#46170](https://github.com/WordPress/gutenberg/pull/46170)). +- `BaseControl`: Add `useBaseControlProps` hook to help generate id-related props ([#46170](https://github.com/WordPress/gutenberg/pull/46170)). ### Bug Fixes @@ -1472,8 +1618,8 @@ - `Popover`: Prevent unnecessary paint caused by using outline ([#46201](https://github.com/WordPress/gutenberg/pull/46201)). - `PaletteEdit`: Global styles: add onChange actions to color palette items [#45681](https://github.com/WordPress/gutenberg/pull/45681). - Lighten the border color on control components ([#46252](https://github.com/WordPress/gutenberg/pull/46252)). -- `Popover`: Prevent unnecessary paint when scrolling by using transform instead of top/left positionning ([#46187](https://github.com/WordPress/gutenberg/pull/46187)). -- `CircularOptionPicker`: Prevent unecessary paint on hover ([#46197](https://github.com/WordPress/gutenberg/pull/46197)). +- `Popover`: Prevent unnecessary paint when scrolling by using transform instead of top/left positioning ([#46187](https://github.com/WordPress/gutenberg/pull/46187)). +- `CircularOptionPicker`: Prevent unnecessary paint on hover ([#46197](https://github.com/WordPress/gutenberg/pull/46197)). ### Experimental @@ -2319,7 +2465,7 @@ ### Bug Fixes -- Improve accessibility and visibility in `ColorPallete` ([#36925](https://github.com/WordPress/gutenberg/pull/36925)) +- Improve accessibility and visibility in `ColorPalette` ([#36925](https://github.com/WordPress/gutenberg/pull/36925)) ## 19.1.3 (2021-12-06) diff --git a/packages/components/CONTRIBUTING.md b/packages/components/CONTRIBUTING.md index 8af8bbf801edfe..7e88400d456eda 100644 --- a/packages/components/CONTRIBUTING.md +++ b/packages/components/CONTRIBUTING.md @@ -2,7 +2,7 @@ Thank you for taking the time to contribute. -The following is a set of guidelines for contributing to the `@wordpress/components` package to be considered in addition to the general ones described in our [Contributing Policy](/CONTRIBUTING.md). +The following is a set of guidelines for contributing to the `@wordpress/components` package to be considered in addition to the general ones described in our [Contributing Policy](https://github.com/WordPress/gutenberg/blob/HEAD/CONTRIBUTING.md). This set of guidelines should apply especially to newly introduced components. In fact, while these guidelines should also be retroactively applied to existing components, it is sometimes impossible to do so for legacy/compatibility reasons. @@ -36,7 +36,7 @@ To determine if a component should be added, ask yourself: Here’s a flowchart that can help determine if a new component is necessary: -[![New component flowchart](https://wordpress.org/gutenberg/files/2019/07/New_component_flowchart.png)](https://coggle.it/diagram/WtUSrld3uAYZHsn-/t/new-ui-component/992b38cbe685d897b4aec6d0dd93cc4b47c06e0d4484eeb0d7d9a47fb2c48d94) +![New component flowchart](https://wordpress.org/gutenberg/files/2019/07/New_component_flowchart.png) ### First steps @@ -95,12 +95,12 @@ In these situations, one possible approach is to "soft-deprecate" a given legacy 2. Updating all places in Gutenberg that use that API. 3. Adding deprecation warnings (only after the previous point is completed, otherwise the Browser Console will be polluted by all those warnings and some e2e tests may fail). -When adding new components or new props to existing components, it's recommended to create a [private version](/packages/private-apis/README.md)) of the component until the changes are stable enough to be exposed as part of the public API. +When adding new components or new props to existing components, it's recommended to create a [private version](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-private-apis/) of the component until the changes are stable enough to be exposed as part of the public API. ### Learn more -- [How to preserve backward compatibility for a React Component](/docs/contributors/code/backward-compatibility.md#how-to-preserve-backward-compatibility-for-a-react-component) -- [Experimental and Unstable APIs](/docs/contributors/code/coding-guidelines.md#legacy-experimental-apis-plugin-only-apis-and-private-apis) +- [How to preserve backward compatibility for a React Component](https://developer.wordpress.org/block-editor/contributors/code/backward-compatibility/#how-to-preserve-backward-compatibility-for-a-react-component) +- [Experimental and Unstable APIs](https://developer.wordpress.org/block-editor/contributors/code/coding-guidelines/#legacy-experimental-apis-plugin-only-apis-and-private-apis) - [Deprecating styles](#deprecating-styles) <!-- ## Polymorphic Components (i.e. the `as` prop) @@ -216,8 +216,8 @@ function Example( A couple of good examples of how hooks are used for composition are: -- the `Card` component, which builds on top of the `Surface` component by [calling the `useSurface` hook inside its own hook](/packages/components/src/card/card/hook.ts); -- the `HStack` component, which builds on top of the `Flex` component and [calls the `useFlex` hook inside its own hook](/packages/components/src/h-stack/hook.tsx). +- the `Card` component, which builds on top of the `Surface` component by [calling the `useSurface` hook inside its own hook](https://github.com/WordPress/gutenberg/blob/trunk/packages/components/src/card/card/hook.ts); +- the `HStack` component, which builds on top of the `Flex` component and [calls the `useFlex` hook inside its own hook](https://github.com/WordPress/gutenberg/blob/trunk/packages/components/src/h-stack/hook.tsx). <!-- ## API Consinstency @@ -411,14 +411,14 @@ export default MyComponent; On the component's main named export, add a JSDoc comment that includes the main description and the example code snippet from the README ([example](https://github.com/WordPress/gutenberg/blob/43d9c82922619c1d1ff6b454f86f75c3157d3de6/packages/components/src/date-time/date-time/index.tsx#L193-L217)). _At the time of writing, the `@example` JSDoc keyword is not recognized by StoryBook's docgen, so please avoid using it_. -<!-- TODO: add to the previous paragraph once the composision section gets added to this document. +<!-- TODO: add to the previous paragraph once the compositions section gets added to this document. (more details about polymorphism can be found above in the "Components composition" section). --> ## Styling All new component should be styled using [Emotion](https://emotion.sh/docs/introduction). -Note: Instead of using Emotion's standard `cx` function, the custom [`useCx` hook](/packages/components/src/utils/hooks/use-cx.ts) should be used instead. +Note: Instead of using Emotion's standard `cx` function, the custom [`useCx` hook](https://github.com/WordPress/gutenberg/blob/trunk/packages/components/src/utils/hooks/use-cx.ts) should be used instead. ### Deprecating styles @@ -462,7 +462,7 @@ export const Wrapper = styled.div` Once deprecated, code examples in docs/stories should include the opt-in prop set to `true` so that new consumers are encouraged to adopt it from the start. -Remember to [add a **Needs Dev Note** label](/docs/contributors/code/backward-compatibility.md##dev-notes) to the pull request so third-party developers can be informed of the deprecation. +Remember to [add a **Needs Dev Note** label](https://developer.wordpress.org/block-editor/contributors/code/backward-compatibility/#dev-notes) to the pull request so third-party developers can be informed of the deprecation. When the grace period is over and the deprecation version arrives, the `__next*` prop, deprecation notice, and deprecated styles should all be completely removed from the codebase. @@ -491,7 +491,7 @@ Components can use this system via a couple of functions: - they can connect to the Context via `contextConnect` - they can read the "computed" values from the context via `useContextSystem` -An example of how this is used can be found in the [`Card` component family](/packages/components/src/card). For example, this is how the `Card` component injects the `size` and `isBorderless` props down to its `CardBody` subcomponent — which makes it use the correct spacing and border settings "auto-magically". +An example of how this is used can be found in the [`Card` component family](https://github.com/WordPress/gutenberg/tree/trunk/packages/components/src/card). For example, this is how the `Card` component injects the `size` and `isBorderless` props down to its `CardBody` subcomponent — which makes it use the correct spacing and border settings "auto-magically". ```jsx //========================================================================= @@ -550,7 +550,7 @@ export function useCardBody( props ) { // Read any derived registered prop from the Context System in the `CardBody` namespace. // If a `CardBody` component is rendered as a child of a `Card` component, the value of // the `size` prop will be the one set by the parent `Card` component via the Context - // System (unless the prop gets explicitely set on the `CardBody` component). + // System (unless the prop gets explicitly set on the `CardBody` component). const { size = 'medium', ...otherDerivedProps } = useContextSystem( props, 'CardBody' @@ -564,7 +564,7 @@ export function useCardBody( props ) { ## Unit tests -Please refer to the [JavaScript Testing Overview docs](/docs/contributors/code/testing-overview.md#snapshot-testing). +Please refer to the [JavaScript Testing Overview docs](https://developer.wordpress.org/block-editor/contributors/code/testing-overview/#snapshot-testing). ## Storybook @@ -596,13 +596,13 @@ Primary.args = { A great tool to use when writing stories is the [Storybook Controls addon](https://storybook.js.org/addons/@storybook/addon-controls). Ideally props should be exposed by using this addon, which provides a graphical UI to interact dynamically with the component without needing to write code. Historically, we used [Knobs](https://storybook.js.org/addons/@storybook/addon-knobs), but it was deprecated and later removed in [#47152](https://github.com/WordPress/gutenberg/pull/47152). -The default value of each control should coincide with the default value of the props (i.e. it should be `undefined` if a prop is not required). A story should, therefore, also explicitly show how values from the Context System are applied to (sub)components. A good example of how this may look like is the [`Card` story](https://wordpress.github.io/gutenberg/?path=/story/components-card--default) (code [here](/packages/components/src/card/stories/index.tsx)). +The default value of each control should coincide with the default value of the props (i.e. it should be `undefined` if a prop is not required). A story should, therefore, also explicitly show how values from the Context System are applied to (sub)components. A good example of how this may look like is the [`Card` story](https://wordpress.github.io/gutenberg/?path=/story/components-card--default) (code [here](https://github.com/WordPress/gutenberg/blob/trunk/packages/components/src/card/stories/index.story.tsx)). Storybook can be started on a local machine by running `npm run storybook:dev`. Alternatively, the components' catalogue (up to date with the latest code on `trunk`) can be found at [wordpress.github.io/gutenberg/](https://wordpress.github.io/gutenberg/). ## Documentation -All components, in addition to being typed, should be using JSDoc when necessary — as explained in the [Coding Guidelines](/docs/contributors/code/coding-guidelines.md#javascript-documentation-using-jsdoc). +All components, in addition to being typed, should be using JSDoc when necessary — as explained in the [Coding Guidelines](https://developer.wordpress.org/block-editor/contributors/code/coding-guidelines/#javascript-documentation-using-jsdoc). Each component that is exported from the `@wordpress/components` package should include a `README.md` file, explaining how to use the component, showing examples, and documenting all the props. @@ -625,7 +625,7 @@ Description of the component. ## Usage -Code example using correct markdown syntax and formatted using project's formatting rules. See [ItemGroup](/packages/components/src/item-group/item-group/README.md#usage) for a real-world example. +Code example using correct markdown syntax and formatted using project's formatting rules. See [ItemGroup](https://github.com/WordPress/gutenberg/blob/trunk/packages/components/src/item-group/item-group/README.md) for a real-world example. ```jsx import { ExampleComponent } from '@wordpress/components'; @@ -653,13 +653,13 @@ Prop description. With a new line before and after the description and before an ### Inherited props -Add this section when there are props that are drilled down into an internal component. See [ClipboardButton](/packages/components/src/clipboard-button/README.md) for an example. +Add this section when there are props that are drilled down into an internal component. See [ClipboardButton](https://github.com/WordPress/gutenberg/blob/trunk/packages/components/src/clipboard-button/README.md) for an example. <!-- Only add the next section if the component relies on the [Context System](#context-system) --> ## Context -See examples for this section for the [ItemGroup](/packages/components/src/item-group/item-group/README.md#context) and [`Card`](/packages/components/src/card/card/README.md#context) components. +See examples for this section for the [ItemGroup](https://github.com/WordPress/gutenberg/blob/trunk/packages/components/src/item-group/item-group/README.md#context) and [`Card`](https://github.com/WordPress/gutenberg/tree/trunk/packages/components/src/card/card#context) components. ```` ## Folder structure @@ -759,13 +759,13 @@ function NewComponentImplementation( props ) { In case that is not possible (eg. too difficult to reconciliate new and legacy implementations, or impossible to preserve backward compatibility), then the legacy implementation can stay as-is. -In any case, extra attention should be payed to legacy component families made of two or more subcomponents. It is possible, in fact, that the a legacy subcomponent is used as a parent / child of a subcomponent from the new version (this can happen, for example, when Gutenberg allows third party developers to inject React components via Slot/Fill). To avoid incompatibility issues and unexpected behavior, there should be some code in the components warning when the above scenario happens — or even better, aliasing to the correct version of the component. +In any case, extra attention should be paid to legacy component families made of two or more subcomponents. It is possible, in fact, that the a legacy subcomponent is used as a parent / child of a subcomponent from the new version (this can happen, for example, when Gutenberg allows third party developers to inject React components via Slot/Fill). To avoid incompatibility issues and unexpected behavior, there should be some code in the components warning when the above scenario happens — or even better, aliasing to the correct version of the component. ##### Naming When it comes to naming the newly added component, there are two options. -If there is a good reason for it, pick a new name for the component. For example, some legacy components have names that don't correspond to the corrent name of UI widget that they implement (for example, `TabPanel` should be called `Tabs`, and `Modal` should be called `Dialog`). +If there is a good reason for it, pick a new name for the component. For example, some legacy components have names that don't correspond to the current name of UI widget that they implement (for example, `TabPanel` should be called `Tabs`, and `Modal` should be called `Dialog`). Alternatively, version the component name. For example, the new version of `Component` could be called `ComponentV2`. This also applies for namespaced subcomponents (ie. `ComponentV2.SubComponent`). diff --git a/packages/components/README.md b/packages/components/README.md index df92e8db57be42..7fdba5511338f1 100644 --- a/packages/components/README.md +++ b/packages/components/README.md @@ -33,7 +33,7 @@ In non-WordPress projects, link to the `build-style/style.css` file directly, it By default, the `Popover` component will render within an extra element appended to the body of the document. -If you want to precisely contol where the popovers render, you will need to use the `Popover.Slot` component. +If you want to precisely control where the popovers render, you will need to use the `Popover.Slot` component. The following example illustrates how you can wrap a component using a `Popover` and have those popovers render to a single location in the DOM. diff --git a/packages/components/package.json b/packages/components/package.json index 6871511cf5b1e5..08707526f52118 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/components", - "version": "28.11.0", + "version": "29.2.0", "description": "UI components for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -32,7 +32,7 @@ "src/**/*.scss" ], "dependencies": { - "@ariakit/react": "^0.4.10", + "@ariakit/react": "^0.4.15", "@babel/runtime": "7.25.7", "@emotion/cache": "^11.7.1", "@emotion/css": "^11.7.1", @@ -44,23 +44,23 @@ "@types/gradient-parser": "0.1.3", "@types/highlight-words-core": "1.2.1", "@use-gesture/react": "^10.3.1", - "@wordpress/a11y": "*", - "@wordpress/compose": "*", - "@wordpress/date": "*", - "@wordpress/deprecated": "*", - "@wordpress/dom": "*", - "@wordpress/element": "*", - "@wordpress/escape-html": "*", - "@wordpress/hooks": "*", - "@wordpress/html-entities": "*", - "@wordpress/i18n": "*", - "@wordpress/icons": "*", - "@wordpress/is-shallow-equal": "*", - "@wordpress/keycodes": "*", - "@wordpress/primitives": "*", - "@wordpress/private-apis": "*", - "@wordpress/rich-text": "*", - "@wordpress/warning": "*", + "@wordpress/a11y": "file:../a11y", + "@wordpress/compose": "file:../compose", + "@wordpress/date": "file:../date", + "@wordpress/deprecated": "file:../deprecated", + "@wordpress/dom": "file:../dom", + "@wordpress/element": "file:../element", + "@wordpress/escape-html": "file:../escape-html", + "@wordpress/hooks": "file:../hooks", + "@wordpress/html-entities": "file:../html-entities", + "@wordpress/i18n": "file:../i18n", + "@wordpress/icons": "file:../icons", + "@wordpress/is-shallow-equal": "file:../is-shallow-equal", + "@wordpress/keycodes": "file:../keycodes", + "@wordpress/primitives": "file:../primitives", + "@wordpress/private-apis": "file:../private-apis", + "@wordpress/rich-text": "file:../rich-text", + "@wordpress/warning": "file:../warning", "change-case": "^4.1.2", "clsx": "^2.1.1", "colord": "^2.7.0", diff --git a/packages/components/src/alignment-matrix-control/README.md b/packages/components/src/alignment-matrix-control/README.md index af97e3ae0607cd..8ba9f6378c1852 100644 --- a/packages/components/src/alignment-matrix-control/README.md +++ b/packages/components/src/alignment-matrix-control/README.md @@ -21,47 +21,48 @@ const Example = () => { ); }; ``` + ## Props ### `defaultValue` -If provided, sets the default alignment value. - - Type: `"center" | "top left" | "top center" | "top right" | "center left" | "center center" | "center right" | "bottom left" | "bottom center" | "bottom right"` - Required: No - Default: `'center center'` -### `label` +If provided, sets the default alignment value. -Accessible label. If provided, sets the `aria-label` attribute of the -underlying `grid` widget. +### `label` - Type: `string` - Required: No - Default: `'Alignment Matrix Control'` -### `onChange` +Accessible label. If provided, sets the `aria-label` attribute of the +underlying `grid` widget. -A function that receives the updated alignment value. +### `onChange` - Type: `(newValue: AlignmentMatrixControlValue) => void` - Required: No -### `value` +A function that receives the updated alignment value. -The current alignment value. +### `value` - Type: `"center" | "top left" | "top center" | "top right" | "center left" | "center center" | "center right" | "bottom left" | "bottom center" | "bottom right"` - Required: No -### `width` +The current alignment value. -If provided, sets the width of the control. +### `width` - Type: `number` - Required: No - Default: `92` +If provided, sets the width of the control. + ## Subcomponents ### AlignmentMatrixControl.Icon @@ -70,16 +71,16 @@ If provided, sets the width of the control. ##### `disablePointerEvents` -If `true`, disables pointer events on the icon. - - Type: `boolean` - Required: No - Default: `true` -##### `value` +If `true`, disables pointer events on the icon. -The current alignment value. +##### `value` - Type: `"center" | "top left" | "top center" | "top right" | "center left" | "center center" | "center right" | "bottom left" | "bottom center" | "bottom right"` - Required: No - Default: `center` + +The current alignment value. diff --git a/packages/components/src/alignment-matrix-control/stories/index.story.tsx b/packages/components/src/alignment-matrix-control/stories/index.story.tsx index 433d7540197da2..e04d8b6690fe8c 100644 --- a/packages/components/src/alignment-matrix-control/stories/index.story.tsx +++ b/packages/components/src/alignment-matrix-control/stories/index.story.tsx @@ -24,8 +24,8 @@ const meta: Meta< typeof AlignmentMatrixControl > = { 'AlignmentMatrixControl.Icon': AlignmentMatrixControl.Icon, }, argTypes: { - onChange: { control: { type: null } }, - value: { control: { type: null } }, + onChange: { control: false }, + value: { control: false }, }, parameters: { actions: { argTypesRegex: '^on.*' }, diff --git a/packages/components/src/angle-picker-control/README.md b/packages/components/src/angle-picker-control/README.md index d9389c6564338f..9908282fd9ef9a 100644 --- a/packages/components/src/angle-picker-control/README.md +++ b/packages/components/src/angle-picker-control/README.md @@ -23,34 +23,35 @@ function Example() { ); } ``` + ## Props ### `as` -The HTML element or React component to render the component as. - - Type: `"symbol" | "object" | "a" | "abbr" | "address" | "area" | "article" | "aside" | "audio" | "b" | ...` - Required: No -### `label` +The HTML element or React component to render the component as. -Label to use for the angle picker. +### `label` - Type: `string` - Required: No - Default: `__( 'Angle' )` -### `onChange` +Label to use for the angle picker. -A function that receives the new value of the input. +### `onChange` - Type: `(value: number) => void` - Required: Yes -### `value` +A function that receives the new value of the input. -The current value of the input. The value represents an angle in degrees -and should be a value between 0 and 360. +### `value` - Type: `string | number` - Required: Yes + +The current value of the input. The value represents an angle in degrees +and should be a value between 0 and 360. diff --git a/packages/components/src/angle-picker-control/index.tsx b/packages/components/src/angle-picker-control/index.tsx index b824660fddb134..9c436c36caf660 100644 --- a/packages/components/src/angle-picker-control/index.tsx +++ b/packages/components/src/angle-picker-control/index.tsx @@ -57,12 +57,12 @@ function UnforwardedAnglePickerControl( <Flex { ...restProps } ref={ ref } className={ classes } gap={ 2 }> <FlexBlock> <NumberControl + __next40pxDefaultSize label={ label } className="components-angle-picker-control__input-field" max={ 360 } min={ 0 } onChange={ handleOnNumberChange } - size="__unstable-large" step="1" value={ value } spinControls="none" diff --git a/packages/components/src/angle-picker-control/stories/index.story.tsx b/packages/components/src/angle-picker-control/stories/index.story.tsx index ebbf3425d802f1..d909f1ed3d205a 100644 --- a/packages/components/src/angle-picker-control/stories/index.story.tsx +++ b/packages/components/src/angle-picker-control/stories/index.story.tsx @@ -17,8 +17,8 @@ const meta: Meta< typeof AnglePickerControl > = { title: 'Components/AnglePickerControl', component: AnglePickerControl, argTypes: { - as: { control: { type: null } }, - value: { control: { type: null } }, + as: { control: false }, + value: { control: false }, }, parameters: { actions: { argTypesRegex: '^on.*' }, diff --git a/packages/components/src/animate/style.scss b/packages/components/src/animate/style.scss index 1d64423e42f1f0..0375b116a552ff 100644 --- a/packages/components/src/animate/style.scss +++ b/packages/components/src/animate/style.scss @@ -1,7 +1,8 @@ .components-animate__appear { - animation: components-animate__appear-animation 0.1s cubic-bezier(0, 0, 0.2, 1) 0s; - animation-fill-mode: forwards; - @include reduce-motion("animation"); + @media not (prefers-reduced-motion) { + animation: components-animate__appear-animation 0.1s cubic-bezier(0, 0, 0.2, 1) 0s; + animation-fill-mode: forwards; + } &.is-from-top, &.is-from-top.is-from-left { @@ -29,16 +30,17 @@ } .components-animate__slide-in { - animation: components-animate__slide-in-animation 0.1s cubic-bezier(0, 0, 0.2, 1); - animation-fill-mode: forwards; - @include reduce-motion("animation"); + @media not (prefers-reduced-motion) { + animation: components-animate__slide-in-animation 0.1s cubic-bezier(0, 0, 0.2, 1); + animation-fill-mode: forwards; - &.is-from-left { - transform: translateX(+100%); - } + &.is-from-left { + transform: translateX(+100%); + } - &.is-from-right { - transform: translateX(-100%); + &.is-from-right { + transform: translateX(-100%); + } } } @@ -49,7 +51,9 @@ } .components-animate__loading { - animation: components-animate__loading 1.6s ease-in-out infinite; + @media not (prefers-reduced-motion) { + animation: components-animate__loading 1.6s ease-in-out infinite; + } } @keyframes components-animate__loading { diff --git a/packages/components/src/animation/index.tsx b/packages/components/src/animation/index.tsx index 6620f8d5d4ecae..1b6796cd61ee9b 100644 --- a/packages/components/src/animation/index.tsx +++ b/packages/components/src/animation/index.tsx @@ -9,5 +9,4 @@ export { motion as __unstableMotion, AnimatePresence as __unstableAnimatePresence, - MotionContext as __unstableMotionContext, } from 'framer-motion'; diff --git a/packages/components/src/autocomplete/autocompleter-ui.tsx b/packages/components/src/autocomplete/autocompleter-ui.tsx index 69105f6c9d3b44..dbbbe724113d7c 100644 --- a/packages/components/src/autocomplete/autocompleter-ui.tsx +++ b/packages/components/src/autocomplete/autocompleter-ui.tsx @@ -57,6 +57,7 @@ function ListBox( { key={ option.key } id={ `components-autocomplete-item-${ instanceId }-${ option.key }` } role="option" + __next40pxDefaultSize aria-selected={ index === selectedIndex } accessibleWhenDisabled disabled={ option.isDisabled } diff --git a/packages/components/src/badge/README.md b/packages/components/src/badge/README.md new file mode 100644 index 00000000000000..2100939684a856 --- /dev/null +++ b/packages/components/src/badge/README.md @@ -0,0 +1,24 @@ +# Badge + +<!-- This file is generated automatically and cannot be edited directly. Make edits via TypeScript types and TSDocs. --> + +🔒 This component is locked as a [private API](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-private-apis/). We do not yet recommend using this outside of the Gutenberg project. + +<p class="callout callout-info">See the <a href="https://wordpress.github.io/gutenberg/?path=/docs/components-badge--docs">WordPress Storybook</a> for more detailed, interactive documentation.</p> + +## Props + +### `children` + + - Type: `string` + - Required: Yes + +Text to display inside the badge. + +### `intent` + + - Type: `"default" | "info" | "success" | "warning" | "error"` + - Required: No + - Default: `default` + +Badge variant. diff --git a/packages/components/src/badge/docs-manifest.json b/packages/components/src/badge/docs-manifest.json new file mode 100644 index 00000000000000..3b70c0ef228432 --- /dev/null +++ b/packages/components/src/badge/docs-manifest.json @@ -0,0 +1,5 @@ +{ + "$schema": "../../schemas/docs-manifest.json", + "displayName": "Badge", + "filePath": "./index.tsx" +} diff --git a/packages/components/src/badge/index.tsx b/packages/components/src/badge/index.tsx new file mode 100644 index 00000000000000..e57be66db0a7aa --- /dev/null +++ b/packages/components/src/badge/index.tsx @@ -0,0 +1,68 @@ +/** + * External dependencies + */ +import clsx from 'clsx'; + +/** + * WordPress dependencies + */ +import { info, caution, error, published } from '@wordpress/icons'; + +/** + * Internal dependencies + */ +import type { BadgeProps } from './types'; +import type { WordPressComponentProps } from '../context'; +import Icon from '../icon'; + +/** + * Returns an icon based on the badge context. + * + * @return The corresponding icon for the provided context. + */ +function contextBasedIcon( intent: BadgeProps[ 'intent' ] = 'default' ) { + switch ( intent ) { + case 'info': + return info; + case 'success': + return published; + case 'warning': + return caution; + case 'error': + return error; + default: + return null; + } +} + +function Badge( { + className, + intent = 'default', + children, + ...props +}: WordPressComponentProps< BadgeProps, 'span', false > ) { + const icon = contextBasedIcon( intent ); + const hasIcon = !! icon; + + return ( + <span + className={ clsx( 'components-badge', className, { + [ `is-${ intent }` ]: intent, + 'has-icon': hasIcon, + } ) } + { ...props } + > + { hasIcon && ( + <Icon + icon={ icon } + size={ 16 } + fill="currentColor" + className="components-badge__icon" + /> + ) } + <span className="components-badge__content">{ children }</span> + </span> + ); +} + +export default Badge; diff --git a/packages/components/src/badge/stories/index.story.tsx b/packages/components/src/badge/stories/index.story.tsx new file mode 100644 index 00000000000000..bbe0bef2a79472 --- /dev/null +++ b/packages/components/src/badge/stories/index.story.tsx @@ -0,0 +1,54 @@ +/** + * External dependencies + */ +import type { Meta, StoryObj } from '@storybook/react'; + +/** + * Internal dependencies + */ +import Badge from '..'; + +const meta: Meta< typeof Badge > = { + component: Badge, + title: 'Components/Containers/Badge', + id: 'components-badge', + tags: [ 'status-private' ], +}; + +export default meta; + +type Story = StoryObj< typeof meta >; + +export const Default: Story = { + args: { + children: 'Code is Poetry', + }, +}; + +export const Info: Story = { + args: { + ...Default.args, + intent: 'info', + }, +}; + +export const Success: Story = { + args: { + ...Default.args, + intent: 'success', + }, +}; + +export const Warning: Story = { + args: { + ...Default.args, + intent: 'warning', + }, +}; + +export const Error: Story = { + args: { + ...Default.args, + intent: 'error', + }, +}; diff --git a/packages/components/src/badge/styles.scss b/packages/components/src/badge/styles.scss new file mode 100644 index 00000000000000..d3f82482cf7743 --- /dev/null +++ b/packages/components/src/badge/styles.scss @@ -0,0 +1,49 @@ +$badge-colors: ( + "info": #3858e9, + "warning": $alert-yellow, + "error": $alert-red, + "success": $alert-green, +); + +.components-badge { + @include reset; + + background-color: color-mix(in srgb, $white 90%, var(--base-color)); + color: color-mix(in srgb, $black 50%, var(--base-color)); + padding: 0 $grid-unit-10; + min-height: $grid-unit-30; + max-width: 100%; + border-radius: $radius-small; + font-size: $font-size-small; + font-weight: 400; + line-height: $font-line-height-small; + display: inline-flex; + align-items: center; + gap: 2px; + + &:where(.is-default) { + background-color: $gray-100; + color: $gray-800; + } + + &.has-icon { + padding-inline-start: $grid-unit-05; + } + + // Generate color variants + @each $type, $color in $badge-colors { + &.is-#{$type} { + --base-color: #{$color}; + } + } +} + +.components-badge__icon { + flex-shrink: 0; +} + +.components-badge__content { + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} diff --git a/packages/components/src/badge/test/index.tsx b/packages/components/src/badge/test/index.tsx new file mode 100644 index 00000000000000..114a8f426c7afd --- /dev/null +++ b/packages/components/src/badge/test/index.tsx @@ -0,0 +1,45 @@ +/** + * External dependencies + */ +import { render, screen } from '@testing-library/react'; + +/** + * Internal dependencies + */ +import _Badge from '..'; + +const testid = 'my-badge'; +const Badge = ( props: React.ComponentProps< typeof _Badge > ) => ( + <_Badge data-testid={ testid } { ...props } /> +); + +describe( 'Badge', () => { + it( 'should render correctly with default props', () => { + render( <Badge>Code is Poetry</Badge> ); + const badge = screen.getByTestId( testid ); + expect( badge ).toBeInTheDocument(); + expect( badge.tagName ).toBe( 'SPAN' ); + expect( badge ).toHaveClass( 'components-badge' ); + } ); + + it( 'should render as per its intent and contain an icon', () => { + render( <Badge intent="error">Code is Poetry</Badge> ); + const badge = screen.getByTestId( testid ); + expect( badge ).toHaveClass( 'components-badge', 'is-error' ); + expect( badge ).toHaveClass( 'has-icon' ); + } ); + + it( 'should combine custom className with default class', () => { + render( <Badge className="custom-class">Code is Poetry</Badge> ); + const badge = screen.getByTestId( testid ); + expect( badge ).toHaveClass( 'components-badge' ); + expect( badge ).toHaveClass( 'custom-class' ); + } ); + + it( 'should pass through additional props', () => { + render( <Badge data-testid="custom-badge">Code is Poetry</Badge> ); + const badge = screen.getByTestId( 'custom-badge' ); + expect( badge ).toHaveTextContent( 'Code is Poetry' ); + expect( badge ).toHaveClass( 'components-badge' ); + } ); +} ); diff --git a/packages/components/src/badge/types.ts b/packages/components/src/badge/types.ts new file mode 100644 index 00000000000000..91cd7c39b549bb --- /dev/null +++ b/packages/components/src/badge/types.ts @@ -0,0 +1,12 @@ +export type BadgeProps = { + /** + * Badge variant. + * + * @default 'default' + */ + intent?: 'default' | 'info' | 'success' | 'warning' | 'error'; + /** + * Text to display inside the badge. + */ + children: string; +}; diff --git a/packages/components/src/base-control/README.md b/packages/components/src/base-control/README.md index 839464b41260b5..2a82c19845e47b 100644 --- a/packages/components/src/base-control/README.md +++ b/packages/components/src/base-control/README.md @@ -25,71 +25,71 @@ const MyCustomTextareaControl = ({ children, ...baseProps }) => ( ); ); ``` + ## Props ### `__nextHasNoMarginBottom` -Start opting into the new margin-free styles that will become the default in a future version. - - Type: `boolean` - Required: No - Default: `false` -### `as` +Start opting into the new margin-free styles that will become the default in a future version. -The HTML element or React component to render the component as. +### `as` - Type: `"symbol" | "object" | "label" | "a" | "abbr" | "address" | "area" | "article" | "aside" | "audio" | "b" | "base" | "bdi" | "bdo" | "big" | "blockquote" | "body" | "br" | "button" | ... 516 more ... | ("view" & FunctionComponent<...>)` - Required: No -### `className` +The HTML element or React component to render the component as. +### `className` - Type: `string` - Required: No ### `children` -The content to be displayed within the `BaseControl`. - - Type: `ReactNode` - Required: Yes +The content to be displayed within the `BaseControl`. + ### `help` + - Type: `ReactNode` + - Required: No + Additional description for the control. Only use for meaningful description or instructions for the control. An element containing the description will be programmatically associated to the BaseControl by the means of an `aria-describedby` attribute. - - Type: `ReactNode` - - Required: No - ### `hideLabelFromVision` -If true, the label will only be visible to screen readers. - - Type: `boolean` - Required: No - Default: `false` +If true, the label will only be visible to screen readers. + ### `id` + - Type: `string` + - Required: No + The HTML `id` of the control element (passed in as a child to `BaseControl`) to which labels and help text are being generated. This is necessary to accessibly associate the label with that element. The recommended way is to use the `useBaseControlProps` hook, which takes care of generating a unique `id` for you. Otherwise, if you choose to pass an explicit `id` to this prop, you are responsible for ensuring the uniqueness of the `id`. - - Type: `string` - - Required: No - ### `label` -If this property is added, a label will be generated using label property as the content. - - Type: `ReactNode` - Required: No +If this property is added, a label will be generated using label property as the content. + ## Subcomponents ### BaseControl.VisualLabel @@ -113,18 +113,19 @@ const MyBaseControl = () => ( </BaseControl> ); ``` + #### Props ##### `as` -The HTML element or React component to render the component as. - - Type: `"symbol" | "object" | "label" | "a" | "abbr" | "address" | "area" | "article" | "aside" | "audio" | ...` - Required: No -##### `children` +The HTML element or React component to render the component as. -The content to be displayed within the `BaseControl.VisualLabel`. +##### `children` - Type: `ReactNode` - Required: Yes + +The content to be displayed within the `BaseControl.VisualLabel`. diff --git a/packages/components/src/base-control/stories/index.story.tsx b/packages/components/src/base-control/stories/index.story.tsx index 90517c75b5e95e..69188f869656da 100644 --- a/packages/components/src/base-control/stories/index.story.tsx +++ b/packages/components/src/base-control/stories/index.story.tsx @@ -18,7 +18,7 @@ const meta: Meta< typeof BaseControl > = { 'BaseControl.VisualLabel': BaseControl.VisualLabel, }, argTypes: { - children: { control: { type: null } }, + children: { control: false }, help: { control: { type: 'text' } }, label: { control: { type: 'text' } }, }, diff --git a/packages/components/src/border-box-control/border-box-control-split-controls/component.tsx b/packages/components/src/border-box-control/border-box-control-split-controls/component.tsx index 8f125cdb8f9261..94e1728076b181 100644 --- a/packages/components/src/border-box-control/border-box-control-split-controls/component.tsx +++ b/packages/components/src/border-box-control/border-box-control-split-controls/component.tsx @@ -67,12 +67,13 @@ const BorderBoxControlSplitControls = ( isCompact: true, __experimentalIsRenderedInSidebar, size, + __shouldNotWarnDeprecated36pxSize: true, }; const mergedRef = useMergeRefs( [ setPopoverAnchor, forwardedRef ] ); return ( - <Grid { ...otherProps } ref={ mergedRef } gap={ 4 }> + <Grid { ...otherProps } ref={ mergedRef } gap={ 3 }> <BorderBoxControlVisualizer value={ value } size={ size } /> <BorderControl className={ centeredClassName } diff --git a/packages/components/src/border-box-control/border-box-control/component.tsx b/packages/components/src/border-box-control/border-box-control/component.tsx index d2d77adc69eb89..0e84c7b56ee483 100644 --- a/packages/components/src/border-box-control/border-box-control/component.tsx +++ b/packages/components/src/border-box-control/border-box-control/component.tsx @@ -118,6 +118,7 @@ const UnconnectedBorderBoxControl = ( __experimentalIsRenderedInSidebar={ __experimentalIsRenderedInSidebar } + __shouldNotWarnDeprecated36pxSize size={ size } /> ) : ( diff --git a/packages/components/src/border-box-control/stories/index.story.tsx b/packages/components/src/border-box-control/stories/index.story.tsx index 0a961d34fb93d3..3f118ae7cb37cc 100644 --- a/packages/components/src/border-box-control/stories/index.story.tsx +++ b/packages/components/src/border-box-control/stories/index.story.tsx @@ -20,7 +20,7 @@ const meta: Meta< typeof BorderBoxControl > = { component: BorderBoxControl, argTypes: { onChange: { action: 'onChange' }, - value: { control: { type: null } }, + value: { control: false }, }, parameters: { controls: { expanded: true }, diff --git a/packages/components/src/border-control/border-control/component.tsx b/packages/components/src/border-control/border-control/component.tsx index 31eeb166a2d60f..f71599b274778d 100644 --- a/packages/components/src/border-control/border-control/component.tsx +++ b/packages/components/src/border-control/border-control/component.tsx @@ -75,6 +75,8 @@ const UnconnectedBorderControl = ( /> <HStack spacing={ 4 } className={ innerWrapperClassName }> <UnitControl + __next40pxDefaultSize={ __next40pxDefaultSize } + __shouldNotWarnDeprecated36pxSize prefix={ <Spacer marginRight={ 1 } marginBottom={ 0 }> <BorderControlDropdown @@ -122,6 +124,7 @@ const UnconnectedBorderControl = ( value={ widthValue || undefined } withInputField={ false } __next40pxDefaultSize={ __next40pxDefaultSize } + __shouldNotWarnDeprecated36pxSize /> ) } </HStack> diff --git a/packages/components/src/border-control/border-control/hook.ts b/packages/components/src/border-control/border-control/hook.ts index 67af7ce42416c3..9b0f064c51921f 100644 --- a/packages/components/src/border-control/border-control/hook.ts +++ b/packages/components/src/border-control/border-control/hook.ts @@ -38,6 +38,7 @@ export function useBorderControl( width, __experimentalIsRenderedInSidebar = false, __next40pxDefaultSize, + __shouldNotWarnDeprecated36pxSize, ...otherProps } = useContextSystem( props, 'BorderControl' ); @@ -45,6 +46,7 @@ export function useBorderControl( componentName: 'BorderControl', __next40pxDefaultSize, size, + __shouldNotWarnDeprecated36pxSize, } ); const computedSize = diff --git a/packages/components/src/border-control/stories/index.story.tsx b/packages/components/src/border-control/stories/index.story.tsx index 3b5fa740d092d6..ca8505c01a0ba0 100644 --- a/packages/components/src/border-control/stories/index.story.tsx +++ b/packages/components/src/border-control/stories/index.story.tsx @@ -23,7 +23,7 @@ const meta: Meta< typeof BorderControl > = { action: 'onChange', }, width: { control: { type: 'text' } }, - value: { control: { type: null } }, + value: { control: false }, }, parameters: { controls: { expanded: true }, diff --git a/packages/components/src/border-control/test/index.js b/packages/components/src/border-control/test/index.js index ec7fb18837f97d..ff9007be28f1a2 100644 --- a/packages/components/src/border-control/test/index.js +++ b/packages/components/src/border-control/test/index.js @@ -1,7 +1,13 @@ /** * External dependencies */ -import { fireEvent, render, screen, waitFor } from '@testing-library/react'; +import { + fireEvent, + render, + screen, + waitFor, + within, +} from '@testing-library/react'; import userEvent from '@testing-library/user-event'; /** @@ -56,7 +62,7 @@ const getButton = ( name ) => { }; const getColorOption = ( color ) => { - return screen.getByRole( 'option', { name: `Color: ${ color }` } ); + return screen.getByRole( 'option', { name: `${ color }` } ); }; const queryButton = ( name ) => { @@ -131,9 +137,11 @@ describe( 'BorderControl', () => { await openPopover( user ); const customColorPicker = getButton( /Custom color picker/ ); - const colorSwatchButtons = screen.getAllByRole( 'option', { - name: /^Color:/, + const circularOptionPicker = screen.getByRole( 'listbox', { + name: 'Custom color picker.', } ); + const colorSwatchButtons = + within( circularOptionPicker ).getAllByRole( 'option' ); const styleLabel = screen.getByText( 'Style' ); const solidButton = getButton( 'Solid' ); const dashedButton = getButton( 'Dashed' ); diff --git a/packages/components/src/border-control/types.ts b/packages/components/src/border-control/types.ts index 8ab614907684d2..ecd3f67c9be08d 100644 --- a/packages/components/src/border-control/types.ts +++ b/packages/components/src/border-control/types.ts @@ -116,6 +116,13 @@ export type BorderControlProps = ColorProps & * @default false */ __next40pxDefaultSize?: boolean; + /** + * Do not throw a warning for the deprecated 36px default size. + * For internal components of other components that already throw the warning. + * + * @ignore + */ + __shouldNotWarnDeprecated36pxSize?: boolean; }; export type DropdownProps = ColorProps & diff --git a/packages/components/src/box-control/README.md b/packages/components/src/box-control/README.md index 77176b49eeb6d8..4c0f100065092e 100644 --- a/packages/components/src/box-control/README.md +++ b/packages/components/src/box-control/README.md @@ -1,106 +1,143 @@ # BoxControl -A control that lets users set values for top, right, bottom, and left. Can be used as an input control for values like `padding` or `margin`. +<!-- This file is generated automatically and cannot be edited directly. Make edits via TypeScript types and TSDocs. --> -## Usage +<p class="callout callout-info">See the <a href="https://wordpress.github.io/gutenberg/?path=/docs/components-boxcontrol--docs">WordPress Storybook</a> for more detailed, interactive documentation.</p> + +A control that lets users set values for top, right, bottom, and left. Can be +used as an input control for values like `padding` or `margin`. ```jsx import { useState } from 'react'; import { BoxControl } from '@wordpress/components'; function Example() { - const [ values, setValues ] = useState( { - top: '50px', - left: '10%', - right: '10%', - bottom: '50px', - } ); - - return ( - <BoxControl - values={ values } - onChange={ ( nextValues ) => setValues( nextValues ) } - /> - ); -} + const [ values, setValues ] = useState( { + top: '50px', + left: '10%', + right: '10%', + bottom: '50px', + } ); + + return ( + <BoxControl + __next40pxDefaultSize + values={ values } + onChange={ setValues } + /> + ); +}; ``` ## Props -### `allowReset`: `boolean` +### `__next40pxDefaultSize` + + - Type: `boolean` + - Required: No + - Default: `false` + +Start opting into the larger default height that will become the default size in a future version. + +### `allowReset` + + - Type: `boolean` + - Required: No + - Default: `true` If this property is true, a button to reset the box control is rendered. -- Required: No -- Default: `true` +### `id` -### `splitOnAxis`: `boolean` + - Type: `string` + - Required: No -If this property is true, when the box control is unlinked, vertical and horizontal controls can be used instead of updating individual sides. +The id to use as a base for the unique HTML id attribute of the control. -- Required: No -- Default: `false` +### `inputProps` -### `inputProps`: `object` + - Type: `UnitControlPassthroughProps` + - Required: No + - Default: `{ + min: 0, + }` -Props for the internal [UnitControl](../unit-control) components. +Props for the internal `UnitControl` components. -- Required: No -- Default: `{ min: 0 }` +### `label` -### `label`: `string` + - Type: `string` + - Required: No + - Default: `__( 'Box Control' )` Heading label for the control. -- Required: No -- Default: `__( 'Box Control' )` +### `onChange` -### `onChange`: `(next: BoxControlValue) => void` + - Type: `(next: BoxControlValue) => void` + - Required: No + - Default: `() => {}` A callback function when an input value changes. -- Required: Yes +### `presets` -### `resetValues`: `object` + - Type: `Preset[]` + - Required: No -The `top`, `right`, `bottom`, and `left` box dimension values to use when the control is reset. +Available presets to pick from. -- Required: No -- Default: `{ top: undefined, right: undefined, bottom: undefined, left: undefined }` +### `presetKey` -### `sides`: `string[]` + - Type: `string` + - Required: No -Collection of sides to allow control of. If omitted or empty, all sides will be available. Allowed values are "top", "right", "bottom", "left", "vertical", and "horizontal". +The key of the preset to apply. +If you provide a list of presets, you must provide a preset key to use. +The format of preset selected values is going to be `var:preset|${ presetKey }|${ presetSlug }` -- Required: No +### `resetValues` -### `units`: `WPUnitControlUnit[]` + - Type: `BoxControlValue` + - Required: No + - Default: `{ + top: undefined, + right: undefined, + bottom: undefined, + left: undefined, + }` -Collection of available units which are compatible with [UnitControl](../unit-control). +The `top`, `right`, `bottom`, and `left` box dimension values to use when the control is reset. -- Required: No +### `sides` -### `values`: `object` + - Type: `readonly (keyof BoxControlValue | "horizontal" | "vertical")[]` + - Required: No -The `top`, `right`, `bottom`, and `left` box dimension values. +Collection of sides to allow control of. If omitted or empty, all sides will be available. -- Required: No +Allowed values are "top", "right", "bottom", "left", "vertical", and "horizontal". -### `onMouseOver`: `function` +### `splitOnAxis` -A handler for onMouseOver events. + - Type: `boolean` + - Required: No + - Default: `false` -- Required: No +If this property is true, when the box control is unlinked, vertical and horizontal controls +can be used instead of updating individual sides. -### `onMouseOut`: `function` +### `units` -A handler for onMouseOut events. + - Type: `WPUnitControlUnit[]` + - Required: No + - Default: `CSS_UNITS` -- Required: No +Available units to select from. -### `__next40pxDefaultSize`: `boolean` +### `values` -Start opting into the larger default size that will become the default size in a future version. + - Type: `BoxControlValue` + - Required: No -- Required: No -- Default: `false` +The current values of the control, expressed as an object of `top`, `right`, `bottom`, and `left` values. diff --git a/packages/components/src/box-control/all-input-control.tsx b/packages/components/src/box-control/all-input-control.tsx deleted file mode 100644 index e9166ff7f692e8..00000000000000 --- a/packages/components/src/box-control/all-input-control.tsx +++ /dev/null @@ -1,108 +0,0 @@ -/** - * WordPress dependencies - */ -import { useInstanceId } from '@wordpress/compose'; -/** - * Internal dependencies - */ -import type { UnitControlProps } from '../unit-control/types'; -import { - FlexedRangeControl, - StyledUnitControl, -} from './styles/box-control-styles'; -import type { BoxControlInputControlProps } from './types'; -import { parseQuantityAndUnitFromRawValue } from '../unit-control'; -import { - LABELS, - applyValueToSides, - getAllValue, - isValuesMixed, - isValuesDefined, - CUSTOM_VALUE_SETTINGS, -} from './utils'; - -const noop = () => {}; - -export default function AllInputControl( { - __next40pxDefaultSize, - onChange = noop, - onFocus = noop, - values, - sides, - selectedUnits, - setSelectedUnits, - ...props -}: BoxControlInputControlProps ) { - const inputId = useInstanceId( AllInputControl, 'box-control-input-all' ); - - const allValue = getAllValue( values, selectedUnits, sides ); - const hasValues = isValuesDefined( values ); - const isMixed = hasValues && isValuesMixed( values, selectedUnits, sides ); - const allPlaceholder = isMixed ? LABELS.mixed : undefined; - - const [ parsedQuantity, parsedUnit ] = - parseQuantityAndUnitFromRawValue( allValue ); - - const handleOnFocus: React.FocusEventHandler< HTMLInputElement > = ( - event - ) => { - onFocus( event, { side: 'all' } ); - }; - - const onValueChange = ( next?: string ) => { - const isNumeric = next !== undefined && ! isNaN( parseFloat( next ) ); - const nextValue = isNumeric ? next : undefined; - const nextValues = applyValueToSides( values, nextValue, sides ); - - onChange( nextValues ); - }; - - const sliderOnChange = ( next?: number ) => { - onValueChange( - next !== undefined ? [ next, parsedUnit ].join( '' ) : undefined - ); - }; - - // Set selected unit so it can be used as fallback by unlinked controls - // when individual sides do not have a value containing a unit. - const handleOnUnitChange: UnitControlProps[ 'onUnitChange' ] = ( unit ) => { - const newUnits = applyValueToSides( selectedUnits, unit, sides ); - setSelectedUnits( newUnits ); - }; - - return ( - <> - <StyledUnitControl - { ...props } - __next40pxDefaultSize={ __next40pxDefaultSize } - className="component-box-control__unit-control" - disableUnits={ isMixed } - id={ inputId } - isPressEnterToChange - value={ allValue } - onChange={ onValueChange } - onUnitChange={ handleOnUnitChange } - onFocus={ handleOnFocus } - placeholder={ allPlaceholder } - label={ LABELS.all } - hideLabelFromVision - /> - - <FlexedRangeControl - __nextHasNoMarginBottom - __next40pxDefaultSize={ __next40pxDefaultSize } - aria-controls={ inputId } - label={ LABELS.all } - hideLabelFromVision - onChange={ sliderOnChange } - min={ 0 } - max={ CUSTOM_VALUE_SETTINGS[ parsedUnit ?? 'px' ]?.max ?? 10 } - step={ - CUSTOM_VALUE_SETTINGS[ parsedUnit ?? 'px' ]?.step ?? 0.1 - } - value={ parsedQuantity ?? 0 } - withInputField={ false } - /> - </> - ); -} diff --git a/packages/components/src/box-control/axial-input-controls.tsx b/packages/components/src/box-control/axial-input-controls.tsx deleted file mode 100644 index c6c0181f6c871c..00000000000000 --- a/packages/components/src/box-control/axial-input-controls.tsx +++ /dev/null @@ -1,163 +0,0 @@ -/** - * WordPress dependencies - */ -import { useInstanceId } from '@wordpress/compose'; -/** - * Internal dependencies - */ -import { parseQuantityAndUnitFromRawValue } from '../unit-control/utils'; -import Tooltip from '../tooltip'; -import { CUSTOM_VALUE_SETTINGS, LABELS } from './utils'; -import { - FlexedBoxControlIcon, - FlexedRangeControl, - InputWrapper, - StyledUnitControl, -} from './styles/box-control-styles'; -import type { BoxControlInputControlProps } from './types'; - -const groupedSides = [ 'vertical', 'horizontal' ] as const; -type GroupedSide = ( typeof groupedSides )[ number ]; - -export default function AxialInputControls( { - __next40pxDefaultSize, - onChange, - onFocus, - values, - selectedUnits, - setSelectedUnits, - sides, - ...props -}: BoxControlInputControlProps ) { - const generatedId = useInstanceId( - AxialInputControls, - `box-control-input` - ); - - const createHandleOnFocus = - ( side: GroupedSide ) => - ( event: React.FocusEvent< HTMLInputElement > ) => { - if ( ! onFocus ) { - return; - } - onFocus( event, { side } ); - }; - - const handleOnValueChange = ( side: GroupedSide, next?: string ) => { - if ( ! onChange ) { - return; - } - const nextValues = { ...values }; - const isNumeric = next !== undefined && ! isNaN( parseFloat( next ) ); - const nextValue = isNumeric ? next : undefined; - - if ( side === 'vertical' ) { - nextValues.top = nextValue; - nextValues.bottom = nextValue; - } - - if ( side === 'horizontal' ) { - nextValues.left = nextValue; - nextValues.right = nextValue; - } - - onChange( nextValues ); - }; - - const createHandleOnUnitChange = - ( side: GroupedSide ) => ( next?: string ) => { - const newUnits = { ...selectedUnits }; - - if ( side === 'vertical' ) { - newUnits.top = next; - newUnits.bottom = next; - } - - if ( side === 'horizontal' ) { - newUnits.left = next; - newUnits.right = next; - } - - setSelectedUnits( newUnits ); - }; - - // Filter sides if custom configuration provided, maintaining default order. - const filteredSides = sides?.length - ? groupedSides.filter( ( side ) => sides.includes( side ) ) - : groupedSides; - - return ( - <> - { filteredSides.map( ( side ) => { - const [ parsedQuantity, parsedUnit ] = - parseQuantityAndUnitFromRawValue( - side === 'vertical' ? values.top : values.left - ); - const selectedUnit = - side === 'vertical' - ? selectedUnits.top - : selectedUnits.left; - - const inputId = [ generatedId, side ].join( '-' ); - - return ( - <InputWrapper key={ side }> - <FlexedBoxControlIcon side={ side } sides={ sides } /> - <Tooltip placement="top-end" text={ LABELS[ side ] }> - <StyledUnitControl - { ...props } - __next40pxDefaultSize={ __next40pxDefaultSize } - className="component-box-control__unit-control" - id={ inputId } - isPressEnterToChange - value={ [ - parsedQuantity, - selectedUnit ?? parsedUnit, - ].join( '' ) } - onChange={ ( newValue ) => - handleOnValueChange( side, newValue ) - } - onUnitChange={ createHandleOnUnitChange( - side - ) } - onFocus={ createHandleOnFocus( side ) } - label={ LABELS[ side ] } - hideLabelFromVision - key={ side } - /> - </Tooltip> - <FlexedRangeControl - __nextHasNoMarginBottom - __next40pxDefaultSize={ __next40pxDefaultSize } - aria-controls={ inputId } - label={ LABELS[ side ] } - hideLabelFromVision - onChange={ ( newValue ) => - handleOnValueChange( - side, - newValue !== undefined - ? [ - newValue, - selectedUnit ?? parsedUnit, - ].join( '' ) - : undefined - ) - } - min={ 0 } - max={ - CUSTOM_VALUE_SETTINGS[ selectedUnit ?? 'px' ] - ?.max ?? 10 - } - step={ - CUSTOM_VALUE_SETTINGS[ selectedUnit ?? 'px' ] - ?.step ?? 0.1 - } - value={ parsedQuantity ?? 0 } - withInputField={ false } - /> - </InputWrapper> - ); - } ) } - </> - ); -} diff --git a/packages/components/src/box-control/docs-manifest.json b/packages/components/src/box-control/docs-manifest.json new file mode 100644 index 00000000000000..c506e7a9e27eef --- /dev/null +++ b/packages/components/src/box-control/docs-manifest.json @@ -0,0 +1,5 @@ +{ + "$schema": "../../schemas/docs-manifest.json", + "displayName": "BoxControl", + "filePath": "./index.tsx" +} diff --git a/packages/components/src/box-control/index.tsx b/packages/components/src/box-control/index.tsx index 41e95aa88bea37..d4d4b03f893036 100644 --- a/packages/components/src/box-control/index.tsx +++ b/packages/components/src/box-control/index.tsx @@ -9,13 +9,10 @@ import { __ } from '@wordpress/i18n'; * Internal dependencies */ import { BaseControl } from '../base-control'; -import AllInputControl from './all-input-control'; -import InputControls from './input-controls'; -import AxialInputControls from './axial-input-controls'; +import InputControl from './input-control'; import LinkedButton from './linked-button'; import { Grid } from '../grid'; import { - FlexedBoxControlIcon, InputWrapper, ResetButton, LinkedButtonWrapper, @@ -24,8 +21,9 @@ import { parseQuantityAndUnitFromRawValue } from '../unit-control/utils'; import { DEFAULT_VALUES, getInitialSide, - isValuesMixed, + isValueMixed, isValuesDefined, + getAllowedSides, } from './utils'; import { useControlledState } from '../utils/hooks'; import type { @@ -33,6 +31,7 @@ import type { BoxControlProps, BoxControlValue, } from './types'; +import { maybeWarnDeprecated36pxSize } from '../utils/deprecated-36px-size'; const defaultInputProps = { min: 0, @@ -51,23 +50,24 @@ function useUniqueId( idProp?: string ) { * used as an input control for values like `padding` or `margin`. * * ```jsx + * import { useState } from 'react'; * import { BoxControl } from '@wordpress/components'; - * import { useState } from '@wordpress/element'; * * function Example() { - * const [ values, setValues ] = useState( { - * top: '50px', - * left: '10%', - * right: '10%', - * bottom: '50px', - * } ); + * const [ values, setValues ] = useState( { + * top: '50px', + * left: '10%', + * right: '10%', + * bottom: '50px', + * } ); * - * return ( - * <BoxControl - * values={ values } - * onChange={ ( nextValues ) => setValues( nextValues ) } - * /> - * ); + * return ( + * <BoxControl + * __next40pxDefaultSize + * values={ values } + * onChange={ setValues } + * /> + * ); * }; * ``` */ @@ -83,6 +83,8 @@ function BoxControl( { splitOnAxis = false, allowReset = true, resetValues = DEFAULT_VALUES, + presets, + presetKey, onMouseOver, onMouseOut, }: BoxControlProps ) { @@ -95,7 +97,7 @@ function BoxControl( { const [ isDirty, setIsDirty ] = useState( hasInitialValue ); const [ isLinked, setIsLinked ] = useState( - ! hasInitialValue || ! isValuesMixed( inputValues ) || hasOneSide + ! hasInitialValue || ! isValueMixed( inputValues ) || hasOneSide ); const [ side, setSide ] = useState< BoxControlIconProps[ 'side' ] >( @@ -141,6 +143,8 @@ function BoxControl( { }; const inputControlProps = { + onMouseOver, + onMouseOut, ...inputProps, onChange: handleOnChange, onFocus: handleOnFocus, @@ -150,11 +154,18 @@ function BoxControl( { setSelectedUnits, sides, values: inputValues, - onMouseOver, - onMouseOut, __next40pxDefaultSize, + presets, + presetKey, }; + maybeWarnDeprecated36pxSize( { + componentName: 'BoxControl', + __next40pxDefaultSize, + size: undefined, + } ); + const sidesToRender = getAllowedSides( sides ); + return ( <Grid id={ id } @@ -168,8 +179,7 @@ function BoxControl( { </BaseControl.VisualLabel> { isLinked && ( <InputWrapper> - <FlexedBoxControlIcon side={ side } sides={ sides } /> - <AllInputControl { ...inputControlProps } /> + <InputControl side="all" { ...inputControlProps } /> </InputWrapper> ) } { ! hasOneSide && ( @@ -181,12 +191,24 @@ function BoxControl( { </LinkedButtonWrapper> ) } - { ! isLinked && splitOnAxis && ( - <AxialInputControls { ...inputControlProps } /> - ) } - { ! isLinked && ! splitOnAxis && ( - <InputControls { ...inputControlProps } /> - ) } + { ! isLinked && + splitOnAxis && + [ 'vertical', 'horizontal' ].map( ( axis ) => ( + <InputControl + key={ axis } + side={ axis as 'horizontal' | 'vertical' } + { ...inputControlProps } + /> + ) ) } + { ! isLinked && + ! splitOnAxis && + Array.from( sidesToRender ).map( ( axis ) => ( + <InputControl + key={ axis } + side={ axis } + { ...inputControlProps } + /> + ) ) } { allowReset && ( <ResetButton className="component-box-control__reset-button" diff --git a/packages/components/src/box-control/input-control.tsx b/packages/components/src/box-control/input-control.tsx new file mode 100644 index 00000000000000..27dff1991d8572 --- /dev/null +++ b/packages/components/src/box-control/input-control.tsx @@ -0,0 +1,299 @@ +/** + * WordPress dependencies + */ +import { useInstanceId } from '@wordpress/compose'; +import { __ } from '@wordpress/i18n'; +import { useState } from '@wordpress/element'; +import { settings } from '@wordpress/icons'; + +/** + * Internal dependencies + */ +import Tooltip from '../tooltip'; +import { parseQuantityAndUnitFromRawValue } from '../unit-control/utils'; +import { + CUSTOM_VALUE_SETTINGS, + getMergedValue, + getAllowedSides, + getPresetIndexFromValue, + getPresetValueFromIndex, + isValuePreset, + isValuesDefined, + isValueMixed, + LABELS, +} from './utils'; +import { + FlexedBoxControlIcon, + FlexedRangeControl, + InputWrapper, + StyledUnitControl, +} from './styles/box-control-styles'; +import type { BoxControlInputControlProps, BoxControlValue } from './types'; +import Button from '../button'; + +const noop = () => {}; + +function getSidesToModify( + side: BoxControlInputControlProps[ 'side' ], + sides: BoxControlInputControlProps[ 'sides' ], + isAlt?: boolean +) { + const allowedSides = getAllowedSides( sides ); + + let modifiedSides: ( keyof BoxControlValue )[] = []; + switch ( side ) { + case 'all': + modifiedSides = [ 'top', 'bottom', 'left', 'right' ]; + break; + case 'horizontal': + modifiedSides = [ 'left', 'right' ]; + break; + case 'vertical': + modifiedSides = [ 'top', 'bottom' ]; + break; + default: + modifiedSides = [ side ]; + } + + if ( isAlt ) { + switch ( side ) { + case 'top': + modifiedSides.push( 'bottom' ); + break; + case 'bottom': + modifiedSides.push( 'top' ); + break; + case 'left': + modifiedSides.push( 'left' ); + break; + case 'right': + modifiedSides.push( 'right' ); + break; + } + } + + return modifiedSides.filter( ( s ) => allowedSides.has( s ) ); +} + +export default function BoxInputControl( { + __next40pxDefaultSize, + onChange = noop, + onFocus = noop, + values, + selectedUnits, + setSelectedUnits, + sides, + side, + min = 0, + presets, + presetKey, + ...props +}: BoxControlInputControlProps ) { + const defaultValuesToModify = getSidesToModify( side, sides ); + + const handleOnFocus = ( event: React.FocusEvent< HTMLInputElement > ) => { + onFocus( event, { side } ); + }; + + const handleOnChange = ( nextValues: BoxControlValue ) => { + onChange( nextValues ); + }; + + const handleRawOnValueChange = ( next?: string ) => { + const nextValues = { ...values }; + defaultValuesToModify.forEach( ( modifiedSide ) => { + nextValues[ modifiedSide ] = next; + } ); + + handleOnChange( nextValues ); + }; + + const handleOnValueChange = ( + next?: string, + extra?: { event: React.SyntheticEvent< Element, Event > } + ) => { + const nextValues = { ...values }; + const isNumeric = next !== undefined && ! isNaN( parseFloat( next ) ); + const nextValue = isNumeric ? next : undefined; + const modifiedSides = getSidesToModify( + side, + sides, + /** + * Supports changing pair sides. For example, holding the ALT key + * when changing the TOP will also update BOTTOM. + */ + // @ts-expect-error - TODO: event.altKey is only present when the change event was + // triggered by a keyboard event. Should this feature be implemented differently so + // it also works with drag events? + !! extra?.event.altKey + ); + + modifiedSides.forEach( ( modifiedSide ) => { + nextValues[ modifiedSide ] = nextValue; + } ); + + handleOnChange( nextValues ); + }; + + const handleOnUnitChange = ( next?: string ) => { + const newUnits = { ...selectedUnits }; + defaultValuesToModify.forEach( ( modifiedSide ) => { + newUnits[ modifiedSide ] = next; + } ); + setSelectedUnits( newUnits ); + }; + + const mergedValue = getMergedValue( values, defaultValuesToModify ); + const hasValues = isValuesDefined( values ); + const isMixed = + hasValues && + defaultValuesToModify.length > 1 && + isValueMixed( values, defaultValuesToModify ); + const [ parsedQuantity, parsedUnit ] = + parseQuantityAndUnitFromRawValue( mergedValue ); + const computedUnit = hasValues + ? parsedUnit + : selectedUnits[ defaultValuesToModify[ 0 ] ]; + const generatedId = useInstanceId( BoxInputControl, 'box-control-input' ); + const inputId = [ generatedId, side ].join( '-' ); + const isMixedUnit = + defaultValuesToModify.length > 1 && + mergedValue === undefined && + defaultValuesToModify.some( + ( s ) => selectedUnits[ s ] !== computedUnit + ); + const usedValue = + mergedValue === undefined && computedUnit ? computedUnit : mergedValue; + const mixedPlaceholder = isMixed || isMixedUnit ? __( 'Mixed' ) : undefined; + const hasPresets = presets && presets.length > 0 && presetKey; + const hasPresetValue = + hasPresets && + mergedValue !== undefined && + ! isMixed && + isValuePreset( mergedValue, presetKey ); + const [ showCustomValueControl, setShowCustomValueControl ] = useState( + ! hasPresets || + ( ! hasPresetValue && ! isMixed && mergedValue !== undefined ) + ); + const presetIndex = hasPresetValue + ? getPresetIndexFromValue( mergedValue, presetKey, presets ) + : undefined; + const marks = hasPresets + ? [ { value: 0, label: '', tooltip: __( 'None' ) } ].concat( + presets.map( ( preset, index ) => ( { + value: index + 1, + label: '', + tooltip: preset.name ?? preset.slug, + } ) ) + ) + : []; + + return ( + <InputWrapper key={ `box-control-${ side }` } expanded> + <FlexedBoxControlIcon side={ side } sides={ sides } /> + { showCustomValueControl && ( + <> + <Tooltip placement="top-end" text={ LABELS[ side ] }> + <StyledUnitControl + { ...props } + min={ min } + __shouldNotWarnDeprecated36pxSize + __next40pxDefaultSize={ __next40pxDefaultSize } + className="component-box-control__unit-control" + id={ inputId } + isPressEnterToChange + disableUnits={ isMixed || isMixedUnit } + value={ usedValue } + onChange={ handleOnValueChange } + onUnitChange={ handleOnUnitChange } + onFocus={ handleOnFocus } + label={ LABELS[ side ] } + placeholder={ mixedPlaceholder } + hideLabelFromVision + /> + </Tooltip> + + <FlexedRangeControl + __nextHasNoMarginBottom + __next40pxDefaultSize={ __next40pxDefaultSize } + __shouldNotWarnDeprecated36pxSize + aria-controls={ inputId } + label={ LABELS[ side ] } + hideLabelFromVision + onChange={ ( newValue ) => { + handleOnValueChange( + newValue !== undefined + ? [ newValue, computedUnit ].join( '' ) + : undefined + ); + } } + min={ isFinite( min ) ? min : 0 } + max={ + CUSTOM_VALUE_SETTINGS[ computedUnit ?? 'px' ] + ?.max ?? 10 + } + step={ + CUSTOM_VALUE_SETTINGS[ computedUnit ?? 'px' ] + ?.step ?? 0.1 + } + value={ parsedQuantity ?? 0 } + withInputField={ false } + /> + </> + ) } + + { hasPresets && ! showCustomValueControl && ( + <FlexedRangeControl + __next40pxDefaultSize + className="spacing-sizes-control__range-control" + value={ presetIndex !== undefined ? presetIndex + 1 : 0 } + onChange={ ( newIndex ) => { + const newValue = + newIndex === 0 || newIndex === undefined + ? undefined + : getPresetValueFromIndex( + newIndex - 1, + presetKey, + presets + ); + handleRawOnValueChange( newValue ); + } } + withInputField={ false } + aria-valuenow={ + presetIndex !== undefined ? presetIndex + 1 : 0 + } + aria-valuetext={ + marks[ presetIndex !== undefined ? presetIndex + 1 : 0 ] + .tooltip + } + renderTooltipContent={ ( index ) => + marks[ ! index ? 0 : index ].tooltip + } + min={ 0 } + max={ marks.length - 1 } + marks={ marks } + label={ LABELS[ side ] } + hideLabelFromVision + __nextHasNoMarginBottom + /> + ) } + + { hasPresets && ( + <Button + label={ + showCustomValueControl + ? __( 'Use size preset' ) + : __( 'Set custom size' ) + } + icon={ settings } + onClick={ () => { + setShowCustomValueControl( ! showCustomValueControl ); + } } + isPressed={ showCustomValueControl } + size="small" + iconSize={ 24 } + /> + ) } + </InputWrapper> + ); +} diff --git a/packages/components/src/box-control/input-controls.tsx b/packages/components/src/box-control/input-controls.tsx deleted file mode 100644 index 553f547abf9b0d..00000000000000 --- a/packages/components/src/box-control/input-controls.tsx +++ /dev/null @@ -1,165 +0,0 @@ -/** - * WordPress dependencies - */ -import { useInstanceId } from '@wordpress/compose'; -/** - * Internal dependencies - */ -import Tooltip from '../tooltip'; -import { parseQuantityAndUnitFromRawValue } from '../unit-control/utils'; -import { ALL_SIDES, CUSTOM_VALUE_SETTINGS, LABELS } from './utils'; -import { - FlexedBoxControlIcon, - FlexedRangeControl, - InputWrapper, - StyledUnitControl, -} from './styles/box-control-styles'; -import type { BoxControlInputControlProps, BoxControlValue } from './types'; - -const noop = () => {}; - -export default function BoxInputControls( { - __next40pxDefaultSize, - onChange = noop, - onFocus = noop, - values, - selectedUnits, - setSelectedUnits, - sides, - ...props -}: BoxControlInputControlProps ) { - const generatedId = useInstanceId( BoxInputControls, 'box-control-input' ); - - const createHandleOnFocus = - ( side: keyof BoxControlValue ) => - ( event: React.FocusEvent< HTMLInputElement > ) => { - onFocus( event, { side } ); - }; - - const handleOnChange = ( nextValues: BoxControlValue ) => { - onChange( nextValues ); - }; - - const handleOnValueChange = ( - side: keyof BoxControlValue, - next?: string, - extra?: { event: React.SyntheticEvent< Element, Event > } - ) => { - const nextValues = { ...values }; - const isNumeric = next !== undefined && ! isNaN( parseFloat( next ) ); - const nextValue = isNumeric ? next : undefined; - - nextValues[ side ] = nextValue; - - /** - * Supports changing pair sides. For example, holding the ALT key - * when changing the TOP will also update BOTTOM. - */ - // @ts-expect-error - TODO: event.altKey is only present when the change event was - // triggered by a keyboard event. Should this feature be implemented differently so - // it also works with drag events? - if ( extra?.event.altKey ) { - switch ( side ) { - case 'top': - nextValues.bottom = nextValue; - break; - case 'bottom': - nextValues.top = nextValue; - break; - case 'left': - nextValues.right = nextValue; - break; - case 'right': - nextValues.left = nextValue; - break; - } - } - - handleOnChange( nextValues ); - }; - - const createHandleOnUnitChange = - ( side: keyof BoxControlValue ) => ( next?: string ) => { - const newUnits = { ...selectedUnits }; - newUnits[ side ] = next; - setSelectedUnits( newUnits ); - }; - - // Filter sides if custom configuration provided, maintaining default order. - const filteredSides = sides?.length - ? ALL_SIDES.filter( ( side ) => sides.includes( side ) ) - : ALL_SIDES; - - return ( - <> - { filteredSides.map( ( side ) => { - const [ parsedQuantity, parsedUnit ] = - parseQuantityAndUnitFromRawValue( values[ side ] ); - - const computedUnit = values[ side ] - ? parsedUnit - : selectedUnits[ side ]; - - const inputId = [ generatedId, side ].join( '-' ); - - return ( - <InputWrapper key={ `box-control-${ side }` } expanded> - <FlexedBoxControlIcon side={ side } sides={ sides } /> - <Tooltip placement="top-end" text={ LABELS[ side ] }> - <StyledUnitControl - { ...props } - __next40pxDefaultSize={ __next40pxDefaultSize } - className="component-box-control__unit-control" - id={ inputId } - isPressEnterToChange - value={ [ parsedQuantity, computedUnit ].join( - '' - ) } - onChange={ ( nextValue, extra ) => - handleOnValueChange( - side, - nextValue, - extra - ) - } - onUnitChange={ createHandleOnUnitChange( - side - ) } - onFocus={ createHandleOnFocus( side ) } - label={ LABELS[ side ] } - hideLabelFromVision - /> - </Tooltip> - - <FlexedRangeControl - __nextHasNoMarginBottom - __next40pxDefaultSize={ __next40pxDefaultSize } - aria-controls={ inputId } - label={ LABELS[ side ] } - hideLabelFromVision - onChange={ ( newValue ) => { - handleOnValueChange( - side, - newValue !== undefined - ? [ newValue, computedUnit ].join( '' ) - : undefined - ); - } } - min={ 0 } - max={ - CUSTOM_VALUE_SETTINGS[ computedUnit ?? 'px' ] - ?.max ?? 10 - } - step={ - CUSTOM_VALUE_SETTINGS[ computedUnit ?? 'px' ] - ?.step ?? 0.1 - } - value={ parsedQuantity ?? 0 } - withInputField={ false } - /> - </InputWrapper> - ); - } ) } - </> - ); -} diff --git a/packages/components/src/box-control/stories/index.story.tsx b/packages/components/src/box-control/stories/index.story.tsx index 783f9d047b1bb0..aa16547d24ab18 100644 --- a/packages/components/src/box-control/stories/index.story.tsx +++ b/packages/components/src/box-control/stories/index.story.tsx @@ -17,7 +17,7 @@ const meta: Meta< typeof BoxControl > = { title: 'Components/BoxControl', component: BoxControl, argTypes: { - values: { control: { type: null } }, + values: { control: false }, }, parameters: { actions: { argTypesRegex: '^on.*' }, @@ -49,6 +49,7 @@ const TemplateControlled: StoryFn< typeof BoxControl > = ( props ) => { export const Default = TemplateUncontrolled.bind( {} ); Default.args = { label: 'Label', + __next40pxDefaultSize: true, }; export const Controlled = TemplateControlled.bind( {} ); @@ -80,3 +81,15 @@ AxialControlsWithSingleSide.args = { sides: [ 'horizontal' ], splitOnAxis: true, }; + +export const ControlWithPresets = TemplateControlled.bind( {} ); +ControlWithPresets.args = { + ...Default.args, + presets: [ + { name: 'Small', slug: 'small', value: '4px' }, + { name: 'Medium', slug: 'medium', value: '8px' }, + { name: 'Large', slug: 'large', value: '12px' }, + { name: 'Extra Large', slug: 'extra-large', value: '16px' }, + ], + presetKey: 'padding', +}; diff --git a/packages/components/src/box-control/test/index.tsx b/packages/components/src/box-control/test/index.tsx index 681e7721d0c13a..185a18b258951f 100644 --- a/packages/components/src/box-control/test/index.tsx +++ b/packages/components/src/box-control/test/index.tsx @@ -15,11 +15,14 @@ import { useState } from '@wordpress/element'; import BoxControl from '..'; import type { BoxControlProps, BoxControlValue } from '../types'; -const Example = ( extraProps: Omit< BoxControlProps, 'onChange' > ) => { +const ControlledBoxControl = ( + extraProps: Omit< BoxControlProps, 'onChange' > +) => { const [ state, setState ] = useState< BoxControlValue >(); return ( <BoxControl + __next40pxDefaultSize values={ state } onChange={ ( next ) => setState( next ) } { ...extraProps } @@ -27,10 +30,17 @@ const Example = ( extraProps: Omit< BoxControlProps, 'onChange' > ) => { ); }; +const UncontrolledBoxControl = ( { + onChange = () => {}, + ...props +}: Omit< BoxControlProps, 'onChange' > & { + onChange?: BoxControlProps[ 'onChange' ]; +} ) => <BoxControl __next40pxDefaultSize onChange={ onChange } { ...props } />; + describe( 'BoxControl', () => { describe( 'Basic rendering', () => { it( 'should render a box control input', () => { - render( <BoxControl onChange={ () => {} } /> ); + render( <UncontrolledBoxControl /> ); expect( screen.getByRole( 'group', { name: 'Box Control' } ) @@ -43,7 +53,7 @@ describe( 'BoxControl', () => { it( 'should update values when interacting with input', async () => { const user = userEvent.setup(); - render( <BoxControl onChange={ () => {} } /> ); + render( <UncontrolledBoxControl /> ); const input = screen.getByRole( 'textbox', { name: 'All sides' } ); @@ -54,7 +64,7 @@ describe( 'BoxControl', () => { } ); it( 'should update input values when interacting with slider', () => { - render( <BoxControl onChange={ () => {} } /> ); + render( <UncontrolledBoxControl /> ); const slider = screen.getByRole( 'slider' ); @@ -68,7 +78,7 @@ describe( 'BoxControl', () => { it( 'should update slider values when interacting with input', async () => { const user = userEvent.setup(); - render( <BoxControl onChange={ () => {} } /> ); + render( <UncontrolledBoxControl /> ); const input = screen.getByRole( 'textbox', { name: 'All sides', @@ -82,7 +92,7 @@ describe( 'BoxControl', () => { } ); it( 'should render the number input with a default min value of 0', () => { - render( <BoxControl onChange={ () => {} } /> ); + render( <UncontrolledBoxControl /> ); const input = screen.getByRole( 'textbox', { name: 'All sides' } ); @@ -91,10 +101,7 @@ describe( 'BoxControl', () => { it( 'should pass down `inputProps` to the underlying number input', () => { render( - <BoxControl - onChange={ () => {} } - inputProps={ { min: 10, max: 50 } } - /> + <UncontrolledBoxControl inputProps={ { min: 10, max: 50 } } /> ); const input = screen.getByRole( 'textbox', { name: 'All sides' } ); @@ -108,7 +115,7 @@ describe( 'BoxControl', () => { it( 'should reset values when clicking Reset', async () => { const user = userEvent.setup(); - render( <BoxControl onChange={ () => {} } /> ); + render( <UncontrolledBoxControl /> ); const input = screen.getByRole( 'textbox', { name: 'All sides', @@ -127,7 +134,7 @@ describe( 'BoxControl', () => { it( 'should reset values when clicking Reset, if controlled', async () => { const user = userEvent.setup(); - render( <Example /> ); + render( <ControlledBoxControl /> ); const input = screen.getByRole( 'textbox', { name: 'All sides', @@ -146,7 +153,7 @@ describe( 'BoxControl', () => { it( 'should reset values when clicking Reset, if controlled <-> uncontrolled state changes', async () => { const user = userEvent.setup(); - render( <Example /> ); + render( <ControlledBoxControl /> ); const input = screen.getByRole( 'textbox', { name: 'All sides', @@ -166,7 +173,9 @@ describe( 'BoxControl', () => { const user = userEvent.setup(); const spyChange = jest.fn(); - render( <BoxControl onChange={ ( v ) => spyChange( v ) } /> ); + render( + <UncontrolledBoxControl onChange={ ( v ) => spyChange( v ) } /> + ); const input = screen.getByRole( 'textbox', { name: 'All sides', @@ -196,7 +205,7 @@ describe( 'BoxControl', () => { it( 'should update a single side value when unlinked', async () => { const user = userEvent.setup(); - render( <Example /> ); + render( <ControlledBoxControl /> ); await user.click( screen.getByRole( 'button', { name: 'Unlink sides' } ) @@ -224,7 +233,7 @@ describe( 'BoxControl', () => { it( 'should update a single side value when using slider unlinked', async () => { const user = userEvent.setup(); - render( <Example /> ); + render( <ControlledBoxControl /> ); await user.click( screen.getByRole( 'button', { name: 'Unlink sides' } ) @@ -252,7 +261,7 @@ describe( 'BoxControl', () => { it( 'should update a whole axis when value is changed when unlinked', async () => { const user = userEvent.setup(); - render( <Example splitOnAxis /> ); + render( <ControlledBoxControl splitOnAxis /> ); await user.click( screen.getByRole( 'button', { name: 'Unlink sides' } ) @@ -276,7 +285,7 @@ describe( 'BoxControl', () => { it( 'should update a whole axis using a slider when value is changed when unlinked', async () => { const user = userEvent.setup(); - render( <Example splitOnAxis /> ); + render( <ControlledBoxControl splitOnAxis /> ); await user.click( screen.getByRole( 'button', { name: 'Unlink sides' } ) @@ -300,7 +309,7 @@ describe( 'BoxControl', () => { it( 'should show "Mixed" label when sides have different values but are linked', async () => { const user = userEvent.setup(); - render( <Example /> ); + render( <ControlledBoxControl /> ); const unlinkButton = screen.getByRole( 'button', { name: 'Unlink sides', @@ -330,7 +339,7 @@ describe( 'BoxControl', () => { const user = userEvent.setup(); // Render control. - render( <BoxControl onChange={ () => {} } /> ); + render( <UncontrolledBoxControl /> ); // Make unit selection on all input control. await user.selectOptions( @@ -362,7 +371,7 @@ describe( 'BoxControl', () => { const user = userEvent.setup(); // Render control. - const { rerender } = render( <BoxControl onChange={ () => {} } /> ); + const { rerender } = render( <UncontrolledBoxControl /> ); // Make unit selection on all input control. await user.selectOptions( @@ -390,9 +399,7 @@ describe( 'BoxControl', () => { } ); // Rerender with individual side value & confirm unit is selected. - rerender( - <BoxControl values={ { top: '2.5em' } } onChange={ () => {} } /> - ); + rerender( <UncontrolledBoxControl values={ { top: '2.5em' } } /> ); const rerenderedControls = screen.getAllByRole( 'combobox', { name: 'Select unit', @@ -414,7 +421,7 @@ describe( 'BoxControl', () => { const user = userEvent.setup(); const onChangeSpy = jest.fn(); - render( <BoxControl onChange={ onChangeSpy } /> ); + render( <UncontrolledBoxControl onChange={ onChangeSpy } /> ); const valueInput = screen.getByRole( 'textbox', { name: 'All sides', @@ -443,7 +450,7 @@ describe( 'BoxControl', () => { const user = userEvent.setup(); const setState = jest.fn(); - render( <BoxControl onChange={ setState } /> ); + render( <UncontrolledBoxControl onChange={ setState } /> ); await user.selectOptions( screen.getByRole( 'combobox', { diff --git a/packages/components/src/box-control/types.ts b/packages/components/src/box-control/types.ts index 5f4071aeed88a7..43629e09258a58 100644 --- a/packages/components/src/box-control/types.ts +++ b/packages/components/src/box-control/types.ts @@ -15,70 +15,103 @@ export type CustomValueUnits = { [ key: string ]: { max: number; step: number }; }; +export interface Preset { + name: string; + slug: string; + value?: string; +} + type UnitControlPassthroughProps = Omit< UnitControlProps, - 'label' | 'onChange' | 'onFocus' | 'onMouseOver' | 'onMouseOut' | 'units' + 'label' | 'onChange' | 'onFocus' | 'units' >; -export type BoxControlProps = Pick< - UnitControlProps, - 'onMouseOver' | 'onMouseOut' | 'units' -> & { - /** - * If this property is true, a button to reset the box control is rendered. - * - * @default true - */ - allowReset?: boolean; - /** - * The id to use as a base for the unique HTML id attribute of the control. - */ - id?: string; - /** - * Props for the internal `UnitControl` components. - * - * @default { min: 0 } - */ - inputProps?: UnitControlPassthroughProps; - /** - * Heading label for the control. - * - * @default __( 'Box Control' ) - */ - label?: string; +type DeprecatedBoxControlProps = { /** - * A callback function when an input value changes. + * @deprecated Pass to the `inputProps` prop instead. + * @ignore */ - onChange: ( next: BoxControlValue ) => void; + onMouseOver?: UnitControlProps[ 'onMouseOver' ]; /** - * The `top`, `right`, `bottom`, and `left` box dimension values to use when the control is reset. - * - * @default { top: undefined, right: undefined, bottom: undefined, left: undefined } + * @deprecated Pass to the `inputProps` prop instead. + * @ignore */ - resetValues?: BoxControlValue; - /** - * Collection of sides to allow control of. If omitted or empty, all sides will be available. - */ - sides?: readonly ( keyof BoxControlValue | 'horizontal' | 'vertical' )[]; - /** - * If this property is true, when the box control is unlinked, vertical and horizontal controls - * can be used instead of updating individual sides. - * - * @default false - */ - splitOnAxis?: boolean; - /** - * The current values of the control, expressed as an object of `top`, `right`, `bottom`, and `left` values. - */ - values?: BoxControlValue; - /** - * Start opting into the larger default height that will become the default size in a future version. - * - * @default false - */ - __next40pxDefaultSize?: boolean; + onMouseOut?: UnitControlProps[ 'onMouseOut' ]; }; +export type BoxControlProps = Pick< UnitControlProps, 'units' > & + DeprecatedBoxControlProps & { + /** + * If this property is true, a button to reset the box control is rendered. + * + * @default true + */ + allowReset?: boolean; + /** + * The id to use as a base for the unique HTML id attribute of the control. + */ + id?: string; + /** + * Props for the internal `UnitControl` components. + * + * @default { min: 0 } + */ + inputProps?: UnitControlPassthroughProps; + /** + * Heading label for the control. + * + * @default __( 'Box Control' ) + */ + label?: string; + /** + * A callback function when an input value changes. + */ + onChange: ( next: BoxControlValue ) => void; + /** + * The `top`, `right`, `bottom`, and `left` box dimension values to use when the control is reset. + * + * @default { top: undefined, right: undefined, bottom: undefined, left: undefined } + */ + resetValues?: BoxControlValue; + /** + * Collection of sides to allow control of. If omitted or empty, all sides will be available. + * + * Allowed values are "top", "right", "bottom", "left", "vertical", and "horizontal". + */ + sides?: readonly ( + | keyof BoxControlValue + | 'horizontal' + | 'vertical' + )[]; + /** + * If this property is true, when the box control is unlinked, vertical and horizontal controls + * can be used instead of updating individual sides. + * + * @default false + */ + splitOnAxis?: boolean; + /** + * The current values of the control, expressed as an object of `top`, `right`, `bottom`, and `left` values. + */ + values?: BoxControlValue; + /** + * Start opting into the larger default height that will become the default size in a future version. + * + * @default false + */ + __next40pxDefaultSize?: boolean; + /** + * Available presets to pick from. + */ + presets?: Preset[]; + /** + * The key of the preset to apply. + * If you provide a list of presets, you must provide a preset key to use. + * The format of preset selected values is going to be `var:preset|${ presetKey }|${ presetSlug }` + */ + presetKey?: string; + }; + export type BoxControlInputControlProps = UnitControlPassthroughProps & { onChange?: ( nextValues: BoxControlValue ) => void; onFocus?: ( @@ -93,8 +126,18 @@ export type BoxControlInputControlProps = UnitControlPassthroughProps & { ) => void; selectedUnits: BoxControlValue; setSelectedUnits: React.Dispatch< React.SetStateAction< BoxControlValue > >; - sides: BoxControlProps[ 'sides' ]; values: BoxControlValue; + /** + * Collection of sides to allow control of. If omitted or empty, all sides will be available. + */ + sides: BoxControlProps[ 'sides' ]; + /** + * Side represents the current side being rendered by the input. + * It can be a concrete side like: left, right, top, bottom or a combined one like: horizontal, vertical. + */ + side: keyof typeof LABELS; + presets?: Preset[]; + presetKey?: string; }; export type BoxControlIconProps = { diff --git a/packages/components/src/box-control/utils.ts b/packages/components/src/box-control/utils.ts index 73c7f4a6a46cfb..26bdae4e559511 100644 --- a/packages/components/src/box-control/utils.ts +++ b/packages/components/src/box-control/utils.ts @@ -6,12 +6,14 @@ import { __ } from '@wordpress/i18n'; /** * Internal dependencies */ -import { parseQuantityAndUnitFromRawValue } from '../unit-control/utils'; import type { + BoxControlInputControlProps, BoxControlProps, BoxControlValue, CustomValueUnits, + Preset, } from './types'; +import deprecated from '@wordpress/deprecated'; export const CUSTOM_VALUE_SETTINGS: CustomValueUnits = { px: { max: 300, step: 1 }, @@ -50,7 +52,6 @@ export const LABELS = { bottom: __( 'Bottom side' ), left: __( 'Left side' ), right: __( 'Right side' ), - mixed: __( 'Mixed' ), vertical: __( 'Top and bottom sides' ), horizontal: __( 'Left and right sides' ), }; @@ -82,56 +83,46 @@ function mode< T >( arr: T[] ) { } /** - * Gets the 'all' input value and unit from values data. + * Gets the merged input value and unit from values data. * * @param values Box values. - * @param selectedUnits Box units. * @param availableSides Available box sides to evaluate. * * @return A value + unit for the 'all' input. */ -export function getAllValue( +export function getMergedValue( values: BoxControlValue = {}, - selectedUnits?: BoxControlValue, availableSides: BoxControlProps[ 'sides' ] = ALL_SIDES ) { const sides = normalizeSides( availableSides ); - const parsedQuantitiesAndUnits = sides.map( ( side ) => - parseQuantityAndUnitFromRawValue( values[ side ] ) - ); - const allParsedQuantities = parsedQuantitiesAndUnits.map( - ( value ) => value[ 0 ] ?? '' - ); - const allParsedUnits = parsedQuantitiesAndUnits.map( - ( value ) => value[ 1 ] - ); - - const commonQuantity = allParsedQuantities.every( - ( v ) => v === allParsedQuantities[ 0 ] - ) - ? allParsedQuantities[ 0 ] - : ''; - - /** - * The typeof === 'number' check is important. On reset actions, the incoming value - * may be null or an empty string. - * - * Also, the value may also be zero (0), which is considered a valid unit value. - * - * typeof === 'number' is more specific for these cases, rather than relying on a - * simple truthy check. - */ - let commonUnit; - if ( typeof commonQuantity === 'number' ) { - commonUnit = mode( allParsedUnits ); - } else { - // Set meaningful unit selection if no commonQuantity and user has previously - // selected units without assigning values while controls were unlinked. - commonUnit = - getAllUnitFallback( selectedUnits ) ?? mode( allParsedUnits ); + if ( + sides.every( + ( side: keyof BoxControlValue ) => + values[ side ] === values[ sides[ 0 ] ] + ) + ) { + return values[ sides[ 0 ] ]; } - return [ commonQuantity, commonUnit ].join( '' ); + return undefined; +} + +/** + * Checks if the values are mixed. + * + * @param values Box values. + * @param availableSides Available box sides to evaluate. + * @return Whether the values are mixed. + */ +export function isValueMixed( + values: BoxControlValue = {}, + availableSides: BoxControlProps[ 'sides' ] = ALL_SIDES +) { + const sides = normalizeSides( availableSides ); + return sides.some( + ( side: keyof BoxControlValue ) => + values[ side ] !== values[ sides[ 0 ] ] + ); } /** @@ -150,26 +141,6 @@ export function getAllUnitFallback( selectedUnits?: BoxControlValue ) { return mode( filteredUnits ); } -/** - * Checks to determine if values are mixed. - * - * @param values Box values. - * @param selectedUnits Box units. - * @param sides Available box sides to evaluate. - * - * @return Whether values are mixed. - */ -export function isValuesMixed( - values: BoxControlValue = {}, - selectedUnits?: BoxControlValue, - sides: BoxControlProps[ 'sides' ] = ALL_SIDES -) { - const allValue = getAllValue( values, selectedUnits, sides ); - const isMixed = isNaN( parseFloat( allValue ) ); - - return isMixed; -} - /** * Checks to determine if values are defined. * @@ -239,6 +210,8 @@ export function normalizeSides( sides: BoxControlProps[ 'sides' ] ) { * Applies a value to an object representing top, right, bottom and left sides * while taking into account any custom side configuration. * + * @deprecated + * * @param currentValues The current values for each side. * @param newValue The value to apply to the sides object. * @param sides Array defining valid sides. @@ -250,6 +223,10 @@ export function applyValueToSides( newValue?: string, sides?: BoxControlProps[ 'sides' ] ): BoxControlValue { + deprecated( 'applyValueToSides', { + since: '6.8', + version: '7.0', + } ); const newValues = { ...currentValues }; if ( sides?.length ) { @@ -270,3 +247,88 @@ export function applyValueToSides( return newValues; } + +/** + * Return the allowed sides based on the sides configuration. + * + * @param sides Sides configuration. + * @return Allowed sides. + */ +export function getAllowedSides( + sides: BoxControlInputControlProps[ 'sides' ] +) { + const allowedSides: Set< keyof BoxControlValue > = new Set( + ! sides ? ALL_SIDES : [] + ); + sides?.forEach( ( allowedSide ) => { + if ( allowedSide === 'vertical' ) { + allowedSides.add( 'top' ); + allowedSides.add( 'bottom' ); + } else if ( allowedSide === 'horizontal' ) { + allowedSides.add( 'right' ); + allowedSides.add( 'left' ); + } else { + allowedSides.add( allowedSide ); + } + } ); + return allowedSides; +} + +/** + * Checks if a value is a preset value. + * + * @param value The value to check. + * @param presetKey The preset key to check against. + * @return Whether the value is a preset value. + */ +export function isValuePreset( value: string, presetKey: string ) { + return value.startsWith( `var:preset|${ presetKey }|` ); +} + +/** + * Returns the index of the preset value in the presets array. + * + * @param value The value to check. + * @param presetKey The preset key to check against. + * @param presets The array of presets to search. + * @return The index of the preset value in the presets array. + */ +export function getPresetIndexFromValue( + value: string, + presetKey: string, + presets: Preset[] +) { + if ( ! isValuePreset( value, presetKey ) ) { + return undefined; + } + + const match = value.match( + new RegExp( `^var:preset\\|${ presetKey }\\|(.+)$` ) + ); + if ( ! match ) { + return undefined; + } + const slug = match[ 1 ]; + const index = presets.findIndex( ( preset ) => { + return preset.slug === slug; + } ); + + return index !== -1 ? index : undefined; +} + +/** + * Returns the preset value from the index. + * + * @param index The index of the preset value in the presets array. + * @param presetKey The preset key to check against. + * @param presets The array of presets to search. + * @return The preset value from the index. + */ +export function getPresetValueFromIndex( + index: number, + presetKey: string, + presets: Preset[] +) { + const preset = presets[ index ]; + return `var:preset|${ presetKey }|${ preset.slug }`; +} diff --git a/packages/components/src/button-group/README.md b/packages/components/src/button-group/README.md index 5c0179d6877af9..579103dc70e062 100644 --- a/packages/components/src/button-group/README.md +++ b/packages/components/src/button-group/README.md @@ -1,5 +1,9 @@ # ButtonGroup +<div class="callout callout-alert"> + This component is deprecated. Use `ToggleGroupControl` instead. +</div> + ButtonGroup can be used to group any related buttons together. To emphasize related buttons, a group should share a common container. ![ButtonGroup component](https://wordpress.org/gutenberg/files/2018/12/s_96EC471FE9C9D91A996770229947AAB54A03351BDE98F444FD3C1BF0CED365EA_1541792995815_ButtonGroup.png) diff --git a/packages/components/src/button-group/index.tsx b/packages/components/src/button-group/index.tsx index fb2659c2a0d7de..e073b0c3b359b8 100644 --- a/packages/components/src/button-group/index.tsx +++ b/packages/components/src/button-group/index.tsx @@ -8,6 +8,7 @@ import type { ForwardedRef } from 'react'; * WordPress dependencies */ import { forwardRef } from '@wordpress/element'; +import deprecated from '@wordpress/deprecated'; /** * Internal dependencies @@ -19,9 +20,16 @@ function UnforwardedButtonGroup( props: WordPressComponentProps< ButtonGroupProps, 'div', false >, ref: ForwardedRef< HTMLDivElement > ) { - const { className, ...restProps } = props; + const { className, __shouldNotWarnDeprecated, ...restProps } = props; const classes = clsx( 'components-button-group', className ); + if ( ! __shouldNotWarnDeprecated ) { + deprecated( 'wp.components.ButtonGroup', { + since: '6.8', + alternative: 'wp.components.__experimentalToggleGroupControl', + } ); + } + return ( <div ref={ ref } role="group" className={ classes } { ...restProps } /> ); @@ -31,6 +39,8 @@ function UnforwardedButtonGroup( * ButtonGroup can be used to group any related buttons together. To emphasize * related buttons, a group should share a common container. * + * @deprecated Use `ToggleGroupControl` instead. + * * ```jsx * import { Button, ButtonGroup } from '@wordpress/components'; * diff --git a/packages/components/src/button-group/stories/index.story.tsx b/packages/components/src/button-group/stories/index.story.tsx index f6af2416977f40..a2df76004d4385 100644 --- a/packages/components/src/button-group/stories/index.story.tsx +++ b/packages/components/src/button-group/stories/index.story.tsx @@ -9,11 +9,18 @@ import type { Meta, StoryObj } from '@storybook/react'; import ButtonGroup from '..'; import Button from '../../button'; +/** + * ButtonGroup can be used to group any related buttons together. + * To emphasize related buttons, a group should share a common container. + * + * This component is deprecated. Use `ToggleGroupControl` instead. + */ const meta: Meta< typeof ButtonGroup > = { - title: 'Components/ButtonGroup', + title: 'Components (Deprecated)/ButtonGroup', + id: 'components-buttongroup', component: ButtonGroup, argTypes: { - children: { control: { type: null } }, + children: { control: false }, }, parameters: { controls: { expanded: true }, diff --git a/packages/components/src/button-group/types.ts b/packages/components/src/button-group/types.ts index 0bc162d5cf1c74..57388c7b5fc095 100644 --- a/packages/components/src/button-group/types.ts +++ b/packages/components/src/button-group/types.ts @@ -8,4 +8,11 @@ export type ButtonGroupProps = { * The children elements. */ children: ReactNode; + /** + * Do not throw a warning for component deprecation. + * For internal components of other components that already throw the warning. + * + * @ignore + */ + __shouldNotWarnDeprecated?: boolean; }; diff --git a/packages/components/src/button/README.md b/packages/components/src/button/README.md index d458771494a338..c67c795addbf4d 100644 --- a/packages/components/src/button/README.md +++ b/packages/components/src/button/README.md @@ -1,304 +1,194 @@ # Button -Buttons let users take actions and make choices with a single click or tap. +<!-- This file is generated automatically and cannot be edited directly. Make edits via TypeScript types and TSDocs. --> -![Button components](https://make.wordpress.org/design/files/2019/03/button.png) +<p class="callout callout-info">See the <a href="https://wordpress.github.io/gutenberg/?path=/docs/components-button--docs">WordPress Storybook</a> for more detailed, interactive documentation.</p> -## Design guidelines - -### Usage - -Buttons tell users what actions they can take and give them a way to interact with the interface. You’ll find them throughout a UI, particularly in places like: - -- Modals -- Forms -- Toolbars - -### Best practices - -Buttons should: - -- **Be clearly and accurately labeled.** -- **Clearly communicate that clicking or tapping will trigger an action.** -- **Use established colors appropriately.** For example, only use red buttons for actions that are difficult or impossible to undo. -- **Prioritize the most important actions.** This helps users focus. Too many calls to action on one screen can be confusing, making users unsure what to do next. -- **Have consistent locations in the interface.** - -### Content guidelines - -Buttons should be clear and predictable—users should be able to anticipate what will happen when they click a button. Never deceive a user by mislabeling a button. - -Buttons text should lead with a strong verb that encourages action, and add a noun that clarifies what will actually change. The only exceptions are common actions like Save, Close, Cancel, or OK. Otherwise, use the {verb}+{noun} format to ensure that your button gives the user enough information. - -Button text should also be quickly scannable — avoid unnecessary words and articles like the, an, or a. - -### Types - -#### Link button - -Link buttons have low emphasis. They don’t stand out much on the page, so they’re used for less-important actions. What’s less important can vary based on context, but it’s usually a supplementary action to the main action we want someone to take. Link buttons are also useful when you don’t want to distract from the content. - -![Link button](https://make.wordpress.org/design/files/2019/03/link-button.png) - -#### Default button - -Default buttons have medium emphasis. The button appearance helps differentiate them from the page background, so they’re useful when you want more emphasis than a link button offers. - -![Default button](https://make.wordpress.org/design/files/2019/03/default-button.png) - -#### Primary button - -Primary buttons have high emphasis. Their color fill and shadow means they pop off the background. - -Since a high-emphasis button commands the most attention, a layout should contain a single primary button. This makes it clear that other buttons have less importance and helps users understand when an action requires their attention. - -![Primary button](https://make.wordpress.org/design/files/2019/03/primary-button.png) - -#### Text label - -All button types use text labels to describe the action that happens when a user taps a button. If there’s no text label, there needs to be a [label](#label) added and an icon to signify what the button does. - -![](https://make.wordpress.org/design/files/2019/03/do-link-button.png) - -**Do** -Use color to distinguish link button labels from other text. - -![](https://make.wordpress.org/design/files/2019/03/dont-wrap-button-text.png) - -**Don’t** -Don’t wrap button text. For maximum legibility, keep text labels on a single line. - -### Hierarchy - -![A layout with a single prominent button](https://make.wordpress.org/design/files/2019/03/button.png) - -A layout should contain a single prominently-located button. If multiple buttons are required, a single high-emphasis button can be joined by medium- and low-emphasis buttons mapped to less-important actions. When using multiple buttons, make sure the available state of one button doesn’t look like the disabled state of another. - -![A diagram showing high emphasis at the top, medium emphasis in the middle, and low emphasis at the bottom](https://make.wordpress.org/design/files/2019/03/button-hierarchy.png) - -A button’s level of emphasis helps determine its appearance, typography, and placement. - -#### Placement - -Use button types to express different emphasis levels for all the actions a user can perform. - -![A link, default, and primary button](https://make.wordpress.org/design/files/2019/03/button-layout.png) - -This screen layout uses: - -1. A primary button for high emphasis. -2. A default button for medium emphasis. -3. A link button for low emphasis. - -Placement best practices: - -- **Do**: When using multiple buttons in a row, show users which action is more important by placing it next to a button with a lower emphasis (e.g. a primary button next to a default button, or a default button next to a link button). -- **Don’t**: Don’t place two primary buttons next to one another — they compete for focus. Only use one primary button per view. -- **Don’t**: Don’t place a button below another button if there is space to place them side by side. -- **Caution**: Avoid using too many buttons on a single page. When designing pages in the app or website, think about the most important actions for users to take. Too many calls to action can cause confusion and make users unsure what to do next — we always want users to feel confident and capable. - -## Development guidelines - -### Usage - -Renders a button with default style. +Lets users take actions and make choices with a single click or tap. ```jsx import { Button } from '@wordpress/components'; - -const MyButton = () => <Button variant="secondary">Click me!</Button>; +const Mybutton = () => ( + <Button + variant="primary" + onClick={ handleClick } + > + Click here + </Button> +); ``` -### Props +## Props -The presence of a `href` prop determines whether an `anchor` element is rendered instead of a `button`. +### `__next40pxDefaultSize` -Props not included in this set will be applied to the `a` or `button` element. + - Type: `boolean` + - Required: No + - Default: `false` -#### `accessibleWhenDisabled`: `boolean` - -Whether to keep the button focusable when disabled. +Start opting into the larger default height that will become the +default size in a future version. -In most cases, it is recommended to set this to `true`. Disabling a control without maintaining focusability can cause accessibility issues, by hiding their presence from screen reader users, or by preventing focus from returning to a trigger element. +### `accessibleWhenDisabled` -Learn more about the [focusability of disabled controls](https://www.w3.org/WAI/ARIA/apg/practices/keyboard-interface/#focusabilityofdisabledcontrols) in the WAI-ARIA Authoring Practices Guide. + - Type: `boolean` + - Required: No + - Default: `false` -- Required: No -- Default: `false` +Whether to keep the button focusable when disabled. -#### `children`: `ReactNode` +In most cases, it is recommended to set this to `true`. Disabling a control without maintaining focusability +can cause accessibility issues, by hiding their presence from screen reader users, +or by preventing focus from returning to a trigger element. -The button's children. +Learn more about the [focusability of disabled controls](https://www.w3.org/WAI/ARIA/apg/practices/keyboard-interface/#focusabilityofdisabledcontrols) +in the WAI-ARIA Authoring Practices Guide. -- Required: No +### `children` -#### `className`: `string` + - Type: `ReactNode` + - Required: No -An optional additional class name to apply to the rendered button. +The button's children. -- Required: No +### `description` -#### `description`: `string` + - Type: `string` + - Required: No -An accessible description for the button. +A visually hidden accessible description for the button. -- Required: No +### `disabled` -#### `disabled`: `boolean` + - Type: `boolean` + - Required: No -Whether the button is disabled. If `true`, this will force a `button` element to be rendered, even when an `href` is given. +Whether the button is disabled. If `true`, this will force a `button` element +to be rendered, even when an `href` is given. In most cases, it is recommended to also set the `accessibleWhenDisabled` prop to `true`. -- Required: No +### `href` -#### `href`: `string` + - Type: `string` + - Required: Yes If provided, renders `a` instead of `button`. -- Required: No +### `icon` -#### `icon`: `IconProps< unknown >[ 'icon' ]` + - Type: `IconType` + - Required: No -If provided, renders an [Icon](/packages/components/src/icon/README.md) component inside the button. +If provided, renders an Icon component inside the button. -- Required: No +### `iconPosition` -#### `iconPosition`: `'left' | 'right'` + - Type: `"left" | "right"` + - Required: No + - Default: `'left'` -If provided with `icon`, sets the position of icon relative to the `text`. Available options are `left|right`. +If provided with `icon`, sets the position of icon relative to the `text`. -- Required: No -- Default: `left` +### `iconSize` -#### `iconSize`: `IconProps< unknown >[ 'size' ]` + - Type: `number` + - Required: No -If provided with `icon`, sets the icon size. Please refer to the [Icon](/packages/components/src/icon/README.md) component for more details regarding the default value of its `size` prop. +If provided with `icon`, sets the icon size. +Please refer to the Icon component for more details regarding +the default value of its `size` prop. -- Required: No +### `isBusy` -#### `isBusy`: `boolean` + - Type: `boolean` + - Required: No Indicates activity while a action is being performed. -- Required: No +### `isDestructive` -#### `isDestructive`: `boolean` + - Type: `boolean` + - Required: No Renders a red text-based button style to indicate destructive behavior. -- Required: No - -#### `isLink`: `boolean` - -Deprecated: Renders a button with an anchor style. -Use `variant` prop with `link` value instead. +### `isPressed` -- Required: No -- Default: `false` - -#### `isPressed`: `boolean` + - Type: `boolean` + - Required: No Renders a pressed button style. -If the native `aria-pressed` attribute is also set, it will take precedence. - -- Required: No - -#### `isPrimary`: `boolean` - -Deprecated: Renders a primary button style. -Use `variant` prop with `primary` value instead. +### `label` -- Required: No -- Default: `false` + - Type: `string` + - Required: No -#### `isSecondary`: `boolean` +Sets the `aria-label` of the component, if none is provided. +Sets the Tooltip content if `showTooltip` is provided. -Deprecated: Renders a default button style. -Use `variant` prop with `secondary` value instead. +### `shortcut` -- Required: No -- Default: `false` + - Type: `string | { display: string; ariaLabel: string; }` + - Required: No -#### `isSmall`: `boolean` +If provided with `showTooltip`, appends the Shortcut label to the tooltip content. +If an object is provided, it should contain `display` and `ariaLabel` keys. -Decreases the size of the button. +### `showTooltip` -Deprecated in favor of the `size` prop. If both props are defined, the `size` prop will take precedence. + - Type: `boolean` + - Required: No -- Required: No +If provided, renders a Tooltip component for the button. -#### `isTertiary`: `boolean` +### `size` -Deprecated: Renders a text-based button style. -Use `variant` prop with `tertiary` value instead. - -- Required: No -- Default: `false` - -#### `label`: `string` - -Sets the `aria-label` of the component, if none is provided. Sets the Tooltip content if `showTooltip` is provided. - -- Required: No - -#### `shortcut`: `string | { display: string; ariaLabel: string; }` - -If provided with `showTooltip`, appends the Shortcut label to the tooltip content. If an object is provided, it should contain `display` and `ariaLabel` keys. - -- Required: No - -#### `showTooltip`: `boolean` - -If provided, renders a [Tooltip](/packages/components/src/tooltip/README.md) component for the button. - -- Required: No - -#### `size`: `'default'` | `'compact'` | `'small'` + - Type: `"small" | "default" | "compact"` + - Required: No + - Default: `'default'` The size of the button. -- `'default'`: For normal text-label buttons, unless it is a toggle button. -- `'compact'`: For toggle buttons, icon buttons, and buttons when used in context of either. -- `'small'`: For icon buttons associated with more advanced or auxiliary features. +- `'default'`: For normal text-label buttons, unless it is a toggle button. +- `'compact'`: For toggle buttons, icon buttons, and buttons when used in context of either. +- `'small'`: For icon buttons associated with more advanced or auxiliary features. If the deprecated `isSmall` prop is also defined, this prop will take precedence. -- Required: No -- Default: `'default'` - -#### `target`: `string` - -If provided with `href`, sets the `target` attribute to the `a`. - -- Required: No +### `text` -#### `text`: `string` + - Type: `string` + - Required: No If provided, displays the given text inside the button. If the button contains children elements, the text is displayed before them. -- Required: No +### `tooltipPosition` -#### `tooltipPosition`: `PopoverProps[ 'position' ]` + - Type: `"top" | "middle" | "bottom" | "top center" | "top left" | "top right" | "middle center" | "middle left" | "middle right" | "bottom center" | ...` + - Required: No -If provided with`showTooltip`, sets the position of the tooltip. Please refer to the [Tooltip](/packages/components/src/tooltip/README.md) component for more details regarding the defaults. +If provided with `showTooltip`, sets the position of the tooltip. +Please refer to the Tooltip component for more details regarding the defaults. -- Required: No +### `target` -#### `variant`: `'primary' | 'secondary' | 'tertiary' | 'link'` + - Type: `string` + - Required: No -Specifies the button's style. The accepted values are `'primary'` (the primary button styles), `'secondary'` (the default button styles), `'tertiary'` (the text-based button styles), and `'link'` (the link button styles). - -- Required: No +If provided with `href`, sets the `target` attribute to the `a`. -#### `__next40pxDefaultSize`: `boolean` +### `variant` -Start opting into the larger default height that will become the default size in a future version. + - Type: `"link" | "primary" | "secondary" | "tertiary"` + - Required: No -- Required: No -- Default: `false` +Specifies the button's style. -## Related components +The accepted values are: -- To group buttons together, use the [ButtonGroup](/packages/components/src/button-group/README.md) component. +1. `'primary'` (the primary button styles) +2. `'secondary'` (the default button styles) +3. `'tertiary'` (the text-based button styles) +4. `'link'` (the link button styles) diff --git a/packages/components/src/button/docs-manifest.json b/packages/components/src/button/docs-manifest.json new file mode 100644 index 00000000000000..0fd0f84f44e102 --- /dev/null +++ b/packages/components/src/button/docs-manifest.json @@ -0,0 +1,5 @@ +{ + "$schema": "../../schemas/docs-manifest.json", + "displayName": "Button", + "filePath": "./index.tsx" +} diff --git a/packages/components/src/button/stories/best-practices.mdx b/packages/components/src/button/stories/best-practices.mdx new file mode 100644 index 00000000000000..66ec44e6738d5f --- /dev/null +++ b/packages/components/src/button/stories/best-practices.mdx @@ -0,0 +1,31 @@ +import { Meta } from '@storybook/blocks'; + +<Meta title="Components/Actions/Button/Best Practices" /> + +# Button + +## Usage +Buttons indicate available actions and allow user interaction within the interface. As key elements in the WordPress UI, they appear in toolbars, modals, and forms. Default buttons support most actions, while primary buttons emphasize the main action in a view. Secondary buttons pair as secondary actions next to a primary action. + +Each layout contains one prominently placed, high-emphasis button. If you need multiple buttons, use one primary button for the main action, secondary for the rest of the actions and tertiary sparingly when an action needs to not stand out at all. + +### Sizes + +- `'default'`: For normal text-label buttons, unless it is a toggle button. +- `'compact'`: For toggle buttons, icon buttons, and buttons when used in context of either. +- `'small'`: For icon buttons associated with more advanced or auxiliary features. + +## Best practices + +- Label buttons to show that a click or tap initiates an action. +- Use established color conventions; for example, reserve red buttons for irreversible or dangerous actions. +- Avoid crowding the screen with multiple calls to action, which confuses users. +- Keep button locations consistent across the interface. + +## Content guidelines + +Buttons should be clear and predictable, showing users what will happen when clicked. Make labels reflect actions accurately to avoid confusion. + +Start button text with a strong action verb and include a noun to specify the change, except for common actions like Save, Close, Cancel, or OK. + +For other actions, use a `{verb}+{noun}` format for context. Keep button text brief and remove unnecessary words like "the," "an," or "a" for easy scanning. diff --git a/packages/components/src/button/stories/index.story.tsx b/packages/components/src/button/stories/index.story.tsx index 808914893de610..605b56686c702f 100644 --- a/packages/components/src/button/stories/index.story.tsx +++ b/packages/components/src/button/stories/index.story.tsx @@ -65,30 +65,48 @@ Default.args = { children: 'Code is poetry', }; +/** + * Primary buttons stand out with bold color fills, making them distinct + * from the background. Since they naturally draw attention, each layout should contain + * only one primary button to guide users toward the most important action. + */ export const Primary = Template.bind( {} ); Primary.args = { ...Default.args, variant: 'primary', }; +/** + * Secondary buttons complement primary buttons. Use them for standard actions that may appear alongside a primary action. + */ export const Secondary = Template.bind( {} ); Secondary.args = { ...Default.args, variant: 'secondary', }; +/** + * Tertiary buttons have minimal emphasis. Use them sparingly to subtly highlight an action. + */ export const Tertiary = Template.bind( {} ); Tertiary.args = { ...Default.args, variant: 'tertiary', }; +/** + * Link buttons have low emphasis and blend into the page, making them suitable for supplementary actions, + * especially those involving navigation away from the current view. + */ export const Link = Template.bind( {} ); Link.args = { ...Default.args, variant: 'link', }; +/** + * Use this variant for irreversible actions. Apply sparingly and only for actions with significant impact. + */ export const IsDestructive = Template.bind( {} ); IsDestructive.args = { ...Default.args, diff --git a/packages/components/src/button/style.scss b/packages/components/src/button/style.scss index 61455a54e26f6b..e7cc40d205e2e3 100644 --- a/packages/components/src/button/style.scss +++ b/packages/components/src/button/style.scss @@ -9,15 +9,17 @@ display: inline-flex; text-decoration: none; font-family: inherit; - font-weight: normal; font-size: $default-font-size; margin: 0; border: 0; cursor: pointer; -webkit-appearance: none; background: none; - transition: box-shadow 0.1s linear; - @include reduce-motion("transition"); + + @media not (prefers-reduced-motion) { + transition: box-shadow 0.1s linear; + } + height: $button-size; align-items: center; box-sizing: border-box; @@ -139,8 +141,10 @@ color: $components-color-accent; background: transparent; - &:hover:not(:disabled, [aria-disabled="true"]) { - box-shadow: inset 0 0 0 $border-width $components-color-accent-darker-10; + &:hover:not(:disabled, [aria-disabled="true"], .is-pressed) { + box-shadow: inset 0 0 0 $border-width $components-color-accent-darker-20; + color: $components-color-accent-darker-20; + background: color-mix(in srgb, $components-color-accent 4%, transparent); } &:disabled:not(:focus), @@ -164,15 +168,12 @@ background: transparent; &:hover:not(:disabled, [aria-disabled="true"]) { - // TODO: Prepare for theming (https://github.com/WordPress/gutenberg/pull/45466/files#r1030872724) - /* stylelint-disable-next-line declaration-property-value-disallowed-list -- Allow tertiary buttons to use colors from the user admin color scheme. */ - background: rgba(var(--wp-admin-theme-color--rgb), 0.04); + background: color-mix(in srgb, $components-color-accent 4%, transparent); + color: $components-color-accent-darker-20; } &:active:not(:disabled, [aria-disabled="true"]) { - // TODO: Prepare for theming (https://github.com/WordPress/gutenberg/pull/45466/files#r1030872724) - /* stylelint-disable-next-line declaration-property-value-disallowed-list -- Allow tertiary buttons to use colors from the user admin color scheme. */ - background: rgba(var(--wp-admin-theme-color--rgb), 0.08); + background: color-mix(in srgb, $components-color-accent 8%, transparent); } // Pull left if the tertiary button stands alone after a description, so as to vertically align with items above. @@ -220,7 +221,8 @@ } } - &.is-tertiary { + &.is-tertiary, + &.is-secondary { &:hover:not(:disabled, [aria-disabled="true"]) { background: rgba($alert-red, 0.04); } @@ -246,10 +248,13 @@ text-align: left; color: $components-color-accent; text-decoration: underline; - transition-property: border, background, color; - transition-duration: 0.05s; - transition-timing-function: ease-in-out; - @include reduce-motion("transition"); + + @media not (prefers-reduced-motion) { + transition-property: border, background, color; + transition-duration: 0.05s; + transition-timing-function: ease-in-out; + } + height: auto; &:focus { @@ -276,11 +281,8 @@ &.is-secondary.is-busy, &.is-secondary.is-busy:disabled, &.is-secondary.is-busy[aria-disabled="true"] { - animation: components-button__busy-animation 2500ms infinite linear; - // This should be refactored to use the reduce-motion("animation") mixin - // as soon as https://github.com/WordPress/gutenberg/issues/55566 is closed. - @media (prefers-reduced-motion: reduce) { - animation-duration: 0s; + @media not (prefers-reduced-motion) { + animation: components-button__busy-animation 2500ms infinite linear; } background-size: 100px 100%; /* stylelint-disable -- Disable reason: This function call looks nicer when each argument is on its own line. */ @@ -377,7 +379,7 @@ fill: currentColor; outline: none; - // Optimizate for high contrast modes. + // Optimize for high contrast modes. // See also https://blogs.windows.com/msedgedev/2020/09/17/styling-for-windows-high-contrast-with-new-standards-for-forced-colors/. @media (forced-colors: active) { fill: CanvasText; diff --git a/packages/components/src/button/test/index.tsx b/packages/components/src/button/test/index.tsx index 8161e68c4e21b6..664c755ac44043 100644 --- a/packages/components/src/button/test/index.tsx +++ b/packages/components/src/button/test/index.tsx @@ -6,19 +6,26 @@ import { render, screen } from '@testing-library/react'; /** * WordPress dependencies */ -import { createRef } from '@wordpress/element'; +import { createRef, forwardRef } from '@wordpress/element'; import { plusCircle } from '@wordpress/icons'; /** * Internal dependencies */ -import Button from '..'; +import _Button from '..'; import Tooltip from '../../tooltip'; import cleanupTooltip from '../../tooltip/test/utils'; import { press } from '@ariakit/test'; jest.mock( '../../icon', () => () => <div data-testid="test-icon" /> ); +const Button = forwardRef( + ( + props: React.ComponentProps< typeof _Button >, + ref: React.ForwardedRef< unknown > + ) => <_Button __next40pxDefaultSize { ...props } ref={ ref } /> +); + describe( 'Button', () => { describe( 'basic rendering', () => { it( 'should render a button element with only one class', () => { diff --git a/packages/components/src/button/types.ts b/packages/components/src/button/types.ts index 7d67b721a5036d..d730f49b1e8138 100644 --- a/packages/components/src/button/types.ts +++ b/packages/components/src/button/types.ts @@ -111,11 +111,13 @@ type BaseButtonProps = { tooltipPosition?: PopoverProps[ 'position' ]; /** * Specifies the button's style. + * * The accepted values are: - * 'primary' (the primary button styles) - * 'secondary' (the default button styles) - * 'tertiary' (the text-based button styles) - * 'link' (the link button styles) + * + * 1. `'primary'` (the primary button styles) + * 2. `'secondary'` (the default button styles) + * 3. `'tertiary'` (the text-based button styles) + * 4. `'link'` (the link button styles) */ variant?: 'primary' | 'secondary' | 'tertiary' | 'link'; }; diff --git a/packages/components/src/card/stories/index.story.tsx b/packages/components/src/card/stories/index.story.tsx index 03726abbe754ee..22fb749461785f 100644 --- a/packages/components/src/card/stories/index.story.tsx +++ b/packages/components/src/card/stories/index.story.tsx @@ -26,10 +26,10 @@ const meta: Meta< typeof Card > = { id: 'components-card', argTypes: { as: { - control: { type: null }, + control: false, }, children: { - control: { type: null }, + control: false, }, }, parameters: { diff --git a/packages/components/src/checkbox-control/stories/index.story.tsx b/packages/components/src/checkbox-control/stories/index.story.tsx index a68e380a8f7332..a2936654c8629a 100644 --- a/packages/components/src/checkbox-control/stories/index.story.tsx +++ b/packages/components/src/checkbox-control/stories/index.story.tsx @@ -24,7 +24,7 @@ const meta: Meta< typeof CheckboxControl > = { action: 'onChange', }, checked: { - control: { type: null }, + control: false, }, help: { control: { type: 'text' } }, }, diff --git a/packages/components/src/checkbox-control/style.scss b/packages/components/src/checkbox-control/style.scss index 25394ba645ee80..63cd023302bcb1 100644 --- a/packages/components/src/checkbox-control/style.scss +++ b/packages/components/src/checkbox-control/style.scss @@ -32,8 +32,10 @@ height: var(--checkbox-input-size); appearance: none; - transition: 0.1s border-color ease-in-out; - @include reduce-motion("transition"); + + @media not (prefers-reduced-motion) { + transition: 0.1s border-color ease-in-out; + } &:focus { @include button-style-outset__focus(var(--wp-admin-theme-color)); diff --git a/packages/components/src/circular-option-picker/README.md b/packages/components/src/circular-option-picker/README.md index 1dc230686cae17..b6db6f06daf456 100644 --- a/packages/components/src/circular-option-picker/README.md +++ b/packages/components/src/circular-option-picker/README.md @@ -29,7 +29,6 @@ const Example = () => { style={ { backgroundColor: color, color } } isSelected={ index === currentColor } onClick={ () => setCurrentColor( index ) } - aria-label={ name } /> ); } ) } @@ -150,6 +149,6 @@ Inherits all of the [`Dropdown` props](/packages/components/src/dropdown/README. Props for the underlying `Button` component. -Inherits all of the [`Button` props](/packages/components/src/button/README.md#props), except for `href`, `target`, and `children`. +Inherits all of the [`Button` props](/packages/components/src/button/README.md#props), except for `href`, `target`, and `children`. - Required: No diff --git a/packages/components/src/circular-option-picker/circular-option-picker-actions.tsx b/packages/components/src/circular-option-picker/circular-option-picker-actions.tsx index 4cf4d6f95da383..339c614e4ca45d 100644 --- a/packages/components/src/circular-option-picker/circular-option-picker-actions.tsx +++ b/packages/components/src/circular-option-picker/circular-option-picker-actions.tsx @@ -47,6 +47,7 @@ export function ButtonAction( { }: WordPressComponentProps< ButtonAsButtonProps, 'button', false > ) { return ( <Button + __next40pxDefaultSize className={ clsx( 'components-circular-option-picker__clear', className diff --git a/packages/components/src/circular-option-picker/circular-option-picker-option.tsx b/packages/components/src/circular-option-picker/circular-option-picker-option.tsx index 276ab7549781ed..8b01bae9173e64 100644 --- a/packages/components/src/circular-option-picker/circular-option-picker-option.tsx +++ b/packages/components/src/circular-option-picker/circular-option-picker-option.tsx @@ -17,7 +17,6 @@ import { Icon, check } from '@wordpress/icons'; import { CircularOptionPickerContext } from './circular-option-picker-context'; import Button from '../button'; import { Composite } from '../composite'; -import Tooltip from '../tooltip'; import type { OptionProps } from './types'; function UnforwardedOptionAsButton( @@ -25,15 +24,17 @@ function UnforwardedOptionAsButton( id?: string; className?: string; isPressed?: boolean; + label?: string; }, forwardedRef: ForwardedRef< any > ) { - const { isPressed, ...additionalProps } = props; + const { isPressed, label, ...additionalProps } = props; return ( <Button { ...additionalProps } aria-pressed={ isPressed } ref={ forwardedRef } + label={ label } /> ); } @@ -45,10 +46,11 @@ function UnforwardedOptionAsOption( id: string; className?: string; isSelected?: boolean; + label?: string; }, forwardedRef: ForwardedRef< any > ) { - const { id, isSelected, ...additionalProps } = props; + const { id, isSelected, label, ...additionalProps } = props; const { setActiveId, activeId } = useContext( CircularOptionPickerContext ); @@ -69,6 +71,7 @@ function UnforwardedOptionAsOption( role="option" aria-selected={ !! isSelected } ref={ forwardedRef } + label={ label } /> } id={ id } @@ -94,14 +97,23 @@ export function Option( { const commonProps = { id, className: 'components-circular-option-picker__option', + __next40pxDefaultSize: true, ...additionalProps, }; const isListbox = setActiveId !== undefined; const optionControl = isListbox ? ( - <OptionAsOption { ...commonProps } isSelected={ isSelected } /> + <OptionAsOption + { ...commonProps } + label={ tooltipText } + isSelected={ isSelected } + /> ) : ( - <OptionAsButton { ...commonProps } isPressed={ isSelected } /> + <OptionAsButton + { ...commonProps } + label={ tooltipText } + isPressed={ isSelected } + /> ); return ( @@ -111,11 +123,7 @@ export function Option( { 'components-circular-option-picker__option-wrapper' ) } > - { tooltipText ? ( - <Tooltip text={ tooltipText }>{ optionControl }</Tooltip> - ) : ( - optionControl - ) } + { optionControl } { isSelected && <Icon icon={ check } { ...selectedIconProps } /> } </div> ); diff --git a/packages/components/src/circular-option-picker/circular-option-picker.tsx b/packages/components/src/circular-option-picker/circular-option-picker.tsx index adf2b386e5cbec..8b6be8cd2215f0 100644 --- a/packages/components/src/circular-option-picker/circular-option-picker.tsx +++ b/packages/components/src/circular-option-picker/circular-option-picker.tsx @@ -51,7 +51,6 @@ import { * style={ { backgroundColor: color, color } } * isSelected={ index === currentColor } * onClick={ () => setCurrentColor( index ) } - * aria-label={ name } * /> * ); * } ) } diff --git a/packages/components/src/circular-option-picker/stories/index.story.tsx b/packages/components/src/circular-option-picker/stories/index.story.tsx index d0314670fe1694..9d45c9bb92f7d0 100644 --- a/packages/components/src/circular-option-picker/stories/index.story.tsx +++ b/packages/components/src/circular-option-picker/stories/index.story.tsx @@ -32,8 +32,8 @@ const meta: Meta< typeof CircularOptionPicker > = { CircularOptionPicker.DropdownLinkAction, }, argTypes: { - actions: { control: { type: null } }, - options: { control: { type: null } }, + actions: { control: false }, + options: { control: false }, children: { control: { type: 'text' } }, }, parameters: { @@ -87,7 +87,6 @@ const DefaultOptions = () => { onClick={ () => { setCurrentColor?.( color ); } } - aria-label={ name } /> ); } ) } diff --git a/packages/components/src/circular-option-picker/style.scss b/packages/components/src/circular-option-picker/style.scss index 24b67eb7e7021e..5cbedb4f89053a 100644 --- a/packages/components/src/circular-option-picker/style.scss +++ b/packages/components/src/circular-option-picker/style.scss @@ -35,9 +35,11 @@ $color-palette-circle-spacing: 12px; width: $color-palette-circle-size; vertical-align: top; transform: scale(1); - transition: 100ms transform ease; - will-change: transform; - @include reduce-motion("transition"); + + @media not (prefers-reduced-motion) { + transition: 100ms transform ease; + will-change: transform; + } &:hover { transform: scale(1.2); @@ -67,14 +69,17 @@ $color-palette-circle-spacing: 12px; .components-circular-option-picker__option { display: inline-block; vertical-align: top; - height: 100%; - width: 100%; + height: 100% !important; + aspect-ratio: 1; border: none; border-radius: $radius-round; background: transparent; box-shadow: inset 0 0 0 ($color-palette-circle-size * 0.5); - transition: 100ms box-shadow ease; - @include reduce-motion("transition"); + + @media not (prefers-reduced-motion) { + transition: 100ms box-shadow ease; + } + cursor: pointer; &:hover { diff --git a/packages/components/src/clipboard-button/index.tsx b/packages/components/src/clipboard-button/index.tsx index 0bf7d177e251ef..492ab64b7290e2 100644 --- a/packages/components/src/clipboard-button/index.tsx +++ b/packages/components/src/clipboard-button/index.tsx @@ -45,9 +45,11 @@ export default function ClipboardButton( { } ); useEffect( () => { - if ( timeoutIdRef.current ) { - clearTimeout( timeoutIdRef.current ); - } + return () => { + if ( timeoutIdRef.current ) { + clearTimeout( timeoutIdRef.current ); + } + }; }, [] ); const classes = clsx( 'components-clipboard-button', className ); diff --git a/packages/components/src/color-palette/index.tsx b/packages/components/src/color-palette/index.tsx index a65508d8278c5f..de4e4f4206fe3a 100644 --- a/packages/components/src/color-palette/index.tsx +++ b/packages/components/src/color-palette/index.tsx @@ -79,13 +79,6 @@ function SinglePalette( { onClick={ isSelected ? clearColor : () => onChange( color, index ) } - aria-label={ - name - ? // translators: %s: The name of the color e.g: "vivid red". - sprintf( __( 'Color: %s' ), name ) - : // translators: %s: color hex code e.g: "#f00". - sprintf( __( 'Color code: %s' ), color ) - } /> ); } ); @@ -249,7 +242,11 @@ function UnforwardedColorPalette( }; const actions = !! clearable && ( - <CircularOptionPicker.ButtonAction onClick={ clearColor }> + <CircularOptionPicker.ButtonAction + onClick={ clearColor } + accessibleWhenDisabled + disabled={ ! value } + > { __( 'Clear' ) } </CircularOptionPicker.ButtonAction> ); diff --git a/packages/components/src/color-palette/stories/index.story.tsx b/packages/components/src/color-palette/stories/index.story.tsx index 5342fc5222be6a..e4c4b89d524448 100644 --- a/packages/components/src/color-palette/stories/index.story.tsx +++ b/packages/components/src/color-palette/stories/index.story.tsx @@ -18,9 +18,9 @@ const meta: Meta< typeof ColorPalette > = { id: 'components-colorpalette', component: ColorPalette, argTypes: { - as: { control: { type: null } }, - onChange: { action: 'onChange', control: { type: null } }, - value: { control: { type: null } }, + as: { control: false }, + onChange: { action: 'onChange', control: false }, + value: { control: false }, }, parameters: { controls: { expanded: true }, diff --git a/packages/components/src/color-palette/test/index.tsx b/packages/components/src/color-palette/test/index.tsx index 8d108469fba3a8..dcdd16e53ee836 100644 --- a/packages/components/src/color-palette/test/index.tsx +++ b/packages/components/src/color-palette/test/index.tsx @@ -50,9 +50,7 @@ describe( 'ColorPalette', () => { /> ); - expect( - screen.getAllByRole( 'option', { name: /^Color:/ } ) - ).toHaveLength( 3 ); + expect( screen.getAllByRole( 'option' ) ).toHaveLength( 3 ); } ); it( 'should call onClick on an active button with undefined', async () => { @@ -67,9 +65,7 @@ describe( 'ColorPalette', () => { /> ); - await user.click( - screen.getByRole( 'option', { name: /^Color:/, selected: true } ) - ); + await user.click( screen.getByRole( 'option', { selected: true } ) ); expect( onChange ).toHaveBeenCalledTimes( 1 ); expect( onChange ).toHaveBeenCalledWith( undefined ); @@ -91,7 +87,6 @@ describe( 'ColorPalette', () => { // (i.e. a button representing a color that is not the current color) await user.click( screen.getAllByRole( 'option', { - name: /^Color:/, selected: false, } )[ 0 ] ); @@ -230,7 +225,6 @@ describe( 'ColorPalette', () => { // Click the first unpressed button await user.click( screen.getAllByRole( 'option', { - name: /^Color:/, selected: false, } )[ 0 ] ); diff --git a/packages/components/src/color-picker/color-copy-button.tsx b/packages/components/src/color-picker/color-copy-button.tsx index b8a4822544322c..6e49fa7ae85e74 100644 --- a/packages/components/src/color-picker/color-copy-button.tsx +++ b/packages/components/src/color-picker/color-copy-button.tsx @@ -9,7 +9,7 @@ import { __ } from '@wordpress/i18n'; /** * Internal dependencies */ -import { CopyButton } from './styles'; +import { Button } from '../button'; import Tooltip from '../tooltip'; import type { ColorCopyButtonProps } from './types'; @@ -55,16 +55,14 @@ export const ColorCopyButton = ( props: ColorCopyButtonProps ) => { }; }, [] ); + const label = + copiedColor === color.toHex() ? __( 'Copied!' ) : __( 'Copy' ); + return ( - <Tooltip - delay={ 0 } - hideOnClick={ false } - text={ - copiedColor === color.toHex() ? __( 'Copied!' ) : __( 'Copy' ) - } - > - <CopyButton - size="small" + <Tooltip delay={ 0 } hideOnClick={ false } text={ label }> + <Button + size="compact" + aria-label={ label } ref={ copyRef } icon={ copy } showTooltip={ false } diff --git a/packages/components/src/color-picker/component.tsx b/packages/components/src/color-picker/component.tsx index a1112d2e1ecd23..a2ddb6cd8ee4b6 100644 --- a/packages/components/src/color-picker/component.tsx +++ b/packages/components/src/color-picker/component.tsx @@ -87,6 +87,7 @@ const UnconnectedColorPicker = ( <AuxiliaryColorArtefactHStackHeader justify="space-between"> <SelectControl __nextHasNoMarginBottom + size="compact" options={ options } value={ colorType } onChange={ ( nextColorType ) => diff --git a/packages/components/src/color-picker/input-with-slider.tsx b/packages/components/src/color-picker/input-with-slider.tsx index 5e08fa42daf80e..221a9289f7450e 100644 --- a/packages/components/src/color-picker/input-with-slider.tsx +++ b/packages/components/src/color-picker/input-with-slider.tsx @@ -31,6 +31,7 @@ export const InputWithSlider = ( { return ( <HStack spacing={ 4 }> <NumberControlWrapper + __next40pxDefaultSize min={ min } max={ max } label={ label } @@ -45,7 +46,6 @@ export const InputWithSlider = ( { </InputControlPrefixWrapper> } spinControls="none" - size="__unstable-large" /> <RangeControl __nextHasNoMarginBottom diff --git a/packages/components/src/color-picker/stories/index.story.tsx b/packages/components/src/color-picker/stories/index.story.tsx index 44040a5265c5d3..0886719a7de52c 100644 --- a/packages/components/src/color-picker/stories/index.story.tsx +++ b/packages/components/src/color-picker/stories/index.story.tsx @@ -13,8 +13,8 @@ const meta: Meta< typeof ColorPicker > = { title: 'Components/Selection & Input/Color/ColorPicker', id: 'components-colorpicker', argTypes: { - as: { control: { type: null } }, - color: { control: { type: null } }, + as: { control: false }, + color: { control: false }, }, parameters: { actions: { argTypesRegex: '^on.*' }, diff --git a/packages/components/src/color-picker/styles.ts b/packages/components/src/color-picker/styles.ts index a78f10de2e4a32..50ce33da9f2333 100644 --- a/packages/components/src/color-picker/styles.ts +++ b/packages/components/src/color-picker/styles.ts @@ -11,7 +11,6 @@ import InnerSelectControl from '../select-control'; import InnerRangeControl from '../range-control'; import { space } from '../utils/space'; import { boxSizingReset } from '../utils'; -import Button from '../button'; import { Flex } from '../flex'; import { HStack } from '../h-stack'; import CONFIG from '../utils/config-values'; @@ -22,7 +21,6 @@ export const NumberControlWrapper = styled( NumberControl )` export const SelectControl = styled( InnerSelectControl )` margin-left: ${ space( -2 ) }; - width: 5em; `; export const RangeControl = styled( InnerRangeControl )` @@ -101,14 +99,3 @@ export const ColorfulWrapper = styled.div` ${ interactiveHueStyles } `; - -export const CopyButton = styled( Button )` - &&&&& { - min-width: ${ space( 6 ) }; - padding: 0; - - > svg { - margin-right: 0; - } - } -`; diff --git a/packages/components/src/combobox-control/README.md b/packages/components/src/combobox-control/README.md index 5831c5ec2832c2..4089cf9c56e9b5 100644 --- a/packages/components/src/combobox-control/README.md +++ b/packages/components/src/combobox-control/README.md @@ -34,6 +34,7 @@ function MyComboboxControl() { const [ filteredOptions, setFilteredOptions ] = useState( options ); return ( <ComboboxControl + __next40pxDefaultSize __nextHasNoMarginBottom label="Font Size" value={ fontSize } diff --git a/packages/components/src/combobox-control/index.tsx b/packages/components/src/combobox-control/index.tsx index 5c6725a51b435d..28510c8653d02e 100644 --- a/packages/components/src/combobox-control/index.tsx +++ b/packages/components/src/combobox-control/index.tsx @@ -26,7 +26,7 @@ import TokenInput from '../form-token-field/token-input'; import SuggestionsList from '../form-token-field/suggestions-list'; import BaseControl from '../base-control'; import Button from '../button'; -import { FlexBlock, FlexItem } from '../flex'; +import { FlexBlock } from '../flex'; import withFocusOutside from '../higher-order/with-focus-outside'; import { useControlledValue } from '../utils/hooks'; import { normalizeTextString } from '../utils/strings'; @@ -34,6 +34,7 @@ import type { ComboboxControlOption, ComboboxControlProps } from './types'; import type { TokenInputProps } from '../form-token-field/types'; import { useDeprecated36pxDefaultSizeProp } from '../utils/use-deprecated-props'; import { withIgnoreIMEEvents } from '../utils/with-ignore-ime-events'; +import { maybeWarnDeprecated36pxSize } from '../utils/deprecated-36px-size'; const noop = () => {}; @@ -92,6 +93,7 @@ const getIndexOfMatchingSuggestion = ( * const [ filteredOptions, setFilteredOptions ] = useState( options ); * return ( * <ComboboxControl + * __next40pxDefaultSize * __nextHasNoMarginBottom * label="Font Size" * value={ fontSize } @@ -313,6 +315,12 @@ function ComboboxControl( props: ComboboxControlProps ) { } }, [ matchingSuggestions, isExpanded ] ); + maybeWarnDeprecated36pxSize( { + componentName: 'ComboboxControl', + __next40pxDefaultSize, + size: undefined, + } ); + // Disable reason: There is no appropriate role which describes the // input container intended accessible usability. // TODO: Refactor click detection to use blur to stop propagation. @@ -355,18 +363,16 @@ function ComboboxControl( props: ComboboxControlProps ) { /> </FlexBlock> { allowReset && ( - <FlexItem> - <Button - className="components-combobox-control__reset" - icon={ closeSmall } - // Disable reason: Focus returns to input field when reset is clicked. - // eslint-disable-next-line no-restricted-syntax - disabled={ ! value } - onClick={ handleOnReset } - onKeyDown={ handleResetStopPropagation } - label={ __( 'Reset' ) } - /> - </FlexItem> + <Button + size="small" + icon={ closeSmall } + // Disable reason: Focus returns to input field when reset is clicked. + // eslint-disable-next-line no-restricted-syntax + disabled={ ! value } + onClick={ handleOnReset } + onKeyDown={ handleResetStopPropagation } + label={ __( 'Reset' ) } + /> ) } </InputWrapperFlex> { isExpanded && ( diff --git a/packages/components/src/combobox-control/stories/index.story.tsx b/packages/components/src/combobox-control/stories/index.story.tsx index 954f0d96fb0d76..f033742a336623 100644 --- a/packages/components/src/combobox-control/stories/index.story.tsx +++ b/packages/components/src/combobox-control/stories/index.story.tsx @@ -38,7 +38,7 @@ const meta: Meta< typeof ComboboxControl > = { id: 'components-comboboxcontrol', component: ComboboxControl, argTypes: { - value: { control: { type: null } }, + value: { control: false }, }, parameters: { actions: { argTypesRegex: '^on.*' }, @@ -77,6 +77,7 @@ const Template: StoryFn< typeof ComboboxControl > = ( { }; export const Default = Template.bind( {} ); Default.args = { + __next40pxDefaultSize: true, __nextHasNoMarginBottom: true, allowReset: false, label: 'Select a country', diff --git a/packages/components/src/combobox-control/style.scss b/packages/components/src/combobox-control/style.scss index 8bd4c2fb156a9f..c8fd8a168c0fb7 100644 --- a/packages/components/src/combobox-control/style.scss +++ b/packages/components/src/combobox-control/style.scss @@ -38,9 +38,3 @@ input.components-combobox-control__input[type="text"] { } } -.components-combobox-control__reset.components-button { - display: flex; - height: $grid-unit-20; - min-width: $grid-unit-20; - padding: 0; -} diff --git a/packages/components/src/combobox-control/test/index.tsx b/packages/components/src/combobox-control/test/index.tsx index 639407ac998ed2..c9276f495d7b16 100644 --- a/packages/components/src/combobox-control/test/index.tsx +++ b/packages/components/src/combobox-control/test/index.tsx @@ -58,7 +58,13 @@ const getOptionSearchString = ( option: ComboboxControlOption ) => option.label.substring( 0, 11 ); const ComboboxControl = ( props: ComboboxControlProps ) => { - return <_ComboboxControl { ...props } __nextHasNoMarginBottom />; + return ( + <_ComboboxControl + { ...props } + __next40pxDefaultSize + __nextHasNoMarginBottom + /> + ); }; const ControlledComboboxControl = ( { @@ -342,7 +348,7 @@ describe.each( [ expect( option ).toHaveTextContent( matches[ optionIndex ].label ); } ); - // Confirm that the corrent option is selected + // Confirm that the current option is selected await user.keyboard( '{Enter}' ); expect( onChangeSpy ).toHaveBeenCalledTimes( 1 ); diff --git a/packages/components/src/composite/hover.tsx b/packages/components/src/composite/hover.tsx index 1507a1879cc19f..a8a0ebfb4729fe 100644 --- a/packages/components/src/composite/hover.tsx +++ b/packages/components/src/composite/hover.tsx @@ -26,5 +26,5 @@ export const CompositeHover = forwardRef< // obfuscated to discourage its use outside of the component's internals. const store = ( props.store ?? context.store ) as Ariakit.CompositeStore; - return <Ariakit.CompositeGroup store={ store } { ...props } ref={ ref } />; + return <Ariakit.CompositeHover store={ store } { ...props } ref={ ref } />; } ); diff --git a/packages/components/src/composite/item.tsx b/packages/components/src/composite/item.tsx index edbf0b92e039af..4a02f76039a5cf 100644 --- a/packages/components/src/composite/item.tsx +++ b/packages/components/src/composite/item.tsx @@ -26,23 +26,5 @@ export const CompositeItem = forwardRef< // obfuscated to discourage its use outside of the component's internals. const store = ( props.store ?? context.store ) as Ariakit.CompositeStore; - // If the active item is not connected, Composite may end up in a state - // where none of the items are tabbable. In this case, we force all items to - // be tabbable, so that as soon as an item received focus, it becomes active - // and Composite goes back to working as expected. - const tabbable = Ariakit.useStoreState( store, ( state ) => { - return ( - state?.activeId !== null && - ! store?.item( state?.activeId )?.element?.isConnected - ); - } ); - - return ( - <Ariakit.CompositeItem - store={ store } - tabbable={ tabbable } - { ...props } - ref={ ref } - /> - ); + return <Ariakit.CompositeItem store={ store } { ...props } ref={ ref } />; } ); diff --git a/packages/components/src/composite/stories/index.story.tsx b/packages/components/src/composite/stories/index.story.tsx index eefcd599134757..63731a15a8c9cd 100644 --- a/packages/components/src/composite/stories/index.story.tsx +++ b/packages/components/src/composite/stories/index.story.tsx @@ -36,9 +36,9 @@ const meta: Meta< typeof Composite > = { 'Composite.Context': Composite.Context, }, argTypes: { - children: { control: { type: null } }, - render: { control: { type: null } }, - setActiveId: { control: { type: null } }, + children: { control: false }, + render: { control: false }, + setActiveId: { control: false }, focusLoop: { control: 'select', options: [ true, false, 'horizontal', 'vertical', 'both' ], diff --git a/packages/components/src/composite/typeahead.tsx b/packages/components/src/composite/typeahead.tsx index 519c59ea374e5d..3a3c3875a33604 100644 --- a/packages/components/src/composite/typeahead.tsx +++ b/packages/components/src/composite/typeahead.tsx @@ -26,5 +26,7 @@ export const CompositeTypeahead = forwardRef< // obfuscated to discourage its use outside of the component's internals. const store = ( props.store ?? context.store ) as Ariakit.CompositeStore; - return <Ariakit.CompositeRow store={ store } { ...props } ref={ ref } />; + return ( + <Ariakit.CompositeTypeahead store={ store } { ...props } ref={ ref } /> + ); } ); diff --git a/packages/components/src/confirm-dialog/stories/index.story.tsx b/packages/components/src/confirm-dialog/stories/index.story.tsx index 9496d85939edf3..7c08d48369a2b0 100644 --- a/packages/components/src/confirm-dialog/stories/index.story.tsx +++ b/packages/components/src/confirm-dialog/stories/index.story.tsx @@ -20,7 +20,7 @@ const meta: Meta< typeof ConfirmDialog > = { id: 'components-experimental-confirmdialog', argTypes: { isOpen: { - control: { type: null }, + control: false, }, }, parameters: { diff --git a/packages/components/src/custom-gradient-picker/gradient-bar/control-points.tsx b/packages/components/src/custom-gradient-picker/gradient-bar/control-points.tsx index 3911e21e0f9348..d68ee7502e1f63 100644 --- a/packages/components/src/custom-gradient-picker/gradient-bar/control-points.tsx +++ b/packages/components/src/custom-gradient-picker/gradient-bar/control-points.tsx @@ -66,6 +66,7 @@ function ControlPointButton( { aria-describedby={ descriptionId } aria-haspopup="true" aria-expanded={ isOpen } + __next40pxDefaultSize className={ clsx( 'components-custom-gradient-picker__control-point-button', { @@ -349,6 +350,7 @@ function InsertPoint( { } } renderToggle={ ( { isOpen, onToggle } ) => ( <Button + __next40pxDefaultSize aria-expanded={ isOpen } aria-haspopup="true" onClick={ () => { diff --git a/packages/components/src/custom-gradient-picker/index.tsx b/packages/components/src/custom-gradient-picker/index.tsx index dd0659515234a6..8d53cd9f3d0ea2 100644 --- a/packages/components/src/custom-gradient-picker/index.tsx +++ b/packages/components/src/custom-gradient-picker/index.tsx @@ -140,6 +140,7 @@ const GradientTypePicker = ( { export function CustomGradientPicker( { value, onChange, + enableAlpha = true, __experimentalIsRenderedInSidebar = false, }: CustomGradientPickerProps ) { const { gradientAST, hasGradient } = getGradientAstWithDefault( value ); @@ -167,6 +168,7 @@ export function CustomGradientPicker( { __experimentalIsRenderedInSidebar={ __experimentalIsRenderedInSidebar } + disableAlpha={ ! enableAlpha } background={ background } hasGradient={ hasGradient } value={ controlPoints } diff --git a/packages/components/src/custom-gradient-picker/style.scss b/packages/components/src/custom-gradient-picker/style.scss index fea18f340951e5..b9f2bee9dbe4eb 100644 --- a/packages/components/src/custom-gradient-picker/style.scss +++ b/packages/components/src/custom-gradient-picker/style.scss @@ -47,7 +47,7 @@ $components-custom-gradient-picker__padding: $grid-unit-20; // 48px container, 1 // Same size as the .components-custom-gradient-picker__control-point-dropdown parent height: inherit; width: inherit; - min-width: $grid-unit-20; + min-width: $grid-unit-20 !important; border-radius: $radius-round; background: $white; diff --git a/packages/components/src/custom-gradient-picker/types.ts b/packages/components/src/custom-gradient-picker/types.ts index f9efb90799daf2..17702c74ef527a 100644 --- a/packages/components/src/custom-gradient-picker/types.ts +++ b/packages/components/src/custom-gradient-picker/types.ts @@ -26,6 +26,12 @@ export type CustomGradientPickerProps = { * the `currentGradient` as an argument. */ onChange: ( currentGradient: string ) => void; + /** + * Whether to enable alpha transparency options in the picker. + * + * @default true + */ + enableAlpha?: boolean; /** * Whether this is rendered in the sidebar. * diff --git a/packages/components/src/custom-select-control-v2/custom-select.tsx b/packages/components/src/custom-select-control-v2/custom-select.tsx index bb458abcc282ff..9c3baf182a399a 100644 --- a/packages/components/src/custom-select-control-v2/custom-select.tsx +++ b/packages/components/src/custom-select-control-v2/custom-select.tsx @@ -2,7 +2,6 @@ * External dependencies */ import * as Ariakit from '@ariakit/react'; -import { useStoreState } from '@ariakit/react'; /** * WordPress dependencies @@ -63,7 +62,7 @@ const CustomSelectButton = ( { CustomSelectStore, 'onChange' > ) => { - const { value: currentValue } = useStoreState( store ); + const { value: currentValue } = Ariakit.useStoreState( store ); const computedRenderSelectedValue = useMemo( () => renderSelectedValue ?? defaultRenderSelectedValue, diff --git a/packages/components/src/custom-select-control-v2/stories/index.story.tsx b/packages/components/src/custom-select-control-v2/stories/index.story.tsx index 3595ee2e951990..b65c599ec9997c 100644 --- a/packages/components/src/custom-select-control-v2/stories/index.story.tsx +++ b/packages/components/src/custom-select-control-v2/stories/index.story.tsx @@ -2,6 +2,7 @@ * External dependencies */ import type { Meta, StoryFn } from '@storybook/react'; +import { fn } from '@storybook/test'; /** * WordPress dependencies @@ -22,8 +23,8 @@ const meta: Meta< typeof CustomSelectControlV2 > = { 'CustomSelectControlV2.Item': CustomSelectControlV2.Item, }, argTypes: { - children: { control: { type: null } }, - value: { control: { type: null } }, + children: { control: false }, + value: { control: false }, }, tags: [ 'status-wip' ], parameters: { @@ -44,6 +45,9 @@ const meta: Meta< typeof CustomSelectControlV2 > = { </div> ), ], + args: { + onChange: fn(), + }, }; export default meta; diff --git a/packages/components/src/custom-select-control/README.md b/packages/components/src/custom-select-control/README.md index a764a0df133eab..6c175b1fcc5d24 100644 --- a/packages/components/src/custom-select-control/README.md +++ b/packages/components/src/custom-select-control/README.md @@ -41,6 +41,7 @@ function MyCustomSelectControl() { const [ , setFontSize ] = useState(); return ( <CustomSelectControl + __next40pxDefaultSize label="Font Size" options={ options } onChange={ ( { selectedItem } ) => setFontSize( selectedItem ) } @@ -52,6 +53,7 @@ function MyControlledCustomSelectControl() { const [ fontSize, setFontSize ] = useState( options[ 0 ] ); return ( <CustomSelectControl + __next40pxDefaultSize label="Font Size" options={ options } onChange={ ( { selectedItem } ) => setFontSize( selectedItem ) } diff --git a/packages/components/src/custom-select-control/index.tsx b/packages/components/src/custom-select-control/index.tsx index ecd9dc37a8f491..e014e4bc642eef 100644 --- a/packages/components/src/custom-select-control/index.tsx +++ b/packages/components/src/custom-select-control/index.tsx @@ -18,6 +18,7 @@ import CustomSelectItem from '../custom-select-control-v2/item'; import * as Styled from '../custom-select-control-v2/styles'; import type { CustomSelectProps, CustomSelectOption } from './types'; import { VisuallyHidden } from '../visually-hidden'; +import { maybeWarnDeprecated36pxSize } from '../utils/deprecated-36px-size'; function useDeprecatedProps< T extends CustomSelectOption >( { __experimentalShowSelectedHint, @@ -56,6 +57,7 @@ function CustomSelectControl< T extends CustomSelectOption >( ) { const { __next40pxDefaultSize = false, + __shouldNotWarnDeprecated36pxSize, describedBy, options, onChange, @@ -66,6 +68,13 @@ function CustomSelectControl< T extends CustomSelectOption >( ...restProps } = useDeprecatedProps( props ); + maybeWarnDeprecated36pxSize( { + componentName: 'CustomSelectControl', + __next40pxDefaultSize, + size, + __shouldNotWarnDeprecated36pxSize, + } ); + const descriptionId = useInstanceId( CustomSelectControl, 'custom-select-control__description' @@ -140,7 +149,7 @@ function CustomSelectControl< T extends CustomSelectOption >( ); } ); - const { value: currentValue } = store.getState(); + const currentValue = Ariakit.useStoreState( store, 'value' ); const renderSelectedValueHint = () => { const selectedOptionHint = options diff --git a/packages/components/src/custom-select-control/stories/index.story.tsx b/packages/components/src/custom-select-control/stories/index.story.tsx index 836fc540c6d1ed..8c5e200f7532e8 100644 --- a/packages/components/src/custom-select-control/stories/index.story.tsx +++ b/packages/components/src/custom-select-control/stories/index.story.tsx @@ -14,11 +14,12 @@ import { useState } from '@wordpress/element'; import CustomSelectControl from '..'; const meta: Meta< typeof CustomSelectControl > = { - title: 'Components/CustomSelectControl', + title: 'Components/Selection & Input/Common/CustomSelectControl', component: CustomSelectControl, + id: 'components-customselectcontrol', argTypes: { - onChange: { control: { type: null } }, - value: { control: { type: null } }, + onChange: { control: false }, + value: { control: false }, }, parameters: { actions: { argTypesRegex: '^on.*' }, @@ -62,6 +63,7 @@ const Template: StoryFn< typeof CustomSelectControl > = ( props ) => { export const Default = Template.bind( {} ); Default.args = { + __next40pxDefaultSize: true, label: 'Label', options: [ { diff --git a/packages/components/src/custom-select-control/test/index.tsx b/packages/components/src/custom-select-control/test/index.tsx index b2ac5c19c6ab3f..61d212c26c619e 100644 --- a/packages/components/src/custom-select-control/test/index.tsx +++ b/packages/components/src/custom-select-control/test/index.tsx @@ -13,7 +13,11 @@ import { useState } from '@wordpress/element'; /** * Internal dependencies */ -import UncontrolledCustomSelectControl from '..'; +import _CustomSelectControl from '..'; + +const UncontrolledCustomSelectControl = ( + props: React.ComponentProps< typeof _CustomSelectControl > +) => <_CustomSelectControl __next40pxDefaultSize { ...props } />; const customClassName = 'amber-skies'; const customStyles = { @@ -716,7 +720,7 @@ describe( 'Type checking', () => { const onChange = (): void => {}; - <UncontrolledCustomSelectControl + <_CustomSelectControl label="Label" options={ options } value={ { @@ -726,7 +730,7 @@ describe( 'Type checking', () => { onChange={ onChange } />; - <UncontrolledCustomSelectControl + <_CustomSelectControl label="Label" options={ options } value={ { @@ -736,7 +740,7 @@ describe( 'Type checking', () => { onChange={ onChange } />; - <UncontrolledCustomSelectControl + <_CustomSelectControl label="Label" options={ options } value={ { @@ -748,7 +752,7 @@ describe( 'Type checking', () => { onChange={ onChange } />; - <UncontrolledCustomSelectControl + <_CustomSelectControl label="Label" options={ options } value={ { @@ -764,7 +768,7 @@ describe( 'Type checking', () => { } />; - <UncontrolledCustomSelectControl + <_CustomSelectControl label="Label" options={ optionsReadOnly } value={ { @@ -774,7 +778,7 @@ describe( 'Type checking', () => { onChange={ onChange } />; - <UncontrolledCustomSelectControl + <_CustomSelectControl label="Label" options={ optionsReadOnly } value={ { @@ -785,7 +789,7 @@ describe( 'Type checking', () => { onChange={ onChange } />; - <UncontrolledCustomSelectControl + <_CustomSelectControl label="Label" options={ optionsReadOnly } value={ { @@ -797,7 +801,7 @@ describe( 'Type checking', () => { onChange={ onChange } />; - <UncontrolledCustomSelectControl + <_CustomSelectControl label="Label" options={ optionsReadOnly } value={ { diff --git a/packages/components/src/custom-select-control/types.ts b/packages/components/src/custom-select-control/types.ts index 0cbc2388e79638..dd3db6d3bb0f95 100644 --- a/packages/components/src/custom-select-control/types.ts +++ b/packages/components/src/custom-select-control/types.ts @@ -120,4 +120,11 @@ export type CustomSelectProps< T extends CustomSelectOption > = { * @default false */ __next40pxDefaultSize?: boolean; + /** + * Do not throw a warning for the deprecated 36px default size. + * For internal components of other components that already throw the warning. + * + * @ignore + */ + __shouldNotWarnDeprecated36pxSize?: boolean; }; diff --git a/packages/components/src/dashicon/types.ts b/packages/components/src/dashicon/types.ts index eeee9c2d40a19d..a4a4f5156aff5f 100644 --- a/packages/components/src/dashicon/types.ts +++ b/packages/components/src/dashicon/types.ts @@ -219,7 +219,6 @@ export type IconKey = | 'insert-before' | 'insert' | 'instagram' - | 'keyboard-hide' | 'laptop' | 'layout' | 'leftright' @@ -266,7 +265,6 @@ export type IconKey = | 'playlist-audio' | 'playlist-video' | 'plus-alt' - | 'plus-light' | 'plus' | 'portfolio' | 'post-status' diff --git a/packages/components/src/date-time/date/index.tsx b/packages/components/src/date-time/date/index.tsx index ca093f9d70847b..e7afcccf249dc0 100644 --- a/packages/components/src/date-time/date/index.tsx +++ b/packages/components/src/date-time/date/index.tsx @@ -306,6 +306,7 @@ function Day( { return ( <DayButton + __next40pxDefaultSize ref={ ref } className="components-datetime__date__day" // Unused, for backwards compatibility. disabled={ isInvalid } diff --git a/packages/components/src/date-time/stories/date-time.story.tsx b/packages/components/src/date-time/stories/date-time.story.tsx index 7636e2fdc80a30..e240b9da470563 100644 --- a/packages/components/src/date-time/stories/date-time.story.tsx +++ b/packages/components/src/date-time/stories/date-time.story.tsx @@ -20,7 +20,7 @@ const meta: Meta< typeof DateTimePicker > = { component: DateTimePicker, argTypes: { currentDate: { control: 'date' }, - onChange: { action: 'onChange', control: { type: null } }, + onChange: { action: 'onChange', control: false }, }, parameters: { controls: { expanded: true }, @@ -51,6 +51,9 @@ const Template: StoryFn< typeof DateTimePicker > = ( { }; export const Default: StoryFn< typeof DateTimePicker > = Template.bind( {} ); +Default.args = { + currentDate: new Date(), +}; export const WithEvents: StoryFn< typeof DateTimePicker > = Template.bind( {} ); WithEvents.args = { diff --git a/packages/components/src/date-time/stories/date.story.tsx b/packages/components/src/date-time/stories/date.story.tsx index 36fef0c5bfd195..d305edf7a29e1c 100644 --- a/packages/components/src/date-time/stories/date.story.tsx +++ b/packages/components/src/date-time/stories/date.story.tsx @@ -20,7 +20,7 @@ const meta: Meta< typeof DatePicker > = { component: DatePicker, argTypes: { currentDate: { control: 'date' }, - onChange: { action: 'onChange', control: { type: null } }, + onChange: { action: 'onChange', control: false }, }, parameters: { controls: { expanded: true }, @@ -51,6 +51,9 @@ const Template: StoryFn< typeof DatePicker > = ( { }; export const Default: StoryFn< typeof DatePicker > = Template.bind( {} ); +Default.args = { + currentDate: new Date(), +}; export const WithEvents: StoryFn< typeof DatePicker > = Template.bind( {} ); WithEvents.args = { diff --git a/packages/components/src/date-time/stories/time.story.tsx b/packages/components/src/date-time/stories/time.story.tsx index c19b5b4f48f5c2..5497b1e84138c0 100644 --- a/packages/components/src/date-time/stories/time.story.tsx +++ b/packages/components/src/date-time/stories/time.story.tsx @@ -21,7 +21,7 @@ const meta: Meta< typeof TimePicker > = { subcomponents: { 'TimePicker.TimeInput': TimePicker.TimeInput }, argTypes: { currentTime: { control: 'date' }, - onChange: { action: 'onChange', control: { type: null } }, + onChange: { action: 'onChange', control: false }, }, parameters: { controls: { expanded: true }, @@ -52,6 +52,9 @@ const Template: StoryFn< typeof TimePicker > = ( { }; export const Default: StoryFn< typeof TimePicker > = Template.bind( {} ); +Default.args = { + currentTime: new Date(), +}; const TimeInputTemplate: StoryFn< typeof TimePicker.TimeInput > = ( args ) => { return <TimePicker.TimeInput { ...args } />; diff --git a/packages/components/src/dimension-control/README.md b/packages/components/src/dimension-control/README.md index 78c1a60275c13a..fd04d99ca4ca93 100644 --- a/packages/components/src/dimension-control/README.md +++ b/packages/components/src/dimension-control/README.md @@ -22,6 +22,7 @@ export default function MyCustomDimensionControl() { return ( <DimensionControl __nextHasNoMarginBottom + __next40pxDefaultSize label={ 'Padding' } icon={ 'desktop' } onChange={ ( value ) => setPaddingSize( value ) } diff --git a/packages/components/src/dimension-control/index.tsx b/packages/components/src/dimension-control/index.tsx index 25880f9b4fdb38..ffdfaeb84ee51d 100644 --- a/packages/components/src/dimension-control/index.tsx +++ b/packages/components/src/dimension-control/index.tsx @@ -18,6 +18,7 @@ import type { DimensionControlProps, Size } from './types'; import type { SelectControlSingleSelectionProps } from '../select-control/types'; import { ContextSystemProvider } from '../context'; import deprecated from '@wordpress/deprecated'; +import { maybeWarnDeprecated36pxSize } from '../utils/deprecated-36px-size'; const CONTEXT_VALUE = { BaseControl: { @@ -41,6 +42,7 @@ const CONTEXT_VALUE = { * * return ( * <DimensionControl + * __next40pxDefaultSize * __nextHasNoMarginBottom * label={ 'Padding' } * icon={ 'desktop' } @@ -68,6 +70,12 @@ export function DimensionControl( props: DimensionControlProps ) { version: '7.0', } ); + maybeWarnDeprecated36pxSize( { + componentName: 'DimensionControl', + __next40pxDefaultSize, + size: undefined, + } ); + const onChangeSpacingSize: SelectControlSingleSelectionProps[ 'onChange' ] = ( val ) => { const theSize = findSizeBySlug( sizes, val ); @@ -105,6 +113,7 @@ export function DimensionControl( props: DimensionControlProps ) { <ContextSystemProvider value={ CONTEXT_VALUE }> <SelectControl __next40pxDefaultSize={ __next40pxDefaultSize } + __shouldNotWarnDeprecated36pxSize __nextHasNoMarginBottom={ __nextHasNoMarginBottom } className={ clsx( className, diff --git a/packages/components/src/dimension-control/stories/index.story.tsx b/packages/components/src/dimension-control/stories/index.story.tsx index 15a63fcf6ccf6c..086d75b198fb05 100644 --- a/packages/components/src/dimension-control/stories/index.story.tsx +++ b/packages/components/src/dimension-control/stories/index.story.tsx @@ -24,7 +24,7 @@ const meta: Meta< typeof DimensionControl > = { id: 'components-dimensioncontrol', argTypes: { onChange: { action: 'onChange' }, - value: { control: { type: null } }, + value: { control: false }, icon: { control: { type: 'select' }, options: [ '-', 'desktop', 'tablet', 'mobile' ], @@ -50,6 +50,7 @@ const Template: StoryFn< typeof DimensionControl > = ( args ) => ( export const Default = Template.bind( {} ); Default.args = { __nextHasNoMarginBottom: true, + __next40pxDefaultSize: true, label: 'Please select a size', sizes, }; diff --git a/packages/components/src/dimension-control/test/__snapshots__/index.test.js.snap b/packages/components/src/dimension-control/test/__snapshots__/index.test.js.snap index bd2c26d641fe72..b1adfd5d9221ab 100644 --- a/packages/components/src/dimension-control/test/__snapshots__/index.test.js.snap +++ b/packages/components/src/dimension-control/test/__snapshots__/index.test.js.snap @@ -63,7 +63,7 @@ exports[`DimensionControl rendering renders with custom sizes 1`] = ` } .emotion-12 { - color: #1e1e1e; + color: var(--wp-components-color-foreground, #1e1e1e); line-height: 1.4; margin: 0; text-wrap: balance; @@ -126,12 +126,12 @@ exports[`DimensionControl rendering renders with custom sizes 1`] = ` white-space: nowrap; text-overflow: ellipsis; font-size: 16px; - height: 32px; - min-height: 32px; + height: 40px; + min-height: 40px; padding-top: 0; padding-bottom: 0; - padding-left: 8px; - padding-right: 26px; + padding-left: 12px; + padding-right: 30px; overflow: hidden; } @@ -157,8 +157,8 @@ exports[`DimensionControl rendering renders with custom sizes 1`] = ` } .emotion-21 { - -webkit-padding-end: 8px; - padding-inline-end: 8px; + -webkit-padding-end: 12px; + padding-inline-end: 12px; position: absolute; pointer-events: none; right: 0; @@ -345,7 +345,7 @@ exports[`DimensionControl rendering renders with defaults 1`] = ` } .emotion-12 { - color: #1e1e1e; + color: var(--wp-components-color-foreground, #1e1e1e); line-height: 1.4; margin: 0; text-wrap: balance; @@ -408,12 +408,12 @@ exports[`DimensionControl rendering renders with defaults 1`] = ` white-space: nowrap; text-overflow: ellipsis; font-size: 16px; - height: 32px; - min-height: 32px; + height: 40px; + min-height: 40px; padding-top: 0; padding-bottom: 0; - padding-left: 8px; - padding-right: 26px; + padding-left: 12px; + padding-right: 30px; overflow: hidden; } @@ -439,8 +439,8 @@ exports[`DimensionControl rendering renders with defaults 1`] = ` } .emotion-21 { - -webkit-padding-end: 8px; - padding-inline-end: 8px; + -webkit-padding-end: 12px; + padding-inline-end: 12px; position: absolute; pointer-events: none; right: 0; @@ -637,7 +637,7 @@ exports[`DimensionControl rendering renders with icon and custom icon label 1`] } .emotion-12 { - color: #1e1e1e; + color: var(--wp-components-color-foreground, #1e1e1e); line-height: 1.4; margin: 0; text-wrap: balance; @@ -700,12 +700,12 @@ exports[`DimensionControl rendering renders with icon and custom icon label 1`] white-space: nowrap; text-overflow: ellipsis; font-size: 16px; - height: 32px; - min-height: 32px; + height: 40px; + min-height: 40px; padding-top: 0; padding-bottom: 0; - padding-left: 8px; - padding-right: 26px; + padding-left: 12px; + padding-right: 30px; overflow: hidden; } @@ -731,8 +731,8 @@ exports[`DimensionControl rendering renders with icon and custom icon label 1`] } .emotion-21 { - -webkit-padding-end: 8px; - padding-inline-end: 8px; + -webkit-padding-end: 12px; + padding-inline-end: 12px; position: absolute; pointer-events: none; right: 0; @@ -941,7 +941,7 @@ exports[`DimensionControl rendering renders with icon and default icon label 1`] } .emotion-12 { - color: #1e1e1e; + color: var(--wp-components-color-foreground, #1e1e1e); line-height: 1.4; margin: 0; text-wrap: balance; @@ -1004,12 +1004,12 @@ exports[`DimensionControl rendering renders with icon and default icon label 1`] white-space: nowrap; text-overflow: ellipsis; font-size: 16px; - height: 32px; - min-height: 32px; + height: 40px; + min-height: 40px; padding-top: 0; padding-bottom: 0; - padding-left: 8px; - padding-right: 26px; + padding-left: 12px; + padding-right: 30px; overflow: hidden; } @@ -1035,8 +1035,8 @@ exports[`DimensionControl rendering renders with icon and default icon label 1`] } .emotion-21 { - -webkit-padding-end: 8px; - padding-inline-end: 8px; + -webkit-padding-end: 12px; + padding-inline-end: 12px; position: absolute; pointer-events: none; right: 0; diff --git a/packages/components/src/dimension-control/test/index.test.js b/packages/components/src/dimension-control/test/index.test.js index 14f1c509f70cf9..8f3cb7ea944cd1 100644 --- a/packages/components/src/dimension-control/test/index.test.js +++ b/packages/components/src/dimension-control/test/index.test.js @@ -15,7 +15,13 @@ import { plus } from '@wordpress/icons'; import { DimensionControl as _DimensionControl } from '../'; const DimensionControl = ( props ) => { - return <_DimensionControl { ...props } __nextHasNoMarginBottom />; + return ( + <_DimensionControl + { ...props } + __next40pxDefaultSize + __nextHasNoMarginBottom + /> + ); }; describe( 'DimensionControl', () => { diff --git a/packages/components/src/disabled/README.md b/packages/components/src/disabled/README.md index 9b257acd0f737b..e9eb6398554d9d 100644 --- a/packages/components/src/disabled/README.md +++ b/packages/components/src/disabled/README.md @@ -13,7 +13,14 @@ import { Button, Disabled, TextControl } from '@wordpress/components'; const MyDisabled = () => { const [ isDisabled, setIsDisabled ] = useState( true ); - let input = <TextControl label="Input" onChange={ () => {} } />; + let input = ( + <TextControl + __next40pxDefaultSize + __nextHasNoMarginBottom + label="Input" + onChange={ () => {} } + /> + ); if ( isDisabled ) { input = <Disabled>{ input }</Disabled>; } @@ -38,12 +45,7 @@ A component can detect if it has been wrapped in a `<Disabled />` by accessing i ```jsx function CustomButton( props ) { const isDisabled = useContext( Disabled.Context ); - return ( - <button - { ...props } - style={ { opacity: isDisabled ? 0.5 : 1 } } - /> - ); + return <button { ...props } style={ { opacity: isDisabled ? 0.5 : 1 } } />; } ``` diff --git a/packages/components/src/disabled/index.tsx b/packages/components/src/disabled/index.tsx index 32baac3411054c..cc55a4d2e6d672 100644 --- a/packages/components/src/disabled/index.tsx +++ b/packages/components/src/disabled/index.tsx @@ -31,7 +31,14 @@ const { Consumer, Provider } = Context; * const MyDisabled = () => { * const [ isDisabled, setIsDisabled ] = useState( true ); * - * let input = <TextControl label="Input" onChange={ () => {} } />; + * let input = ( + * <TextControl + * __next40pxDefaultSize + * __nextHasNoMarginBottom + * label="Input" + * onChange={ () => {} } + * /> + * ); * if ( isDisabled ) { * input = <Disabled>{ input }</Disabled>; * } diff --git a/packages/components/src/disabled/stories/index.story.tsx b/packages/components/src/disabled/stories/index.story.tsx index 59ff84dec43fca..591118681a82de 100644 --- a/packages/components/src/disabled/stories/index.story.tsx +++ b/packages/components/src/disabled/stories/index.story.tsx @@ -22,8 +22,8 @@ const meta: Meta< typeof Disabled > = { id: 'components-disabled', component: Disabled, argTypes: { - as: { control: { type: null } }, - children: { control: { type: null } }, + as: { control: false }, + children: { control: false }, }, parameters: { controls: { @@ -42,6 +42,7 @@ const Form = () => { <VStack> <TextControl __nextHasNoMarginBottom + __next40pxDefaultSize label="Text Control" value={ textControlValue } onChange={ setTextControlValue } @@ -54,6 +55,7 @@ const Form = () => { /> <SelectControl __nextHasNoMarginBottom + __next40pxDefaultSize label="Select Control" onChange={ () => {} } options={ [ @@ -81,7 +83,7 @@ Default.args = { export const ContentEditable: StoryFn< typeof Disabled > = ( args ) => { return ( <Disabled { ...args }> - <div contentEditable tabIndex={ 0 }> + <div contentEditable tabIndex={ 0 } suppressContentEditableWarning> contentEditable </div> </Disabled> diff --git a/packages/components/src/divider/stories/index.story.tsx b/packages/components/src/divider/stories/index.story.tsx index 4910c1b591c524..3f143fc5237694 100644 --- a/packages/components/src/divider/stories/index.story.tsx +++ b/packages/components/src/divider/stories/index.story.tsx @@ -24,7 +24,7 @@ const meta: Meta< typeof Divider > = { control: { type: 'text' }, }, wrapElement: { - control: { type: null }, + control: false, }, ref: { table: { diff --git a/packages/components/src/draggable/stories/index.story.tsx b/packages/components/src/draggable/stories/index.story.tsx index 6ecb54a07a3fb2..537dd9b40d7f36 100644 --- a/packages/components/src/draggable/stories/index.story.tsx +++ b/packages/components/src/draggable/stories/index.story.tsx @@ -21,8 +21,8 @@ const meta: Meta< typeof Draggable > = { title: 'Components/Utilities/Draggable', id: 'components-draggable', argTypes: { - elementId: { control: { type: null } }, - __experimentalDragComponent: { control: { type: null } }, + elementId: { control: false }, + __experimentalDragComponent: { control: false }, }, parameters: { actions: { argTypesRegex: '^on.*' }, diff --git a/packages/components/src/drop-zone/index.tsx b/packages/components/src/drop-zone/index.tsx index b1bd0199e877d8..dd8b97149a0598 100644 --- a/packages/components/src/drop-zone/index.tsx +++ b/packages/components/src/drop-zone/index.tsx @@ -15,7 +15,7 @@ import { __experimentalUseDropZone as useDropZone } from '@wordpress/compose'; /** * Internal dependencies */ -import type { DropType, DropZoneProps } from './types'; +import type { DropZoneProps } from './types'; import type { WordPressComponentProps } from '../context'; /** @@ -47,19 +47,22 @@ export function DropZoneComponent( { onFilesDrop, onHTMLDrop, onDrop, + isEligible = () => true, ...restProps }: WordPressComponentProps< DropZoneProps, 'div', false > ) { const [ isDraggingOverDocument, setIsDraggingOverDocument ] = useState< boolean >(); const [ isDraggingOverElement, setIsDraggingOverElement ] = useState< boolean >(); - const [ type, setType ] = useState< DropType >(); + const [ isActive, setIsActive ] = useState< boolean >(); const ref = useDropZone( { onDrop( event ) { - const files = event.dataTransfer - ? getFilesFromDataTransfer( event.dataTransfer ) - : []; - const html = event.dataTransfer?.getData( 'text/html' ); + if ( ! event.dataTransfer ) { + return; + } + + const files = getFilesFromDataTransfer( event.dataTransfer ); + const html = event.dataTransfer.getData( 'text/html' ); /** * From Windows Chrome 96, the `event.dataTransfer` returns both file object and HTML. @@ -76,32 +79,31 @@ export function DropZoneComponent( { onDragStart( event ) { setIsDraggingOverDocument( true ); - let _type: DropType = 'default'; + if ( ! event.dataTransfer ) { + return; + } /** * From Windows Chrome 96, the `event.dataTransfer` returns both file object and HTML. * The order of the checks is important to recognize the HTML drop. */ - if ( event.dataTransfer?.types.includes( 'text/html' ) ) { - _type = 'html'; + if ( event.dataTransfer.types.includes( 'text/html' ) ) { + setIsActive( !! onHTMLDrop ); } else if ( // Check for the types because sometimes the files themselves // are only available on drop. - event.dataTransfer?.types.includes( 'Files' ) || - ( event.dataTransfer - ? getFilesFromDataTransfer( event.dataTransfer ) - : [] - ).length > 0 + event.dataTransfer.types.includes( 'Files' ) || + getFilesFromDataTransfer( event.dataTransfer ).length > 0 ) { - _type = 'file'; + setIsActive( !! onFilesDrop ); + } else { + setIsActive( !! onDrop && isEligible( event.dataTransfer ) ); } - - setType( _type ); }, onDragEnd() { setIsDraggingOverElement( false ); setIsDraggingOverDocument( false ); - setType( undefined ); + setIsActive( undefined ); }, onDragEnter() { setIsDraggingOverElement( true ); @@ -112,14 +114,9 @@ export function DropZoneComponent( { } ); const classes = clsx( 'components-drop-zone', className, { - 'is-active': - ( isDraggingOverDocument || isDraggingOverElement ) && - ( ( type === 'file' && onFilesDrop ) || - ( type === 'html' && onHTMLDrop ) || - ( type === 'default' && onDrop ) ), + 'is-active': isActive, 'is-dragging-over-document': isDraggingOverDocument, 'is-dragging-over-element': isDraggingOverElement, - [ `is-dragging-${ type }` ]: !! type, } ); return ( diff --git a/packages/components/src/drop-zone/stories/index.story.tsx b/packages/components/src/drop-zone/stories/index.story.tsx index 7e2dcbf03c03b1..fe0be94e74fe85 100644 --- a/packages/components/src/drop-zone/stories/index.story.tsx +++ b/packages/components/src/drop-zone/stories/index.story.tsx @@ -21,7 +21,13 @@ export default meta; const Template: StoryFn< typeof DropZone > = ( props ) => { return ( - <div style={ { background: 'lightgray', padding: 16 } }> + <div + style={ { + background: 'lightgray', + padding: 32, + position: 'relative', + } } + > Drop something here <DropZone { ...props } /> </div> diff --git a/packages/components/src/drop-zone/style.scss b/packages/components/src/drop-zone/style.scss index d66eaee87b8a1f..4c2da2df0b4a52 100644 --- a/packages/components/src/drop-zone/style.scss +++ b/packages/components/src/drop-zone/style.scss @@ -46,9 +46,8 @@ .components-drop-zone__content { opacity: 1; - transition: opacity 0.2s ease-in-out; - @media (prefers-reduced-motion) { - transition: none; + @media not (prefers-reduced-motion) { + transition: opacity 0.2s ease-in-out; } } @@ -56,12 +55,10 @@ opacity: 1; transform: scale(1); - transition: - opacity 0.1s ease-in-out 0.1s, - transform 0.1s ease-in-out 0.1s; - - @media (prefers-reduced-motion) { - transition: none; + @media not (prefers-reduced-motion) { + transition: + opacity 0.1s ease-in-out 0.1s, + transform 0.1s ease-in-out 0.1s; } } } diff --git a/packages/components/src/drop-zone/types.ts b/packages/components/src/drop-zone/types.ts index 3982889a4f3eac..503f400bc4be45 100644 --- a/packages/components/src/drop-zone/types.ts +++ b/packages/components/src/drop-zone/types.ts @@ -26,4 +26,9 @@ export type DropZoneProps = { * It receives the HTML being dropped as an argument. */ onHTMLDrop?: ( html: string ) => void; + /** + * A function to determine if the drop zone is eligible to handle the drop + * data transfer items. + */ + isEligible?: ( dataTransfer: DataTransfer ) => boolean; }; diff --git a/packages/components/src/dropdown-menu/index.tsx b/packages/components/src/dropdown-menu/index.tsx index 0e4501be4839c0..195595fb9dc0de 100644 --- a/packages/components/src/dropdown-menu/index.tsx +++ b/packages/components/src/dropdown-menu/index.tsx @@ -164,11 +164,14 @@ function UnconnectedDropdownMenu( dropdownMenuProps: DropdownMenuProps ) { { controlSets?.flatMap( ( controlSet, indexOfSet ) => controlSet.map( ( control, indexOfControl ) => ( <Button + __next40pxDefaultSize key={ [ indexOfSet, indexOfControl, ].join() } - onClick={ ( event ) => { + onClick={ ( + event: React.MouseEvent< HTMLButtonElement > + ) => { event.stopPropagation(); props.onClose(); if ( control.onClick ) { diff --git a/packages/components/src/dropdown-menu/stories/index.story.tsx b/packages/components/src/dropdown-menu/stories/index.story.tsx index dd4907bd0b96b1..7b06ae979de84a 100644 --- a/packages/components/src/dropdown-menu/stories/index.story.tsx +++ b/packages/components/src/dropdown-menu/stories/index.story.tsx @@ -23,8 +23,9 @@ import { } from '@wordpress/icons'; const meta: Meta< typeof DropdownMenu > = { - title: 'Components/DropdownMenu', + title: 'Components/Actions/DropdownMenu', component: DropdownMenu, + id: 'components-dropdownmenu', parameters: { actions: { argTypesRegex: '^on.*' }, controls: { expanded: true }, @@ -36,9 +37,9 @@ const meta: Meta< typeof DropdownMenu > = { mapping: { menu, chevronDown, more }, control: { type: 'select' }, }, - open: { control: { type: null } }, - defaultOpen: { control: { type: null } }, - onToggle: { control: { type: null } }, + open: { control: false }, + defaultOpen: { control: false }, + onToggle: { control: false }, }, }; export default meta; diff --git a/packages/components/src/dropdown-menu/style.scss b/packages/components/src/dropdown-menu/style.scss index 1c716d80410e12..29fd6db18ba283 100644 --- a/packages/components/src/dropdown-menu/style.scss +++ b/packages/components/src/dropdown-menu/style.scss @@ -53,7 +53,7 @@ .components-menu-item__button, .components-menu-item__button.components-button { - min-height: $button-size; + min-height: $button-size-next-default-40px; height: auto; text-align: left; padding-left: $grid-unit-10; diff --git a/packages/components/src/dropdown/stories/index.story.tsx b/packages/components/src/dropdown/stories/index.story.tsx index bfa51a07a97170..ff4d0101a377ef 100644 --- a/packages/components/src/dropdown/stories/index.story.tsx +++ b/packages/components/src/dropdown/stories/index.story.tsx @@ -25,13 +25,13 @@ const meta: Meta< typeof Dropdown > = { type: 'radio', }, }, - position: { control: { type: null } }, - renderContent: { control: { type: null } }, - renderToggle: { control: { type: null } }, - open: { control: { type: null } }, - defaultOpen: { control: { type: null } }, - onToggle: { control: { type: null } }, - onClose: { control: { type: null } }, + position: { control: false }, + renderContent: { control: false }, + renderToggle: { control: false }, + open: { control: false }, + defaultOpen: { control: false }, + onToggle: { control: false }, + onClose: { control: false }, }, parameters: { actions: { argTypesRegex: '^on.*' }, diff --git a/packages/components/src/duotone-picker/color-list-picker/index.tsx b/packages/components/src/duotone-picker/color-list-picker/index.tsx index bd009c5db1d7d4..e3925b7d064fdc 100644 --- a/packages/components/src/duotone-picker/color-list-picker/index.tsx +++ b/packages/components/src/duotone-picker/color-list-picker/index.tsx @@ -12,7 +12,6 @@ import Button from '../../button'; import ColorPalette from '../../color-palette'; import ColorIndicator from '../../color-indicator'; import Icon from '../../icon'; -import { HStack } from '../../h-stack'; import type { ColorListPickerProps, ColorOptionProps } from './types'; import { useInstanceId } from '@wordpress/compose'; @@ -32,23 +31,24 @@ function ColorOption( { return ( <> <Button + __next40pxDefaultSize className="components-color-list-picker__swatch-button" + id={ labelId } onClick={ () => setIsOpen( ( prev ) => ! prev ) } aria-expanded={ isOpen } aria-controls={ contentId } - > - <HStack justify="flex-start" spacing={ 2 }> - { value ? ( + icon={ + value ? ( <ColorIndicator colorValue={ value } className="components-color-list-picker__swatch-color" /> ) : ( <Icon icon={ swatch } /> - ) } - <span id={ labelId }>{ label }</span> - </HStack> - </Button> + ) + } + text={ label } + /> <div role="group" id={ contentId } diff --git a/packages/components/src/duotone-picker/color-list-picker/style.scss b/packages/components/src/duotone-picker/color-list-picker/style.scss index dd0c79ffb8062b..56f2546158329a 100644 --- a/packages/components/src/duotone-picker/color-list-picker/style.scss +++ b/packages/components/src/duotone-picker/color-list-picker/style.scss @@ -7,12 +7,6 @@ margin: $grid-unit-10 0; } -.components-color-list-picker__swatch-button { - // Used to simulate styles as a .button.has-icon (which this component can't - // opt into, because it has to show more than a simple SVG as the "icon") - padding: 6px; -} - .components-color-list-picker__swatch-color { // Match the 24px size of the `swatch` icon (used when no color is set) margin: 2px; diff --git a/packages/components/src/duotone-picker/duotone-picker.tsx b/packages/components/src/duotone-picker/duotone-picker.tsx index ee54c9cdf4235e..8764b401c38296 100644 --- a/packages/components/src/duotone-picker/duotone-picker.tsx +++ b/packages/components/src/duotone-picker/duotone-picker.tsx @@ -168,6 +168,8 @@ function DuotonePicker( { !! clearable && ( <CircularOptionPicker.ButtonAction onClick={ () => onChange( undefined ) } + accessibleWhenDisabled + disabled={ ! value } > { __( 'Clear' ) } </CircularOptionPicker.ButtonAction> diff --git a/packages/components/src/duotone-picker/stories/duotone-picker.story.tsx b/packages/components/src/duotone-picker/stories/duotone-picker.story.tsx index f06d0ee40a6ce3..bf8439c38bb854 100644 --- a/packages/components/src/duotone-picker/stories/duotone-picker.story.tsx +++ b/packages/components/src/duotone-picker/stories/duotone-picker.story.tsx @@ -19,7 +19,7 @@ const meta: Meta< typeof DuotonePicker > = { component: DuotonePicker, argTypes: { onChange: { action: 'onChange' }, - value: { control: { type: null } }, + value: { control: false }, }, parameters: { controls: { expanded: true }, diff --git a/packages/components/src/flex/stories/index.story.tsx b/packages/components/src/flex/stories/index.story.tsx index 142f2796657219..fac5b2e7c31e05 100644 --- a/packages/components/src/flex/stories/index.story.tsx +++ b/packages/components/src/flex/stories/index.story.tsx @@ -17,7 +17,7 @@ const meta: Meta< typeof Flex > = { argTypes: { align: { control: { type: 'text' } }, as: { control: { type: 'text' } }, - children: { control: { type: null } }, + children: { control: false }, gap: { control: { type: 'text' } }, justify: { control: { type: 'text' } }, // Disabled isReversed because it's deprecated. diff --git a/packages/components/src/font-size-picker/README.md b/packages/components/src/font-size-picker/README.md index 5d7fe2b39a7373..39d916c0c7b212 100644 --- a/packages/components/src/font-size-picker/README.md +++ b/packages/components/src/font-size-picker/README.md @@ -29,6 +29,7 @@ const MyFontSizePicker = () => { return ( <FontSizePicker + __next40pxDefaultSize fontSizes={ fontSizes } value={ fontSize } fallbackFontSize={ fallbackFontSize } diff --git a/packages/components/src/font-size-picker/font-size-picker-select.tsx b/packages/components/src/font-size-picker/font-size-picker-select.tsx index 19eaba1cfbecd0..b33c382f3393e4 100644 --- a/packages/components/src/font-size-picker/font-size-picker-select.tsx +++ b/packages/components/src/font-size-picker/font-size-picker-select.tsx @@ -69,6 +69,7 @@ const FontSizePickerSelect = ( props: FontSizePickerSelectProps ) => { return ( <CustomSelectControl __next40pxDefaultSize={ __next40pxDefaultSize } + __shouldNotWarnDeprecated36pxSize className="components-font-size-picker__select" label={ __( 'Font size' ) } hideLabelFromVision diff --git a/packages/components/src/font-size-picker/font-size-picker-toggle-group.tsx b/packages/components/src/font-size-picker/font-size-picker-toggle-group.tsx index 1e4cbcd1b72660..1b3619c800e453 100644 --- a/packages/components/src/font-size-picker/font-size-picker-toggle-group.tsx +++ b/packages/components/src/font-size-picker/font-size-picker-toggle-group.tsx @@ -19,6 +19,7 @@ const FontSizePickerToggleGroup = ( props: FontSizePickerToggleGroupProps ) => { <ToggleGroupControl __nextHasNoMarginBottom __next40pxDefaultSize={ __next40pxDefaultSize } + __shouldNotWarnDeprecated36pxSize label={ __( 'Font size' ) } hideLabelFromVision value={ value } diff --git a/packages/components/src/font-size-picker/index.native.js b/packages/components/src/font-size-picker/index.native.js index 5c22cb86175dbd..90af5d33e25706 100644 --- a/packages/components/src/font-size-picker/index.native.js +++ b/packages/components/src/font-size-picker/index.native.js @@ -126,7 +126,7 @@ function FontSizePicker( { </View> </BottomSheet.Cell> { fontSizes.map( ( item, index ) => { - // Only display a choice that we can currenly select. + // Only display a choice that we can currently select. if ( ! parseFloat( item.sizePx ) ) { return null; } diff --git a/packages/components/src/font-size-picker/index.tsx b/packages/components/src/font-size-picker/index.tsx index f93a09440a0688..a47812640f1a29 100644 --- a/packages/components/src/font-size-picker/index.tsx +++ b/packages/components/src/font-size-picker/index.tsx @@ -35,6 +35,7 @@ import { Spacer } from '../spacer'; import FontSizePickerSelect from './font-size-picker-select'; import FontSizePickerToggleGroup from './font-size-picker-toggle-group'; import { T_SHIRT_NAMES } from './constants'; +import { maybeWarnDeprecated36pxSize } from '../utils/deprecated-36px-size'; const DEFAULT_UNITS = [ 'px', 'em', 'rem', 'vw', 'vh' ]; @@ -123,6 +124,12 @@ const UnforwardedFontSizePicker = ( !! valueUnit && [ 'em', 'rem', 'vw', 'vh' ].includes( valueUnit ); const isDisabled = value === undefined; + maybeWarnDeprecated36pxSize( { + componentName: 'FontSizePicker', + __next40pxDefaultSize, + size, + } ); + return ( <Container ref={ ref } className="components-font-size-picker"> <VisuallyHidden as="legend">{ __( 'Font size' ) }</VisuallyHidden> @@ -205,6 +212,7 @@ const UnforwardedFontSizePicker = ( <FlexItem isBlock> <UnitControl __next40pxDefaultSize={ __next40pxDefaultSize } + __shouldNotWarnDeprecated36pxSize label={ __( 'Custom' ) } labelPosition="top" hideLabelFromVision @@ -235,6 +243,7 @@ const UnforwardedFontSizePicker = ( __next40pxDefaultSize={ __next40pxDefaultSize } + __shouldNotWarnDeprecated36pxSize className="components-font-size-picker__custom-input" label={ __( 'Custom Size' ) } hideLabelFromVision diff --git a/packages/components/src/font-size-picker/stories/index.story.tsx b/packages/components/src/font-size-picker/stories/index.story.tsx index eec8f5173d9655..f6502fcfd537c8 100644 --- a/packages/components/src/font-size-picker/stories/index.story.tsx +++ b/packages/components/src/font-size-picker/stories/index.story.tsx @@ -17,7 +17,7 @@ const meta: Meta< typeof FontSizePicker > = { title: 'Components/FontSizePicker', component: FontSizePicker, argTypes: { - value: { control: { type: null } }, + value: { control: false }, }, parameters: { actions: { argTypesRegex: '^on.*' }, @@ -66,6 +66,7 @@ const TwoFontSizePickersWithState: StoryFn< typeof FontSizePicker > = ( { export const Default: StoryFn< typeof FontSizePicker > = FontSizePickerWithState.bind( {} ); Default.args = { + __next40pxDefaultSize: true, disableCustomFontSizes: false, fontSizes: [ { diff --git a/packages/components/src/font-size-picker/styles.ts b/packages/components/src/font-size-picker/styles.ts index f47ca41b51eb71..b0e33b5aea3a2e 100644 --- a/packages/components/src/font-size-picker/styles.ts +++ b/packages/components/src/font-size-picker/styles.ts @@ -16,6 +16,7 @@ export const Container = styled.fieldset` border: 0; margin: 0; padding: 0; + display: contents; `; export const Header = styled( HStack )` diff --git a/packages/components/src/font-size-picker/test/index.tsx b/packages/components/src/font-size-picker/test/index.tsx index e7205e57eefaa6..34e8ce17c67fa6 100644 --- a/packages/components/src/font-size-picker/test/index.tsx +++ b/packages/components/src/font-size-picker/test/index.tsx @@ -8,13 +8,17 @@ import { render } from '@ariakit/test/react'; /** * Internal dependencies */ -import FontSizePicker from '../'; +import _FontSizePicker from '../'; import type { FontSize } from '../types'; /** * WordPress dependencies */ import { useState } from '@wordpress/element'; +const FontSizePicker = ( + props: React.ComponentProps< typeof _FontSizePicker > +) => <_FontSizePicker __next40pxDefaultSize { ...props } />; + const ControlledFontSizePicker = ( { onChange, ...props diff --git a/packages/components/src/form-file-upload/README.md b/packages/components/src/form-file-upload/README.md index 4dd8affc5f54a0..74e6e369383383 100644 --- a/packages/components/src/form-file-upload/README.md +++ b/packages/components/src/form-file-upload/README.md @@ -1,95 +1,106 @@ # FormFileUpload -FormFileUpload is a component that allows users to select files from their local device. +<!-- This file is generated automatically and cannot be edited directly. Make edits via TypeScript types and TSDocs. --> -## Usage +<p class="callout callout-info">See the <a href="https://wordpress.github.io/gutenberg/?path=/docs/components-formfileupload--docs">WordPress Storybook</a> for more detailed, interactive documentation.</p> + +FormFileUpload allows users to select files from their local device. ```jsx import { FormFileUpload } from '@wordpress/components'; const MyFormFileUpload = () => ( - <FormFileUpload - accept="image/*" - onChange={ ( event ) => console.log( event.currentTarget.files ) } - > - Upload - </FormFileUpload> + <FormFileUpload + __next40pxDefaultSize + accept="image/*" + onChange={ ( event ) => console.log( event.currentTarget.files ) } + > + Upload + </FormFileUpload> ); ``` ## Props -The component accepts the following props. Props not included in this set will be passed to the `Button` component. +### `__next40pxDefaultSize` + + - Type: `boolean` + - Required: No + - Default: `false` + +Start opting into the larger default height that will become the default size in a future version. + +### `accept` -### accept + - Type: `string` + - Required: No -A string passed to `input` element that tells the browser which file types can be upload to the upload by the user use. e.g: `image/*,video/*`. -More information about this string is available in https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#Unique_file_type_specifiers. +A string passed to the `input` element that tells the browser which +[file types](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#Unique_file_type_specifiers) +can be uploaded by the user. e.g: `image/*,video/*`. -- Type: `String` -- Required: No +### `children` -### children + - Type: `ReactNode` + - Required: No Children are passed as children of `Button`. -- Type: `Boolean` -- Required: No +### `icon` -### icon + - Type: `IconType` + - Required: No -The icon to render. Supported values are: Dashicons (specified as strings), functions, Component instances and `null`. +The icon to render in the default button. -- Type: `String|Function|Component|null` -- Required: No -- Default: `null` +See the `Icon` component docs for more information. -### multiple +### `multiple` + + - Type: `boolean` + - Required: No + - Default: `false` Whether to allow multiple selection of files or not. -- Type: `Boolean` -- Required: No -- Default: `false` +### `onChange` -### onChange + - Type: `ChangeEventHandler<HTMLInputElement>` + - Required: Yes Callback function passed directly to the `input` file element. Select files will be available in `event.currentTarget.files`. -- Type: `Function` -- Required: Yes +### `onClick` -### onClick + - Type: `MouseEventHandler<HTMLInputElement>` + - Required: No Callback function passed directly to the `input` file element. -This can be useful when you want to force a `change` event to fire when the user chooses the same file again. To do this, set the target value to an empty string in the `onClick` function. +This can be useful when you want to force a `change` event to fire when +the user chooses the same file again. To do this, set the target value to +an empty string in the `onClick` function. ```jsx <FormFileUpload - onClick={ ( event ) => ( event.target.value = '' ) } - onChange={ onChange } + __next40pxDefaultSize + onClick={ ( event ) => ( event.target.value = '' ) } + onChange={ onChange } > - Upload + Upload </FormFileUpload> ``` -- Type: `Function` -- Required: No - -### render - -Optional callback function used to render the UI. If passed, the component does not render the default UI (a button) and calls this function to render it. The function receives an object with property `openFileDialog`, a function that, when called, opens the browser native file upload modal window. +### `render` -- Type: `Function` -- Required: No + - Type: `(arg: { openFileDialog: () => void; }) => ReactNode` + - Required: No -### __next40pxDefaultSize - -Start opting into the larger default height that will become the default size in a future version. +Optional callback function used to render the UI. -- Type: `Boolean` -- Required: No -- Default: `false` +If passed, the component does not render the default UI (a button) and +calls this function to render it. The function receives an object with +property `openFileDialog`, a function that, when called, opens the browser +native file upload modal window. diff --git a/packages/components/src/form-file-upload/docs-manifest.json b/packages/components/src/form-file-upload/docs-manifest.json new file mode 100644 index 00000000000000..cb000536d73562 --- /dev/null +++ b/packages/components/src/form-file-upload/docs-manifest.json @@ -0,0 +1,5 @@ +{ + "$schema": "../../schemas/docs-manifest.json", + "displayName": "FormFileUpload", + "filePath": "./index.tsx" +} diff --git a/packages/components/src/form-file-upload/index.tsx b/packages/components/src/form-file-upload/index.tsx index 66f0b2ea6d6480..378dc144c6fe8c 100644 --- a/packages/components/src/form-file-upload/index.tsx +++ b/packages/components/src/form-file-upload/index.tsx @@ -9,15 +9,17 @@ import { useRef } from '@wordpress/element'; import Button from '../button'; import type { WordPressComponentProps } from '../context'; import type { FormFileUploadProps } from './types'; +import { maybeWarnDeprecated36pxSize } from '../utils/deprecated-36px-size'; /** - * FormFileUpload is a component that allows users to select files from their local device. + * FormFileUpload allows users to select files from their local device. * * ```jsx * import { FormFileUpload } from '@wordpress/components'; * * const MyFormFileUpload = () => ( * <FormFileUpload + * __next40pxDefaultSize * accept="image/*" * onChange={ ( event ) => console.log( event.currentTarget.files ) } * > @@ -40,6 +42,15 @@ export function FormFileUpload( { ref.current?.click(); }; + if ( ! render ) { + maybeWarnDeprecated36pxSize( { + componentName: 'FormFileUpload', + __next40pxDefaultSize: props.__next40pxDefaultSize, + // @ts-expect-error - We don't "officially" support all Button props but this likely happens. + size: props.size, + } ); + } + const ui = render ? ( render( { openFileDialog } ) ) : ( @@ -50,9 +61,15 @@ export function FormFileUpload( { // @todo: Temporary fix a bug that prevents Chromium browsers from selecting ".heic" files // from the file upload. See https://core.trac.wordpress.org/ticket/62268#comment:4. // This can be removed once the Chromium fix is in the stable channel. - const compatAccept = !! accept?.includes( 'image/*' ) - ? `${ accept }, image/heic, image/heif` - : accept; + // Prevent Safari from adding "image/heic" and "image/heif" to the accept attribute. + const isSafari = + globalThis.window?.navigator.userAgent.includes( 'Safari' ) && + ! globalThis.window?.navigator.userAgent.includes( 'Chrome' ) && + ! globalThis.window?.navigator.userAgent.includes( 'Chromium' ); + const compatAccept = + ! isSafari && !! accept?.includes( 'image/*' ) + ? `${ accept }, image/heic, image/heif` + : accept; return ( <div className="components-form-file-upload"> diff --git a/packages/components/src/form-file-upload/stories/index.story.tsx b/packages/components/src/form-file-upload/stories/index.story.tsx index 3599ccc51c22eb..cec182346c0a72 100644 --- a/packages/components/src/form-file-upload/stories/index.story.tsx +++ b/packages/components/src/form-file-upload/stories/index.story.tsx @@ -18,9 +18,9 @@ const meta: Meta< typeof FormFileUpload > = { id: 'components-formfileupload', component: FormFileUpload, argTypes: { - icon: { control: { type: null } }, - onChange: { action: 'onChange', control: { type: null } }, - onClick: { control: { type: null } }, + icon: { control: false }, + onChange: { action: 'onChange', control: false }, + onClick: { control: false }, }, parameters: { controls: { expanded: true }, @@ -36,6 +36,7 @@ const Template: StoryFn< typeof FormFileUpload > = ( props ) => { export const Default = Template.bind( {} ); Default.args = { children: 'Select file', + __next40pxDefaultSize: true, }; export const RestrictFileTypes = Template.bind( {} ); diff --git a/packages/components/src/form-file-upload/test/index.tsx b/packages/components/src/form-file-upload/test/index.tsx index 3035bcaa670647..b82dcd754bcd20 100644 --- a/packages/components/src/form-file-upload/test/index.tsx +++ b/packages/components/src/form-file-upload/test/index.tsx @@ -7,13 +7,17 @@ import userEvent from '@testing-library/user-event'; /** * Internal dependencies */ -import FormFileUpload from '..'; +import _FormFileUpload from '..'; /** * Browser dependencies */ const { File } = window; +const FormFileUpload = ( + props: React.ComponentProps< typeof _FormFileUpload > +) => <_FormFileUpload __next40pxDefaultSize { ...props } />; + // @testing-library/user-event considers changing <input type="file"> to a string as a change, but it do not occur on real browsers, so the comparisons will be against this result const fakePath = expect.objectContaining( { target: expect.objectContaining( { diff --git a/packages/components/src/form-file-upload/types.ts b/packages/components/src/form-file-upload/types.ts index 728ed959aba76e..3bdbbf5ac2d4c0 100644 --- a/packages/components/src/form-file-upload/types.ts +++ b/packages/components/src/form-file-upload/types.ts @@ -17,10 +17,9 @@ export type FormFileUploadProps = { */ __next40pxDefaultSize?: boolean; /** - * A string passed to `input` element that tells the browser which file types can be - * upload to the upload by the user use. e.g: `image/*,video/*`. - * - * @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#Unique_file_type_specifiers. + * A string passed to the `input` element that tells the browser which + * [file types](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#Unique_file_type_specifiers) + * can be uploaded by the user. e.g: `image/*,video/*`. */ accept?: InputHTMLAttributes< HTMLInputElement >[ 'accept' ]; /** @@ -28,7 +27,9 @@ export type FormFileUploadProps = { */ children?: ReactNode; /** - * The icon to render in the `Button`. + * The icon to render in the default button. + * + * See the `Icon` component docs for more information. */ icon?: ComponentProps< typeof Icon >[ 'icon' ]; /** @@ -50,10 +51,11 @@ export type FormFileUploadProps = { * * ```jsx * <FormFileUpload - * onClick={ ( event ) => ( event.target.value = '' ) } - * onChange={ onChange } + * __next40pxDefaultSize + * onClick={ ( event ) => ( event.target.value = '' ) } + * onChange={ onChange } * > - * Upload + * Upload * </FormFileUpload> * ``` */ diff --git a/packages/components/src/form-toggle/style.scss b/packages/components/src/form-toggle/style.scss index 900874b59004b8..8ae46d23558276 100644 --- a/packages/components/src/form-toggle/style.scss +++ b/packages/components/src/form-toggle/style.scss @@ -24,10 +24,13 @@ $transition-duration: 0.2s; width: $toggle-width; height: $toggle-height; border-radius: math.div($toggle-height, 2); - transition: - $transition-duration background-color ease, - $transition-duration border-color ease; - @include reduce-motion("transition"); + + @media not (prefers-reduced-motion) { + transition: + $transition-duration background-color ease, + $transition-duration border-color ease; + } + overflow: hidden; // Windows High Contrast Mode @@ -39,8 +42,9 @@ $transition-duration: 0.2s; // Expand the border to fake a solid in Windows High Contrast Mode. border-top: #{ $toggle-height } solid transparent; - transition: $transition-duration opacity ease; - @include reduce-motion("transition"); + @media not (prefers-reduced-motion) { + transition: $transition-duration opacity ease; + } opacity: 0; } @@ -55,10 +59,13 @@ $transition-duration: 0.2s; width: $toggle-thumb-size; height: $toggle-thumb-size; border-radius: $radius-round; - transition: - $transition-duration transform ease, - $transition-duration background-color ease-out; - @include reduce-motion("transition"); + + @media not (prefers-reduced-motion) { + transition: + $transition-duration transform ease, + $transition-duration background-color ease-out; + } + background-color: $gray-900; box-shadow: $elevation-x-small; diff --git a/packages/components/src/form-token-field/README.md b/packages/components/src/form-token-field/README.md index 70e9bd09a61a36..a04ba5ec7b9d29 100644 --- a/packages/components/src/form-token-field/README.md +++ b/packages/components/src/form-token-field/README.md @@ -85,6 +85,7 @@ const MyFormTokenField = () => { return ( <FormTokenField + __next40pxDefaultSize value={ selectedContinents } suggestions={ continents } onChange={ ( tokens ) => setSelectedContinents( tokens ) } diff --git a/packages/components/src/form-token-field/index.tsx b/packages/components/src/form-token-field/index.tsx index 4f2f325e409a76..987c75d769b727 100644 --- a/packages/components/src/form-token-field/index.tsx +++ b/packages/components/src/form-token-field/index.tsx @@ -30,6 +30,7 @@ import { import { Spacer } from '../spacer'; import { useDeprecated36pxDefaultSizeProp } from '../utils/use-deprecated-props'; import { withIgnoreIMEEvents } from '../utils/with-ignore-ime-events'; +import { maybeWarnDeprecated36pxSize } from '../utils/deprecated-36px-size'; const identity = ( value: string ) => value; @@ -86,6 +87,12 @@ export function FormTokenField( props: FormTokenFieldProps ) { } ); } + maybeWarnDeprecated36pxSize( { + componentName: 'FormTokenField', + size: undefined, + __next40pxDefaultSize, + } ); + const instanceId = useInstanceId( FormTokenField ); // We reset to these initial values again in the onBlur diff --git a/packages/components/src/form-token-field/stories/index.story.tsx b/packages/components/src/form-token-field/stories/index.story.tsx index 729120ad456553..52daabe5608b0a 100644 --- a/packages/components/src/form-token-field/stories/index.story.tsx +++ b/packages/components/src/form-token-field/stories/index.story.tsx @@ -19,10 +19,10 @@ const meta: Meta< typeof FormTokenField > = { id: 'components-formtokenfield', argTypes: { value: { - control: { type: null }, + control: false, }, __experimentalValidateInput: { - control: { type: null }, + control: false, }, }, parameters: { @@ -64,6 +64,7 @@ Default.args = { label: 'Type a continent', suggestions: continents, __nextHasNoMarginBottom: true, + __next40pxDefaultSize: true, }; export const Async: StoryFn< typeof FormTokenField > = ( { @@ -102,6 +103,7 @@ Async.args = { label: 'Type a continent', suggestions: continents, __nextHasNoMarginBottom: true, + __next40pxDefaultSize: true, }; export const DropdownSelector: StoryFn< typeof FormTokenField > = diff --git a/packages/components/src/form-token-field/style.scss b/packages/components/src/form-token-field/style.scss index d18ca274d76764..40e5aca989fbe1 100644 --- a/packages/components/src/form-token-field/style.scss +++ b/packages/components/src/form-token-field/style.scss @@ -124,8 +124,10 @@ height: auto; background: $gray-300; min-width: unset; - transition: all 0.2s cubic-bezier(0.4, 1, 0.4, 1); - @include reduce-motion; + + @media not (prefers-reduced-motion) { + transition: all 0.2s cubic-bezier(0.4, 1, 0.4, 1); + } } .components-form-token-field__token-text { @@ -154,8 +156,11 @@ min-width: 100%; max-height: $grid-unit-80 * 2; overflow-y: auto; - transition: all 0.15s ease-in-out; - @include reduce-motion("transition"); + + @media not (prefers-reduced-motion) { + transition: all 0.15s ease-in-out; + } + list-style: none; box-shadow: inset 0 $border-width 0 0 $gray-600; // Matches the border color of the input. margin: 0; diff --git a/packages/components/src/form-token-field/test/index.tsx b/packages/components/src/form-token-field/test/index.tsx index 961214a574c90d..60c17112717bd1 100644 --- a/packages/components/src/form-token-field/test/index.tsx +++ b/packages/components/src/form-token-field/test/index.tsx @@ -21,7 +21,11 @@ import { useState } from '@wordpress/element'; /** * Internal dependencies */ -import FormTokenField from '../'; +import _FormTokenField from '../'; + +const FormTokenField = ( props: ComponentProps< typeof _FormTokenField > ) => ( + <_FormTokenField __next40pxDefaultSize { ...props } /> +); const FormTokenFieldWithState = ( { onChange, diff --git a/packages/components/src/gradient-picker/README.md b/packages/components/src/gradient-picker/README.md index 815b3d8eb5dd75..275c46ec5958c9 100644 --- a/packages/components/src/gradient-picker/README.md +++ b/packages/components/src/gradient-picker/README.md @@ -1,110 +1,157 @@ # GradientPicker -GradientPicker is a React component that renders a color gradient picker to define a multi step gradient. There's either a _linear_ or a _radial_ type available. +<!-- This file is generated automatically and cannot be edited directly. Make edits via TypeScript types and TSDocs. --> -![gradient-picker](https://user-images.githubusercontent.com/881729/147505438-3818c4c7-65b5-4394-b97b-af903c62adce.png) +<p class="callout callout-info">See the <a href="https://wordpress.github.io/gutenberg/?path=/docs/components-gradientpicker--docs">WordPress Storybook</a> for more detailed, interactive documentation.</p> -## Usage - -Render a GradientPicker. +GradientPicker is a React component that renders a color gradient picker to +define a multi step gradient. There's either a _linear_ or a _radial_ type +available. ```jsx import { useState } from 'react'; import { GradientPicker } from '@wordpress/components'; -const myGradientPicker = () => { - const [ gradient, setGradient ] = useState( null ); - - return ( - <GradientPicker - value={ gradient } - onChange={ ( currentGradient ) => setGradient( currentGradient ) } - gradients={ [ - { - name: 'JShine', - gradient: - 'linear-gradient(135deg,#12c2e9 0%,#c471ed 50%,#f64f59 100%)', - slug: 'jshine', - }, - { - name: 'Moonlit Asteroid', - gradient: - 'linear-gradient(135deg,#0F2027 0%, #203A43 0%, #2c5364 100%)', - slug: 'moonlit-asteroid', - }, - { - name: 'Rastafarie', - gradient: - 'linear-gradient(135deg,#1E9600 0%, #FFF200 0%, #FF0000 100%)', - slug: 'rastafari', - }, - ] } - /> - ); +const MyGradientPicker = () => { + const [ gradient, setGradient ] = useState( null ); + + return ( + <GradientPicker + value={ gradient } + onChange={ ( currentGradient ) => setGradient( currentGradient ) } + gradients={ [ + { + name: 'JShine', + gradient: + 'linear-gradient(135deg,#12c2e9 0%,#c471ed 50%,#f64f59 100%)', + slug: 'jshine', + }, + { + name: 'Moonlit Asteroid', + gradient: + 'linear-gradient(135deg,#0F2027 0%, #203A43 0%, #2c5364 100%)', + slug: 'moonlit-asteroid', + }, + { + name: 'Rastafarie', + gradient: + 'linear-gradient(135deg,#1E9600 0%, #FFF200 0%, #FF0000 100%)', + slug: 'rastafari', + }, + ] } + /> + ); }; ``` ## Props -The component accepts the following props: +### `__experimentalIsRenderedInSidebar` -### `className`: `string` + - Type: `boolean` + - Required: No + - Default: `false` -The class name added to the wrapper. +Whether this is rendered in the sidebar. -- Required: No +### `asButtons` -### `value`: `string` + - Type: `boolean` + - Required: No + - Default: `false` -The current value of the gradient. Pass a css gradient like `linear-gradient(90deg, rgb(6, 147, 227) 0%, rgb(155, 81, 224) 100%)`. Optionally pass in a `null` value to specify no gradient is currently selected. +Whether the control should present as a set of buttons, +each with its own tab stop. -- Required: No -- Default: `linear-gradient(90deg, rgb(6, 147, 227) 0%, rgb(155, 81, 224) 100%)` +### `aria-label` -### `onChange`: `( currentGradient: string | undefined ) => void` + - Type: `string` + - Required: No -The function called when a new gradient has been defined. It is passed the `currentGradient` as an argument. +A label to identify the purpose of the control. -- Required: Yes +### `aria-labelledby` -### `gradients`: `GradientsProp[]` + - Type: `string` + - Required: No -An array of objects of predefined gradients displayed above the gradient selector. +An ID of an element to provide a label for the control. -- Required: No -- Default: `[]` +### `className` -### `clearable`: `boolean` + - Type: `string` + - Required: No + +The class name added to the wrapper. + +### `clearable` + + - Type: `boolean` + - Required: No + - Default: `true` Whether the palette should have a clearing button or not. -- Required: No -- Default: true +### `disableCustomGradients` + + - Type: `boolean` + - Required: No + - Default: `false` + +If true, the gradient picker will not be displayed and only defined +gradients from `gradients` will be shown. + +### `enableAlpha` + + - Type: `boolean` + - Required: No + - Default: `true` + +Whether to enable alpha transparency options in the picker. + +### `gradients` + + - Type: `GradientsProp` + - Required: No + - Default: `[]` + +An array of objects as predefined gradients displayed above the gradient +selector. Alternatively, if there are multiple sets (or 'origins') of +gradients, you can pass an array of objects each with a `name` and a +`gradients` array which will in turn contain the predefined gradient objects. -### `disableCustomGradients`: `boolean` +### `headingLevel` -If true, the gradient picker will not be displayed and only defined gradients from `gradients` are available. + - Type: `1 | 2 | 3 | 4 | 5 | 6 | "1" | "2" | "3" | "4" | ...` + - Required: No + - Default: `2` -- Required: No -- Default: false +The heading level. Only applies in cases where gradients are provided +from multiple origins (i.e. when the array passed as the `gradients` prop +contains two or more items). -### `headingLevel`: `1 | 2 | 3 | 4 | 5 | 6 | '1' | '2' | '3' | '4' | '5' | '6'` +### `loop` -The heading level. Only applies in cases where gradients are provided from multiple origins (ie. when the array passed as the `gradients` prop contains two or more items). + - Type: `boolean` + - Required: No + - Default: `true` -- Required: No -- Default: `2` +Prevents keyboard interaction from wrapping around. +Only used when `asButtons` is not true. -### `asButtons`: `boolean` +### `onChange` -Whether the control should present as a set of buttons, each with its own tab stop. + - Type: `(currentGradient: string) => void` + - Required: Yes -- Required: No -- Default: `false` +The function called when a new gradient has been defined. It is passed to +the `currentGradient` as an argument. -### `loop`: `boolean` +### `value` -Prevents keyboard interaction from wrapping around. Only used when `asButtons` is not true. + - Type: `string` + - Required: No + - Default: `'linear-gradient(135deg,rgba(6,147,227,1) 0%,rgb(155,81,224) 100%)'` -- Required: No -- Default: `true` +The current value of the gradient. Pass a css gradient string (See default value for example). +Optionally pass in a `null` value to specify no gradient is currently selected. diff --git a/packages/components/src/gradient-picker/docs-manifest.json b/packages/components/src/gradient-picker/docs-manifest.json new file mode 100644 index 00000000000000..6bea56ccc678c6 --- /dev/null +++ b/packages/components/src/gradient-picker/docs-manifest.json @@ -0,0 +1,5 @@ +{ + "$schema": "../../schemas/docs-manifest.json", + "displayName": "GradientPicker", + "filePath": "./index.tsx" +} diff --git a/packages/components/src/gradient-picker/index.tsx b/packages/components/src/gradient-picker/index.tsx index 8368279b8afd70..28491d8a56010e 100644 --- a/packages/components/src/gradient-picker/index.tsx +++ b/packages/components/src/gradient-picker/index.tsx @@ -166,44 +166,44 @@ function Component( props: PickerProps< any > ) { } /** - * GradientPicker is a React component that renders a color gradient picker to + * GradientPicker is a React component that renders a color gradient picker to * define a multi step gradient. There's either a _linear_ or a _radial_ type * available. * * ```jsx - *import { GradientPicker } from '@wordpress/components'; - *import { useState } from '@wordpress/element'; + * import { useState } from 'react'; + * import { GradientPicker } from '@wordpress/components'; * - *const myGradientPicker = () => { - * const [ gradient, setGradient ] = useState( null ); + * const MyGradientPicker = () => { + * const [ gradient, setGradient ] = useState( null ); * - * return ( - * <GradientPicker - * value={ gradient } - * onChange={ ( currentGradient ) => setGradient( currentGradient ) } - * gradients={ [ - * { - * name: 'JShine', - * gradient: - * 'linear-gradient(135deg,#12c2e9 0%,#c471ed 50%,#f64f59 100%)', - * slug: 'jshine', - * }, - * { - * name: 'Moonlit Asteroid', - * gradient: - * 'linear-gradient(135deg,#0F2027 0%, #203A43 0%, #2c5364 100%)', - * slug: 'moonlit-asteroid', - * }, - * { - * name: 'Rastafarie', - * gradient: - * 'linear-gradient(135deg,#1E9600 0%, #FFF200 0%, #FF0000 100%)', - * slug: 'rastafari', - * }, - * ] } - * /> - * ); - *}; + * return ( + * <GradientPicker + * value={ gradient } + * onChange={ ( currentGradient ) => setGradient( currentGradient ) } + * gradients={ [ + * { + * name: 'JShine', + * gradient: + * 'linear-gradient(135deg,#12c2e9 0%,#c471ed 50%,#f64f59 100%)', + * slug: 'jshine', + * }, + * { + * name: 'Moonlit Asteroid', + * gradient: + * 'linear-gradient(135deg,#0F2027 0%, #203A43 0%, #2c5364 100%)', + * slug: 'moonlit-asteroid', + * }, + * { + * name: 'Rastafarie', + * gradient: + * 'linear-gradient(135deg,#1E9600 0%, #FFF200 0%, #FF0000 100%)', + * slug: 'rastafari', + * }, + * ] } + * /> + * ); + * }; *``` * */ @@ -213,6 +213,7 @@ export function GradientPicker( { onChange, value, clearable = true, + enableAlpha = true, disableCustomGradients = false, __experimentalIsRenderedInSidebar, headingLevel = 2, @@ -230,6 +231,7 @@ export function GradientPicker( { __experimentalIsRenderedInSidebar={ __experimentalIsRenderedInSidebar } + enableAlpha={ enableAlpha } value={ value } onChange={ onChange } /> @@ -247,6 +249,8 @@ export function GradientPicker( { ! disableCustomGradients && ( <CircularOptionPicker.ButtonAction onClick={ clearGradient } + accessibleWhenDisabled + disabled={ ! value } > { __( 'Clear' ) } </CircularOptionPicker.ButtonAction> diff --git a/packages/components/src/gradient-picker/stories/index.story.tsx b/packages/components/src/gradient-picker/stories/index.story.tsx index b2b73b8b609966..7dc5f62df726db 100644 --- a/packages/components/src/gradient-picker/stories/index.story.tsx +++ b/packages/components/src/gradient-picker/stories/index.story.tsx @@ -22,7 +22,7 @@ const meta: Meta< typeof GradientPicker > = { actions: { argTypesRegex: '^on.*' }, }, argTypes: { - value: { control: { type: null } }, + value: { control: false }, }, }; export default meta; diff --git a/packages/components/src/gradient-picker/types.ts b/packages/components/src/gradient-picker/types.ts index b563653e33e4c4..3497dd8c5ac008 100644 --- a/packages/components/src/gradient-picker/types.ts +++ b/packages/components/src/gradient-picker/types.ts @@ -36,7 +36,7 @@ type GradientPickerBaseProps = { clearable?: boolean; /** * The heading level. Only applies in cases where gradients are provided - * from multiple origins (ie. when the array passed as the `gradients` prop + * from multiple origins (i.e. when the array passed as the `gradients` prop * contains two or more items). * * @default 2 @@ -56,21 +56,25 @@ type GradientPickerBaseProps = { * @default true */ loop?: boolean; + /** + * Whether to enable alpha transparency options in the picker. + * + * @default true + */ + enableAlpha?: boolean; } & ( | { + // TODO: [#54055] Either this or `aria-labelledby` should be required /** * A label to identify the purpose of the control. - * - * @todo [#54055] Either this or `aria-labelledby` should be required */ 'aria-label'?: string; 'aria-labelledby'?: never; } | { + // TODO: [#54055] Either this or `aria-label` should be required /** * An ID of an element to provide a label for the control. - * - * @todo [#54055] Either this or `aria-label` should be required */ 'aria-labelledby'?: string; 'aria-label'?: never; diff --git a/packages/components/src/grid/stories/index.story.tsx b/packages/components/src/grid/stories/index.story.tsx index 171b324e033c04..5b2284e22d27e3 100644 --- a/packages/components/src/grid/stories/index.story.tsx +++ b/packages/components/src/grid/stories/index.story.tsx @@ -15,7 +15,7 @@ const meta: Meta< typeof Grid > = { argTypes: { as: { control: { type: 'text' } }, align: { control: { type: 'text' } }, - children: { control: { type: null } }, + children: { control: false }, columnGap: { control: { type: 'text' } }, columns: { table: { type: { summary: 'number' } }, diff --git a/packages/components/src/h-stack/stories/index.story.tsx b/packages/components/src/h-stack/stories/index.story.tsx index 025c3384bddceb..a2e5b4fa55e9fa 100644 --- a/packages/components/src/h-stack/stories/index.story.tsx +++ b/packages/components/src/h-stack/stories/index.story.tsx @@ -46,10 +46,10 @@ const meta: Meta< typeof HStack > = { id: 'components-experimental-hstack', argTypes: { as: { - control: { type: null }, + control: false, }, children: { - control: { type: null }, + control: false, }, alignment: { control: { type: 'select' }, diff --git a/packages/components/src/heading/hook.ts b/packages/components/src/heading/hook.ts index d242afe1fdb2f5..132595d69c4f76 100644 --- a/packages/components/src/heading/hook.ts +++ b/packages/components/src/heading/hook.ts @@ -14,7 +14,7 @@ export function useHeading( const { as: asProp, level = 2, - color = COLORS.gray[ 900 ], + color = COLORS.theme.foreground, isBlock = true, weight = CONFIG.fontWeightHeading as import('react').CSSProperties[ 'fontWeight' ], ...otherProps diff --git a/packages/components/src/heading/test/__snapshots__/index.tsx.snap b/packages/components/src/heading/test/__snapshots__/index.tsx.snap index cf863c4b2bb2ef..675810948404fe 100644 --- a/packages/components/src/heading/test/__snapshots__/index.tsx.snap +++ b/packages/components/src/heading/test/__snapshots__/index.tsx.snap @@ -2,12 +2,12 @@ exports[`props should render correctly 1`] = ` .emotion-0 { - color: #1e1e1e; + color: var(--wp-components-color-foreground, #1e1e1e); line-height: 1.4; margin: 0; text-wrap: balance; text-wrap: pretty; - color: #1e1e1e; + color: var(--wp-components-color-foreground, #1e1e1e); font-size: calc(1.95 * 13px); font-weight: 600; display: block; @@ -30,7 +30,7 @@ Snapshot Diff: @@ -1,10 +1,10 @@ Array [ Object { - "color": "#1e1e1e", + "color": "var(--wp-components-color-foreground, #1e1e1e)", "display": "block", - "font-size": "calc(1.25 * 13px)", + "font-size": "calc(1.95 * 13px)", @@ -49,7 +49,7 @@ Snapshot Diff: @@ -1,10 +1,10 @@ Array [ Object { - "color": "#1e1e1e", + "color": "var(--wp-components-color-foreground, #1e1e1e)", "display": "block", - "font-size": "calc(1.25 * 13px)", + "font-size": "calc(1.95 * 13px)", diff --git a/packages/components/src/higher-order/navigate-regions/style.scss b/packages/components/src/higher-order/navigate-regions/style.scss index 5fc1e210dea871..1196acf5b93275 100644 --- a/packages/components/src/higher-order/navigate-regions/style.scss +++ b/packages/components/src/higher-order/navigate-regions/style.scss @@ -8,7 +8,7 @@ $regionOutlineRatio: 2; [role="region"] { position: relative; - // Handles the focus when we programatically send focus to this region + // Handles the focus when we programmatically send focus to this region &.interface-interface-skeleton__content:focus-visible::after { @include region-selection-focus; } @@ -26,7 +26,7 @@ $regionOutlineRatio: 2; // the navigable regions should always have a computed size. For now, we can // fix some edge cases but these CSS rules should be later removed in favor of // a more abstracted approach to make the navigable regions focus style work - // regardles of the CSS used on other components. + // regardless of the CSS used on other components. // Header top bar when Distraction free mode is on. &.is-distraction-free .interface-interface-skeleton__header .edit-post-header, diff --git a/packages/components/src/higher-order/with-constrained-tabbing/README.md b/packages/components/src/higher-order/with-constrained-tabbing/README.md index 47cb8a033dfbdd..417ab7c133fea5 100644 --- a/packages/components/src/higher-order/with-constrained-tabbing/README.md +++ b/packages/components/src/higher-order/with-constrained-tabbing/README.md @@ -22,8 +22,18 @@ const MyComponentWithConstrainedTabbing = () => { const [ isConstrainedTabbing, setIsConstrainedTabbing ] = useState( false ); let form = ( <form> - <TextControl label="Input 1" onChange={ () => {} } /> - <TextControl label="Input 2" onChange={ () => {} } /> + <TextControl + __next40pxDefaultSize + __nextHasNoMarginBottom + label="Input 1" + onChange={ () => {} } + /> + <TextControl + __next40pxDefaultSize + __nextHasNoMarginBottom + label="Input 2" + onChange={ () => {} } + /> </form> ); if ( isConstrainedTabbing ) { @@ -43,5 +53,5 @@ const MyComponentWithConstrainedTabbing = () => { </Button> </div> ); -} +}; ``` diff --git a/packages/components/src/higher-order/with-focus-return/README.md b/packages/components/src/higher-order/with-focus-return/README.md index b99d76bc6f1c9a..81cecad4310f11 100644 --- a/packages/components/src/higher-order/with-focus-return/README.md +++ b/packages/components/src/higher-order/with-focus-return/README.md @@ -13,7 +13,12 @@ import { withFocusReturn, TextControl, Button } from '@wordpress/components'; const EnhancedComponent = withFocusReturn( () => ( <div> Focus will return to the previous input when this component is unmounted - <TextControl autoFocus={ true } onChange={ () => {} } /> + <TextControl + __nextHasNoMarginBottom + __next40pxDefaultSize + autoFocus={ true } + onChange={ () => {} } + /> </div> ) ); @@ -27,6 +32,8 @@ const MyComponentWithFocusReturn = () => { return ( <div> <TextControl + __nextHasNoMarginBottom + __next40pxDefaultSize placeholder="Type something" value={ text } onChange={ ( value ) => setText( value ) } @@ -39,7 +46,7 @@ const MyComponentWithFocusReturn = () => { ) } </div> ); -} +}; ``` `withFocusReturn` can optionally be called as a higher-order function creator. Provided an options object, a new higher-order function is returned. diff --git a/packages/components/src/higher-order/with-focus-return/index.tsx b/packages/components/src/higher-order/with-focus-return/index.tsx index 196226def624c1..cfd795188794c8 100644 --- a/packages/components/src/higher-order/with-focus-return/index.tsx +++ b/packages/components/src/higher-order/with-focus-return/index.tsx @@ -32,7 +32,7 @@ type Props = { * describing the component and the * focus return characteristics. * - * @return Higher Order Component with the focus restauration behaviour. + * @return Higher Order Component with the focus restoration behaviour. */ export default createHigherOrderComponent( // @ts-expect-error TODO: Reconcile with intended `createHigherOrderComponent` types diff --git a/packages/components/src/icon/README.md b/packages/components/src/icon/README.md index 5e78f029f169f7..2c9726dbcf5418 100644 --- a/packages/components/src/icon/README.md +++ b/packages/components/src/icon/README.md @@ -1,82 +1,40 @@ # Icon -Allows you to render a raw icon without any initial styling or wrappers. +<!-- This file is generated automatically and cannot be edited directly. Make edits via TypeScript types and TSDocs. --> -## Usage +<p class="callout callout-info">See the <a href="https://wordpress.github.io/gutenberg/?path=/docs/components-icon--docs">WordPress Storybook</a> for more detailed, interactive documentation.</p> -#### With a Dashicon +Renders a raw icon without any initial styling or wrappers. ```jsx -import { Icon } from '@wordpress/components'; +import { wordpress } from '@wordpress/icons'; -const MyIcon = () => <Icon icon="screenoptions" />; +<Icon icon={ wordpress } /> ``` -#### With a function - -```jsx -import { Icon } from '@wordpress/components'; - -const MyIcon = () => ( - <Icon - icon={ () => ( - <svg> - <path d="M5 4v3h5.5v12h3V7H19V4z" /> - </svg> - ) } - /> -); -``` - -#### With a Component - -```jsx -import { MyIconComponent } from '../my-icon-component'; -import { Icon } from '@wordpress/components'; - -const MyIcon = () => <Icon icon={ MyIconComponent } />; -``` - -#### With an SVG - -```jsx -import { Icon } from '@wordpress/components'; - -const MyIcon = () => ( - <Icon - icon={ - <svg> - <path d="M5 4v3h5.5v12h3V7H19V4z" /> - </svg> - } - /> -); -``` - -#### Specifying a className +## Props -```jsx -import { Icon } from '@wordpress/components'; +### `icon` -const MyIcon = () => <Icon icon="screenoptions" className="example-class" />; -``` - -## Props + - Type: `IconType` + - Required: No + - Default: `null` -The component accepts the following props. Any additional props are passed through to the underlying icon element. +The icon to render. In most cases, you should use an icon from +[the `@wordpress/icons` package](https://wordpress.github.io/gutenberg/?path=/story/icons-icon--library). -### icon +Other supported values are: component instances, functions, +[Dashicons](https://developer.wordpress.org/resource/dashicons/) +(specified as strings), and `null`. -The icon to render. Supported values are: Dashicons (specified as strings), functions, Component instances and `null`. +The `size` value, as well as any other additional props, will be passed through. -- Type: `String|Function|Component|null` -- Required: No -- Default: `null` +### `size` -### size + - Type: `number` + - Required: No + - Default: `'string' === typeof icon ? 20 : 24` The size (width and height) of the icon. -- Type: `Number` -- Required: No -- Default: `20` when a Dashicon is rendered, `24` for all other icons. +Defaults to `20` when `icon` is a string (i.e. a Dashicon id), otherwise `24`. diff --git a/packages/components/src/icon/docs-manifest.json b/packages/components/src/icon/docs-manifest.json new file mode 100644 index 00000000000000..4794049a3eb6ce --- /dev/null +++ b/packages/components/src/icon/docs-manifest.json @@ -0,0 +1,5 @@ +{ + "$schema": "../../schemas/docs-manifest.json", + "displayName": "Icon", + "filePath": "./index.tsx" +} diff --git a/packages/components/src/icon/index.tsx b/packages/components/src/icon/index.tsx index 3fbf4d18c5a00e..283b9cd179cd1c 100644 --- a/packages/components/src/icon/index.tsx +++ b/packages/components/src/icon/index.tsx @@ -25,10 +25,22 @@ export type IconType = | ( ( props: { size?: number } ) => JSX.Element ) | JSX.Element; -interface BaseProps { +type AdditionalProps< T > = T extends ComponentType< infer U > + ? U + : T extends DashiconIconKey + ? SVGProps< SVGSVGElement > + : {}; + +export type Props = { /** - * The icon to render. Supported values are: Dashicons (specified as - * strings), functions, Component instances and `null`. + * The icon to render. In most cases, you should use an icon from + * [the `@wordpress/icons` package](https://wordpress.github.io/gutenberg/?path=/story/icons-icon--library). + * + * Other supported values are: component instances, functions, + * [Dashicons](https://developer.wordpress.org/resource/dashicons/) + * (specified as strings), and `null`. + * + * The `size` value, as well as any other additional props, will be passed through. * * @default null */ @@ -36,19 +48,22 @@ interface BaseProps { /** * The size (width and height) of the icon. * - * @default `20` when a Dashicon is rendered, `24` for all other icons. + * Defaults to `20` when `icon` is a string (i.e. a Dashicon id), otherwise `24`. + * + * @default `'string' === typeof icon ? 20 : 24`. */ size?: number; -} - -type AdditionalProps< T > = T extends ComponentType< infer U > - ? U - : T extends DashiconIconKey - ? SVGProps< SVGSVGElement > - : {}; - -export type Props = BaseProps & AdditionalProps< IconType >; +} & AdditionalProps< IconType >; +/** + * Renders a raw icon without any initial styling or wrappers. + * + * ```jsx + * import { wordpress } from '@wordpress/icons'; + * + * <Icon icon={ wordpress } /> + * ``` + */ function Icon( { icon = null, size = 'string' === typeof icon ? 20 : 24, diff --git a/packages/components/src/icon/stories/index.story.tsx b/packages/components/src/icon/stories/index.story.tsx index 7d61be8df7f3ce..d1eabf2e98b771 100644 --- a/packages/components/src/icon/stories/index.story.tsx +++ b/packages/components/src/icon/stories/index.story.tsx @@ -47,26 +47,68 @@ FillColor.args = { ...Default.args, }; +/** + * When `icon` is a function, it will be passed the `size` prop and any other additional props. + */ export const WithAFunction = Template.bind( {} ); WithAFunction.args = { ...Default.args, - icon: () => ( - <SVG> - <Path d="M5 4v3h5.5v12h3V7H19V4z" /> - </SVG> + icon: ( { size }: { size?: number } ) => ( + <img + width={ size } + height={ size } + src="https://s.w.org/style/images/about/WordPress-logotype-wmark.png" + alt="WordPress" + /> ), }; +WithAFunction.parameters = { + docs: { + source: { + code: ` +<Icon + icon={ ( { size } ) => ( + <img + width={ size } + height={ size } + src="https://s.w.org/style/images/about/WordPress-logotype-wmark.png" + alt="WordPress" + /> + ) } +/> + `, + }, + }, +}; -const MyIconComponent = () => ( - <SVG> +const MyIconComponent = ( { size }: { size?: number } ) => ( + <SVG width={ size } height={ size }> <Path d="M5 4v3h5.5v12h3V7H19V4z" /> </SVG> ); +/** + * When `icon` is a component, it will be passed the `size` prop and any other additional props. + */ export const WithAComponent = Template.bind( {} ); WithAComponent.args = { ...Default.args, - icon: MyIconComponent, + icon: <MyIconComponent />, +}; +WithAComponent.parameters = { + docs: { + source: { + code: ` +const MyIconComponent = ( { size } ) => ( + <SVG width={ size } height={ size }> + <Path d="M5 4v3h5.5v12h3V7H19V4z" /> + </SVG> +); + +<Icon icon={ <MyIconComponent /> } /> + `, + }, + }, }; export const WithAnSVG = Template.bind( {} ); @@ -80,7 +122,7 @@ WithAnSVG.args = { }; /** - * Although it's preferred to use icons from the `@wordpress/icons` package, Dashicons are still supported, + * Although it's preferred to use icons from the `@wordpress/icons` package, [Dashicons](https://developer.wordpress.org/resource/dashicons/) are still supported, * as long as you are in a context where the Dashicons stylesheet is loaded. To simulate that here, * use the Global CSS Injector in the Storybook toolbar at the top and select the "WordPress" preset. */ diff --git a/packages/components/src/index.ts b/packages/components/src/index.ts index e82d6da70279e8..2acd609992d6ad 100644 --- a/packages/components/src/index.ts +++ b/packages/components/src/index.ts @@ -21,11 +21,7 @@ export { default as Animate, getAnimateClassName as __unstableGetAnimateClassName, } from './animate'; -export { - __unstableMotion, - __unstableAnimatePresence, - __unstableMotionContext, -} from './animation'; +export { __unstableMotion, __unstableAnimatePresence } from './animation'; export { default as AnglePickerControl } from './angle-picker-control'; export { default as Autocomplete, diff --git a/packages/components/src/input-control/README.md b/packages/components/src/input-control/README.md index 58a3b4a3b1a094..ff5c70decebeb7 100644 --- a/packages/components/src/input-control/README.md +++ b/packages/components/src/input-control/README.md @@ -17,6 +17,7 @@ const Example = () => { return ( <InputControl + __next40pxDefaultSize value={ value } onChange={ ( nextValue ) => setValue( nextValue ?? '' ) } /> diff --git a/packages/components/src/input-control/index.tsx b/packages/components/src/input-control/index.tsx index fd0fc0a5c45536..d346d1b31b1118 100644 --- a/packages/components/src/input-control/index.tsx +++ b/packages/components/src/input-control/index.tsx @@ -20,6 +20,7 @@ import { space } from '../utils/space'; import { useDraft } from './utils'; import BaseControl from '../base-control'; import { useDeprecated36pxDefaultSizeProp } from '../utils/use-deprecated-props'; +import { maybeWarnDeprecated36pxSize } from '../utils/deprecated-36px-size'; const noop = () => {}; @@ -36,6 +37,7 @@ export function UnforwardedInputControl( ) { const { __next40pxDefaultSize, + __shouldNotWarnDeprecated36pxSize, __unstableStateReducer: stateReducer = ( state ) => state, __unstableInputWidth, className, @@ -68,6 +70,13 @@ export function UnforwardedInputControl( const helpProp = !! help ? { 'aria-describedby': `${ id }__help` } : {}; + maybeWarnDeprecated36pxSize( { + componentName: 'InputControl', + __next40pxDefaultSize, + size, + __shouldNotWarnDeprecated36pxSize, + } ); + return ( <BaseControl className={ classes } @@ -125,6 +134,7 @@ export function UnforwardedInputControl( * * return ( * <InputControl + * __next40pxDefaultSize * value={ value } * onChange={ ( nextValue ) => setValue( nextValue ?? '' ) } * /> diff --git a/packages/components/src/input-control/stories/index.story.tsx b/packages/components/src/input-control/stories/index.story.tsx index 8cef6a5d37c81b..40630938dbb370 100644 --- a/packages/components/src/input-control/stories/index.story.tsx +++ b/packages/components/src/input-control/stories/index.story.tsx @@ -23,10 +23,10 @@ const meta: Meta< typeof InputControl > = { subcomponents: { InputControlPrefixWrapper, InputControlSuffixWrapper }, argTypes: { __unstableInputWidth: { control: { type: 'text' } }, - __unstableStateReducer: { control: { type: null } }, - onChange: { control: { type: null } }, - prefix: { control: { type: null } }, - suffix: { control: { type: null } }, + __unstableStateReducer: { control: false }, + onChange: { control: false }, + prefix: { control: false }, + suffix: { control: false }, type: { control: { type: 'text' } }, value: { control: { disable: true } }, }, @@ -46,6 +46,7 @@ export const Default = Template.bind( {} ); Default.args = { label: 'Value', placeholder: 'Placeholder', + __next40pxDefaultSize: true, }; export const WithHelpText = Template.bind( {} ); @@ -117,7 +118,6 @@ export const ShowPassword: StoryFn< typeof InputControl > = ( args ) => { return ( <InputControl type={ visible ? 'text' : 'password' } - label="Password" suffix={ <InputControlSuffixWrapper variant="control"> <Button @@ -132,3 +132,8 @@ export const ShowPassword: StoryFn< typeof InputControl > = ( args ) => { /> ); }; +ShowPassword.args = { + ...Default.args, + label: 'Password', + placeholder: undefined, +}; diff --git a/packages/components/src/input-control/styles/input-control-styles.tsx b/packages/components/src/input-control/styles/input-control-styles.tsx index 39eea8fdb029a1..db24a5e60f137a 100644 --- a/packages/components/src/input-control/styles/input-control-styles.tsx +++ b/packages/components/src/input-control/styles/input-control-styles.tsx @@ -287,6 +287,12 @@ export const Input = styled.input< InputProps >` &::-webkit-input-placeholder { line-height: normal; } + + &[type='email'], + &[type='url'] { + /* rtl:ignore */ + direction: ltr; + } } `; diff --git a/packages/components/src/input-control/test/index.js b/packages/components/src/input-control/test/index.js index ace3086c388c8b..46332eb6eea704 100644 --- a/packages/components/src/input-control/test/index.js +++ b/packages/components/src/input-control/test/index.js @@ -17,9 +17,15 @@ import BaseInputControl from '../'; const getInput = () => screen.getByTestId( 'input' ); describe( 'InputControl', () => { - const InputControl = ( props ) => ( - <BaseInputControl { ...props } data-testid="input" /> - ); + const InputControl = ( props ) => { + return ( + <BaseInputControl + { ...props } + __next40pxDefaultSize + data-testid="input" + /> + ); + }; describe( 'Basic rendering', () => { it( 'should render', () => { diff --git a/packages/components/src/input-control/types.ts b/packages/components/src/input-control/types.ts index 13f078cd89cc1f..edb69def619057 100644 --- a/packages/components/src/input-control/types.ts +++ b/packages/components/src/input-control/types.ts @@ -40,6 +40,13 @@ interface BaseProps { * @default false */ __next40pxDefaultSize?: boolean; + /** + * Do not throw a warning for the deprecated 36px default size. + * For internal components of other components that already throw the warning. + * + * @ignore + */ + __shouldNotWarnDeprecated36pxSize?: boolean; __unstableInputWidth?: CSSProperties[ 'width' ]; /** * If true, the label will only be visible to screen readers. @@ -129,7 +136,7 @@ export interface InputBaseProps extends BaseProps, FlexProps { * If you want to apply standard padding in accordance with the size variant, wrap the element in * the provided `<InputControlPrefixWrapper>` component. * - * @example + * ```jsx * import { * __experimentalInputControl as InputControl, * __experimentalInputControlPrefixWrapper as InputControlPrefixWrapper, @@ -138,6 +145,7 @@ export interface InputBaseProps extends BaseProps, FlexProps { * <InputControl * prefix={<InputControlPrefixWrapper>@</InputControlPrefixWrapper>} * /> + * ``` */ prefix?: ReactNode; /** @@ -147,7 +155,7 @@ export interface InputBaseProps extends BaseProps, FlexProps { * If you want to apply standard padding in accordance with the size variant, wrap the element in * the provided `<InputControlSuffixWrapper>` component. * - * @example + * ```jsx * import { * __experimentalInputControl as InputControl, * __experimentalInputControlSuffixWrapper as InputControlSuffixWrapper, @@ -156,6 +164,7 @@ export interface InputBaseProps extends BaseProps, FlexProps { * <InputControl * suffix={<InputControlSuffixWrapper>%</InputControlSuffixWrapper>} * /> + * ``` */ suffix?: ReactNode; /** diff --git a/packages/components/src/item-group/stories/index.story.tsx b/packages/components/src/item-group/stories/index.story.tsx index 99309b81ea314f..845843d2433db9 100644 --- a/packages/components/src/item-group/stories/index.story.tsx +++ b/packages/components/src/item-group/stories/index.story.tsx @@ -17,8 +17,8 @@ const meta: Meta< typeof ItemGroup > = { subcomponents: { Item }, title: 'Components (Experimental)/ItemGroup', argTypes: { - as: { control: { type: null } }, - children: { control: { type: null } }, + as: { control: false }, + children: { control: false }, }, parameters: { controls: { expanded: true }, diff --git a/packages/components/src/menu-group/stories/index.story.tsx b/packages/components/src/menu-group/stories/index.story.tsx index 7cb9004b45a8c2..a0d12d9d05c4aa 100644 --- a/packages/components/src/menu-group/stories/index.story.tsx +++ b/packages/components/src/menu-group/stories/index.story.tsx @@ -16,10 +16,11 @@ import MenuItemsChoice from '../../menu-items-choice'; import type { Meta, StoryFn } from '@storybook/react'; const meta: Meta< typeof MenuGroup > = { - title: 'Components/MenuGroup', + title: 'Components/Actions/MenuGroup', component: MenuGroup, + id: 'components-menugroup', argTypes: { - children: { control: { type: null } }, + children: { control: false }, }, parameters: { controls: { expanded: true }, @@ -75,8 +76,8 @@ const MultiGroupsTemplate: StoryFn< typeof MenuGroup > = ( args ) => { * When other menu items exist above or below a MenuGroup, the group * should have a divider line between it and the adjacent item. */ -export const WithSeperator = MultiGroupsTemplate.bind( {} ); -WithSeperator.args = { +export const WithSeparator = MultiGroupsTemplate.bind( {} ); +WithSeparator.args = { ...Default.args, hideSeparator: false, label: 'Editor', diff --git a/packages/components/src/menu-item/stories/index.story.tsx b/packages/components/src/menu-item/stories/index.story.tsx index 763ee6e96be922..bef548c59d9461 100644 --- a/packages/components/src/menu-item/stories/index.story.tsx +++ b/packages/components/src/menu-item/stories/index.story.tsx @@ -17,9 +17,10 @@ import Shortcut from '../../shortcut'; const meta: Meta< typeof MenuItem > = { component: MenuItem, - title: 'Components/MenuItem', + title: 'Components/Actions/MenuItem', + id: 'components-menuitem', argTypes: { - children: { control: { type: null } }, + children: { control: false }, icon: { control: { type: 'select' }, options: [ 'check', 'link', 'more' ], diff --git a/packages/components/src/menu-items-choice/stories/index.story.tsx b/packages/components/src/menu-items-choice/stories/index.story.tsx index 02e76158981e8e..b634eb5becedbf 100644 --- a/packages/components/src/menu-items-choice/stories/index.story.tsx +++ b/packages/components/src/menu-items-choice/stories/index.story.tsx @@ -16,11 +16,12 @@ import MenuGroup from '../../menu-group'; const meta: Meta< typeof MenuItemsChoice > = { component: MenuItemsChoice, - title: 'Components/MenuItemsChoice', + title: 'Components/Actions/MenuItemsChoice', + id: 'components-menuitemschoice', argTypes: { onHover: { action: 'onHover' }, onSelect: { action: 'onSelect' }, - value: { control: { type: null } }, + value: { control: false }, }, parameters: { controls: { diff --git a/packages/components/src/menu-items-choice/style.scss b/packages/components/src/menu-items-choice/style.scss index 383eb4066ba86b..c33ce43301842b 100644 --- a/packages/components/src/menu-items-choice/style.scss +++ b/packages/components/src/menu-items-choice/style.scss @@ -1,5 +1,6 @@ .components-menu-items-choice, .components-menu-items-choice.components-button { + min-height: $button-size-next-default-40px; height: auto; svg { diff --git a/packages/components/src/menu/README.md b/packages/components/src/menu/README.md index 6732610c0c6cae..12f120b871f85d 100644 --- a/packages/components/src/menu/README.md +++ b/packages/components/src/menu/README.md @@ -1,344 +1,591 @@ -# `Menu` +# Menu -<div class="callout callout-alert"> -This feature is still experimental. “Experimental” means this is an early implementation subject to drastic and breaking changes. -</div> +<!-- This file is generated automatically and cannot be edited directly. Make edits via TypeScript types and TSDocs. --> -`Menu` displays a menu to the user (such as a set of actions or functions). The menu is rendered in a popover (this pattern is also known as a "Dropdown menu"), which is triggered by a button. +🔒 This component is locked as a [private API](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-private-apis/). We do not yet recommend using this outside of the Gutenberg project. -## Design guidelines +<p class="callout callout-info">See the <a href="https://wordpress.github.io/gutenberg/?path=/docs/components-menu--docs">WordPress Storybook</a> for more detailed, interactive documentation.</p> -### Usage +Menu is a collection of React components that combine to render +ARIA-compliant [menu](https://www.w3.org/WAI/ARIA/apg/patterns/menu/) and +[menu button](https://www.w3.org/WAI/ARIA/apg/patterns/menubutton/) patterns. -#### When to use a `Menu` +`Menu` itself is a wrapper component and context provider. +It is responsible for managing the state of the menu and its items, and for +rendering the `Menu.TriggerButton` (or the `Menu.SubmenuTriggerItem`) +component, and the `Menu.Popover` component. -Use a `Menu` when you want users to: +## Props -- Choose an action or change a setting from a list, AND -- Only see the available choices contextually. +### `as` -`Menu` is a React component to render an expandable menu of buttons. It is similar in purpose to a `<select>` element, with the distinction that it does not maintain a value. Instead, each option behaves as an action button. + - Type: `"symbol" | "object" | "a" | "abbr" | "address" | "area" | "article" | "aside" | "audio" | "b" | "base" | "bdi" | "bdo" | "big" | "blockquote" | "body" | "br" | "button" | "canvas" | ... 516 more ... | ("view" & FunctionComponent<...>)` + - Required: No -If you need to display all the available options at all times, consider using a Toolbar instead. Use a `Menu` to display a list of actions after the user interacts with a button. +The HTML element or React component to render the component as. -**Do** -Use a `Menu` to display a list of actions after the user interacts with an icon. +### `children` -**Don’t** use a `Menu` for important actions that should always be visible. Use a `Toolbar` instead. + - Type: `ReactNode` + - Required: No -**Don’t** -Don’t use a `Menu` for frequently used actions. Use a `Toolbar` instead. +The elements, which should include one instance of the `Menu.TriggerButton` +component and one instance of the `Menu.Popover` component. -#### Behavior +### `defaultOpen` -Generally, the parent button should indicate that interacting with it will show a `Menu`. + - Type: `boolean` + - Required: No + - Default: `false` -The parent button should retain the same visual styling regardless of whether the `Menu` is displayed or not. +Whether the menu popover and its contents should be visible by default. -#### Placement +Note: this prop will be overridden by the `open` prop if it is +provided (meaning the component will be used in "controlled" mode). -The `Menu` should typically appear directly below, or below and to the left of, the parent button. If there isn’t enough space below to display the full `Menu`, it can be displayed instead above the parent button. +### `open` -## Development guidelines + - Type: `boolean` + - Required: No -This component is still highly experimental, and it's not normally accessible to consumers of the `@wordpress/components` package. +Whether the menu popover and its contents should be visible. +Should be used in conjunction with `onOpenChange` in order to control +the open state of the menu popover. -The component exposes a set of components that are meant to be used in combination with each other in order to implement a `Menu` correctly. +Note: this prop will set the component in "controlled" mode, and it will +override the `defaultOpen` prop. -### `Menu` +### `onOpenChange` -The root component, used to specify the menu's trigger and its contents. + - Type: `(open: boolean) => void` + - Required: No + +A callback that gets called when the `open` state changes. + +### `placement` + + - Type: `"top" | "bottom" | "left" | "right" | "top-start" | "bottom-start" | "left-start" | "right-start" | "top-end" | "bottom-end" | ...` + - Required: No + - Default: `'bottom-start' for root-level menus, 'right-start' for submenus` + +The placement of the menu popover. + +## Subcomponents + +### Menu.TriggerButton + +Renders a menu button that toggles the visibility of a sibling +`Menu.Popover` component when clicked or when using arrow keys. #### Props -The component accepts the following props: +##### `accessibleWhenDisabled` -##### `trigger`: `React.ReactNode` + - Type: `boolean` + - Required: No -The button triggering the menu popover. +Indicates whether the element should be focusable even when it is +`disabled`. -- Required: yes +This is important when discoverability is a concern. For example: -##### `children`: `React.ReactNode` +> A toolbar in an editor contains a set of special smart paste functions +that are disabled when the clipboard is empty or when the function is not +applicable to the current content of the clipboard. It could be helpful to +keep the disabled buttons focusable if the ability to discover their +functionality is primarily via their presence on the toolbar. -The contents of the menu (ie. one or more menu items). +Learn more on [Focusability of disabled +controls](https://www.w3.org/WAI/ARIA/apg/practices/keyboard-interface/#focusabilityofdisabledcontrols). -- Required: yes +##### `children` -##### `defaultOpen`: `boolean` + - Type: `ReactNode` + - Required: No -The open state of the menu popover when it is initially rendered. Use when not wanting to control its open state. +The contents of the menu trigger button. -- Required: no -- Default: `false` +##### `disabled` -##### `open`: `boolean` + - Type: `boolean` + - Required: No + - Default: `false` -The controlled open state of the menu popover. Must be used in conjunction with `onOpenChange`. +Determines if the element is disabled. This sets the `aria-disabled` +attribute accordingly, enabling support for all elements, including those +that don't support the native `disabled` attribute. -- Required: no +This feature can be combined with the `accessibleWhenDisabled` prop to +make disabled elements still accessible via keyboard. -##### `onOpenChange`: `(open: boolean) => void` +##### `render` -Event handler called when the open state of the menu popover changes. + - Type: `ReactElement<any, string | JSXElementConstructor<any>> | RenderProp<HTMLAttributes<any> & { ref?: Ref<any>; }>` + - Required: No -- Required: no +Allows the component to be rendered as a different HTML element or React +component. The value can be a React element or a function that takes in the +original component props and gives back a React element with the props +merged. -##### `modal`: `boolean` +### Menu.Popover -The modality of the menu popover. When set to true, interaction with outside elements will be disabled and only menu content will be visible to screen readers. +Renders a dropdown menu element that's controlled by a sibling +`Menu.TriggerButton` component. It renders a popover and automatically +focuses on items when the menu is shown. -- Required: no -- Default: `true` +The only valid children of `Menu.Popover` are `Menu.Item`, +`Menu.RadioItem`, `Menu.CheckboxItem`, `Menu.Group`, `Menu.Separator`, +and `Menu` (for nested dropdown menus). -##### `placement`: ``'top' | 'top-start' | 'top-end' | 'right' | 'right-start' | 'right-end' | 'bottom' | 'bottom-start' | 'bottom-end' | 'left' | 'left-start' | 'left-end'` +#### Props -The placement of the menu popover. +##### `children` + + - Type: `ReactNode` + - Required: No + +The contents of the menu popover, which should include instances of the +`Menu.Item`, `Menu.CheckboxItem`, `Menu.RadioItem`, `Menu.Group`, and +`Menu.Separator` components. + +##### `gutter` + + - Type: `number` + - Required: No + - Default: `8 for root-level menus, 16 for nested menus` + +The distance between the popover and the anchor element. + +##### `hideOnEscape` + + - Type: `BooleanOrCallback<KeyboardEvent | React.KeyboardEvent<Element>>` + - Required: No + - Default: ``( event ) => { event.preventDefault(); return true; }`` -- Required: no -- Default: `'bottom-start'` for root-level menus, `'right-start'` for nested menus +Determines if the menu popover will hide when the user presses the +Escape key. -##### `gutter`: `number` +This prop can be either a boolean or a function that accepts an event as an +argument and returns a boolean. The event object represents the keydown +event that initiated the hide action, which could be either a native +keyboard event or a React synthetic event. -The distance in pixels from the trigger. +##### `modal` -- Required: no -- Default: `8` for root-level menus, `16` for nested menus + - Type: `boolean` + - Required: No + - Default: `true` -##### `shift`: `number` +The modality of the menu popover. When set to true, interaction with +outside elements will be disabled and only menu content will be visible to +screen readers. -The skidding of the popover along the anchor element. Can be set to negative values to make the popover shift to the opposite side. +Determines whether the menu popover is modal. Modal dialogs have distinct +states and behaviors: +- The `portal` and `preventBodyScroll` props are set to `true`. They can + still be manually set to `false`. +- When the dialog is open, element tree outside it will be inert. -- Required: no -- Default: `0` for root-level menus, `-8` for nested menus +##### `shift` -### `Menu.Item` + - Type: `number` + - Required: No + - Default: `0 for root-level menus, -8 for nested menus` -Used to render a menu item. +The skidding of the popover along the anchor element. Can be set to +negative values to make the popover shift to the opposite side. + +### Menu.Item + +Renders a menu item inside the `Menu.Popover` or `Menu.Group` components. + +It can optionally contain one instance of the `Menu.ItemLabel` component +and one instance of the `Menu.ItemHelpText` component. #### Props -The component accepts the following props: +##### `children` + + - Type: `ReactNode` + - Required: Yes + +The contents of the menu item, which could include one instance of the +`Menu.ItemLabel` component and/or one instance of the `Menu.ItemHelpText` +component. -##### `children`: `React.ReactNode` +##### `disabled` -The contents of the item + - Type: `boolean` + - Required: No + - Default: `false` -- Required: yes +Determines if the element is disabled. This sets the `aria-disabled` +attribute accordingly, enabling support for all elements, including those +that don't support the native `disabled` attribute. -##### `prefix`: `React.ReactNode` +##### `hideOnClick` -The contents of the item's prefix. + - Type: `BooleanOrCallback<MouseEvent<HTMLElement, MouseEvent>>` + - Required: No + - Default: `true` -- Required: no +Determines if the menu should hide when this item is clicked. -##### `suffix`: `React.ReactNode` +**Note**: This behavior isn't triggered if this menu item is rendered as a +link and modifier keys are used to either open the link in a new tab or +download it. -The contents of the item's suffix. +##### `prefix` -- Required: no + - Type: `ReactNode` + - Required: No -##### `hideOnClick`: `boolean` +The contents of the menu item's prefix, such as an icon. -Whether to hide the menu popover when the menu item is clicked. +##### `render` -- Required: no -- Default: `true` + - Type: `ReactElement<any, string | JSXElementConstructor<any>> | RenderProp<HTMLAttributes<any> & { ref?: Ref<any>; }>` + - Required: No -##### `disabled`: `boolean` +Allows the component to be rendered as a different HTML element or React +component. The value can be a React element or a function that takes in the +original component props and gives back a React element with the props +merged. -Determines if the element is disabled. +##### `suffix` -- Required: no -- Default: `false` + - Type: `ReactNode` + - Required: No -### `Menu.CheckboxItem` +The contents of the menu item's suffix, such as a keyboard shortcut. -Used to render a checkbox item. +### Menu.RadioItem + +Renders a radio menu item inside the `Menu.Popover` or `Menu.Group` +components. + +It can optionally contain one instance of the `Menu.ItemLabel` component +and one instance of the `Menu.ItemHelpText` component. #### Props -The component accepts the following props: +##### `children` -##### `children`: `React.ReactNode` + - Type: `ReactNode` + - Required: Yes -The contents of the item +The contents of the menu item, which could include one instance of the +`Menu.ItemLabel` component and/or one instance of the `Menu.ItemHelpText` +component. -- Required: yes +##### `checked` -##### `suffix`: `React.ReactNode` + - Type: `boolean` + - Required: No -The contents of the item's suffix. +The controlled checked state of the radio menu item. -- Required: no +Note: this prop will override the `defaultChecked` prop. -##### `hideOnClick`: `boolean` +##### `disabled` -Whether to hide the menu popover when the menu item is clicked. + - Type: `boolean` + - Required: No + - Default: `false` -- Required: no -- Default: `false` +Determines if the element is disabled. This sets the `aria-disabled` +attribute accordingly, enabling support for all elements, including those +that don't support the native `disabled` attribute. -##### `disabled`: `boolean` +##### `defaultChecked` -Determines if the element is disabled. + - Type: `boolean` + - Required: No -- Required: no -- Default: `false` +The checked state of the radio menu item when it is initially rendered. +Use when not wanting to control its checked state. -##### `name`: `string` +Note: this prop will be overriden by the `checked` prop, if it is defined. -The checkbox item's name. +##### `hideOnClick` -- Required: yes + - Type: `BooleanOrCallback<MouseEvent<HTMLElement, MouseEvent>>` + - Required: No + - Default: `false` -##### `value`: `string` +Determines if the menu should hide when this item is clicked. -The checkbox item's value, useful when using multiple checkbox items -associated to the same `name`. +**Note**: This behavior isn't triggered if this menu item is rendered as a +link and modifier keys are used to either open the link in a new tab or +download it. -- Required: no +##### `name` -##### `checked`: `boolean` + - Type: `string` + - Required: Yes -The checkbox item's value, useful when using multiple checkbox items -associated to the same `name`. +The radio item's name. + +##### `onChange` + + - Type: `BivariantCallback<(event: ChangeEvent<HTMLInputElement>) => void>` + - Required: No + +A function that is called when the checkbox's checked state changes. -- Required: no +##### `render` -##### `defaultChecked`: `boolean` + - Type: `ReactElement<any, string | JSXElementConstructor<any>> | RenderProp<HTMLAttributes<any> & { ref?: Ref<any>; }>` + - Required: No -The checked state of the checkbox menu item when it is initially rendered. Use when not wanting to control its checked state. +Allows the component to be rendered as a different HTML element or React +component. The value can be a React element or a function that takes in the +original component props and gives back a React element with the props +merged. -- Required: no +##### `suffix` -##### `onChange`: `( event: React.ChangeEvent< HTMLInputElement > ) => void;` + - Type: `ReactNode` + - Required: No -Event handler called when the checked state of the checkbox menu item changes. +The contents of the menu item's suffix, such as a keyboard shortcut. + +##### `value` + + - Type: `string | number` + - Required: Yes + +The radio item's value. -- Required: no +### Menu.CheckboxItem -### `Menu.RadioItem` +Renders a checkbox menu item inside the `Menu.Popover` or `Menu.Group` +components. -Used to render a radio item. +It can optionally contain one instance of the `Menu.ItemLabel` component +and one instance of the `Menu.ItemHelpText` component. #### Props -The component accepts the following props: +##### `children` -##### `children`: `React.ReactNode` + - Type: `ReactNode` + - Required: Yes -The contents of the item +The contents of the menu item, which could include one instance of the +`Menu.ItemLabel` component and/or one instance of the `Menu.ItemHelpText` +component. -- Required: yes +##### `checked` -##### `suffix`: `React.ReactNode` + - Type: `boolean` + - Required: No -The contents of the item's suffix. +The controlled checked state of the checkbox menu item. -- Required: no +Note: this prop will override the `defaultChecked` prop. -##### `hideOnClick`: `boolean` +##### `disabled` -Whether to hide the menu popover when the menu item is clicked. + - Type: `boolean` + - Required: No + - Default: `false` -- Required: no -- Default: `false` +Determines if the element is disabled. This sets the `aria-disabled` +attribute accordingly, enabling support for all elements, including those +that don't support the native `disabled` attribute. -##### `disabled`: `boolean` +##### `defaultChecked` -Determines if the element is disabled. + - Type: `boolean` + - Required: No -- Required: no -- Default: `false` +The checked state of the checkbox menu item when it is initially rendered. +Use when not wanting to control its checked state. -##### `name`: `string` +Note: this prop will be overriden by the `checked` prop, if it is defined. -The radio item's name. +##### `hideOnClick` -- Required: yes + - Type: `BooleanOrCallback<MouseEvent<HTMLElement, MouseEvent>>` + - Required: No + - Default: `false` -##### `value`: `string | number` +Determines if the menu should hide when this item is clicked. -The radio item's value. +**Note**: This behavior isn't triggered if this menu item is rendered as a +link and modifier keys are used to either open the link in a new tab or +download it. -- Required: yes +##### `name` -##### `checked`: `boolean` + - Type: `string` + - Required: Yes -The checkbox item's value, useful when using multiple checkbox items -associated to the same `name`. +The checkbox menu item's name. + +##### `onChange` + + - Type: `ChangeEventHandler<HTMLInputElement>` + - Required: No -- Required: no +A function that is called when the checkbox's checked state changes. -##### `defaultChecked`: `boolean` +##### `render` -The checked state of the radio menu item when it is initially rendered. Use when not wanting to control its checked state. + - Type: `ReactElement<any, string | JSXElementConstructor<any>> | RenderProp<HTMLAttributes<any> & { ref?: Ref<any>; }>` + - Required: No -- Required: no +Allows the component to be rendered as a different HTML element or React +component. The value can be a React element or a function that takes in the +original component props and gives back a React element with the props +merged. -##### `onChange`: `( event: React.ChangeEvent< HTMLInputElement > ) => void;` +##### `suffix` -Event handler called when the checked radio menu item changes. + - Type: `ReactNode` + - Required: No -- Required: no +The contents of the menu item's suffix, such as a keyboard shortcut. + +##### `value` + + - Type: `string | number | readonly string[]` + - Required: No + +The checkbox item's value, useful when using multiple checkbox menu items +associated to the same `name`. -### `Menu.ItemLabel` +### Menu.ItemLabel -Used to render the menu item's label. +Renders a menu item's label text. It should be wrapped with `Menu.Item`, +`Menu.RadioItem`, or `Menu.CheckboxItem`. #### Props -The component accepts the following props: +##### `as` -##### `children`: `React.ReactNode` + - Type: `"symbol" | "object" | "a" | "abbr" | "address" | "area" | "article" | "aside" | "audio" | "b" | ...` + - Required: No -The label contents. +The HTML element or React component to render the component as. -- Required: yes +### Menu.ItemHelpText -### `Menu.ItemHelpText` +Renders a menu item's help text. It should be wrapped with `Menu.Item`, +`Menu.RadioItem`, or `Menu.CheckboxItem`. -Used to render the menu item's help text. +#### Props + +##### `as` + + - Type: `"symbol" | "object" | "a" | "abbr" | "address" | "area" | "article" | "aside" | "audio" | "b" | ...` + - Required: No + +The HTML element or React component to render the component as. + +### Menu.Group + +Renders a group for menu items. + +It should contain one instance of `Menu.GroupLabel` and one or more +instances of `Menu.Item`, `Menu.RadioItem`, or `Menu.CheckboxItem`. #### Props -The component accepts the following props: +##### `children` -##### `children`: `React.ReactNode` + - Type: `ReactNode` + - Required: Yes -The help text contents. +The contents of the menu group, which should include one instance of the +`Menu.GroupLabel` component and one or more instances of `Menu.Item`, +`Menu.CheckboxItem`, and `Menu.RadioItem`. -- Required: yes +### Menu.GroupLabel -### `Menu.Group` +Renders a label in a menu group. -Used to group menu items. +This component should be wrapped with `Menu.Group` so the +`aria-labelledby` is correctly set on the group element. #### Props -The component accepts the following props: +##### `children` -##### `children`: `React.ReactNode` + - Type: `ReactNode` + - Required: Yes -The contents of the menu group (ie. an optional menu group label and one or more menu items). +The contents of the menu group label, which should provide an accessible +label for the menu group. -- Required: yes +### Menu.Separator -### `Menu.GroupLabel` +Renders a divider between menu items or menu groups. -Used to render a group label. The label text should be kept as short as possible. +#### Props + +### Menu.SubmenuTriggerItem + +Renders a menu item that toggles the visibility of a sibling +`Menu.Popover` component when clicked or when using arrow keys. + +This component is used to create a nested dropdown menu. #### Props -The component accepts the following props: +##### `children` + + - Type: `ReactNode` + - Required: Yes + +The contents of the menu item, which could include one instance of the +`Menu.ItemLabel` component and/or one instance of the `Menu.ItemHelpText` +component. + +##### `disabled` + + - Type: `boolean` + - Required: No + - Default: `false` + +Determines if the element is disabled. This sets the `aria-disabled` +attribute accordingly, enabling support for all elements, including those +that don't support the native `disabled` attribute. + +##### `hideOnClick` + + - Type: `BooleanOrCallback<MouseEvent<HTMLElement, MouseEvent>>` + - Required: No + - Default: `true` + +Determines if the menu should hide when this item is clicked. + +**Note**: This behavior isn't triggered if this menu item is rendered as a +link and modifier keys are used to either open the link in a new tab or +download it. + +##### `prefix` + + - Type: `ReactNode` + - Required: No + +The contents of the menu item's prefix, such as an icon. + +##### `render` -##### `children`: `React.ReactNode` + - Type: `ReactElement<any, string | JSXElementConstructor<any>> | RenderProp<HTMLAttributes<any> & { ref?: Ref<any>; }>` + - Required: No -The contents of the menu group label. +Allows the component to be rendered as a different HTML element or React +component. The value can be a React element or a function that takes in the +original component props and gives back a React element with the props +merged. -- Required: yes +##### `suffix` -### `Menu.Separator` + - Type: `ReactNode` + - Required: No -Used to render a visual separator. +The contents of the menu item's suffix, such as a keyboard shortcut. diff --git a/packages/components/src/menu/checkbox-item.tsx b/packages/components/src/menu/checkbox-item.tsx index b9a9b8105e517e..a3ae4d77085986 100644 --- a/packages/components/src/menu/checkbox-item.tsx +++ b/packages/components/src/menu/checkbox-item.tsx @@ -13,33 +13,36 @@ import { Icon, check } from '@wordpress/icons'; * Internal dependencies */ import type { WordPressComponentProps } from '../context'; -import { MenuContext } from './context'; -import type { MenuCheckboxItemProps } from './types'; +import { Context } from './context'; +import type { CheckboxItemProps } from './types'; import * as Styled from './styles'; -import { useTemporaryFocusVisibleFix } from './use-temporary-focus-visible-fix'; -export const MenuCheckboxItem = forwardRef< +export const CheckboxItem = forwardRef< HTMLDivElement, - WordPressComponentProps< MenuCheckboxItemProps, 'div', false > ->( function MenuCheckboxItem( - { suffix, children, onBlur, hideOnClick = false, ...props }, + WordPressComponentProps< CheckboxItemProps, 'div', false > +>( function CheckboxItem( + { suffix, children, disabled = false, hideOnClick = false, ...props }, ref ) { - // TODO: Remove when https://github.com/ariakit/ariakit/issues/4083 is fixed - const focusVisibleFixProps = useTemporaryFocusVisibleFix( { onBlur } ); - const menuContext = useContext( MenuContext ); + const menuContext = useContext( Context ); + + if ( ! menuContext?.store ) { + throw new Error( + 'Menu.CheckboxItem can only be rendered inside a Menu component' + ); + } return ( - <Styled.MenuCheckboxItem + <Styled.CheckboxItem ref={ ref } { ...props } - { ...focusVisibleFixProps } accessibleWhenDisabled + disabled={ disabled } hideOnClick={ hideOnClick } - store={ menuContext?.store } + store={ menuContext.store } > <Ariakit.MenuItemCheck - store={ menuContext?.store } + store={ menuContext.store } render={ <Styled.ItemPrefixWrapper /> } // Override some ariakit inline styles style={ { width: 'auto', height: 'auto' } } @@ -47,17 +50,17 @@ export const MenuCheckboxItem = forwardRef< <Icon icon={ check } size={ 24 } /> </Ariakit.MenuItemCheck> - <Styled.MenuItemContentWrapper> - <Styled.MenuItemChildrenWrapper> + <Styled.ItemContentWrapper> + <Styled.ItemChildrenWrapper> { children } - </Styled.MenuItemChildrenWrapper> + </Styled.ItemChildrenWrapper> { suffix && ( <Styled.ItemSuffixWrapper> { suffix } </Styled.ItemSuffixWrapper> ) } - </Styled.MenuItemContentWrapper> - </Styled.MenuCheckboxItem> + </Styled.ItemContentWrapper> + </Styled.CheckboxItem> ); } ); diff --git a/packages/components/src/menu/context.tsx b/packages/components/src/menu/context.tsx index 1205015c57cbee..fa38f2c75aea61 100644 --- a/packages/components/src/menu/context.tsx +++ b/packages/components/src/menu/context.tsx @@ -6,8 +6,6 @@ import { createContext } from '@wordpress/element'; /** * Internal dependencies */ -import type { MenuContext as MenuContextType } from './types'; +import type { ContextProps } from './types'; -export const MenuContext = createContext< MenuContextType | undefined >( - undefined -); +export const Context = createContext< ContextProps | undefined >( undefined ); diff --git a/packages/components/src/menu/docs-manifest.json b/packages/components/src/menu/docs-manifest.json new file mode 100644 index 00000000000000..c47fd97e8e09f7 --- /dev/null +++ b/packages/components/src/menu/docs-manifest.json @@ -0,0 +1,62 @@ +{ + "$schema": "../../schemas/docs-manifest.json", + "displayName": "Menu", + "filePath": "./index.tsx", + "subcomponents": [ + { + "displayName": "TriggerButton", + "preferredDisplayName": "Menu.TriggerButton", + "filePath": "./trigger-button.tsx" + }, + { + "displayName": "Popover", + "preferredDisplayName": "Menu.Popover", + "filePath": "./popover.tsx" + }, + { + "displayName": "Item", + "preferredDisplayName": "Menu.Item", + "filePath": "./item.tsx" + }, + { + "displayName": "RadioItem", + "preferredDisplayName": "Menu.RadioItem", + "filePath": "./radio-item.tsx" + }, + { + "displayName": "CheckboxItem", + "preferredDisplayName": "Menu.CheckboxItem", + "filePath": "./checkbox-item.tsx" + }, + { + "displayName": "ItemLabel", + "preferredDisplayName": "Menu.ItemLabel", + "filePath": "./item-label.tsx" + }, + { + "displayName": "ItemHelpText", + "preferredDisplayName": "Menu.ItemHelpText", + "filePath": "./item-help-text.tsx" + }, + { + "displayName": "Group", + "preferredDisplayName": "Menu.Group", + "filePath": "./group.tsx" + }, + { + "displayName": "GroupLabel", + "preferredDisplayName": "Menu.GroupLabel", + "filePath": "./group-label.tsx" + }, + { + "displayName": "Separator", + "preferredDisplayName": "Menu.Separator", + "filePath": "./separator.tsx" + }, + { + "displayName": "SubmenuTriggerItem", + "preferredDisplayName": "Menu.SubmenuTriggerItem", + "filePath": "./submenu-trigger-item.tsx" + } + ] +} diff --git a/packages/components/src/menu/group-label.tsx b/packages/components/src/menu/group-label.tsx index 71c5c7de69941e..ce6ecb06900d06 100644 --- a/packages/components/src/menu/group-label.tsx +++ b/packages/components/src/menu/group-label.tsx @@ -7,18 +7,25 @@ import { forwardRef, useContext } from '@wordpress/element'; * Internal dependencies */ import type { WordPressComponentProps } from '../context'; -import { MenuContext } from './context'; +import { Context } from './context'; import { Text } from '../text'; -import type { MenuGroupLabelProps } from './types'; +import type { GroupLabelProps } from './types'; import * as Styled from './styles'; -export const MenuGroupLabel = forwardRef< +export const GroupLabel = forwardRef< HTMLDivElement, - WordPressComponentProps< MenuGroupLabelProps, 'div', false > ->( function MenuGroup( props, ref ) { - const menuContext = useContext( MenuContext ); + WordPressComponentProps< GroupLabelProps, 'div', false > +>( function Group( props, ref ) { + const menuContext = useContext( Context ); + + if ( ! menuContext?.store ) { + throw new Error( + 'Menu.GroupLabel can only be rendered inside a Menu component' + ); + } + return ( - <Styled.MenuGroupLabel + <Styled.GroupLabel ref={ ref } render={ // @ts-expect-error The `children` prop is passed @@ -31,7 +38,7 @@ export const MenuGroupLabel = forwardRef< /> } { ...props } - store={ menuContext?.store } + store={ menuContext.store } /> ); } ); diff --git a/packages/components/src/menu/group.tsx b/packages/components/src/menu/group.tsx index f9a4138fe43580..3b503069a61556 100644 --- a/packages/components/src/menu/group.tsx +++ b/packages/components/src/menu/group.tsx @@ -7,20 +7,23 @@ import { forwardRef, useContext } from '@wordpress/element'; * Internal dependencies */ import type { WordPressComponentProps } from '../context'; -import { MenuContext } from './context'; -import type { MenuGroupProps } from './types'; +import { Context } from './context'; +import type { GroupProps } from './types'; import * as Styled from './styles'; -export const MenuGroup = forwardRef< +export const Group = forwardRef< HTMLDivElement, - WordPressComponentProps< MenuGroupProps, 'div', false > ->( function MenuGroup( props, ref ) { - const menuContext = useContext( MenuContext ); + WordPressComponentProps< GroupProps, 'div', false > +>( function Group( props, ref ) { + const menuContext = useContext( Context ); + + if ( ! menuContext?.store ) { + throw new Error( + 'Menu.Group can only be rendered inside a Menu component' + ); + } + return ( - <Styled.MenuGroup - ref={ ref } - { ...props } - store={ menuContext?.store } - /> + <Styled.Group ref={ ref } { ...props } store={ menuContext.store } /> ); } ); diff --git a/packages/components/src/menu/index.tsx b/packages/components/src/menu/index.tsx index 6f6e89b0a1c72b..e129c4e5867f02 100644 --- a/packages/components/src/menu/index.tsx +++ b/packages/components/src/menu/index.tsx @@ -2,80 +2,57 @@ * External dependencies */ import * as Ariakit from '@ariakit/react'; -import { useStoreState } from '@ariakit/react'; /** * WordPress dependencies */ -import { - useContext, - useMemo, - cloneElement, - isValidElement, - useCallback, -} from '@wordpress/element'; -import { isRTL } from '@wordpress/i18n'; -import { chevronRightSmall } from '@wordpress/icons'; +import { useContext, useMemo } from '@wordpress/element'; +import { isRTL as isRTLFn } from '@wordpress/i18n'; /** * Internal dependencies */ -import { useContextSystem, contextConnect } from '../context'; -import type { WordPressComponentProps } from '../context'; -import type { MenuContext as MenuContextType, MenuProps } from './types'; -import * as Styled from './styles'; -import { MenuContext } from './context'; -import { MenuItem } from './item'; -import { MenuCheckboxItem } from './checkbox-item'; -import { MenuRadioItem } from './radio-item'; -import { MenuGroup } from './group'; -import { MenuGroupLabel } from './group-label'; -import { MenuSeparator } from './separator'; -import { MenuItemLabel } from './item-label'; -import { MenuItemHelpText } from './item-help-text'; - -const UnconnectedMenu = ( - props: WordPressComponentProps< MenuProps, 'div', false >, - ref: React.ForwardedRef< HTMLDivElement > -) => { +import { useContextSystem, contextConnectWithoutRef } from '../context'; +import type { ContextProps, Props } from './types'; +import { Context } from './context'; +import { Item } from './item'; +import { CheckboxItem } from './checkbox-item'; +import { RadioItem } from './radio-item'; +import { Group } from './group'; +import { GroupLabel } from './group-label'; +import { Separator } from './separator'; +import { ItemLabel } from './item-label'; +import { ItemHelpText } from './item-help-text'; +import { TriggerButton } from './trigger-button'; +import { SubmenuTriggerItem } from './submenu-trigger-item'; +import { Popover } from './popover'; + +const UnconnectedMenu = ( props: Props ) => { const { - // Store props - open, + children, defaultOpen = false, + open, onOpenChange, placement, - // Menu trigger props - trigger, - - // Menu props - gutter, - children, - shift, - modal = true, - // From internal components context variant, + } = useContextSystem< + // @ts-expect-error TODO: missing 'className' in MenuProps + typeof props & Pick< ContextProps, 'variant' > + >( props, 'Menu' ); - // Rest - ...otherProps - } = useContextSystem< typeof props & Pick< MenuContextType, 'variant' > >( - props, - 'Menu' - ); - - const parentContext = useContext( MenuContext ); + const parentContext = useContext( Context ); - const computedDirection = isRTL() ? 'rtl' : 'ltr'; + const rtl = isRTLFn(); // If an explicit value for the `placement` prop is not passed, // apply a default placement of `bottom-start` for the root menu popover, // and of `right-start` for nested menu popovers. let computedPlacement = - props.placement ?? - ( parentContext?.store ? 'right-start' : 'bottom-start' ); + placement ?? ( parentContext?.store ? 'right-start' : 'bottom-start' ); // Swap left/right in case of RTL direction - if ( computedDirection === 'rtl' ) { + if ( rtl ) { if ( /right/.test( computedPlacement ) ) { computedPlacement = computedPlacement.replace( 'right', @@ -98,7 +75,7 @@ const UnconnectedMenu = ( setOpen( willBeOpen ) { onOpenChange?.( willBeOpen ); }, - rtl: computedDirection === 'rtl', + rtl, } ); const contextValue = useMemo( @@ -106,134 +83,123 @@ const UnconnectedMenu = ( [ menuStore, variant ] ); - // Extract the side from the applied placement — useful for animations. - // Using `currentPlacement` instead of `placement` to make sure that we - // use the final computed placement (including "flips" etc). - const appliedPlacementSide = useStoreState( - menuStore, - 'currentPlacement' - ).split( '-' )[ 0 ]; - - if ( - menuStore.parent && - ! ( isValidElement( trigger ) && MenuItem === trigger.type ) - ) { - // eslint-disable-next-line no-console - console.warn( - 'For nested Menus, the `trigger` should always be a `MenuItem`.' - ); - } - - const hideOnEscape = useCallback( - ( event: React.KeyboardEvent< Element > ) => { - // Pressing Escape can cause unexpected consequences (ie. exiting - // full screen mode on MacOs, close parent modals...). - event.preventDefault(); - // Returning `true` causes the menu to hide. - return true; - }, - [] - ); - - const wrapperProps = useMemo( - () => ( { - dir: computedDirection, - style: { - direction: - computedDirection as React.CSSProperties[ 'direction' ], - }, - } ), - [ computedDirection ] - ); - return ( - <> - { /* Menu trigger */ } - <Ariakit.MenuButton - ref={ ref } - store={ menuStore } - render={ - menuStore.parent - ? cloneElement( trigger, { - // Add submenu arrow, unless a `suffix` is explicitly specified - suffix: ( - <> - { trigger.props.suffix } - <Styled.SubmenuChevronIcon - aria-hidden="true" - icon={ chevronRightSmall } - size={ 24 } - preserveAspectRatio="xMidYMid slice" - /> - </> - ), - } ) - : trigger - } - /> - - { /* Menu popover */ } - <Ariakit.Menu - { ...otherProps } - modal={ modal } - store={ menuStore } - // Root menu has an 8px distance from its trigger, - // otherwise 0 (which causes the submenu to slightly overlap) - gutter={ gutter ?? ( menuStore.parent ? 0 : 8 ) } - // Align nested menu by the same (but opposite) amount - // as the menu container's padding. - shift={ shift ?? ( menuStore.parent ? -4 : 0 ) } - hideOnHoverOutside={ false } - data-side={ appliedPlacementSide } - wrapperProps={ wrapperProps } - hideOnEscape={ hideOnEscape } - unmountOnHide - render={ ( renderProps ) => ( - // Two wrappers are needed for the entry animation, where the menu - // container scales with a different factor than its contents. - // The {...renderProps} are passed to the inner wrapper, so that the - // menu element is the direct parent of the menu item elements. - <Styled.MenuPopoverOuterWrapper variant={ variant }> - <Styled.MenuPopoverInnerWrapper { ...renderProps } /> - </Styled.MenuPopoverOuterWrapper> - ) } - > - <MenuContext.Provider value={ contextValue }> - { children } - </MenuContext.Provider> - </Ariakit.Menu> - </> + <Context.Provider value={ contextValue }>{ children }</Context.Provider> ); }; -export const Menu = Object.assign( contextConnect( UnconnectedMenu, 'Menu' ), { - Context: Object.assign( MenuContext, { - displayName: 'Menu.Context', - } ), - Item: Object.assign( MenuItem, { - displayName: 'Menu.Item', - } ), - RadioItem: Object.assign( MenuRadioItem, { - displayName: 'Menu.RadioItem', - } ), - CheckboxItem: Object.assign( MenuCheckboxItem, { - displayName: 'Menu.CheckboxItem', - } ), - Group: Object.assign( MenuGroup, { - displayName: 'Menu.Group', - } ), - GroupLabel: Object.assign( MenuGroupLabel, { - displayName: 'Menu.GroupLabel', - } ), - Separator: Object.assign( MenuSeparator, { - displayName: 'Menu.Separator', - } ), - ItemLabel: Object.assign( MenuItemLabel, { - displayName: 'Menu.ItemLabel', - } ), - ItemHelpText: Object.assign( MenuItemHelpText, { - displayName: 'Menu.ItemHelpText', - } ), -} ); +/** + * Menu is a collection of React components that combine to render + * ARIA-compliant [menu](https://www.w3.org/WAI/ARIA/apg/patterns/menu/) and + * [menu button](https://www.w3.org/WAI/ARIA/apg/patterns/menubutton/) patterns. + * + * `Menu` itself is a wrapper component and context provider. + * It is responsible for managing the state of the menu and its items, and for + * rendering the `Menu.TriggerButton` (or the `Menu.SubmenuTriggerItem`) + * component, and the `Menu.Popover` component. + */ +export const Menu = Object.assign( + contextConnectWithoutRef( UnconnectedMenu, 'Menu' ), + { + Context: Object.assign( Context, { + displayName: 'Menu.Context', + } ), + /** + * Renders a menu item inside the `Menu.Popover` or `Menu.Group` components. + * + * It can optionally contain one instance of the `Menu.ItemLabel` component + * and one instance of the `Menu.ItemHelpText` component. + */ + Item: Object.assign( Item, { + displayName: 'Menu.Item', + } ), + /** + * Renders a radio menu item inside the `Menu.Popover` or `Menu.Group` + * components. + * + * It can optionally contain one instance of the `Menu.ItemLabel` component + * and one instance of the `Menu.ItemHelpText` component. + */ + RadioItem: Object.assign( RadioItem, { + displayName: 'Menu.RadioItem', + } ), + /** + * Renders a checkbox menu item inside the `Menu.Popover` or `Menu.Group` + * components. + * + * It can optionally contain one instance of the `Menu.ItemLabel` component + * and one instance of the `Menu.ItemHelpText` component. + */ + CheckboxItem: Object.assign( CheckboxItem, { + displayName: 'Menu.CheckboxItem', + } ), + /** + * Renders a group for menu items. + * + * It should contain one instance of `Menu.GroupLabel` and one or more + * instances of `Menu.Item`, `Menu.RadioItem`, or `Menu.CheckboxItem`. + */ + Group: Object.assign( Group, { + displayName: 'Menu.Group', + } ), + /** + * Renders a label in a menu group. + * + * This component should be wrapped with `Menu.Group` so the + * `aria-labelledby` is correctly set on the group element. + */ + GroupLabel: Object.assign( GroupLabel, { + displayName: 'Menu.GroupLabel', + } ), + /** + * Renders a divider between menu items or menu groups. + */ + Separator: Object.assign( Separator, { + displayName: 'Menu.Separator', + } ), + /** + * Renders a menu item's label text. It should be wrapped with `Menu.Item`, + * `Menu.RadioItem`, or `Menu.CheckboxItem`. + */ + ItemLabel: Object.assign( ItemLabel, { + displayName: 'Menu.ItemLabel', + } ), + /** + * Renders a menu item's help text. It should be wrapped with `Menu.Item`, + * `Menu.RadioItem`, or `Menu.CheckboxItem`. + */ + ItemHelpText: Object.assign( ItemHelpText, { + displayName: 'Menu.ItemHelpText', + } ), + /** + * Renders a dropdown menu element that's controlled by a sibling + * `Menu.TriggerButton` component. It renders a popover and automatically + * focuses on items when the menu is shown. + * + * The only valid children of `Menu.Popover` are `Menu.Item`, + * `Menu.RadioItem`, `Menu.CheckboxItem`, `Menu.Group`, `Menu.Separator`, + * and `Menu` (for nested dropdown menus). + */ + Popover: Object.assign( Popover, { + displayName: 'Menu.Popover', + } ), + /** + * Renders a menu button that toggles the visibility of a sibling + * `Menu.Popover` component when clicked or when using arrow keys. + */ + TriggerButton: Object.assign( TriggerButton, { + displayName: 'Menu.TriggerButton', + } ), + /** + * Renders a menu item that toggles the visibility of a sibling + * `Menu.Popover` component when clicked or when using arrow keys. + * + * This component is used to create a nested dropdown menu. + */ + SubmenuTriggerItem: Object.assign( SubmenuTriggerItem, { + displayName: 'Menu.SubmenuTriggerItem', + } ), + } +); export default Menu; diff --git a/packages/components/src/menu/item-help-text.tsx b/packages/components/src/menu/item-help-text.tsx index 0ccc8f7461a8ff..e47c54d702342f 100644 --- a/packages/components/src/menu/item-help-text.tsx +++ b/packages/components/src/menu/item-help-text.tsx @@ -1,19 +1,26 @@ /** * WordPress dependencies */ -import { forwardRef } from '@wordpress/element'; +import { forwardRef, useContext } from '@wordpress/element'; /** * Internal dependencies */ import type { WordPressComponentProps } from '../context'; +import { Context } from './context'; import * as Styled from './styles'; -export const MenuItemHelpText = forwardRef< +export const ItemHelpText = forwardRef< HTMLSpanElement, WordPressComponentProps< { children: React.ReactNode }, 'span', true > ->( function MenuItemHelpText( props, ref ) { - return ( - <Styled.MenuItemHelpText numberOfLines={ 2 } ref={ ref } { ...props } /> - ); +>( function ItemHelpText( props, ref ) { + const menuContext = useContext( Context ); + + if ( ! menuContext?.store ) { + throw new Error( + 'Menu.ItemHelpText can only be rendered inside a Menu component' + ); + } + + return <Styled.ItemHelpText numberOfLines={ 2 } ref={ ref } { ...props } />; } ); diff --git a/packages/components/src/menu/item-label.tsx b/packages/components/src/menu/item-label.tsx index 458f69558eafbc..3a3367f4b481fe 100644 --- a/packages/components/src/menu/item-label.tsx +++ b/packages/components/src/menu/item-label.tsx @@ -1,19 +1,26 @@ /** * WordPress dependencies */ -import { forwardRef } from '@wordpress/element'; +import { forwardRef, useContext } from '@wordpress/element'; /** * Internal dependencies */ import type { WordPressComponentProps } from '../context'; +import { Context } from './context'; import * as Styled from './styles'; -export const MenuItemLabel = forwardRef< +export const ItemLabel = forwardRef< HTMLSpanElement, WordPressComponentProps< { children: React.ReactNode }, 'span', true > ->( function MenuItemLabel( props, ref ) { - return ( - <Styled.MenuItemLabel numberOfLines={ 1 } ref={ ref } { ...props } /> - ); +>( function ItemLabel( props, ref ) { + const menuContext = useContext( Context ); + + if ( ! menuContext?.store ) { + throw new Error( + 'Menu.ItemLabel can only be rendered inside a Menu component' + ); + } + + return <Styled.ItemLabel numberOfLines={ 1 } ref={ ref } { ...props } />; } ); diff --git a/packages/components/src/menu/item.tsx b/packages/components/src/menu/item.tsx index f8ae670846f55f..560d20c30436ce 100644 --- a/packages/components/src/menu/item.tsx +++ b/packages/components/src/menu/item.tsx @@ -7,44 +7,61 @@ import { forwardRef, useContext } from '@wordpress/element'; * Internal dependencies */ import type { WordPressComponentProps } from '../context'; -import type { MenuItemProps } from './types'; +import type { ItemProps } from './types'; import * as Styled from './styles'; -import { MenuContext } from './context'; -import { useTemporaryFocusVisibleFix } from './use-temporary-focus-visible-fix'; +import { Context } from './context'; -export const MenuItem = forwardRef< +export const Item = forwardRef< HTMLDivElement, - WordPressComponentProps< MenuItemProps, 'div', false > ->( function MenuItem( - { prefix, suffix, children, onBlur, hideOnClick = true, ...props }, + WordPressComponentProps< ItemProps, 'div', false > +>( function Item( + { + prefix, + suffix, + children, + disabled = false, + hideOnClick = true, + store, + ...props + }, ref ) { - // TODO: Remove when https://github.com/ariakit/ariakit/issues/4083 is fixed - const focusVisibleFixProps = useTemporaryFocusVisibleFix( { onBlur } ); - const menuContext = useContext( MenuContext ); + const menuContext = useContext( Context ); + + if ( ! menuContext?.store ) { + throw new Error( + 'Menu.Item can only be rendered inside a Menu component' + ); + } + + // In most cases, the menu store will be retrieved from context (ie. the store + // created by the top-level menu component). But in rare cases (ie. + // `Menu.SubmenuTriggerItem`), the context store wouldn't be correct. This is + // why the component accepts a `store` prop to override the context store. + const computedStore = store ?? menuContext.store; return ( - <Styled.MenuItem + <Styled.Item ref={ ref } { ...props } - { ...focusVisibleFixProps } accessibleWhenDisabled + disabled={ disabled } hideOnClick={ hideOnClick } - store={ menuContext?.store } + store={ computedStore } > <Styled.ItemPrefixWrapper>{ prefix }</Styled.ItemPrefixWrapper> - <Styled.MenuItemContentWrapper> - <Styled.MenuItemChildrenWrapper> + <Styled.ItemContentWrapper> + <Styled.ItemChildrenWrapper> { children } - </Styled.MenuItemChildrenWrapper> + </Styled.ItemChildrenWrapper> { suffix && ( <Styled.ItemSuffixWrapper> { suffix } </Styled.ItemSuffixWrapper> ) } - </Styled.MenuItemContentWrapper> - </Styled.MenuItem> + </Styled.ItemContentWrapper> + </Styled.Item> ); } ); diff --git a/packages/components/src/menu/popover.tsx b/packages/components/src/menu/popover.tsx new file mode 100644 index 00000000000000..6a3ad9eb683b51 --- /dev/null +++ b/packages/components/src/menu/popover.tsx @@ -0,0 +1,103 @@ +/** + * External dependencies + */ +import * as Ariakit from '@ariakit/react'; + +/** + * WordPress dependencies + */ +import { + useContext, + useMemo, + forwardRef, + useCallback, +} from '@wordpress/element'; + +/** + * Internal dependencies + */ +import type { WordPressComponentProps } from '../context'; +import type { PopoverProps } from './types'; +import * as Styled from './styles'; +import { Context } from './context'; + +export const Popover = forwardRef< + HTMLDivElement, + WordPressComponentProps< PopoverProps, 'div', false > +>( function Popover( + { gutter, children, shift, modal = true, ...otherProps }, + ref +) { + const menuContext = useContext( Context ); + + // Extract the side from the applied placement — useful for animations. + // Using `currentPlacement` instead of `placement` to make sure that we + // use the final computed placement (including "flips" etc). + const appliedPlacementSide = Ariakit.useStoreState( + menuContext?.store, + 'currentPlacement' + )?.split( '-' )[ 0 ]; + + const hideOnEscape = useCallback( + ( event: React.KeyboardEvent< Element > ) => { + // Pressing Escape can cause unexpected consequences (ie. exiting + // full screen mode on MacOs, close parent modals...). + event.preventDefault(); + // Returning `true` causes the menu to hide. + return true; + }, + [] + ); + + const computedDirection = Ariakit.useStoreState( menuContext?.store, 'rtl' ) + ? 'rtl' + : 'ltr'; + + const wrapperProps = useMemo( + () => ( { + dir: computedDirection, + style: { + direction: + computedDirection as React.CSSProperties[ 'direction' ], + }, + } ), + [ computedDirection ] + ); + + if ( ! menuContext?.store ) { + throw new Error( + 'Menu.Popover can only be rendered inside a Menu component' + ); + } + + return ( + <Ariakit.Menu + { ...otherProps } + ref={ ref } + modal={ modal } + store={ menuContext.store } + // Root menu has an 8px distance from its trigger, + // otherwise 0 (which causes the submenu to slightly overlap) + gutter={ gutter ?? ( menuContext.store.parent ? 0 : 8 ) } + // Align nested menu by the same (but opposite) amount + // as the menu container's padding. + shift={ shift ?? ( menuContext.store.parent ? -4 : 0 ) } + hideOnHoverOutside={ false } + data-side={ appliedPlacementSide } + wrapperProps={ wrapperProps } + hideOnEscape={ hideOnEscape } + unmountOnHide + render={ ( renderProps ) => ( + // Two wrappers are needed for the entry animation, where the menu + // container scales with a different factor than its contents. + // The {...renderProps} are passed to the inner wrapper, so that the + // menu element is the direct parent of the menu item elements. + <Styled.PopoverOuterWrapper variant={ menuContext.variant }> + <Styled.PopoverInnerWrapper { ...renderProps } /> + </Styled.PopoverOuterWrapper> + ) } + > + { children } + </Ariakit.Menu> + ); +} ); diff --git a/packages/components/src/menu/radio-item.tsx b/packages/components/src/menu/radio-item.tsx index 3848d2062c0c25..1da6d573c26852 100644 --- a/packages/components/src/menu/radio-item.tsx +++ b/packages/components/src/menu/radio-item.tsx @@ -13,11 +13,10 @@ import { Icon } from '@wordpress/icons'; * Internal dependencies */ import type { WordPressComponentProps } from '../context'; -import { MenuContext } from './context'; -import type { MenuRadioItemProps } from './types'; +import { Context } from './context'; +import type { RadioItemProps } from './types'; import * as Styled from './styles'; import { SVG, Circle } from '@wordpress/primitives'; -import { useTemporaryFocusVisibleFix } from './use-temporary-focus-visible-fix'; const radioCheck = ( <SVG xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> @@ -25,28 +24,32 @@ const radioCheck = ( </SVG> ); -export const MenuRadioItem = forwardRef< +export const RadioItem = forwardRef< HTMLDivElement, - WordPressComponentProps< MenuRadioItemProps, 'div', false > ->( function MenuRadioItem( - { suffix, children, onBlur, hideOnClick = false, ...props }, + WordPressComponentProps< RadioItemProps, 'div', false > +>( function RadioItem( + { suffix, children, disabled = false, hideOnClick = false, ...props }, ref ) { - // TODO: Remove when https://github.com/ariakit/ariakit/issues/4083 is fixed - const focusVisibleFixProps = useTemporaryFocusVisibleFix( { onBlur } ); - const menuContext = useContext( MenuContext ); + const menuContext = useContext( Context ); + + if ( ! menuContext?.store ) { + throw new Error( + 'Menu.RadioItem can only be rendered inside a Menu component' + ); + } return ( - <Styled.MenuRadioItem + <Styled.RadioItem ref={ ref } { ...props } - { ...focusVisibleFixProps } accessibleWhenDisabled + disabled={ disabled } hideOnClick={ hideOnClick } - store={ menuContext?.store } + store={ menuContext.store } > <Ariakit.MenuItemCheck - store={ menuContext?.store } + store={ menuContext.store } render={ <Styled.ItemPrefixWrapper /> } // Override some ariakit inline styles style={ { width: 'auto', height: 'auto' } } @@ -54,17 +57,17 @@ export const MenuRadioItem = forwardRef< <Icon icon={ radioCheck } size={ 24 } /> </Ariakit.MenuItemCheck> - <Styled.MenuItemContentWrapper> - <Styled.MenuItemChildrenWrapper> + <Styled.ItemContentWrapper> + <Styled.ItemChildrenWrapper> { children } - </Styled.MenuItemChildrenWrapper> + </Styled.ItemChildrenWrapper> { suffix && ( <Styled.ItemSuffixWrapper> { suffix } </Styled.ItemSuffixWrapper> ) } - </Styled.MenuItemContentWrapper> - </Styled.MenuRadioItem> + </Styled.ItemContentWrapper> + </Styled.RadioItem> ); } ); diff --git a/packages/components/src/menu/separator.tsx b/packages/components/src/menu/separator.tsx index 5d0110016d9c4a..bdf79c8bb472a2 100644 --- a/packages/components/src/menu/separator.tsx +++ b/packages/components/src/menu/separator.tsx @@ -7,21 +7,28 @@ import { forwardRef, useContext } from '@wordpress/element'; * Internal dependencies */ import type { WordPressComponentProps } from '../context'; -import { MenuContext } from './context'; -import type { MenuSeparatorProps } from './types'; +import { Context } from './context'; +import type { SeparatorProps } from './types'; import * as Styled from './styles'; -export const MenuSeparator = forwardRef< +export const Separator = forwardRef< HTMLHRElement, - WordPressComponentProps< MenuSeparatorProps, 'hr', false > ->( function MenuSeparator( props, ref ) { - const menuContext = useContext( MenuContext ); + WordPressComponentProps< SeparatorProps, 'hr', false > +>( function Separator( props, ref ) { + const menuContext = useContext( Context ); + + if ( ! menuContext?.store ) { + throw new Error( + 'Menu.Separator can only be rendered inside a Menu component' + ); + } + return ( - <Styled.MenuSeparator + <Styled.Separator ref={ ref } { ...props } - store={ menuContext?.store } - variant={ menuContext?.variant } + store={ menuContext.store } + variant={ menuContext.variant } /> ); } ); diff --git a/packages/components/src/menu/stories/best-practices.mdx b/packages/components/src/menu/stories/best-practices.mdx new file mode 100644 index 00000000000000..148e9c187c600d --- /dev/null +++ b/packages/components/src/menu/stories/best-practices.mdx @@ -0,0 +1,38 @@ +import { Meta } from '@storybook/blocks'; + +import * as MenuStories from './index.story'; + +<Meta of={ MenuStories } name="Best Practices" /> + +# Menu + +## Usage + +### When to use a `Menu` + +Use a `Menu` when you want users to: + +- Choose an action or change a setting from a list, AND +- Only see the available choices contextually. + +`Menu` is a React component to render an expandable menu of buttons. It is similar in purpose to a `<select>` element, with the distinction that it does not maintain a value. Instead, each option behaves as an action button. + +If you need to display all the available options at all times, consider using a Toolbar instead. Use a `Menu` to display a list of actions after the user interacts with a button. + +**Do** +Use a `Menu` to display a list of actions after the user interacts with an icon. + +**Don’t** use a `Menu` for important actions that should always be visible. Use a `Toolbar` instead. + +**Don’t** +Don’t use a `Menu` for frequently used actions. Use a `Toolbar` instead. + +### Behavior + +Generally, the parent button should indicate that interacting with it will show a `Menu`. + +The parent button should retain the same visual styling regardless of whether the `Menu` is displayed or not. + +### Placement + +The `Menu` should typically appear directly below, or below and to the left of, the parent button. If there isn’t enough space below to display the full `Menu`, it can be displayed instead above the parent button. diff --git a/packages/components/src/menu/stories/index.story.tsx b/packages/components/src/menu/stories/index.story.tsx index 92501c33269580..de9e4cdd652102 100644 --- a/packages/components/src/menu/stories/index.story.tsx +++ b/packages/components/src/menu/stories/index.story.tsx @@ -1,7 +1,7 @@ /** * External dependencies */ -import type { Meta, StoryFn } from '@storybook/react'; +import type { StoryObj, Meta } from '@storybook/react'; import { css } from '@emotion/react'; /** @@ -20,9 +20,10 @@ import Button from '../../button'; import Modal from '../../modal'; import { createSlotFill, Provider as SlotFillProvider } from '../../slot-fill'; import { ContextSystemProvider } from '../../context'; +import type { Props } from '../types'; const meta: Meta< typeof Menu > = { - id: 'components-experimental-menu', + id: 'components-menu', title: 'Components (Experimental)/Actions/Menu', component: Menu, subcomponents: { @@ -44,10 +45,15 @@ const meta: Meta< typeof Menu > = { ItemLabel: Menu.ItemLabel, // @ts-expect-error - See https://github.com/storybookjs/storybook/issues/23170 ItemHelpText: Menu.ItemHelpText, + // @ts-expect-error - See https://github.com/storybookjs/storybook/issues/23170 + TriggerButton: Menu.TriggerButton, + // @ts-expect-error - See https://github.com/storybookjs/storybook/issues/23170 + SubmenuTriggerItem: Menu.SubmenuTriggerItem, + // @ts-expect-error - See https://github.com/storybookjs/storybook/issues/23170 + Popover: Menu.Popover, }, argTypes: { - children: { control: { type: null } }, - trigger: { control: { type: null } }, + children: { control: false }, }, tags: [ 'status-private' ], parameters: { @@ -61,259 +67,341 @@ const meta: Meta< typeof Menu > = { }; export default meta; -export const Default: StoryFn< typeof Menu > = ( props ) => ( - <Menu { ...props }> - <Menu.Item> - <Menu.ItemLabel>Label</Menu.ItemLabel> - </Menu.Item> - <Menu.Item> - <Menu.ItemLabel>Label</Menu.ItemLabel> - <Menu.ItemHelpText>Help text</Menu.ItemHelpText> - </Menu.Item> - <Menu.Item> - <Menu.ItemLabel>Label</Menu.ItemLabel> - <Menu.ItemHelpText> - The menu item help text is automatically truncated when there - are more than two lines of text - </Menu.ItemHelpText> - </Menu.Item> - <Menu.Item hideOnClick={ false }> - <Menu.ItemLabel>Label</Menu.ItemLabel> - <Menu.ItemHelpText> - This item doesn&apos;t close the menu on click - </Menu.ItemHelpText> - </Menu.Item> - <Menu.Item disabled>Disabled item</Menu.Item> - <Menu.Separator /> - <Menu.Group> - <Menu.GroupLabel>Group label</Menu.GroupLabel> - <Menu.Item prefix={ <Icon icon={ customLink } size={ 24 } /> }> - <Menu.ItemLabel>With prefix</Menu.ItemLabel> - </Menu.Item> - <Menu.Item suffix="⌘S">With suffix</Menu.Item> - <Menu.Item - disabled - prefix={ <Icon icon={ formatCapitalize } size={ 24 } /> } - suffix="⌥⌘T" - > - <Menu.ItemLabel>Disabled with prefix and suffix</Menu.ItemLabel> - <Menu.ItemHelpText>And help text</Menu.ItemHelpText> - </Menu.Item> - </Menu.Group> - </Menu> -); -Default.args = { - trigger: ( - <Button __next40pxDefaultSize variant="secondary"> - Open menu - </Button> - ), -}; - -export const WithSubmenu: StoryFn< typeof Menu > = ( props ) => ( - <Menu { ...props }> - <Menu.Item>Level 1 item</Menu.Item> - <Menu - trigger={ - <Menu.Item suffix="Suffix"> - <Menu.ItemLabel> - Submenu trigger item with a long label - </Menu.ItemLabel> - </Menu.Item> - } - > - <Menu.Item> - <Menu.ItemLabel>Level 2 item</Menu.ItemLabel> - </Menu.Item> - <Menu.Item> - <Menu.ItemLabel>Level 2 item</Menu.ItemLabel> - </Menu.Item> - <Menu - trigger={ +export const Default: StoryObj< typeof Menu > = { + args: { + children: ( + <> + <Menu.TriggerButton + render={ + <Button __next40pxDefaultSize variant="secondary" /> + } + > + Open menu + </Menu.TriggerButton> + <Menu.Popover> <Menu.Item> - <Menu.ItemLabel>Submenu trigger</Menu.ItemLabel> + <Menu.ItemLabel>Label</Menu.ItemLabel> </Menu.Item> - } - > - <Menu.Item> - <Menu.ItemLabel>Level 3 item</Menu.ItemLabel> - </Menu.Item> - <Menu.Item> - <Menu.ItemLabel>Level 3 item</Menu.ItemLabel> - </Menu.Item> - </Menu> - </Menu> - </Menu> -); -WithSubmenu.args = { - ...Default.args, + <Menu.Item> + <Menu.ItemLabel>Label</Menu.ItemLabel> + <Menu.ItemHelpText>Help text</Menu.ItemHelpText> + </Menu.Item> + <Menu.Item> + <Menu.ItemLabel>Label</Menu.ItemLabel> + <Menu.ItemHelpText> + The menu item help text is automatically truncated + when there are more than two lines of text + </Menu.ItemHelpText> + </Menu.Item> + <Menu.Item hideOnClick={ false }> + <Menu.ItemLabel>Label</Menu.ItemLabel> + <Menu.ItemHelpText> + This item doesn&apos;t close the menu on click + </Menu.ItemHelpText> + </Menu.Item> + <Menu.Item disabled>Disabled item</Menu.Item> + <Menu.Separator /> + <Menu.Group> + <Menu.GroupLabel>Group label</Menu.GroupLabel> + <Menu.Item + prefix={ <Icon icon={ customLink } size={ 24 } /> } + > + <Menu.ItemLabel>With prefix</Menu.ItemLabel> + </Menu.Item> + <Menu.Item suffix="⌘S">With suffix</Menu.Item> + <Menu.Item + disabled + prefix={ + <Icon icon={ formatCapitalize } size={ 24 } /> + } + suffix="⌥⌘T" + > + <Menu.ItemLabel> + Disabled with prefix and suffix + </Menu.ItemLabel> + <Menu.ItemHelpText>And help text</Menu.ItemHelpText> + </Menu.Item> + </Menu.Group> + </Menu.Popover> + </> + ), + }, }; -export const WithCheckboxes: StoryFn< typeof Menu > = ( props ) => { - const [ isAChecked, setAChecked ] = useState( false ); - const [ isBChecked, setBChecked ] = useState( true ); - const [ multipleCheckboxesValue, setMultipleCheckboxesValue ] = useState< - string[] - >( [ 'b' ] ); - - const onMultipleCheckboxesCheckedChange: React.ComponentProps< - typeof Menu.CheckboxItem - >[ 'onChange' ] = ( e ) => { - setMultipleCheckboxesValue( ( prevValues ) => { - if ( prevValues.includes( e.target.value ) ) { - return prevValues.filter( ( val ) => val !== e.target.value ); - } - return [ ...prevValues, e.target.value ]; - } ); - }; - - return ( - <Menu { ...props }> - <Menu.Group> - <Menu.GroupLabel> - Single selection, uncontrolled - </Menu.GroupLabel> - <Menu.CheckboxItem - name="checkbox-individual-uncontrolled-a" - value="a" - suffix="⌥⌘T" - > - <Menu.ItemLabel>Checkbox item A</Menu.ItemLabel> - <Menu.ItemHelpText>Initially unchecked</Menu.ItemHelpText> - </Menu.CheckboxItem> - <Menu.CheckboxItem - name="checkbox-individual-uncontrolled-b" - value="b" - defaultChecked - > - <Menu.ItemLabel>Checkbox item B</Menu.ItemLabel> - <Menu.ItemHelpText>Initially checked</Menu.ItemHelpText> - </Menu.CheckboxItem> - </Menu.Group> - <Menu.Separator /> - <Menu.Group> - <Menu.GroupLabel>Single selection, controlled</Menu.GroupLabel> - <Menu.CheckboxItem - name="checkbox-individual-controlled-a" - value="a" - checked={ isAChecked } - onChange={ ( e ) => setAChecked( e.target.checked ) } - > - <Menu.ItemLabel>Checkbox item A</Menu.ItemLabel> - <Menu.ItemHelpText>Initially unchecked</Menu.ItemHelpText> - </Menu.CheckboxItem> - <Menu.CheckboxItem - name="checkbox-individual-controlled-b" - value="b" - checked={ isBChecked } - onChange={ ( e ) => setBChecked( e.target.checked ) } - > - <Menu.ItemLabel>Checkbox item B</Menu.ItemLabel> - <Menu.ItemHelpText>Initially checked</Menu.ItemHelpText> - </Menu.CheckboxItem> - </Menu.Group> - <Menu.Separator /> - <Menu.Group> - <Menu.GroupLabel> - Multiple selection, uncontrolled - </Menu.GroupLabel> - <Menu.CheckboxItem - name="checkbox-multiple-uncontrolled" - value="a" - > - <Menu.ItemLabel>Checkbox item A</Menu.ItemLabel> - <Menu.ItemHelpText>Initially unchecked</Menu.ItemHelpText> - </Menu.CheckboxItem> - <Menu.CheckboxItem - name="checkbox-multiple-uncontrolled" - value="b" - defaultChecked - > - <Menu.ItemLabel>Checkbox item B</Menu.ItemLabel> - <Menu.ItemHelpText>Initially checked</Menu.ItemHelpText> - </Menu.CheckboxItem> - </Menu.Group> - <Menu.Separator /> - <Menu.Group> - <Menu.GroupLabel> - Multiple selection, controlled - </Menu.GroupLabel> - <Menu.CheckboxItem - name="checkbox-multiple-controlled" - value="a" - checked={ multipleCheckboxesValue.includes( 'a' ) } - onChange={ onMultipleCheckboxesCheckedChange } - > - <Menu.ItemLabel>Checkbox item A</Menu.ItemLabel> - <Menu.ItemHelpText>Initially unchecked</Menu.ItemHelpText> - </Menu.CheckboxItem> - <Menu.CheckboxItem - name="checkbox-multiple-controlled" - value="b" - checked={ multipleCheckboxesValue.includes( 'b' ) } - onChange={ onMultipleCheckboxesCheckedChange } +export const WithSubmenu: StoryObj< typeof Menu > = { + args: { + ...Default.args, + children: ( + <> + <Menu.TriggerButton + render={ + <Button __next40pxDefaultSize variant="secondary" /> + } > - <Menu.ItemLabel>Checkbox item B</Menu.ItemLabel> - <Menu.ItemHelpText>Initially checked</Menu.ItemHelpText> - </Menu.CheckboxItem> - </Menu.Group> - </Menu> - ); -}; -WithCheckboxes.args = { - ...Default.args, + Open menu + </Menu.TriggerButton> + <Menu.Popover> + <Menu.Item>Level 1 item</Menu.Item> + <Menu> + <Menu.SubmenuTriggerItem suffix="Suffix"> + <Menu.ItemLabel> + Submenu trigger item with a long label + </Menu.ItemLabel> + </Menu.SubmenuTriggerItem> + <Menu.Popover> + <Menu.Item> + <Menu.ItemLabel>Level 2 item</Menu.ItemLabel> + </Menu.Item> + <Menu.Item> + <Menu.ItemLabel>Level 2 item</Menu.ItemLabel> + </Menu.Item> + <Menu> + <Menu.SubmenuTriggerItem> + <Menu.ItemLabel> + Submenu trigger + </Menu.ItemLabel> + </Menu.SubmenuTriggerItem> + <Menu.Popover> + <Menu.Item> + <Menu.ItemLabel> + Level 3 item + </Menu.ItemLabel> + </Menu.Item> + <Menu.Item> + <Menu.ItemLabel> + Level 3 item + </Menu.ItemLabel> + </Menu.Item> + </Menu.Popover> + </Menu> + </Menu.Popover> + </Menu> + </Menu.Popover> + </> + ), + }, }; -export const WithRadios: StoryFn< typeof Menu > = ( props ) => { - const [ radioValue, setRadioValue ] = useState( 'two' ); - const onRadioChange: React.ComponentProps< - typeof Menu.RadioItem - >[ 'onChange' ] = ( e ) => setRadioValue( e.target.value ); +export const WithCheckboxes: StoryObj< typeof Menu > = { + render: function WithCheckboxes( props: Props ) { + const [ isAChecked, setAChecked ] = useState( false ); + const [ isBChecked, setBChecked ] = useState( true ); + const [ multipleCheckboxesValue, setMultipleCheckboxesValue ] = + useState< string[] >( [ 'b' ] ); - return ( - <Menu { ...props }> - <Menu.Group> - <Menu.GroupLabel>Uncontrolled</Menu.GroupLabel> - <Menu.RadioItem name="radio-uncontrolled" value="one"> - <Menu.ItemLabel>Radio item 1</Menu.ItemLabel> - <Menu.ItemHelpText>Initially unchecked</Menu.ItemHelpText> - </Menu.RadioItem> - <Menu.RadioItem - name="radio-uncontrolled" - value="two" - defaultChecked - > - <Menu.ItemLabel>Radio item 2</Menu.ItemLabel> - <Menu.ItemHelpText>Initially checked</Menu.ItemHelpText> - </Menu.RadioItem> - </Menu.Group> - <Menu.Separator /> - <Menu.Group> - <Menu.GroupLabel>Controlled</Menu.GroupLabel> - <Menu.RadioItem - name="radio-controlled" - value="one" - checked={ radioValue === 'one' } - onChange={ onRadioChange } - > - <Menu.ItemLabel>Radio item 1</Menu.ItemLabel> - <Menu.ItemHelpText>Initially unchecked</Menu.ItemHelpText> - </Menu.RadioItem> - <Menu.RadioItem - name="radio-controlled" - value="two" - checked={ radioValue === 'two' } - onChange={ onRadioChange } + const onMultipleCheckboxesCheckedChange: React.ComponentProps< + typeof Menu.CheckboxItem + >[ 'onChange' ] = ( e ) => { + setMultipleCheckboxesValue( ( prevValues ) => { + if ( prevValues.includes( e.target.value ) ) { + return prevValues.filter( + ( val ) => val !== e.target.value + ); + } + return [ ...prevValues, e.target.value ]; + } ); + }; + + return ( + <Menu { ...props }> + <Menu.TriggerButton + render={ + <Button __next40pxDefaultSize variant="secondary" /> + } > - <Menu.ItemLabel>Radio item 2</Menu.ItemLabel> - <Menu.ItemHelpText>Initially checked</Menu.ItemHelpText> - </Menu.RadioItem> - </Menu.Group> - </Menu> - ); + Open menu + </Menu.TriggerButton> + <Menu.Popover> + <Menu.Group> + <Menu.GroupLabel> + Single selection, uncontrolled + </Menu.GroupLabel> + <Menu.CheckboxItem + name="checkbox-individual-uncontrolled-a" + value="a" + suffix="⌥⌘T" + > + <Menu.ItemLabel>Checkbox item A</Menu.ItemLabel> + <Menu.ItemHelpText> + Initially unchecked + </Menu.ItemHelpText> + </Menu.CheckboxItem> + <Menu.CheckboxItem + name="checkbox-individual-uncontrolled-b" + value="b" + defaultChecked + > + <Menu.ItemLabel>Checkbox item B</Menu.ItemLabel> + <Menu.ItemHelpText> + Initially checked + </Menu.ItemHelpText> + </Menu.CheckboxItem> + </Menu.Group> + <Menu.Separator /> + <Menu.Group> + <Menu.GroupLabel> + Single selection, controlled + </Menu.GroupLabel> + <Menu.CheckboxItem + name="checkbox-individual-controlled-a" + value="a" + checked={ isAChecked } + onChange={ ( e ) => { + setAChecked( e.target.checked ); + } } + > + <Menu.ItemLabel>Checkbox item A</Menu.ItemLabel> + <Menu.ItemHelpText> + Initially unchecked + </Menu.ItemHelpText> + </Menu.CheckboxItem> + <Menu.CheckboxItem + name="checkbox-individual-controlled-b" + value="b" + checked={ isBChecked } + onChange={ ( e ) => + setBChecked( e.target.checked ) + } + > + <Menu.ItemLabel>Checkbox item B</Menu.ItemLabel> + <Menu.ItemHelpText> + Initially checked + </Menu.ItemHelpText> + </Menu.CheckboxItem> + </Menu.Group> + <Menu.Separator /> + <Menu.Group> + <Menu.GroupLabel> + Multiple selection, uncontrolled + </Menu.GroupLabel> + <Menu.CheckboxItem + name="checkbox-multiple-uncontrolled" + value="a" + > + <Menu.ItemLabel>Checkbox item A</Menu.ItemLabel> + <Menu.ItemHelpText> + Initially unchecked + </Menu.ItemHelpText> + </Menu.CheckboxItem> + <Menu.CheckboxItem + name="checkbox-multiple-uncontrolled" + value="b" + defaultChecked + > + <Menu.ItemLabel>Checkbox item B</Menu.ItemLabel> + <Menu.ItemHelpText> + Initially checked + </Menu.ItemHelpText> + </Menu.CheckboxItem> + </Menu.Group> + <Menu.Separator /> + <Menu.Group> + <Menu.GroupLabel> + Multiple selection, controlled + </Menu.GroupLabel> + <Menu.CheckboxItem + name="checkbox-multiple-controlled" + value="a" + checked={ multipleCheckboxesValue.includes( 'a' ) } + onChange={ onMultipleCheckboxesCheckedChange } + > + <Menu.ItemLabel>Checkbox item A</Menu.ItemLabel> + <Menu.ItemHelpText> + Initially unchecked + </Menu.ItemHelpText> + </Menu.CheckboxItem> + <Menu.CheckboxItem + name="checkbox-multiple-controlled" + value="b" + checked={ multipleCheckboxesValue.includes( 'b' ) } + onChange={ onMultipleCheckboxesCheckedChange } + > + <Menu.ItemLabel>Checkbox item B</Menu.ItemLabel> + <Menu.ItemHelpText> + Initially checked + </Menu.ItemHelpText> + </Menu.CheckboxItem> + </Menu.Group> + </Menu.Popover> + </Menu> + ); + }, + + args: { + ...Default.args, + }, }; -WithRadios.args = { - ...Default.args, + +export const WithRadios: StoryObj< typeof Menu > = { + render: function WithRadios( props: Props ) { + const [ radioValue, setRadioValue ] = useState( 'two' ); + const onRadioChange: React.ComponentProps< + typeof Menu.RadioItem + >[ 'onChange' ] = ( e ) => setRadioValue( e.target.value ); + + return ( + <Menu { ...props }> + <Menu.TriggerButton + render={ + <Button __next40pxDefaultSize variant="secondary" /> + } + > + Open menu + </Menu.TriggerButton> + <Menu.Popover> + <Menu.Group> + <Menu.GroupLabel>Uncontrolled</Menu.GroupLabel> + <Menu.RadioItem name="radio-uncontrolled" value="one"> + <Menu.ItemLabel>Radio item 1</Menu.ItemLabel> + <Menu.ItemHelpText> + Initially unchecked + </Menu.ItemHelpText> + </Menu.RadioItem> + <Menu.RadioItem + name="radio-uncontrolled" + value="two" + defaultChecked + > + <Menu.ItemLabel>Radio item 2</Menu.ItemLabel> + <Menu.ItemHelpText> + Initially checked + </Menu.ItemHelpText> + </Menu.RadioItem> + </Menu.Group> + <Menu.Separator /> + <Menu.Group> + <Menu.GroupLabel>Controlled</Menu.GroupLabel> + <Menu.RadioItem + name="radio-controlled" + value="one" + checked={ radioValue === 'one' } + onChange={ onRadioChange } + > + <Menu.ItemLabel>Radio item 1</Menu.ItemLabel> + <Menu.ItemHelpText> + Initially unchecked + </Menu.ItemHelpText> + </Menu.RadioItem> + <Menu.RadioItem + name="radio-controlled" + value="two" + checked={ radioValue === 'two' } + onChange={ onRadioChange } + > + <Menu.ItemLabel>Radio item 2</Menu.ItemLabel> + <Menu.ItemHelpText> + Initially checked + </Menu.ItemHelpText> + </Menu.RadioItem> + </Menu.Group> + </Menu.Popover> + </Menu> + ); + }, + + args: { + ...Default.args, + }, }; const modalOnTopOfMenuPopover = css` @@ -322,57 +410,72 @@ const modalOnTopOfMenuPopover = css` } `; -// For more examples with `Modal`, check https://ariakit.org/examples/menu-wordpress-modal -export const WithModals: StoryFn< typeof Menu > = ( props ) => { - const [ isOuterModalOpen, setOuterModalOpen ] = useState( false ); - const [ isInnerModalOpen, setInnerModalOpen ] = useState( false ); +export const WithModals: StoryObj< typeof Menu > = { + render: function WithModals( props: Props ) { + const [ isOuterModalOpen, setOuterModalOpen ] = useState( false ); + const [ isInnerModalOpen, setInnerModalOpen ] = useState( false ); - const cx = useCx(); - const modalOverlayClassName = cx( modalOnTopOfMenuPopover ); + const cx = useCx(); + const modalOverlayClassName = cx( modalOnTopOfMenuPopover ); - return ( - <> - <Menu { ...props }> - <Menu.Item - onClick={ () => setOuterModalOpen( true ) } - hideOnClick={ false } - > - <Menu.ItemLabel>Open outer modal</Menu.ItemLabel> - </Menu.Item> - <Menu.Item - onClick={ () => setInnerModalOpen( true ) } - hideOnClick={ false } - > - <Menu.ItemLabel>Open inner modal</Menu.ItemLabel> - </Menu.Item> - { isInnerModalOpen && ( + return ( + <> + <Menu { ...props }> + <Menu.TriggerButton + render={ + <Button __next40pxDefaultSize variant="secondary" /> + } + > + Open menu + </Menu.TriggerButton> + <Menu.Popover> + <Menu.Item + onClick={ () => setOuterModalOpen( true ) } + hideOnClick={ false } + > + <Menu.ItemLabel>Open outer modal</Menu.ItemLabel> + </Menu.Item> + <Menu.Item + onClick={ () => setInnerModalOpen( true ) } + hideOnClick={ false } + > + <Menu.ItemLabel>Open inner modal</Menu.ItemLabel> + </Menu.Item> + { isInnerModalOpen && ( + <Modal + onRequestClose={ () => + setInnerModalOpen( false ) + } + overlayClassName={ modalOverlayClassName } + > + Modal&apos;s contents + <button + onClick={ () => setInnerModalOpen( false ) } + > + Close + </button> + </Modal> + ) } + </Menu.Popover> + </Menu> + { isOuterModalOpen && ( <Modal - onRequestClose={ () => setInnerModalOpen( false ) } + onRequestClose={ () => setOuterModalOpen( false ) } overlayClassName={ modalOverlayClassName } > Modal&apos;s contents - <button onClick={ () => setInnerModalOpen( false ) }> + <button onClick={ () => setOuterModalOpen( false ) }> Close </button> </Modal> ) } - </Menu> - { isOuterModalOpen && ( - <Modal - onRequestClose={ () => setOuterModalOpen( false ) } - overlayClassName={ modalOverlayClassName } - > - Modal&apos;s contents - <button onClick={ () => setOuterModalOpen( false ) }> - Close - </button> - </Modal> - ) } - </> - ); -}; -WithModals.args = { - ...Default.args, + </> + ); + }, + + args: { + ...Default.args, + }, }; const ExampleSlotFill = createSlotFill( 'Example' ); @@ -423,37 +526,50 @@ const Fill = ( { children }: { children: React.ReactNode } ) => { ); }; -export const WithSlotFill: StoryFn< typeof Menu > = ( props ) => { - return ( - <SlotFillProvider> - <Menu { ...props }> - <Menu.Item> - <Menu.ItemLabel>Item</Menu.ItemLabel> - </Menu.Item> - <Slot /> - </Menu> - - <Fill> - <Menu.Item> - <Menu.ItemLabel>Item from fill</Menu.ItemLabel> - </Menu.Item> - <Menu - trigger={ +export const WithSlotFill: StoryObj< typeof Menu > = { + render: ( props: Props ) => { + return ( + <SlotFillProvider> + <Menu { ...props }> + <Menu.TriggerButton + render={ + <Button __next40pxDefaultSize variant="secondary" /> + } + > + Open menu + </Menu.TriggerButton> + <Menu.Popover> <Menu.Item> - <Menu.ItemLabel>Submenu from fill</Menu.ItemLabel> + <Menu.ItemLabel>Item</Menu.ItemLabel> </Menu.Item> - } - > + <Slot /> + </Menu.Popover> + </Menu> + + <Fill> <Menu.Item> - <Menu.ItemLabel>Submenu item from fill</Menu.ItemLabel> + <Menu.ItemLabel>Item from fill</Menu.ItemLabel> </Menu.Item> - </Menu> - </Fill> - </SlotFillProvider> - ); -}; -WithSlotFill.args = { - ...Default.args, + <Menu> + <Menu.SubmenuTriggerItem> + <Menu.ItemLabel>Submenu from fill</Menu.ItemLabel> + </Menu.SubmenuTriggerItem> + <Menu.Popover> + <Menu.Item> + <Menu.ItemLabel> + Submenu item from fill + </Menu.ItemLabel> + </Menu.Item> + </Menu.Popover> + </Menu> + </Fill> + </SlotFillProvider> + ); + }, + + args: { + ...Default.args, + }, }; const toolbarVariantContextValue = { @@ -461,83 +577,119 @@ const toolbarVariantContextValue = { variant: 'toolbar', }, }; -export const ToolbarVariant: StoryFn< typeof Menu > = ( props ) => ( - // TODO: add toolbar - <ContextSystemProvider value={ toolbarVariantContextValue }> - <Menu { ...props }> - <Menu.Item> - <Menu.ItemLabel>Level 1 item</Menu.ItemLabel> - </Menu.Item> - <Menu.Item> - <Menu.ItemLabel>Level 1 item</Menu.ItemLabel> - </Menu.Item> - <Menu.Separator /> - <Menu - trigger={ + +export const ToolbarVariant: StoryObj< typeof Menu > = { + render: ( props: Props ) => ( + // TODO: add toolbar + <ContextSystemProvider value={ toolbarVariantContextValue }> + <Menu { ...props }> + <Menu.TriggerButton + render={ + <Button __next40pxDefaultSize variant="secondary" /> + } + > + Open menu + </Menu.TriggerButton> + <Menu.Popover> <Menu.Item> - <Menu.ItemLabel>Submenu trigger</Menu.ItemLabel> + <Menu.ItemLabel>Level 1 item</Menu.ItemLabel> </Menu.Item> - } - > - <Menu.Item> - <Menu.ItemLabel>Level 2 item</Menu.ItemLabel> - </Menu.Item> + <Menu.Item> + <Menu.ItemLabel>Level 1 item</Menu.ItemLabel> + </Menu.Item> + <Menu.Separator /> + <Menu> + <Menu.SubmenuTriggerItem> + <Menu.ItemLabel>Submenu trigger</Menu.ItemLabel> + </Menu.SubmenuTriggerItem> + <Menu.Popover> + <Menu.Item> + <Menu.ItemLabel>Level 2 item</Menu.ItemLabel> + </Menu.Item> + </Menu.Popover> + </Menu> + </Menu.Popover> </Menu> - </Menu> - </ContextSystemProvider> -); -ToolbarVariant.args = { - ...Default.args, + </ContextSystemProvider> + ), + + args: { + ...Default.args, + }, }; -export const InsideModal: StoryFn< typeof Menu > = ( props ) => { - const [ isModalOpen, setModalOpen ] = useState( false ); - return ( - <> - <Button - onClick={ () => setModalOpen( true ) } - __next40pxDefaultSize - variant="secondary" - > - Open modal - </Button> - { isModalOpen && ( - <Modal onRequestClose={ () => setModalOpen( false ) }> - <Menu { ...props }> - <Menu.Item> - <Menu.ItemLabel>Level 1 item</Menu.ItemLabel> - </Menu.Item> - <Menu.Item> - <Menu.ItemLabel>Level 1 item</Menu.ItemLabel> - </Menu.Item> - <Menu.Separator /> - <Menu - trigger={ +export const InsideModal: StoryObj< typeof Menu > = { + render: function InsideModal( props: Props ) { + const [ isModalOpen, setModalOpen ] = useState( false ); + return ( + <> + <Button + onClick={ () => setModalOpen( true ) } + __next40pxDefaultSize + variant="secondary" + > + Open modal + </Button> + { isModalOpen && ( + <Modal + onRequestClose={ () => setModalOpen( false ) } + title="Menu inside modal" + > + <Menu { ...props }> + <Menu.TriggerButton + render={ + <Button + __next40pxDefaultSize + variant="secondary" + /> + } + > + Open menu + </Menu.TriggerButton> + <Menu.Popover> <Menu.Item> <Menu.ItemLabel> - Submenu trigger + Level 1 item </Menu.ItemLabel> </Menu.Item> - } - > - <Menu.Item> - <Menu.ItemLabel>Level 2 item</Menu.ItemLabel> - </Menu.Item> + <Menu.Item> + <Menu.ItemLabel> + Level 1 item + </Menu.ItemLabel> + </Menu.Item> + <Menu.Separator /> + <Menu> + <Menu.SubmenuTriggerItem> + <Menu.ItemLabel> + Submenu trigger + </Menu.ItemLabel> + </Menu.SubmenuTriggerItem> + <Menu.Popover> + <Menu.Item> + <Menu.ItemLabel> + Level 2 item + </Menu.ItemLabel> + </Menu.Item> + </Menu.Popover> + </Menu> + </Menu.Popover> </Menu> - </Menu> - <Button onClick={ () => setModalOpen( false ) }> - Close modal - </Button> - </Modal> - ) } - </> - ); -}; -InsideModal.args = { - ...Default.args, -}; -InsideModal.parameters = { - docs: { - source: { type: 'code' }, + <Button onClick={ () => setModalOpen( false ) }> + Close modal + </Button> + </Modal> + ) } + </> + ); + }, + + args: { + ...Default.args, + }, + + parameters: { + docs: { + source: { type: 'code' }, + }, }, }; diff --git a/packages/components/src/menu/styles.ts b/packages/components/src/menu/styles.ts index 3312c8cb2de161..1235d6ae7ec1b4 100644 --- a/packages/components/src/menu/styles.ts +++ b/packages/components/src/menu/styles.ts @@ -12,7 +12,7 @@ import { COLORS, font, rtl, CONFIG } from '../utils'; import { space } from '../utils/space'; import Icon from '../icon'; import { Truncate } from '../truncate'; -import type { MenuContext } from './types'; +import type { ContextProps } from './types'; const ANIMATION_PARAMS = { SCALE_AMOUNT_OUTER: 0.82, @@ -42,8 +42,8 @@ const TOOLBAR_VARIANT_BOX_SHADOW = `0 0 0 ${ CONFIG.borderWidth } ${ TOOLBAR_VAR const GRID_TEMPLATE_COLS = 'minmax( 0, max-content ) 1fr'; -export const MenuPopoverOuterWrapper = styled.div< - Pick< MenuContext, 'variant' > +export const PopoverOuterWrapper = styled.div< + Pick< ContextProps, 'variant' > >` position: relative; @@ -95,7 +95,7 @@ export const MenuPopoverOuterWrapper = styled.div< } `; -export const MenuPopoverInnerWrapper = styled.div` +export const PopoverInnerWrapper = styled.div` position: relative; /* Same as popover component */ /* TODO: is there a way to read the sass variable? */ @@ -201,7 +201,7 @@ const baseItem = css` [aria-disabled='true'] ) { background-color: ${ COLORS.theme.accent }; - color: ${ COLORS.white }; + color: ${ COLORS.theme.accentInverted }; } /* Keyboard focus (focus-visible) */ @@ -219,7 +219,7 @@ const baseItem = css` } /* When the item is the trigger of an open submenu */ - ${ MenuPopoverInnerWrapper }:not(:focus) &:not(:focus)[aria-expanded="true"] { + ${ PopoverInnerWrapper }:not(:focus) &:not(:focus)[aria-expanded="true"] { background-color: ${ LIGHT_BACKGROUND_COLOR }; color: ${ COLORS.theme.foreground }; } @@ -229,15 +229,15 @@ const baseItem = css` } `; -export const MenuItem = styled( Ariakit.MenuItem )` +export const Item = styled( Ariakit.MenuItem )` ${ baseItem }; `; -export const MenuCheckboxItem = styled( Ariakit.MenuItemCheckbox )` +export const CheckboxItem = styled( Ariakit.MenuItemCheckbox )` ${ baseItem }; `; -export const MenuRadioItem = styled( Ariakit.MenuItemRadio )` +export const RadioItem = styled( Ariakit.MenuItemRadio )` ${ baseItem }; `; @@ -249,14 +249,14 @@ export const ItemPrefixWrapper = styled.span` * Even when the item is not checked, occupy the same screen space to avoid * the space collapside when no items are checked. */ - ${ MenuCheckboxItem } > &, - ${ MenuRadioItem } > & { + ${ CheckboxItem } > &, + ${ RadioItem } > & { /* Same width as the check icons */ min-width: ${ space( 6 ) }; } - ${ MenuCheckboxItem } > &, - ${ MenuRadioItem } > &, + ${ CheckboxItem } > &, + ${ RadioItem } > &, &:not( :empty ) { margin-inline-end: ${ space( 2 ) }; } @@ -278,7 +278,7 @@ export const ItemPrefixWrapper = styled.span` } `; -export const MenuItemContentWrapper = styled.div` +export const ItemContentWrapper = styled.div` /* * Always occupy the second column, since the first column * is taken by the prefix wrapper (when displayed). @@ -293,7 +293,7 @@ export const MenuItemContentWrapper = styled.div` pointer-events: none; `; -export const MenuItemChildrenWrapper = styled.div` +export const ItemChildrenWrapper = styled.div` flex: 1; display: inline-flex; @@ -317,19 +317,19 @@ export const ItemSuffixWrapper = styled.span` * When the parent menu item is active, except when it's a non-focused/hovered * submenu trigger (in that case, color should not be inherited) */ - [data-active-item]:not( [data-focus-visible] ) *:not(${ MenuPopoverInnerWrapper }) &, + [data-active-item]:not( [data-focus-visible] ) *:not(${ PopoverInnerWrapper }) &, /* When the parent menu item is disabled */ - [aria-disabled='true'] *:not(${ MenuPopoverInnerWrapper }) & { + [aria-disabled='true'] *:not(${ PopoverInnerWrapper }) & { color: inherit; } `; -export const MenuGroup = styled( Ariakit.MenuGroup )` +export const Group = styled( Ariakit.MenuGroup )` /* Ignore this element when calculating the layout. Useful for subgrid */ display: contents; `; -export const MenuGroupLabel = styled( Ariakit.MenuGroupLabel )` +export const GroupLabel = styled( Ariakit.MenuGroupLabel )` /* Occupy the width of all grid columns (ie. full width) */ grid-column: 1 / -1; @@ -338,8 +338,8 @@ export const MenuGroupLabel = styled( Ariakit.MenuGroupLabel )` padding-inline: ${ ITEM_PADDING_INLINE }; `; -export const MenuSeparator = styled( Ariakit.MenuSeparator )< - Pick< MenuContext, 'variant' > +export const Separator = styled( Ariakit.MenuSeparator )< + Pick< ContextProps, 'variant' > >` /* Occupy the width of all grid columns (ie. full width) */ grid-column: 1 / -1; @@ -370,22 +370,22 @@ export const SubmenuChevronIcon = styled( Icon )` ) }; `; -export const MenuItemLabel = styled( Truncate )` +export const ItemLabel = styled( Truncate )` font-size: ${ font( 'default.fontSize' ) }; line-height: 20px; color: inherit; `; -export const MenuItemHelpText = styled( Truncate )` +export const ItemHelpText = styled( Truncate )` font-size: ${ font( 'helpText.fontSize' ) }; line-height: 16px; color: ${ LIGHTER_TEXT_COLOR }; - word-break: break-all; + overflow-wrap: anywhere; [data-active-item]:not( [data-focus-visible] ) - *:not( ${ MenuPopoverInnerWrapper } ) + *:not( ${ PopoverInnerWrapper } ) &, - [aria-disabled='true'] *:not( ${ MenuPopoverInnerWrapper } ) & { + [aria-disabled='true'] *:not( ${ PopoverInnerWrapper } ) & { color: inherit; } `; diff --git a/packages/components/src/menu/submenu-trigger-item.tsx b/packages/components/src/menu/submenu-trigger-item.tsx new file mode 100644 index 00000000000000..9ea24d259af300 --- /dev/null +++ b/packages/components/src/menu/submenu-trigger-item.tsx @@ -0,0 +1,61 @@ +/** + * External dependencies + */ +import * as Ariakit from '@ariakit/react'; + +/** + * WordPress dependencies + */ +import { forwardRef, useContext } from '@wordpress/element'; +import { chevronRightSmall } from '@wordpress/icons'; + +/** + * Internal dependencies + */ +import type { WordPressComponentProps } from '../context'; +import type { ItemProps } from './types'; +import { Context } from './context'; +import { Item } from './item'; +import * as Styled from './styles'; + +export const SubmenuTriggerItem = forwardRef< + HTMLDivElement, + WordPressComponentProps< ItemProps, 'div', false > +>( function SubmenuTriggerItem( { suffix, ...otherProps }, ref ) { + const menuContext = useContext( Context ); + + if ( ! menuContext?.store.parent ) { + throw new Error( + 'Menu.SubmenuTriggerItem can only be rendered inside a nested Menu component' + ); + } + + return ( + <Ariakit.MenuButton + ref={ ref } + accessibleWhenDisabled + store={ menuContext.store } + render={ + <Item + { ...otherProps } + // The menu item needs to register and be part of the parent menu. + // Without specifying the store explicitly, the `Item` component + // would otherwise read the store via context and pick up the one from + // the sub-menu `Menu` component. + store={ menuContext.store.parent } + suffix={ + <> + { suffix } + <Styled.SubmenuChevronIcon + aria-hidden="true" + icon={ chevronRightSmall } + size={ 24 } + preserveAspectRatio="xMidYMid slice" + /> + </> + } + /> + } + /> + ); +} ); diff --git a/packages/components/src/menu/test/index.tsx b/packages/components/src/menu/test/index.tsx index 60276cdb2379a0..42e1516d94bbba 100644 --- a/packages/components/src/menu/test/index.tsx +++ b/packages/components/src/menu/test/index.tsx @@ -18,17 +18,28 @@ const delay = ( delayInMs: number ) => { return new Promise( ( resolve ) => setTimeout( resolve, delayInMs ) ); }; +// Open dropdown => open menu +// Submenu trigger item => open submenu + describe( 'Menu', () => { // See https://www.w3.org/WAI/ARIA/apg/patterns/menu-button/ it( 'should follow the WAI-ARIA spec', async () => { render( - <Menu trigger={ <button>Open dropdown</button> }> - <Menu.Item>Menu item</Menu.Item> - <Menu.Separator /> - <Menu trigger={ <Menu.Item>Submenu trigger item</Menu.Item> }> - <Menu.Item>Submenu item 1</Menu.Item> - <Menu.Item>Submenu item 2</Menu.Item> - </Menu> + <Menu> + <Menu.TriggerButton>Open dropdown</Menu.TriggerButton> + <Menu.Popover> + <Menu.Item>Menu item</Menu.Item> + <Menu.Separator /> + <Menu> + <Menu.SubmenuTriggerItem> + Submenu trigger item + </Menu.SubmenuTriggerItem> + <Menu.Popover> + <Menu.Item>Submenu item 1</Menu.Item> + <Menu.Item>Submenu item 2</Menu.Item> + </Menu.Popover> + </Menu> + </Menu.Popover> </Menu> ); @@ -84,8 +95,11 @@ describe( 'Menu', () => { describe( 'pointer and keyboard interactions', () => { it( 'should open and focus the menu when clicking the trigger', async () => { render( - <Menu trigger={ <button>Open dropdown</button> }> - <Menu.Item>Menu item</Menu.Item> + <Menu> + <Menu.TriggerButton>Open dropdown</Menu.TriggerButton> + <Menu.Popover> + <Menu.Item>Menu item</Menu.Item> + </Menu.Popover> </Menu> ); @@ -105,10 +119,13 @@ describe( 'Menu', () => { it( 'should open and focus the first item when pressing the arrow down key on the trigger', async () => { render( - <Menu trigger={ <button>Open dropdown</button> }> - <Menu.Item disabled>First item</Menu.Item> - <Menu.Item>Second item</Menu.Item> - <Menu.Item>Third item</Menu.Item> + <Menu> + <Menu.TriggerButton>Open dropdown</Menu.TriggerButton> + <Menu.Popover> + <Menu.Item disabled>First item</Menu.Item> + <Menu.Item>Second item</Menu.Item> + <Menu.Item>Third item</Menu.Item> + </Menu.Popover> </Menu> ); @@ -135,10 +152,13 @@ describe( 'Menu', () => { it( 'should open and focus the first item when pressing the space key on the trigger', async () => { render( - <Menu trigger={ <button>Open dropdown</button> }> - <Menu.Item disabled>First item</Menu.Item> - <Menu.Item>Second item</Menu.Item> - <Menu.Item>Third item</Menu.Item> + <Menu> + <Menu.TriggerButton>Open dropdown</Menu.TriggerButton> + <Menu.Popover> + <Menu.Item disabled>First item</Menu.Item> + <Menu.Item>Second item</Menu.Item> + <Menu.Item>Third item</Menu.Item> + </Menu.Popover> </Menu> ); @@ -165,8 +185,11 @@ describe( 'Menu', () => { it( 'should close when pressing the escape key', async () => { render( - <Menu trigger={ <button>Open dropdown</button> }> - <Menu.Item>Menu item</Menu.Item> + <Menu> + <Menu.TriggerButton>Open dropdown</Menu.TriggerButton> + <Menu.Popover> + <Menu.Item>Menu item</Menu.Item> + </Menu.Popover> </Menu> ); @@ -194,8 +217,11 @@ describe( 'Menu', () => { it( 'should close when clicking outside of the content', async () => { render( - <Menu defaultOpen trigger={ <button>Open dropdown</button> }> - <Menu.Item>Menu item</Menu.Item> + <Menu defaultOpen> + <Menu.TriggerButton>Open dropdown</Menu.TriggerButton> + <Menu.Popover> + <Menu.Item>Menu item</Menu.Item> + </Menu.Popover> </Menu> ); @@ -209,8 +235,11 @@ describe( 'Menu', () => { it( 'should close when clicking on a menu item', async () => { render( - <Menu defaultOpen trigger={ <button>Open dropdown</button> }> - <Menu.Item>Menu item</Menu.Item> + <Menu defaultOpen> + <Menu.TriggerButton>Open dropdown</Menu.TriggerButton> + <Menu.Popover> + <Menu.Item>Menu item</Menu.Item> + </Menu.Popover> </Menu> ); @@ -224,8 +253,11 @@ describe( 'Menu', () => { it( 'should not close when clicking on a menu item when the `hideOnClick` prop is set to `false`', async () => { render( - <Menu defaultOpen trigger={ <button>Open dropdown</button> }> - <Menu.Item hideOnClick={ false }>Menu item</Menu.Item> + <Menu defaultOpen> + <Menu.TriggerButton>Open dropdown</Menu.TriggerButton> + <Menu.Popover> + <Menu.Item hideOnClick={ false }>Menu item</Menu.Item> + </Menu.Popover> </Menu> ); @@ -239,8 +271,11 @@ describe( 'Menu', () => { it( 'should not close when clicking on a disabled menu item', async () => { render( - <Menu defaultOpen trigger={ <button>Open dropdown</button> }> - <Menu.Item disabled>Menu item</Menu.Item> + <Menu defaultOpen> + <Menu.TriggerButton>Open dropdown</Menu.TriggerButton> + <Menu.Popover> + <Menu.Item disabled>Menu item</Menu.Item> + </Menu.Popover> </Menu> ); @@ -254,16 +289,22 @@ describe( 'Menu', () => { it( 'should reveal submenu content when hovering over the submenu trigger', async () => { render( - <Menu defaultOpen trigger={ <button>Open dropdown</button> }> - <Menu.Item>Menu item 1</Menu.Item> - <Menu.Item>Menu item 2</Menu.Item> - <Menu - trigger={ <Menu.Item>Submenu trigger item</Menu.Item> } - > - <Menu.Item>Submenu item 1</Menu.Item> - <Menu.Item>Submenu item 2</Menu.Item> - </Menu> - <Menu.Item>Menu item 3</Menu.Item> + <Menu defaultOpen> + <Menu.TriggerButton>Open dropdown</Menu.TriggerButton> + <Menu.Popover> + <Menu.Item>Menu item 1</Menu.Item> + <Menu.Item>Menu item 2</Menu.Item> + <Menu> + <Menu.SubmenuTriggerItem> + Submenu trigger item + </Menu.SubmenuTriggerItem> + <Menu.Popover> + <Menu.Item>Submenu item 1</Menu.Item> + <Menu.Item>Submenu item 2</Menu.Item> + </Menu.Popover> + </Menu> + <Menu.Item>Menu item 3</Menu.Item> + </Menu.Popover> </Menu> ); @@ -288,16 +329,22 @@ describe( 'Menu', () => { it( 'should navigate menu items and subitems using the arrow, spacebar and enter keys', async () => { render( - <Menu defaultOpen trigger={ <button>Open dropdown</button> }> - <Menu.Item>Menu item 1</Menu.Item> - <Menu.Item>Menu item 2</Menu.Item> - <Menu - trigger={ <Menu.Item>Submenu trigger item</Menu.Item> } - > - <Menu.Item>Submenu item 1</Menu.Item> - <Menu.Item>Submenu item 2</Menu.Item> - </Menu> - <Menu.Item>Menu item 3</Menu.Item> + <Menu defaultOpen> + <Menu.TriggerButton>Open dropdown</Menu.TriggerButton> + <Menu.Popover> + <Menu.Item>Menu item 1</Menu.Item> + <Menu.Item>Menu item 2</Menu.Item> + <Menu> + <Menu.SubmenuTriggerItem> + Submenu trigger item + </Menu.SubmenuTriggerItem> + <Menu.Popover> + <Menu.Item>Submenu item 1</Menu.Item> + <Menu.Item>Submenu item 2</Menu.Item> + </Menu.Popover> + </Menu> + <Menu.Item>Menu item 3</Menu.Item> + </Menu.Popover> </Menu> ); @@ -407,25 +454,28 @@ describe( 'Menu', () => { setRadioValue( e.target.value ); }; return ( - <Menu trigger={ <button>Open dropdown</button> }> - <Menu.Group> - <Menu.RadioItem - name="radio-test" - value="radio-one" - checked={ radioValue === 'radio-one' } - onChange={ onRadioChange } - > - Radio item one - </Menu.RadioItem> - <Menu.RadioItem - name="radio-test" - value="radio-two" - checked={ radioValue === 'radio-two' } - onChange={ onRadioChange } - > - Radio item two - </Menu.RadioItem> - </Menu.Group> + <Menu> + <Menu.TriggerButton>Open dropdown</Menu.TriggerButton> + <Menu.Popover> + <Menu.Group> + <Menu.RadioItem + name="radio-test" + value="radio-one" + checked={ radioValue === 'radio-one' } + onChange={ onRadioChange } + > + Radio item one + </Menu.RadioItem> + <Menu.RadioItem + name="radio-test" + value="radio-two" + checked={ radioValue === 'radio-two' } + onChange={ onRadioChange } + > + Radio item two + </Menu.RadioItem> + </Menu.Group> + </Menu.Popover> </Menu> ); }; @@ -484,28 +534,31 @@ describe( 'Menu', () => { it( 'should check radio items and keep the menu open when clicking (uncontrolled)', async () => { const onRadioValueChangeSpy = jest.fn(); render( - <Menu trigger={ <button>Open dropdown</button> }> - <Menu.Group> - <Menu.RadioItem - name="radio-test" - value="radio-one" - onChange={ ( e ) => - onRadioValueChangeSpy( e.target.value ) - } - > - Radio item one - </Menu.RadioItem> - <Menu.RadioItem - name="radio-test" - value="radio-two" - defaultChecked - onChange={ ( e ) => - onRadioValueChangeSpy( e.target.value ) - } - > - Radio item two - </Menu.RadioItem> - </Menu.Group> + <Menu> + <Menu.TriggerButton>Open dropdown</Menu.TriggerButton> + <Menu.Popover> + <Menu.Group> + <Menu.RadioItem + name="radio-test" + value="radio-one" + onChange={ ( e ) => + onRadioValueChangeSpy( e.target.value ) + } + > + Radio item one + </Menu.RadioItem> + <Menu.RadioItem + name="radio-test" + value="radio-two" + defaultChecked + onChange={ ( e ) => + onRadioValueChangeSpy( e.target.value ) + } + > + Radio item two + </Menu.RadioItem> + </Menu.Group> + </Menu.Popover> </Menu> ); @@ -568,38 +621,41 @@ describe( 'Menu', () => { useState< boolean >(); return ( - <Menu trigger={ <button>Open dropdown</button> }> - <Menu.CheckboxItem - name="item-one" - value="item-one-value" - checked={ itemOneChecked } - onChange={ ( e ) => { - onCheckboxValueChangeSpy( - e.target.name, - e.target.value, - e.target.checked - ); - setItemOneChecked( e.target.checked ); - } } - > - Checkbox item one - </Menu.CheckboxItem> - - <Menu.CheckboxItem - name="item-two" - value="item-two-value" - checked={ itemTwoChecked } - onChange={ ( e ) => { - onCheckboxValueChangeSpy( - e.target.name, - e.target.value, - e.target.checked - ); - setItemTwoChecked( e.target.checked ); - } } - > - Checkbox item two - </Menu.CheckboxItem> + <Menu> + <Menu.TriggerButton>Open dropdown</Menu.TriggerButton> + <Menu.Popover> + <Menu.CheckboxItem + name="item-one" + value="item-one-value" + checked={ itemOneChecked } + onChange={ ( e ) => { + onCheckboxValueChangeSpy( + e.target.name, + e.target.value, + e.target.checked + ); + setItemOneChecked( e.target.checked ); + } } + > + Checkbox item one + </Menu.CheckboxItem> + + <Menu.CheckboxItem + name="item-two" + value="item-two-value" + checked={ itemTwoChecked } + onChange={ ( e ) => { + onCheckboxValueChangeSpy( + e.target.name, + e.target.value, + e.target.checked + ); + setItemTwoChecked( e.target.checked ); + } } + > + Checkbox item two + </Menu.CheckboxItem> + </Menu.Popover> </Menu> ); }; @@ -691,35 +747,38 @@ describe( 'Menu', () => { const onCheckboxValueChangeSpy = jest.fn(); render( - <Menu trigger={ <button>Open dropdown</button> }> - <Menu.CheckboxItem - name="item-one" - value="item-one-value" - onChange={ ( e ) => { - onCheckboxValueChangeSpy( - e.target.name, - e.target.value, - e.target.checked - ); - } } - > - Checkbox item one - </Menu.CheckboxItem> - - <Menu.CheckboxItem - name="item-two" - value="item-two-value" - defaultChecked - onChange={ ( e ) => { - onCheckboxValueChangeSpy( - e.target.name, - e.target.value, - e.target.checked - ); - } } - > - Checkbox item two - </Menu.CheckboxItem> + <Menu> + <Menu.TriggerButton>Open dropdown</Menu.TriggerButton> + <Menu.Popover> + <Menu.CheckboxItem + name="item-one" + value="item-one-value" + onChange={ ( e ) => { + onCheckboxValueChangeSpy( + e.target.name, + e.target.value, + e.target.checked + ); + } } + > + Checkbox item one + </Menu.CheckboxItem> + + <Menu.CheckboxItem + name="item-two" + value="item-two-value" + defaultChecked + onChange={ ( e ) => { + onCheckboxValueChangeSpy( + e.target.name, + e.target.value, + e.target.checked + ); + } } + > + Checkbox item two + </Menu.CheckboxItem> + </Menu.Popover> </Menu> ); @@ -809,8 +868,11 @@ describe( 'Menu', () => { it( 'should be modal by default', async () => { render( <> - <Menu trigger={ <button>Open dropdown</button> }> - <Menu.Item>Menu item</Menu.Item> + <Menu> + <Menu.TriggerButton>Open dropdown</Menu.TriggerButton> + <Menu.Popover> + <Menu.Item>Menu item</Menu.Item> + </Menu.Popover> </Menu> <button>Button outside of dropdown</button> </> @@ -836,11 +898,11 @@ describe( 'Menu', () => { it( 'should not be modal when the `modal` prop is set to `false`', async () => { render( <> - <Menu - trigger={ <button>Open dropdown</button> } - modal={ false } - > - <Menu.Item>Menu item</Menu.Item> + <Menu> + <Menu.TriggerButton>Open dropdown</Menu.TriggerButton> + <Menu.Popover modal={ false }> + <Menu.Item>Menu item</Menu.Item> + </Menu.Popover> </Menu> <button>Button outside of dropdown</button> </> @@ -873,8 +935,13 @@ describe( 'Menu', () => { describe( 'items prefix and suffix', () => { it( 'should display a prefix on regular items', async () => { render( - <Menu trigger={ <button>Open dropdown</button> }> - <Menu.Item prefix={ <>Item prefix</> }>Menu item</Menu.Item> + <Menu> + <Menu.TriggerButton>Open dropdown</Menu.TriggerButton> + <Menu.Popover> + <Menu.Item prefix={ <>Item prefix</> }> + Menu item + </Menu.Item> + </Menu.Popover> </Menu> ); @@ -895,8 +962,13 @@ describe( 'Menu', () => { it( 'should display a suffix on regular items', async () => { render( - <Menu trigger={ <button>Open dropdown</button> }> - <Menu.Item suffix={ <>Item suffix</> }>Menu item</Menu.Item> + <Menu> + <Menu.TriggerButton>Open dropdown</Menu.TriggerButton> + <Menu.Popover> + <Menu.Item suffix={ <>Item suffix</> }> + Menu item + </Menu.Item> + </Menu.Popover> </Menu> ); @@ -917,14 +989,17 @@ describe( 'Menu', () => { it( 'should display a suffix on radio items', async () => { render( - <Menu trigger={ <button>Open dropdown</button> }> - <Menu.RadioItem - name="radio-test" - value="radio-one" - suffix="Radio suffix" - > - Radio item one - </Menu.RadioItem> + <Menu> + <Menu.TriggerButton>Open dropdown</Menu.TriggerButton> + <Menu.Popover> + <Menu.RadioItem + name="radio-test" + value="radio-one" + suffix="Radio suffix" + > + Radio item one + </Menu.RadioItem> + </Menu.Popover> </Menu> ); @@ -945,14 +1020,17 @@ describe( 'Menu', () => { it( 'should display a suffix on checkbox items', async () => { render( - <Menu trigger={ <button>Open dropdown</button> }> - <Menu.CheckboxItem - name="checkbox-test" - value="checkbox-one" - suffix="Checkbox suffix" - > - Checkbox item one - </Menu.CheckboxItem> + <Menu> + <Menu.TriggerButton>Open dropdown</Menu.TriggerButton> + <Menu.Popover> + <Menu.CheckboxItem + name="checkbox-test" + value="checkbox-one" + suffix="Checkbox suffix" + > + Checkbox item one + </Menu.CheckboxItem> + </Menu.Popover> </Menu> ); @@ -975,9 +1053,12 @@ describe( 'Menu', () => { describe( 'typeahead', () => { it( 'should highlight matching item', async () => { render( - <Menu trigger={ <button>Open dropdown</button> }> - <Menu.Item>One</Menu.Item> - <Menu.Item>Two</Menu.Item> + <Menu> + <Menu.TriggerButton>Open dropdown</Menu.TriggerButton> + <Menu.Popover> + <Menu.Item>One</Menu.Item> + <Menu.Item>Two</Menu.Item> + </Menu.Popover> </Menu> ); @@ -1008,9 +1089,12 @@ describe( 'Menu', () => { it( 'should keep previous focus when no matches are found', async () => { render( - <Menu trigger={ <button>Open dropdown</button> }> - <Menu.Item>One</Menu.Item> - <Menu.Item>Two</Menu.Item> + <Menu> + <Menu.TriggerButton>Open dropdown</Menu.TriggerButton> + <Menu.Popover> + <Menu.Item>One</Menu.Item> + <Menu.Item>Two</Menu.Item> + </Menu.Popover> </Menu> ); diff --git a/packages/components/src/menu/trigger-button.tsx b/packages/components/src/menu/trigger-button.tsx new file mode 100644 index 00000000000000..b5309ae44aadb7 --- /dev/null +++ b/packages/components/src/menu/trigger-button.tsx @@ -0,0 +1,46 @@ +/** + * External dependencies + */ +import * as Ariakit from '@ariakit/react'; + +/** + * WordPress dependencies + */ +import { forwardRef, useContext } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import type { WordPressComponentProps } from '../context'; +import type { TriggerButtonProps } from './types'; +import { Context } from './context'; + +export const TriggerButton = forwardRef< + HTMLDivElement, + WordPressComponentProps< TriggerButtonProps, 'button', false > +>( function TriggerButton( { children, disabled = false, ...props }, ref ) { + const menuContext = useContext( Context ); + + if ( ! menuContext?.store ) { + throw new Error( + 'Menu.TriggerButton can only be rendered inside a Menu component' + ); + } + + if ( menuContext.store.parent ) { + throw new Error( + 'Menu.TriggerButton should not be rendered inside a nested Menu component. Use Menu.SubmenuTriggerItem instead.' + ); + } + + return ( + <Ariakit.MenuButton + ref={ ref } + { ...props } + disabled={ disabled } + store={ menuContext.store } + > + { children } + </Ariakit.MenuButton> + ); +} ); diff --git a/packages/components/src/menu/types.ts b/packages/components/src/menu/types.ts index 7b58cef241743e..4532d97fb13dd9 100644 --- a/packages/components/src/menu/types.ts +++ b/packages/components/src/menu/types.ts @@ -2,9 +2,8 @@ * External dependencies */ import type * as Ariakit from '@ariakit/react'; -import type { Placement } from '@floating-ui/react-dom'; -export interface MenuContext { +export interface ContextProps { /** * The ariakit store shared across all Menu subcomponents. */ @@ -15,172 +14,320 @@ export interface MenuContext { variant?: 'toolbar'; } -export interface MenuProps { +export interface Props { /** - * The button triggering the menu popover. + * The elements, which should include one instance of the `Menu.TriggerButton` + * component and one instance of the `Menu.Popover` component. */ - trigger: React.ReactElement; + children?: Ariakit.MenuProviderProps[ 'children' ]; /** - * The contents of the menu (ie. one or more menu items). + * Whether the menu popover and its contents should be visible by default. + * + * Note: this prop will be overridden by the `open` prop if it is + * provided (meaning the component will be used in "controlled" mode). + * + * @default false */ - children?: React.ReactNode; + defaultOpen?: Ariakit.MenuProviderProps[ 'defaultOpen' ]; /** - * The open state of the menu popover when it is initially rendered. Use when - * not wanting to control its open state. + * Whether the menu popover and its contents should be visible. + * Should be used in conjunction with `onOpenChange` in order to control + * the open state of the menu popover. * - * @default false + * Note: this prop will set the component in "controlled" mode, and it will + * override the `defaultOpen` prop. */ - defaultOpen?: boolean; + open?: Ariakit.MenuProviderProps[ 'open' ]; /** - * The controlled open state of the menu popover. Must be used in conjunction - * with `onOpenChange`. + * A callback that gets called when the `open` state changes. */ - open?: boolean; + onOpenChange?: Ariakit.MenuProviderProps[ 'setOpen' ]; /** - * Event handler called when the open state of the menu popover changes. + * The placement of the menu popover. + * + * @default 'bottom-start' for root-level menus, 'right-start' for submenus */ - onOpenChange?: ( open: boolean ) => void; + placement?: Ariakit.MenuProviderProps[ 'placement' ]; +} + +export interface PopoverProps { + /** + * The contents of the menu popover, which should include instances of the + * `Menu.Item`, `Menu.CheckboxItem`, `Menu.RadioItem`, `Menu.Group`, and + * `Menu.Separator` components. + */ + children?: Ariakit.MenuProps[ 'children' ]; /** * The modality of the menu popover. When set to true, interaction with * outside elements will be disabled and only menu content will be visible to * screen readers. * - * @default true - */ - modal?: boolean; - /** - * The placement of the menu popover. + * Determines whether the menu popover is modal. Modal dialogs have distinct + * states and behaviors: + * - The `portal` and `preventBodyScroll` props are set to `true`. They can + * still be manually set to `false`. + * - When the dialog is open, element tree outside it will be inert. * - * @default 'bottom-start' for root-level menus, 'right-start' for nested menus + * @default true */ - placement?: Placement; + modal?: Ariakit.MenuProps[ 'modal' ]; /** * The distance between the popover and the anchor element. * * @default 8 for root-level menus, 16 for nested menus */ - gutter?: number; + gutter?: Ariakit.MenuProps[ 'gutter' ]; /** * The skidding of the popover along the anchor element. Can be set to * negative values to make the popover shift to the opposite side. * * @default 0 for root-level menus, -8 for nested menus */ - shift?: number; + shift?: Ariakit.MenuProps[ 'shift' ]; /** - * Determines whether the menu popover will be hidden when the user presses - * the Escape key. + * Determines if the menu popover will hide when the user presses the + * Escape key. + * + * This prop can be either a boolean or a function that accepts an event as an + * argument and returns a boolean. The event object represents the keydown + * event that initiated the hide action, which could be either a native + * keyboard event or a React synthetic event. * * @default `( event ) => { event.preventDefault(); return true; }` */ - hideOnEscape?: - | boolean - | ( ( - event: KeyboardEvent | React.KeyboardEvent< Element > - ) => boolean ); + hideOnEscape?: Ariakit.MenuProps[ 'hideOnEscape' ]; +} + +export interface TriggerButtonProps { + /** + * The contents of the menu trigger button. + */ + children?: Ariakit.MenuButtonProps[ 'children' ]; + /** + * Allows the component to be rendered as a different HTML element or React + * component. The value can be a React element or a function that takes in the + * original component props and gives back a React element with the props + * merged. + */ + render?: Ariakit.MenuButtonProps[ 'render' ]; + /** + * Determines if the element is disabled. This sets the `aria-disabled` + * attribute accordingly, enabling support for all elements, including those + * that don't support the native `disabled` attribute. + * + * This feature can be combined with the `accessibleWhenDisabled` prop to + * make disabled elements still accessible via keyboard. + * + * @default false + */ + disabled?: Ariakit.MenuButtonProps[ 'disabled' ]; + /** + * Indicates whether the element should be focusable even when it is + * `disabled`. + * + * This is important when discoverability is a concern. For example: + * + * > A toolbar in an editor contains a set of special smart paste functions + * that are disabled when the clipboard is empty or when the function is not + * applicable to the current content of the clipboard. It could be helpful to + * keep the disabled buttons focusable if the ability to discover their + * functionality is primarily via their presence on the toolbar. + * + * Learn more on [Focusability of disabled + * controls](https://www.w3.org/WAI/ARIA/apg/practices/keyboard-interface/#focusabilityofdisabledcontrols). + */ + accessibleWhenDisabled?: Ariakit.MenuButtonProps[ 'accessibleWhenDisabled' ]; } -export interface MenuGroupProps { +export interface GroupProps { /** - * The contents of the menu group (ie. an optional menu group label and one - * or more menu items). + * The contents of the menu group, which should include one instance of the + * `Menu.GroupLabel` component and one or more instances of `Menu.Item`, + * `Menu.CheckboxItem`, and `Menu.RadioItem`. */ - children: React.ReactNode; + children: Ariakit.MenuGroupProps[ 'children' ]; } -export interface MenuGroupLabelProps { +export interface GroupLabelProps { /** - * The contents of the menu group label. + * The contents of the menu group label, which should provide an accessible + * label for the menu group. */ - children: React.ReactNode; + children: Ariakit.MenuGroupLabelProps[ 'children' ]; } -export interface MenuItemProps { +export interface ItemProps { /** - * The contents of the menu item. + * The contents of the menu item, which could include one instance of the + * `Menu.ItemLabel` component and/or one instance of the `Menu.ItemHelpText` + * component. */ - children: React.ReactNode; + children: Ariakit.MenuItemProps[ 'children' ]; /** - * The contents of the menu item's prefix. + * The contents of the menu item's prefix, such as an icon. */ prefix?: React.ReactNode; /** - * The contents of the menu item's suffix. + * The contents of the menu item's suffix, such as a keyboard shortcut. */ suffix?: React.ReactNode; /** - * Whether to hide the menu popover when the menu item is clicked. + * Determines if the menu should hide when this item is clicked. + * + * **Note**: This behavior isn't triggered if this menu item is rendered as a + * link and modifier keys are used to either open the link in a new tab or + * download it. * * @default true */ - hideOnClick?: boolean; + hideOnClick?: Ariakit.MenuItemProps[ 'hideOnClick' ]; /** - * Determines if the element is disabled. + * Determines if the element is disabled. This sets the `aria-disabled` + * attribute accordingly, enabling support for all elements, including those + * that don't support the native `disabled` attribute. + * + * @default false + */ + disabled?: Ariakit.MenuItemProps[ 'disabled' ]; + /** + * Allows the component to be rendered as a different HTML element or React + * component. The value can be a React element or a function that takes in the + * original component props and gives back a React element with the props + * merged. + */ + render?: Ariakit.MenuItemProps[ 'render' ]; + /** + * The ariakit menu store. This prop is only meant for internal use. + * @ignore */ - disabled?: boolean; + store?: Ariakit.MenuItemProps[ 'store' ]; } -export interface MenuCheckboxItemProps - extends Omit< MenuItemProps, 'prefix' | 'hideOnClick' > { +export interface CheckboxItemProps { /** - * Whether to hide the menu popover when the menu item is clicked. + * The contents of the menu item, which could include one instance of the + * `Menu.ItemLabel` component and/or one instance of the `Menu.ItemHelpText` + * component. + */ + children: Ariakit.MenuItemCheckboxProps[ 'children' ]; + /** + * The contents of the menu item's suffix, such as a keyboard shortcut. + */ + suffix?: React.ReactNode; + /** + * Determines if the menu should hide when this item is clicked. + * + * **Note**: This behavior isn't triggered if this menu item is rendered as a + * link and modifier keys are used to either open the link in a new tab or + * download it. + * + * @default false + */ + hideOnClick?: Ariakit.MenuItemCheckboxProps[ 'hideOnClick' ]; + /** + * Determines if the element is disabled. This sets the `aria-disabled` + * attribute accordingly, enabling support for all elements, including those + * that don't support the native `disabled` attribute. * * @default false */ - hideOnClick?: boolean; + disabled?: Ariakit.MenuItemCheckboxProps[ 'disabled' ]; + /** + * Allows the component to be rendered as a different HTML element or React + * component. The value can be a React element or a function that takes in the + * original component props and gives back a React element with the props + * merged. + */ + render?: Ariakit.MenuItemCheckboxProps[ 'render' ]; /** * The checkbox menu item's name. */ - name: string; + name: Ariakit.MenuItemCheckboxProps[ 'name' ]; /** * The checkbox item's value, useful when using multiple checkbox menu items * associated to the same `name`. */ - value?: string; + value?: Ariakit.MenuItemCheckboxProps[ 'value' ]; /** * The controlled checked state of the checkbox menu item. + * + * Note: this prop will override the `defaultChecked` prop. */ - checked?: boolean; + checked?: Ariakit.MenuItemCheckboxProps[ 'checked' ]; /** * The checked state of the checkbox menu item when it is initially rendered. * Use when not wanting to control its checked state. + * + * Note: this prop will be overriden by the `checked` prop, if it is defined. */ - defaultChecked?: boolean; + defaultChecked?: Ariakit.MenuItemCheckboxProps[ 'defaultChecked' ]; /** - * Event handler called when the checked state of the checkbox menu item changes. + * A function that is called when the checkbox's checked state changes. */ - onChange?: ( event: React.ChangeEvent< HTMLInputElement > ) => void; + onChange?: Ariakit.MenuItemCheckboxProps[ 'onChange' ]; } -export interface MenuRadioItemProps - extends Omit< MenuItemProps, 'prefix' | 'hideOnClick' > { +export interface RadioItemProps { + /** + * The contents of the menu item, which could include one instance of the + * `Menu.ItemLabel` component and/or one instance of the `Menu.ItemHelpText` + * component. + */ + children: Ariakit.MenuItemRadioProps[ 'children' ]; + /** + * The contents of the menu item's suffix, such as a keyboard shortcut. + */ + suffix?: React.ReactNode; + /** + * Determines if the menu should hide when this item is clicked. + * + * **Note**: This behavior isn't triggered if this menu item is rendered as a + * link and modifier keys are used to either open the link in a new tab or + * download it. + * + * @default false + */ + hideOnClick?: Ariakit.MenuItemRadioProps[ 'hideOnClick' ]; /** - * Whether to hide the menu popover when the menu item is clicked. + * Determines if the element is disabled. This sets the `aria-disabled` + * attribute accordingly, enabling support for all elements, including those + * that don't support the native `disabled` attribute. * * @default false */ - hideOnClick?: boolean; + disabled?: Ariakit.MenuItemRadioProps[ 'disabled' ]; + /** + * Allows the component to be rendered as a different HTML element or React + * component. The value can be a React element or a function that takes in the + * original component props and gives back a React element with the props + * merged. + */ + render?: Ariakit.MenuItemRadioProps[ 'render' ]; /** * The radio item's name. */ - name: string; + name: Ariakit.MenuItemRadioProps[ 'name' ]; /** * The radio item's value. */ - value: string | number; + value: Ariakit.MenuItemRadioProps[ 'value' ]; /** * The controlled checked state of the radio menu item. + * + * Note: this prop will override the `defaultChecked` prop. */ - checked?: boolean; + checked?: Ariakit.MenuItemRadioProps[ 'checked' ]; /** * The checked state of the radio menu item when it is initially rendered. * Use when not wanting to control its checked state. + * + * Note: this prop will be overriden by the `checked` prop, if it is defined. */ - defaultChecked?: boolean; + defaultChecked?: Ariakit.MenuItemRadioProps[ 'defaultChecked' ]; /** - * Event handler called when the checked radio menu item changes. + * A function that is called when the checkbox's checked state changes. */ - onChange?: ( event: React.ChangeEvent< HTMLInputElement > ) => void; + onChange?: Ariakit.MenuItemRadioProps[ 'onChange' ]; } -export interface MenuSeparatorProps {} +export interface SeparatorProps {} diff --git a/packages/components/src/menu/use-temporary-focus-visible-fix.ts b/packages/components/src/menu/use-temporary-focus-visible-fix.ts deleted file mode 100644 index 0df13133739609..00000000000000 --- a/packages/components/src/menu/use-temporary-focus-visible-fix.ts +++ /dev/null @@ -1,22 +0,0 @@ -/** - * WordPress dependencies - */ -import { useState, flushSync } from '@wordpress/element'; - -export function useTemporaryFocusVisibleFix( { - onBlur: onBlurProp, -}: { - onBlur?: React.FocusEventHandler< HTMLDivElement >; -} ) { - const [ focusVisible, setFocusVisible ] = useState( false ); - return { - 'data-focus-visible': focusVisible || undefined, - onFocusVisible: () => { - flushSync( () => setFocusVisible( true ) ); - }, - onBlur: ( ( event ) => { - onBlurProp?.( event ); - setFocusVisible( false ); - } ) as React.FocusEventHandler< HTMLDivElement >, - }; -} diff --git a/packages/components/src/mobile/bottom-sheet/nav-bar/action-button.native.js b/packages/components/src/mobile/bottom-sheet/nav-bar/action-button.native.js index 6a13bca18e9efb..e8a4a1de07b9fb 100644 --- a/packages/components/src/mobile/bottom-sheet/nav-bar/action-button.native.js +++ b/packages/components/src/mobile/bottom-sheet/nav-bar/action-button.native.js @@ -8,7 +8,7 @@ import { View, TouchableWithoutFeedback } from 'react-native'; */ import styles from './styles.scss'; -// Action button component is used by both Back and Apply Button componenets. +// Action button component is used by both Back and Apply Button components. function ActionButton( { onPress, accessibilityLabel, diff --git a/packages/components/src/mobile/utils/get-px-from-css-unit.native.js b/packages/components/src/mobile/utils/get-px-from-css-unit.native.js index 8689de98696095..13812a5e7a6f6e 100644 --- a/packages/components/src/mobile/utils/get-px-from-css-unit.native.js +++ b/packages/components/src/mobile/utils/get-px-from-css-unit.native.js @@ -71,7 +71,7 @@ function getFunctionUnitValue( functionUnitValue, options ) { * Take a css function such as min, max, calc, clamp and returns parsedUnit * * How this works for the nested function is that it first replaces the inner function call. - * Then it tackles the outer onces. + * Then it tackles the outer ones. * So for example: min( max(25px, 35px), 40px ) * in the first pass we would replace max(25px, 35px) with 35px. * then we would try to evaluate min( 35px, 40px ) @@ -101,7 +101,7 @@ function parseUnitFunction( cssUnit ) { /** * Return true if we think this is a math expression. * - * @param {string} cssUnit the cssUnit value being evaluted. + * @param {string} cssUnit the cssUnit value being evaluated. * @return {boolean} Whether the cssUnit is a math expression. */ function isMathExpression( cssUnit ) { @@ -115,7 +115,7 @@ function isMathExpression( cssUnit ) { /** * Evaluates the math expression and return a px value. * - * @param {string} cssUnit the cssUnit value being evaluted. + * @param {string} cssUnit the cssUnit value being evaluated. * @return {string} return a converfted value to px. */ function evalMathExpression( cssUnit ) { diff --git a/packages/components/src/modal/index.tsx b/packages/components/src/modal/index.tsx index 7d55c9a4d5d1e6..2dec0f65240f9b 100644 --- a/packages/components/src/modal/index.tsx +++ b/packages/components/src/modal/index.tsx @@ -335,10 +335,10 @@ function UnforwardedModal( <> <Spacer marginBottom={ 0 } - marginLeft={ 3 } + marginLeft={ 2 } /> <Button - size="small" + size="compact" onClick={ ( event: React.MouseEvent< HTMLButtonElement > ) => diff --git a/packages/components/src/modal/stories/index.story.tsx b/packages/components/src/modal/stories/index.story.tsx index 92c922bcb8a977..3a7b817458ad28 100644 --- a/packages/components/src/modal/stories/index.story.tsx +++ b/packages/components/src/modal/stories/index.story.tsx @@ -23,10 +23,10 @@ const meta: Meta< typeof Modal > = { id: 'components-modal', argTypes: { children: { - control: { type: null }, + control: false, }, onKeyDown: { - control: { type: null }, + control: false, }, focusOnMount: { options: [ true, false, 'firstElement', 'firstContentElement' ], @@ -75,7 +75,10 @@ const Template: StoryFn< typeof Modal > = ( { onRequestClose, ...args } ) => { anim id est laborum. </p> - <InputControl style={ { marginBottom: '20px' } } /> + <InputControl + __next40pxDefaultSize + style={ { marginBottom: '20px' } } + /> <Button variant="secondary" onClick={ closeModal }> Close Modal @@ -111,7 +114,7 @@ export const WithHeaderActions: StoryFn< typeof Modal > = Template.bind( {} ); WithHeaderActions.args = { ...Default.args, headerActions: ( - <Button icon={ fullscreen } label="Fullscreen mode" size="small" /> + <Button icon={ fullscreen } label="Fullscreen mode" size="compact" /> ), children: <div style={ { height: '200px' } } />, }; diff --git a/packages/components/src/modal/style.scss b/packages/components/src/modal/style.scss index 70959f69392d1c..de35d46a704d78 100644 --- a/packages/components/src/modal/style.scss +++ b/packages/components/src/modal/style.scss @@ -33,10 +33,12 @@ display: flex; // Animate the modal frame/contents appearing on the page. animation-name: components-modal__appear-animation; - animation-duration: var(--modal-frame-animation-duration); animation-fill-mode: forwards; animation-timing-function: cubic-bezier(0.29, 0, 0, 1); - @include reduce-motion("animation"); + + @media not (prefers-reduced-motion) { + animation-duration: var(--modal-frame-animation-duration); + } .components-modal__screen-overlay.is-animating-out & { animation-name: components-modal__disappear-animation; diff --git a/packages/components/src/modal/test/index.tsx b/packages/components/src/modal/test/index.tsx index a0d0ee2653edb4..b53e12b450a171 100644 --- a/packages/components/src/modal/test/index.tsx +++ b/packages/components/src/modal/test/index.tsx @@ -269,7 +269,8 @@ describe( 'Modal', () => { } ); describe( 'Focus handling', () => { - let originalGetClientRects: () => DOMRectList; + const originalGetClientRects = + window.HTMLElement.prototype.getClientRects; const FocusMountDemo = ( { focusOnMount, @@ -397,7 +398,8 @@ describe( 'Modal', () => { const [ isAShown, setIsAShown ] = useState( false ); const [ isA1Shown, setIsA1Shown ] = useState( false ); const [ isBShown, setIsBShown ] = useState( false ); - const [ isClassOverriden, setIsClassOverriden ] = useState( false ); + const [ isClassOverridden, setIsClassOverridden ] = + useState( false ); useEffect( () => { const toggles: ( e: KeyboardEvent ) => void = ( { key, @@ -413,7 +415,7 @@ describe( 'Modal', () => { return setIsBShown( ( v ) => ! v ); } if ( key === 'c' ) { - return setIsClassOverriden( ( v ) => ! v ); + return setIsClassOverridden( ( v ) => ! v ); } }; document.addEventListener( 'keydown', toggles ); @@ -425,7 +427,7 @@ describe( 'Modal', () => { { isAShown && ( <Modal bodyOpenClassName={ - isClassOverriden ? overrideClass : 'is-A-open' + isClassOverridden ? overrideClass : 'is-A-open' } onRequestClose={ () => setIsAShown( false ) } > @@ -445,7 +447,7 @@ describe( 'Modal', () => { { isBShown && ( <Modal bodyOpenClassName={ - isClassOverriden ? overrideClass : 'is-B-open' + isClassOverridden ? overrideClass : 'is-B-open' } onRequestClose={ () => setIsBShown( false ) } > diff --git a/packages/components/src/navigable-container/stories/navigable-menu.story.tsx b/packages/components/src/navigable-container/stories/navigable-menu.story.tsx index 2ad7e028d6faf1..30986ff479e43f 100644 --- a/packages/components/src/navigable-container/stories/navigable-menu.story.tsx +++ b/packages/components/src/navigable-container/stories/navigable-menu.story.tsx @@ -13,7 +13,7 @@ const meta: Meta< typeof NavigableMenu > = { id: 'components-navigablemenu', component: NavigableMenu, argTypes: { - children: { control: { type: null } }, + children: { control: false }, }, parameters: { actions: { argTypesRegex: '^on.*' }, diff --git a/packages/components/src/navigable-container/stories/tabbable-container.story.tsx b/packages/components/src/navigable-container/stories/tabbable-container.story.tsx index 07c87a0c20f1bf..afb4119015b52a 100644 --- a/packages/components/src/navigable-container/stories/tabbable-container.story.tsx +++ b/packages/components/src/navigable-container/stories/tabbable-container.story.tsx @@ -13,7 +13,7 @@ const meta: Meta< typeof TabbableContainer > = { id: 'components-tabbablecontainer', component: TabbableContainer, argTypes: { - children: { control: { type: null } }, + children: { control: false }, }, parameters: { actions: { argTypesRegex: '^on.*' }, diff --git a/packages/components/src/navigation/back-button/index.tsx b/packages/components/src/navigation/back-button/index.tsx index 077e5a8dbdc6d4..ce4a90d9ae7a51 100644 --- a/packages/components/src/navigation/back-button/index.tsx +++ b/packages/components/src/navigation/back-button/index.tsx @@ -49,6 +49,7 @@ function UnforwardedNavigationBackButton( const icon = isRTL() ? chevronRight : chevronLeft; return ( <MenuBackButtonUI + __next40pxDefaultSize className={ classes } href={ href } variant="tertiary" diff --git a/packages/components/src/navigation/index.tsx b/packages/components/src/navigation/index.tsx index 92f431dfb22fc7..ef37caf2f52140 100644 --- a/packages/components/src/navigation/index.tsx +++ b/packages/components/src/navigation/index.tsx @@ -6,6 +6,7 @@ import clsx from 'clsx'; /** * WordPress dependencies */ +import deprecated from '@wordpress/deprecated'; import { useEffect, useRef, useState } from '@wordpress/element'; import { isRTL } from '@wordpress/i18n'; @@ -79,6 +80,12 @@ export function Navigation( { const navigationTree = useCreateNavigationTree(); const defaultSlideOrigin = isRTL() ? 'right' : 'left'; + deprecated( 'wp.components.Navigation (and all subcomponents)', { + since: '6.8', + version: '7.1', + alternative: 'wp.components.Navigator', + } ); + const setActiveMenu: NavigationContextType[ 'setActiveMenu' ] = ( menuId, slideInOrigin = defaultSlideOrigin diff --git a/packages/components/src/navigation/item/index.tsx b/packages/components/src/navigation/item/index.tsx index 4f4cc2a5dc7a22..160ed36ac63680 100644 --- a/packages/components/src/navigation/item/index.tsx +++ b/packages/components/src/navigation/item/index.tsx @@ -79,6 +79,8 @@ export function NavigationItem( props: NavigationItemProps ) { ? restProps : { as: Button, + __next40pxDefaultSize: + 'as' in restProps ? restProps.as === undefined : true, href, onClick: onItemClick, 'aria-current': isActive ? 'page' : undefined, diff --git a/packages/components/src/navigation/stories/index.story.tsx b/packages/components/src/navigation/stories/index.story.tsx index 2f09ace29f16e5..8510b6f20b5370 100644 --- a/packages/components/src/navigation/stories/index.story.tsx +++ b/packages/components/src/navigation/stories/index.story.tsx @@ -39,10 +39,10 @@ const meta: Meta< typeof Navigation > = { NavigationMenu, }, argTypes: { - activeItem: { control: { type: null } }, - activeMenu: { control: { type: null } }, - children: { control: { type: null } }, - onActivateMenu: { control: { type: null } }, + activeItem: { control: false }, + activeMenu: { control: false }, + children: { control: false }, + onActivateMenu: { control: false }, }, parameters: { actions: { argTypesRegex: '^on.*' }, diff --git a/packages/components/src/navigation/styles/navigation-styles.tsx b/packages/components/src/navigation/styles/navigation-styles.tsx index 580c0eef4dba8f..aa0976a9a0f277 100644 --- a/packages/components/src/navigation/styles/navigation-styles.tsx +++ b/packages/components/src/navigation/styles/navigation-styles.tsx @@ -134,11 +134,12 @@ export const ItemBaseUI = styled.li` &.is-active { background-color: ${ COLORS.theme.accent }; - color: ${ COLORS.white }; + color: ${ COLORS.theme.accentInverted }; > button, + .components-button:hover, > a { - color: ${ COLORS.white }; + color: ${ COLORS.theme.accentInverted }; opacity: 1; } } diff --git a/packages/components/src/navigation/test/index.tsx b/packages/components/src/navigation/test/index.tsx index 20646a6c809bfc..fed939068c0bfd 100644 --- a/packages/components/src/navigation/test/index.tsx +++ b/packages/components/src/navigation/test/index.tsx @@ -176,6 +176,10 @@ describe( 'Navigation', () => { const menuItems = screen.getAllByRole( 'listitem' ); + expect( console ).toHaveWarnedWith( + 'wp.components.Navigation (and all subcomponents) is deprecated since version 6.8 and will be removed in version 7.1. Please use wp.components.Navigator instead.' + ); + expect( menuItems ).toHaveLength( 4 ); expect( menuItems[ 0 ] ).toHaveTextContent( 'Item 1' ); expect( menuItems[ 1 ] ).toHaveTextContent( 'Item 2' ); diff --git a/packages/components/src/navigator/stories/index.story.tsx b/packages/components/src/navigator/stories/index.story.tsx index bd2cdc17a1263c..0baf6a1c9cf5bf 100644 --- a/packages/components/src/navigator/stories/index.story.tsx +++ b/packages/components/src/navigator/stories/index.story.tsx @@ -24,9 +24,9 @@ const meta: Meta< typeof Navigator > = { title: 'Components/Navigation/Navigator', id: 'components-navigator', argTypes: { - as: { control: { type: null } }, - children: { control: { type: null } }, - initialPath: { control: { type: null } }, + as: { control: false }, + children: { control: false }, + initialPath: { control: false }, }, parameters: { controls: { expanded: true }, diff --git a/packages/components/src/navigator/test/index.tsx b/packages/components/src/navigator/test/index.tsx index cab6e9a4cdadff..07b118eaaef70d 100644 --- a/packages/components/src/navigator/test/index.tsx +++ b/packages/components/src/navigator/test/index.tsx @@ -75,6 +75,7 @@ function CustomNavigatorButton( { } ) { return ( <Navigator.Button + __next40pxDefaultSize onClick={ () => { // Used to spy on the values passed to `navigator.goTo`. onClick?.( { type: 'goTo', path } ); @@ -95,6 +96,7 @@ function CustomNavigatorGoToBackButton( { const { goTo } = useNavigator(); return ( <Button + __next40pxDefaultSize onClick={ () => { goTo( path, { isBack: true } ); // Used to spy on the values passed to `navigator.goTo`. @@ -115,6 +117,7 @@ function CustomNavigatorGoToSkipFocusButton( { const { goTo } = useNavigator(); return ( <Button + __next40pxDefaultSize onClick={ () => { goTo( path, { skipFocus: true } ); // Used to spy on the values passed to `navigator.goTo`. @@ -136,6 +139,7 @@ function CustomNavigatorBackButton( { } ) { return ( <Navigator.BackButton + __next40pxDefaultSize onClick={ () => { // Used to spy on the values passed to `navigator.goBack`. onClick?.( { type: 'goBack' } ); diff --git a/packages/components/src/notice/README.md b/packages/components/src/notice/README.md index 2efb8276cb7584..d2249d0aef76cc 100644 --- a/packages/components/src/notice/README.md +++ b/packages/components/src/notice/README.md @@ -134,9 +134,9 @@ Whether the notice should be dismissible or not #### `onDismiss` : `() => void` -A deprecated alternative to `onRemove`. This prop is kept for compatibilty reasons but should be avoided. +A deprecated alternative to `onRemove`. This prop is kept for compatibility reasons but should be avoided. -- Requiered: No +- Required: No - Default: `noop` #### `actions`: `Array<NoticeAction>`. @@ -154,4 +154,4 @@ The default appearance of an action button is inferred based on whether `url` or ## Related components - To create a more prominent message that requires action, use a Modal. -- For low priority, non-interruptive messsages, use Snackbar. +- For low priority, non-interruptive messages, use Snackbar. diff --git a/packages/components/src/notice/types.ts b/packages/components/src/notice/types.ts index 2af7bc22c7ea5d..8671f630643a6c 100644 --- a/packages/components/src/notice/types.ts +++ b/packages/components/src/notice/types.ts @@ -83,7 +83,7 @@ export type NoticeProps = { isDismissible?: boolean; /** * A deprecated alternative to `onRemove`. This prop is kept for - * compatibilty reasons but should be avoided. + * compatibility reasons but should be avoided. * * @default noop */ diff --git a/packages/components/src/number-control/README.md b/packages/components/src/number-control/README.md index 8421691296e90a..486092790548ee 100644 --- a/packages/components/src/number-control/README.md +++ b/packages/components/src/number-control/README.md @@ -16,6 +16,7 @@ const Example = () => { return ( <NumberControl + __next40pxDefaultSize isShiftStepEnabled={ true } onChange={ setValue } shiftStep={ 10 } @@ -146,4 +147,4 @@ Start opting into the larger default height that will become the default size in - Type: `Boolean` - Required: No -- Default: `false` \ No newline at end of file +- Default: `false` diff --git a/packages/components/src/number-control/index.tsx b/packages/components/src/number-control/index.tsx index 5c846aaafc1e02..6dd1af4024af7f 100644 --- a/packages/components/src/number-control/index.tsx +++ b/packages/components/src/number-control/index.tsx @@ -26,6 +26,7 @@ import { HStack } from '../h-stack'; import { Spacer } from '../spacer'; import { useCx } from '../utils'; import { useDeprecated36pxDefaultSizeProp } from '../utils/use-deprecated-props'; +import { maybeWarnDeprecated36pxSize } from '../utils/deprecated-36px-size'; const noop = () => {}; @@ -53,9 +54,17 @@ function UnforwardedNumberControl( size = 'default', suffix, onChange = noop, + __shouldNotWarnDeprecated36pxSize, ...restProps } = useDeprecated36pxDefaultSizeProp< NumberControlProps >( props ); + maybeWarnDeprecated36pxSize( { + componentName: 'NumberControl', + size, + __next40pxDefaultSize: restProps.__next40pxDefaultSize, + __shouldNotWarnDeprecated36pxSize, + } ); + if ( hideHTMLArrows ) { deprecated( 'wp.components.NumberControl hideHTMLArrows prop ', { alternative: 'spinControls="none"', @@ -233,6 +242,7 @@ function UnforwardedNumberControl( return stateReducerProp?.( baseState, action ) ?? baseState; } } size={ size } + __shouldNotWarnDeprecated36pxSize suffix={ spinControls === 'custom' ? ( <> diff --git a/packages/components/src/number-control/stories/index.story.tsx b/packages/components/src/number-control/stories/index.story.tsx index 3feb0d63eadc2a..e66be3490bb716 100644 --- a/packages/components/src/number-control/stories/index.story.tsx +++ b/packages/components/src/number-control/stories/index.story.tsx @@ -23,7 +23,7 @@ const meta: Meta< typeof NumberControl > = { step: { control: { type: 'text' } }, suffix: { control: { type: 'text' } }, type: { control: { type: 'text' } }, - value: { control: null }, + value: { control: false }, }, parameters: { controls: { expanded: true }, @@ -62,4 +62,5 @@ const Template: StoryFn< typeof NumberControl > = ( { export const Default = Template.bind( {} ); Default.args = { label: 'Value', + __next40pxDefaultSize: true, }; diff --git a/packages/components/src/number-control/test/index.tsx b/packages/components/src/number-control/test/index.tsx index 3cf3368f1636ba..bf97b520673ea4 100644 --- a/packages/components/src/number-control/test/index.tsx +++ b/packages/components/src/number-control/test/index.tsx @@ -12,9 +12,13 @@ import { useState } from '@wordpress/element'; /** * Internal dependencies */ -import NumberControl from '..'; +import _NumberControl from '..'; import type { NumberControlProps } from '../types'; +const NumberControl = ( + props: React.ComponentProps< typeof _NumberControl > +) => <_NumberControl __next40pxDefaultSize { ...props } />; + function StatefulNumberControl( props: NumberControlProps ) { const [ value, setValue ] = useState( props.value ); const handleOnChange = ( v: string | undefined ) => setValue( v ); diff --git a/packages/components/src/number-control/types.ts b/packages/components/src/number-control/types.ts index 8d198e777bd557..2a0fbf402d3569 100644 --- a/packages/components/src/number-control/types.ts +++ b/packages/components/src/number-control/types.ts @@ -91,4 +91,11 @@ export type NumberControlProps = Omit< * The value of the input. */ value?: number | string; + /** + * Do not throw a warning for the deprecated 36px default size. + * For internal components of other components that already throw the warning. + * + * @ignore + */ + __shouldNotWarnDeprecated36pxSize?: boolean; }; diff --git a/packages/components/src/palette-edit/index.tsx b/packages/components/src/palette-edit/index.tsx index a58ecbb685e514..2eb6e3bbe3b6fc 100644 --- a/packages/components/src/palette-edit/index.tsx +++ b/packages/components/src/palette-edit/index.tsx @@ -60,6 +60,7 @@ const DEFAULT_COLOR = '#000'; function NameInput( { value, onChange, label }: NameInputProps ) { return ( <NameInputControl + size="compact" label={ label } hideLabelFromVision value={ value } diff --git a/packages/components/src/palette-edit/test/index.tsx b/packages/components/src/palette-edit/test/index.tsx index 7dc00dbba22042..7f3a0335eb6814 100644 --- a/packages/components/src/palette-edit/test/index.tsx +++ b/packages/components/src/palette-edit/test/index.tsx @@ -416,7 +416,7 @@ describe( 'PaletteEdit', () => { /> ); - await click( screen.getByLabelText( 'Color: Primary' ) ); + await click( screen.getByLabelText( 'Primary' ) ); const hexInput = screen.getByRole( 'textbox', { name: 'Hex color', } ); diff --git a/packages/components/src/panel/stories/index.story.tsx b/packages/components/src/panel/stories/index.story.tsx index af9cf626f8eecd..1392247036c2cc 100644 --- a/packages/components/src/panel/stories/index.story.tsx +++ b/packages/components/src/panel/stories/index.story.tsx @@ -23,7 +23,7 @@ const meta: Meta< typeof Panel > = { // @ts-expect-error - See https://github.com/storybookjs/storybook/issues/23170 subcomponents: { PanelRow, PanelBody }, argTypes: { - children: { control: { type: null } }, + children: { control: false }, }, parameters: { controls: { expanded: true }, @@ -74,12 +74,12 @@ _PanelRow.args = { children: ( <PanelBody title="My Profile"> <PanelRow> - <InputControl label="First name" /> - <InputControl label="Last name" /> + <InputControl label="First name" __next40pxDefaultSize /> + <InputControl label="Last name" __next40pxDefaultSize /> </PanelRow> <PanelRow> <div style={ { flex: 1 } }> - <InputControl label="Email" /> + <InputControl label="Email" __next40pxDefaultSize /> </div> </PanelRow> </PanelBody> diff --git a/packages/components/src/panel/style.scss b/packages/components/src/panel/style.scss index e525cd92569182..3aff9d24b079f5 100644 --- a/packages/components/src/panel/style.scss +++ b/packages/components/src/panel/style.scss @@ -62,9 +62,12 @@ font-size: inherit; margin-top: 0; margin-bottom: 0; - transition: 0.1s background ease-in-out; - @include reduce-motion("transition"); + + @media not (prefers-reduced-motion) { + transition: 0.1s background ease-in-out; + } } + .components-panel__body.is-opened > .components-panel__body-title { margin: -1 * $grid-unit-20; margin-bottom: 5px; @@ -87,8 +90,11 @@ color: $gray-900; border: none; box-shadow: none; - transition: 0.1s background ease-in-out; - @include reduce-motion("transition"); + + @media not (prefers-reduced-motion) { + transition: 0.1s background ease-in-out; + } + height: auto; &:focus { @@ -103,8 +109,10 @@ transform: translateY(-50%); color: $gray-900; fill: currentColor; - transition: 0.1s color ease-in-out; - @include reduce-motion("transition"); + + @media not (prefers-reduced-motion) { + transition: 0.1s color ease-in-out; + } } // mirror the arrow horizontally in RTL languages diff --git a/packages/components/src/placeholder/stories/index.story.tsx b/packages/components/src/placeholder/stories/index.story.tsx index 541eeceedc27d4..ffe60df0b4b857 100644 --- a/packages/components/src/placeholder/stories/index.story.tsx +++ b/packages/components/src/placeholder/stories/index.story.tsx @@ -21,9 +21,9 @@ const meta: Meta< typeof Placeholder > = { component: Placeholder, title: 'Components/Placeholder', argTypes: { - children: { control: { type: null } }, - notices: { control: { type: null } }, - preview: { control: { type: null } }, + children: { control: false }, + notices: { control: false }, + preview: { control: false }, icon: { control: { type: 'select' }, options: Object.keys( ICONS ), @@ -45,6 +45,7 @@ const Template: StoryFn< typeof Placeholder > = ( args ) => { <div> <TextControl __nextHasNoMarginBottom + __next40pxDefaultSize label="Sample Field" placeholder="Enter something here" value={ value } diff --git a/packages/components/src/placeholder/style.scss b/packages/components/src/placeholder/style.scss index a38d7d3e3ace8b..fe151eeccf584a 100644 --- a/packages/components/src/placeholder/style.scss +++ b/packages/components/src/placeholder/style.scss @@ -48,7 +48,7 @@ .block-editor-block-icon { margin-right: $grid-unit-05; fill: currentColor; - // Optimizate for high contrast modes. + // Optimize for high contrast modes. // See also https://blogs.windows.com/msedgedev/2020/09/17/styling-for-windows-high-contrast-with-new-standards-for-forced-colors/. @media (forced-colors: active) { fill: CanvasText; @@ -173,8 +173,10 @@ .components-button { opacity: 0; pointer-events: none; - transition: opacity 0.1s linear; - @include reduce-motion("transition"); + + @media not (prefers-reduced-motion) { + transition: opacity 0.1s linear; + } } .is-selected > & { diff --git a/packages/components/src/popover/stories/index.story.tsx b/packages/components/src/popover/stories/index.story.tsx index 3d804f5d24d5c0..1e1d4225bd0233 100644 --- a/packages/components/src/popover/stories/index.story.tsx +++ b/packages/components/src/popover/stories/index.story.tsx @@ -37,18 +37,18 @@ const meta: Meta< typeof Popover > = { id: 'components-popover', component: Popover, argTypes: { - anchor: { control: { type: null } }, - anchorRef: { control: { type: null } }, - anchorRect: { control: { type: null } }, - children: { control: { type: null } }, + anchor: { control: false }, + anchorRef: { control: false }, + anchorRect: { control: false }, + children: { control: false }, focusOnMount: { control: { type: 'select' }, options: [ 'firstElement', true, false ], }, - getAnchorRect: { control: { type: null } }, + getAnchorRect: { control: false }, onClose: { action: 'onClose' }, onFocusOutside: { action: 'onFocusOutside' }, - __unstableSlotName: { control: { type: null } }, + __unstableSlotName: { control: false }, }, parameters: { controls: { expanded: true }, @@ -58,7 +58,9 @@ const meta: Meta< typeof Popover > = { export default meta; const PopoverWithAnchor = ( args: PopoverProps ) => { - const anchorRef = useRef( null ); + const [ popoverAnchor, setPopoverAnchor ] = useState< Element | null >( + null + ); return ( <div @@ -71,11 +73,11 @@ const PopoverWithAnchor = ( args: PopoverProps ) => { > <p style={ { padding: '8px', background: 'salmon' } } - ref={ anchorRef } + ref={ setPopoverAnchor } > Popover&apos;s anchor </p> - <Popover { ...args } anchorRef={ anchorRef } /> + <Popover { ...args } anchor={ popoverAnchor } /> </div> ); }; diff --git a/packages/components/src/private-apis.ts b/packages/components/src/private-apis.ts index bea16b719a463d..f5a9ee90519c2d 100644 --- a/packages/components/src/private-apis.ts +++ b/packages/components/src/private-apis.ts @@ -2,21 +2,21 @@ * Internal dependencies */ import { positionToPlacement as __experimentalPopoverLegacyPositionToPlacement } from './popover/utils'; -import { createPrivateSlotFill } from './slot-fill'; import { Menu } from './menu'; import { ComponentsContext } from './context/context-system-provider'; import Theme from './theme'; import { Tabs } from './tabs'; import { kebabCase } from './utils/strings'; import { lock } from './lock-unlock'; +import Badge from './badge'; export const privateApis = {}; lock( privateApis, { __experimentalPopoverLegacyPositionToPlacement, - createPrivateSlotFill, ComponentsContext, Tabs, Theme, Menu, kebabCase, + Badge, } ); diff --git a/packages/components/src/progress-bar/stories/index.story.tsx b/packages/components/src/progress-bar/stories/index.story.tsx index 2f6bb4dbe000fd..110dab79124c62 100644 --- a/packages/components/src/progress-bar/stories/index.story.tsx +++ b/packages/components/src/progress-bar/stories/index.story.tsx @@ -43,7 +43,7 @@ const withCustomWidthCustomCSS = ` * You can override the default `width` by passing a custom CSS class via the * `className` prop. * - * This example shows a progress bar with an overriden `width` of `100%` which + * This example shows a progress bar with an overridden `width` of `100%` which * makes it fit all available horizontal space of the parent element. The CSS * class looks like this: * diff --git a/packages/components/src/query-controls/stories/index.story.tsx b/packages/components/src/query-controls/stories/index.story.tsx index 04fe185a59eac1..ad28d9aed0d0d9 100644 --- a/packages/components/src/query-controls/stories/index.story.tsx +++ b/packages/components/src/query-controls/stories/index.story.tsx @@ -22,12 +22,12 @@ const meta: Meta< typeof QueryControls > = { title: 'Components/QueryControls', component: QueryControls, argTypes: { - numberOfItems: { control: { type: null } }, - order: { control: { type: null } }, - orderBy: { control: { type: null } }, - selectedAuthorId: { control: { type: null } }, - selectedCategories: { control: { type: null } }, - selectedCategoryId: { control: { type: null } }, + numberOfItems: { control: false }, + order: { control: false }, + orderBy: { control: false }, + selectedAuthorId: { control: false }, + selectedCategories: { control: false }, + selectedCategoryId: { control: false }, }, parameters: { actions: { argTypesRegex: '^on.*' }, diff --git a/packages/components/src/radio-control/stories/index.story.tsx b/packages/components/src/radio-control/stories/index.story.tsx index 3c76f7610d0d79..7b7bc773f323a9 100644 --- a/packages/components/src/radio-control/stories/index.story.tsx +++ b/packages/components/src/radio-control/stories/index.story.tsx @@ -22,7 +22,7 @@ const meta: Meta< typeof RadioControl > = { action: 'onChange', }, selected: { - control: { type: null }, + control: false, }, label: { control: { type: 'text' }, diff --git a/packages/components/src/radio-group/index.tsx b/packages/components/src/radio-group/index.tsx index e59775c00a8023..589d20ffdaae5b 100644 --- a/packages/components/src/radio-group/index.tsx +++ b/packages/components/src/radio-group/index.tsx @@ -6,6 +6,7 @@ import * as Ariakit from '@ariakit/react'; /** * WordPress dependencies */ +import deprecated from '@wordpress/deprecated'; import { useMemo, forwardRef } from '@wordpress/element'; import { isRTL } from '@wordpress/i18n'; @@ -46,11 +47,21 @@ function UnforwardedRadioGroup( [ radioStore, disabled ] ); + deprecated( 'wp.components.__experimentalRadioGroup', { + alternative: + 'wp.components.RadioControl or wp.components.__experimentalToggleGroupControl', + since: '6.8', + } ); + return ( <RadioGroupContext.Provider value={ contextValue }> <Ariakit.RadioGroup store={ radioStore } - render={ <ButtonGroup>{ children }</ButtonGroup> } + render={ + <ButtonGroup __shouldNotWarnDeprecated> + { children } + </ButtonGroup> + } aria-label={ label } ref={ ref } { ...props } diff --git a/packages/components/src/radio-group/radio.tsx b/packages/components/src/radio-group/radio.tsx index 782a737b6ba28a..4c54e0694f4bde 100644 --- a/packages/components/src/radio-group/radio.tsx +++ b/packages/components/src/radio-group/radio.tsx @@ -7,7 +7,6 @@ import { forwardRef, useContext } from '@wordpress/element'; * External dependencies */ import * as Ariakit from '@ariakit/react'; -import { useStoreState } from '@ariakit/react'; /** * Internal dependencies @@ -28,7 +27,7 @@ function UnforwardedRadio( ) { const { store, disabled } = useContext( RadioGroupContext ); - const selectedValue = useStoreState( store, 'value' ); + const selectedValue = Ariakit.useStoreState( store, 'value' ); const isChecked = selectedValue !== undefined && selectedValue === value; maybeWarnDeprecated36pxSize( { diff --git a/packages/components/src/radio-group/stories/index.story.tsx b/packages/components/src/radio-group/stories/index.story.tsx index a19fb077e7ec46..aee8610e1b7002 100644 --- a/packages/components/src/radio-group/stories/index.story.tsx +++ b/packages/components/src/radio-group/stories/index.story.tsx @@ -21,8 +21,8 @@ const meta: Meta< typeof RadioGroup > = { // @ts-expect-error - See https://github.com/storybookjs/storybook/issues/23170 subcomponents: { Radio }, argTypes: { - onChange: { control: { type: null } }, - children: { control: { type: null } }, + onChange: { control: false }, + children: { control: false }, checked: { control: { type: 'text' } }, }, parameters: { @@ -99,5 +99,5 @@ Controlled.args = { id: 'controlled-radiogroup', }; Controlled.argTypes = { - checked: { control: { type: null } }, + checked: { control: false }, }; diff --git a/packages/components/src/range-control/README.md b/packages/components/src/range-control/README.md index cfa8c76740e74f..f21285c5f26256 100644 --- a/packages/components/src/range-control/README.md +++ b/packages/components/src/range-control/README.md @@ -88,9 +88,10 @@ import { RangeControl } from '@wordpress/components'; const MyRangeControl = () => { const [ columns, setColumns ] = useState( 2 ); - return( + return ( <RangeControl __nextHasNoMarginBottom + __next40pxDefaultSize label="Columns" value={ columns } onChange={ ( value ) => setColumns( value ) } @@ -153,7 +154,6 @@ Disables the `input`, preventing new values from being applied. - Required: No - Platform: Web - ### `help`: `string|Element` If this property is added, a help text will be generated using help property as the content. @@ -165,7 +165,7 @@ If this property is added, a help text will be generated using help property as Provides control over whether the label will only be visible to screen readers. -- Required: No +- Required: No ### `icon`: `string` @@ -334,6 +334,7 @@ The minimum amount by which `value` changes. It is also a factor in validation a - Required: No - Platform: Web + ### `trackColor`: `CSSProperties[ 'color' ]` CSS color string to customize the track element's background. diff --git a/packages/components/src/range-control/index.tsx b/packages/components/src/range-control/index.tsx index c9fbdc0055c855..89dd8248a1e614 100644 --- a/packages/components/src/range-control/index.tsx +++ b/packages/components/src/range-control/index.tsx @@ -38,6 +38,7 @@ import { import type { RangeControlProps } from './types'; import type { WordPressComponentProps } from '../context'; import { space } from '../utils/space'; +import { maybeWarnDeprecated36pxSize } from '../utils/deprecated-36px-size'; const noop = () => {}; @@ -96,6 +97,7 @@ function UnforwardedRangeControl( trackColor, value: valueProp, withInputField = true, + __shouldNotWarnDeprecated36pxSize, ...otherProps } = props; @@ -229,6 +231,14 @@ function UnforwardedRangeControl( [ isRTL() ? 'right' : 'left' ]: fillValueOffset, }; + // Add default size deprecation warning. + maybeWarnDeprecated36pxSize( { + componentName: 'RangeControl', + __next40pxDefaultSize, + size: undefined, + __shouldNotWarnDeprecated36pxSize, + } ); + return ( <BaseControl __nextHasNoMarginBottom={ __nextHasNoMarginBottom } @@ -340,6 +350,7 @@ function UnforwardedRangeControl( step={ step } // @ts-expect-error TODO: Investigate if the `null` value is necessary value={ inputSliderValue } + __shouldNotWarnDeprecated36pxSize /> ) } { allowReset && ( @@ -384,6 +395,7 @@ function UnforwardedRangeControl( * return ( * <RangeControl * __nextHasNoMarginBottom + * __next40pxDefaultSize * help="Please select how transparent you would like this." * initialPosition={50} * label="Opacity" diff --git a/packages/components/src/range-control/mark.tsx b/packages/components/src/range-control/mark.tsx index a17665bb628aba..8692db679f2562 100644 --- a/packages/components/src/range-control/mark.tsx +++ b/packages/components/src/range-control/mark.tsx @@ -38,7 +38,6 @@ export default function RangeMark( { ...otherProps } aria-hidden="true" className={ classes } - isFilled={ isFilled } style={ style } /> { label && ( diff --git a/packages/components/src/range-control/stories/index.story.tsx b/packages/components/src/range-control/stories/index.story.tsx index dcff3513733527..5a4b2342a49ffb 100644 --- a/packages/components/src/range-control/stories/index.story.tsx +++ b/packages/components/src/range-control/stories/index.story.tsx @@ -33,18 +33,18 @@ const meta: Meta< typeof RangeControl > = { }, color: { control: { type: 'color' } }, help: { control: { type: 'text' } }, - icon: { control: { type: null } }, + icon: { control: false }, marks: { control: { type: 'object' } }, - onBlur: { control: { type: null } }, - onChange: { control: { type: null } }, - onFocus: { control: { type: null } }, - onMouseLeave: { control: { type: null } }, - onMouseMove: { control: { type: null } }, + onBlur: { control: false }, + onChange: { control: false }, + onFocus: { control: false }, + onMouseLeave: { control: false }, + onMouseMove: { control: false }, railColor: { control: { type: 'color' } }, step: { control: { type: 'number' } }, trackColor: { control: { type: 'color' } }, type: { control: { type: 'check' }, options: [ 'stepper' ] }, - value: { control: { type: null } }, + value: { control: false }, }, parameters: { actions: { argTypesRegex: '^on.*' }, @@ -72,6 +72,7 @@ const Template: StoryFn< typeof RangeControl > = ( { onChange, ...args } ) => { export const Default: StoryFn< typeof RangeControl > = Template.bind( {} ); Default.args = { __nextHasNoMarginBottom: true, + __next40pxDefaultSize: true, help: 'Please select how transparent you would like this.', initialPosition: 50, label: 'Opacity', @@ -107,6 +108,7 @@ export const WithAnyStep: StoryFn< typeof RangeControl > = ( { }; WithAnyStep.args = { __nextHasNoMarginBottom: true, + __next40pxDefaultSize: true, label: 'Brightness', step: 'any', }; @@ -171,6 +173,7 @@ export const WithIntegerStepAndMarks: StoryFn< typeof RangeControl > = WithIntegerStepAndMarks.args = { __nextHasNoMarginBottom: true, + __next40pxDefaultSize: true, label: 'Integer Step', marks: marksBase, max: 10, @@ -188,6 +191,7 @@ export const WithDecimalStepAndMarks: StoryFn< typeof RangeControl > = WithDecimalStepAndMarks.args = { __nextHasNoMarginBottom: true, + __next40pxDefaultSize: true, marks: [ ...marksBase, { value: 3.5, label: '3.5' }, @@ -208,6 +212,7 @@ export const WithNegativeMinimumAndMarks: StoryFn< typeof RangeControl > = WithNegativeMinimumAndMarks.args = { __nextHasNoMarginBottom: true, + __next40pxDefaultSize: true, marks: marksWithNegatives, max: 10, min: -10, @@ -224,6 +229,7 @@ export const WithNegativeRangeAndMarks: StoryFn< typeof RangeControl > = WithNegativeRangeAndMarks.args = { __nextHasNoMarginBottom: true, + __next40pxDefaultSize: true, marks: marksWithNegatives, max: -1, min: -10, @@ -240,6 +246,7 @@ export const WithAnyStepAndMarks: StoryFn< typeof RangeControl > = WithAnyStepAndMarks.args = { __nextHasNoMarginBottom: true, + __next40pxDefaultSize: true, marks: marksBase, max: 10, min: 0, diff --git a/packages/components/src/range-control/styles/range-control-styles.ts b/packages/components/src/range-control/styles/range-control-styles.ts index 6e9c68ace97532..c86c57800cac46 100644 --- a/packages/components/src/range-control/styles/range-control-styles.ts +++ b/packages/components/src/range-control/styles/range-control-styles.ts @@ -130,6 +130,12 @@ export const Track = styled.span` margin-top: ${ ( rangeHeightValue - railHeight ) / 2 }px; top: 0; + .is-marked & { + @media not ( prefers-reduced-motion ) { + transition: width ease 0.1s; + } + } + ${ trackBackgroundColor }; `; @@ -139,28 +145,18 @@ export const MarksWrapper = styled.span` position: relative; width: 100%; user-select: none; + margin-top: 17px; `; -const markFill = ( { disabled, isFilled }: RangeMarkProps ) => { - let backgroundColor = isFilled ? 'currentColor' : COLORS.gray[ 300 ]; - - if ( disabled ) { - backgroundColor = COLORS.gray[ 400 ]; - } - - return css( { - backgroundColor, - } ); -}; - export const Mark = styled.span` - height: ${ thumbSize }px; - left: 0; position: absolute; - top: 9px; - width: 1px; - - ${ markFill }; + left: 0; + top: -4px; + height: 4px; + width: 2px; + transform: translateX( -50% ); + background-color: ${ COLORS.ui.background }; + z-index: 1; `; const markLabelFill = ( { isFilled }: RangeMarkProps ) => { @@ -173,7 +169,7 @@ export const MarkLabel = styled.span` color: ${ COLORS.gray[ 300 ] }; font-size: 11px; position: absolute; - top: 22px; + top: 8px; white-space: nowrap; ${ rtl( { left: 0 } ) }; @@ -207,6 +203,13 @@ export const ThumbWrapper = styled.span` user-select: none; width: ${ thumbSize }px; border-radius: ${ CONFIG.radiusRound }; + z-index: 3; + + .is-marked & { + @media not ( prefers-reduced-motion ) { + transition: left ease 0.1s; + } + } ${ thumbColor }; ${ rtl( { marginLeft: -10 } ) }; diff --git a/packages/components/src/range-control/test/index.tsx b/packages/components/src/range-control/test/index.tsx index 3ce741867d0dbc..3d2db30eea186f 100644 --- a/packages/components/src/range-control/test/index.tsx +++ b/packages/components/src/range-control/test/index.tsx @@ -18,7 +18,13 @@ const fireChangeEvent = ( input: HTMLInputElement, value?: number | string ) => const RangeControl = ( props: React.ComponentProps< typeof _RangeControl > ) => { - return <_RangeControl { ...props } __nextHasNoMarginBottom />; + return ( + <_RangeControl + { ...props } + __nextHasNoMarginBottom + __next40pxDefaultSize + /> + ); }; describe( 'RangeControl', () => { diff --git a/packages/components/src/range-control/types.ts b/packages/components/src/range-control/types.ts index a427ab4f942af6..e4792296f83144 100644 --- a/packages/components/src/range-control/types.ts +++ b/packages/components/src/range-control/types.ts @@ -233,6 +233,13 @@ export type RangeControlProps = Pick< * @default true */ withInputField?: boolean; + /** + * Do not throw a warning for the deprecated 36px default size. + * For internal components of other components that already throw the warning. + * + * @ignore + */ + __shouldNotWarnDeprecated36pxSize?: boolean; }; export type RailProps = MarksProps & { diff --git a/packages/components/src/resizable-box/index.tsx b/packages/components/src/resizable-box/index.tsx index 1b05270ea0bf20..3bf3d36aa0d5c3 100644 --- a/packages/components/src/resizable-box/index.tsx +++ b/packages/components/src/resizable-box/index.tsx @@ -112,6 +112,16 @@ function UnforwardedResizableBox( showHandle && 'has-show-handle', className ) } + // Add a focusable element within the drag handle. Unfortunately, + // `re-resizable` does not make them properly focusable by default, + // causing focus to move the the block wrapper which triggers block + // drag. + handleComponent={ Object.fromEntries( + Object.keys( HANDLE_CLASSES ).map( ( key ) => [ + key, + <div key={ key } tabIndex={ -1 } />, + ] ) + ) } handleClasses={ HANDLE_CLASSES } handleStyles={ HANDLE_STYLES } ref={ ref } diff --git a/packages/components/src/resizable-box/stories/index.story.tsx b/packages/components/src/resizable-box/stories/index.story.tsx index 489a094c33f119..aa5b080d00b51e 100644 --- a/packages/components/src/resizable-box/stories/index.story.tsx +++ b/packages/components/src/resizable-box/stories/index.story.tsx @@ -18,7 +18,7 @@ const meta: Meta< typeof ResizableBox > = { id: 'components-resizablebox', component: ResizableBox, argTypes: { - children: { control: { type: null } }, + children: { control: false }, enable: { control: 'object' }, onResizeStop: { action: 'onResizeStop' }, }, diff --git a/packages/components/src/resizable-box/style.scss b/packages/components/src/resizable-box/style.scss index 3c9efd27136460..a9ff7ea237e5f6 100644 --- a/packages/components/src/resizable-box/style.scss +++ b/packages/components/src/resizable-box/style.scss @@ -15,6 +15,14 @@ $resize-handler-container-size: $resize-handler-size + ($grid-unit-05 * 2); // M .components-resizable-box__container.has-show-handle & { display: block; } + + > div { + position: relative; + width: 100%; + height: 100%; + z-index: z-index(".components-resizable-box__handle"); + outline: none; + } } // Make the image inside the resize to get the full width @@ -52,9 +60,12 @@ $resize-handler-container-size: $resize-handler-size + ($grid-unit-05 * 2); // M position: absolute; top: calc(50% - 1px); right: calc(50% - 1px); - transition: transform 0.1s ease-in; - @include reduce-motion("transition"); - will-change: transform; + + @media not (prefers-reduced-motion) { + transition: transform 0.1s ease-in; + will-change: transform; + } + opacity: 0; } @@ -94,18 +105,20 @@ $resize-handler-container-size: $resize-handler-size + ($grid-unit-05 * 2); // M .components-resizable-box__side-handle.components-resizable-box__handle-bottom:hover::before, .components-resizable-box__side-handle.components-resizable-box__handle-top:active::before, .components-resizable-box__side-handle.components-resizable-box__handle-bottom:active::before { - animation: components-resizable-box__top-bottom-animation 0.1s ease-out 0s; - animation-fill-mode: forwards; - @include reduce-motion("animation"); + @media not (prefers-reduced-motion) { + animation: components-resizable-box__top-bottom-animation 0.1s ease-out 0s; + animation-fill-mode: forwards; + } } .components-resizable-box__side-handle.components-resizable-box__handle-left:hover::before, .components-resizable-box__side-handle.components-resizable-box__handle-right:hover::before, .components-resizable-box__side-handle.components-resizable-box__handle-left:active::before, .components-resizable-box__side-handle.components-resizable-box__handle-right:active::before { - animation: components-resizable-box__left-right-animation 0.1s ease-out 0s; - animation-fill-mode: forwards; - @include reduce-motion("animation"); + @media not (prefers-reduced-motion) { + animation: components-resizable-box__left-right-animation 0.1s ease-out 0s; + animation-fill-mode: forwards; + } } /* This CSS is shown only to Safari, which has a bug with table-caption making it jumpy. diff --git a/packages/components/src/responsive-wrapper/stories/index.story.tsx b/packages/components/src/responsive-wrapper/stories/index.story.tsx index d684a00c870028..5a834b999b715b 100644 --- a/packages/components/src/responsive-wrapper/stories/index.story.tsx +++ b/packages/components/src/responsive-wrapper/stories/index.story.tsx @@ -13,7 +13,7 @@ const meta: Meta< typeof ResponsiveWrapper > = { title: 'Components/Layout/ResponsiveWrapper', id: 'components-responsivewrapper', argTypes: { - children: { control: { type: null } }, + children: { control: false }, }, parameters: { controls: { expanded: true }, diff --git a/packages/components/src/sandbox/stories/index.story.tsx b/packages/components/src/sandbox/stories/index.story.tsx index 0d083eac3e9026..6d5eaa4868e788 100644 --- a/packages/components/src/sandbox/stories/index.story.tsx +++ b/packages/components/src/sandbox/stories/index.story.tsx @@ -13,7 +13,7 @@ const meta: Meta< typeof SandBox > = { title: 'Components/Utilities/SandBox', id: 'components-sandbox', argTypes: { - onFocus: { control: { type: null } }, + onFocus: { control: false }, }, parameters: { actions: { argTypesRegex: '^on.*' }, diff --git a/packages/components/src/scrollable/stories/index.story.tsx b/packages/components/src/scrollable/stories/index.story.tsx index 53d4919de3aabf..4970b3720e8a07 100644 --- a/packages/components/src/scrollable/stories/index.story.tsx +++ b/packages/components/src/scrollable/stories/index.story.tsx @@ -22,7 +22,7 @@ const meta: Meta< typeof Scrollable > = { control: { type: 'text' }, }, children: { - control: { type: null }, + control: false, }, }, parameters: { @@ -70,6 +70,7 @@ const Template: StoryFn< typeof Scrollable > = ( { ...args } ) => { } } type="text" value="Focus me" + readOnly /> </View> </Scrollable> diff --git a/packages/components/src/search-control/index.tsx b/packages/components/src/search-control/index.tsx index 0a1b821a0a079a..54ef5f3eb9ca56 100644 --- a/packages/components/src/search-control/index.tsx +++ b/packages/components/src/search-control/index.tsx @@ -86,7 +86,7 @@ function UnforwardedSearchControl( () => ( { BaseControl: { // Overrides the underlying BaseControl `__nextHasNoMarginBottom` via the context system - // to provide backwards compatibile margin for SearchControl. + // to provide backwards compatible margin for SearchControl. // (In a standard InputControl, the BaseControl `__nextHasNoMarginBottom` is always set to true.) _overrides: { __nextHasNoMarginBottom }, __associatedWPComponentName: 'SearchControl', diff --git a/packages/components/src/search-control/stories/index.story.tsx b/packages/components/src/search-control/stories/index.story.tsx index 5e5f6b594e73e7..c3385c4eb21b44 100644 --- a/packages/components/src/search-control/stories/index.story.tsx +++ b/packages/components/src/search-control/stories/index.story.tsx @@ -19,7 +19,7 @@ const meta: Meta< typeof SearchControl > = { component: SearchControl, argTypes: { onChange: { action: 'onChange' }, - value: { control: { type: null } }, + value: { control: false }, }, parameters: { controls: { expanded: true }, diff --git a/packages/components/src/select-control/README.md b/packages/components/src/select-control/README.md index c240243408fab3..d8742fce74f54e 100644 --- a/packages/components/src/select-control/README.md +++ b/packages/components/src/select-control/README.md @@ -92,6 +92,7 @@ const MySelectControl = () => { { label: 'Small', value: '25%' }, ] } onChange={ ( newSize ) => setSize( newSize ) } + __next40pxDefaultSize __nextHasNoMarginBottom /> ); @@ -114,6 +115,7 @@ Render a user interface to select multiple users from a list. { value: 'b', label: 'User B' }, { value: 'c', label: 'User c' }, ] } + __next40pxDefaultSize __nextHasNoMarginBottom /> ``` @@ -129,6 +131,7 @@ const [ item, setItem ] = useState( '' ); label={ __( 'My dinosaur' ) } value={ item } // e.g: value = 'a' onChange={ ( selection ) => { setItem( selection ) } } + __next40pxDefaultSize __nextHasNoMarginBottom > <optgroup label="Theropods"> diff --git a/packages/components/src/select-control/index.tsx b/packages/components/src/select-control/index.tsx index 3686661b8a58dc..e93e9385a9c23b 100644 --- a/packages/components/src/select-control/index.tsx +++ b/packages/components/src/select-control/index.tsx @@ -18,6 +18,7 @@ import type { WordPressComponentProps } from '../context'; import type { SelectControlProps } from './types'; import SelectControlChevronDown from './chevron-down'; import { useDeprecated36pxDefaultSizeProp } from '../utils/use-deprecated-props'; +import { maybeWarnDeprecated36pxSize } from '../utils/deprecated-36px-size'; function useUniqueId( idProp?: string ) { const instanceId = useInstanceId( SelectControl ); @@ -65,6 +66,7 @@ function UnforwardedSelectControl< V extends string >( variant = 'default', __next40pxDefaultSize = false, __nextHasNoMarginBottom = false, + __shouldNotWarnDeprecated36pxSize, ...restProps } = useDeprecated36pxDefaultSizeProp( props ); const id = useUniqueId( idProp ); @@ -94,6 +96,13 @@ function UnforwardedSelectControl< V extends string >( const classes = clsx( 'components-select-control', className ); + maybeWarnDeprecated36pxSize( { + componentName: 'SelectControl', + __next40pxDefaultSize, + size, + __shouldNotWarnDeprecated36pxSize, + } ); + return ( <BaseControl help={ help } @@ -154,6 +163,7 @@ function UnforwardedSelectControl< V extends string >( * * return ( * <SelectControl + * __next40pxDefaultSize * __nextHasNoMarginBottom * label="Size" * value={ size } diff --git a/packages/components/src/select-control/stories/index.story.tsx b/packages/components/src/select-control/stories/index.story.tsx index e9461ef6904f6f..e9f7f09a1ddb05 100644 --- a/packages/components/src/select-control/stories/index.story.tsx +++ b/packages/components/src/select-control/stories/index.story.tsx @@ -23,7 +23,7 @@ const meta: Meta< typeof SelectControl > = { label: { control: { type: 'text' } }, prefix: { control: { type: 'text' } }, suffix: { control: { type: 'text' } }, - value: { control: { type: null } }, + value: { control: false }, }, parameters: { actions: { argTypesRegex: '^on.*' }, @@ -65,6 +65,7 @@ const SelectControlWithState: StoryFn< typeof SelectControl > = ( props ) => { export const Default = SelectControlWithState.bind( {} ); Default.args = { + __next40pxDefaultSize: true, __nextHasNoMarginBottom: true, label: 'Label', options: [ @@ -87,6 +88,7 @@ WithLabelAndHelpText.args = { */ export const WithCustomChildren = SelectControlWithState.bind( {} ); WithCustomChildren.args = { + __next40pxDefaultSize: true, __nextHasNoMarginBottom: true, label: 'Label', children: ( diff --git a/packages/components/src/select-control/test/select-control.tsx b/packages/components/src/select-control/test/select-control.tsx index 47b684cd20e280..37935d60384b15 100644 --- a/packages/components/src/select-control/test/select-control.tsx +++ b/packages/components/src/select-control/test/select-control.tsx @@ -12,7 +12,13 @@ import _SelectControl from '..'; const SelectControl = ( props: React.ComponentProps< typeof _SelectControl > ) => { - return <_SelectControl { ...props } __nextHasNoMarginBottom />; + return ( + <_SelectControl + { ...props } + __nextHasNoMarginBottom + __next40pxDefaultSize + /> + ); }; describe( 'SelectControl', () => { diff --git a/packages/components/src/select-control/types.ts b/packages/components/src/select-control/types.ts index 4e7211ab9abfb2..3d9f06385c7532 100644 --- a/packages/components/src/select-control/types.ts +++ b/packages/components/src/select-control/types.ts @@ -13,6 +13,7 @@ type SelectControlBaseProps< V extends string > = Pick< InputBaseProps, | '__next36pxDefaultSize' | '__next40pxDefaultSize' + | '__shouldNotWarnDeprecated36pxSize' | 'disabled' | 'hideLabelFromVision' | 'label' diff --git a/packages/components/src/slot-fill/README.md b/packages/components/src/slot-fill/README.md index 9059566deefdf4..3f14b9ccde9eeb 100644 --- a/packages/components/src/slot-fill/README.md +++ b/packages/components/src/slot-fill/README.md @@ -1,18 +1,18 @@ -# Slot Fill +# Slot/Fill -Slot and Fill are a pair of components which enable developers to render elsewhere in a React element tree, a pattern often referred to as "portal" rendering. It is a pattern for component extensibility, where a single Slot may be occupied by an indeterminate number of Fills elsewhere in the application. +`Slot` and `Fill` are a pair of components which enable developers to render React UI elsewhere in a React element tree, a pattern often referred to as "portal" rendering. It is a pattern for component extensibility, where a single `Slot` may be occupied by multiple `Fill`s rendered in different parts of the application. -Slot Fill is heavily inspired by the [`react-slot-fill` library](https://github.com/camwest/react-slot-fill), but uses React's own portal rendering API. +Slot/Fill was originally inspired by the [`react-slot-fill` library](https://github.com/camwest/react-slot-fill). ## Usage -At the root of your application, you must render a `SlotFillProvider` which coordinates Slot and Fill rendering. +At the root of your application, you must render a `SlotFillProvider` which coordinates `Slot` and `Fill` rendering. -Then, render a Slot component anywhere in your application, giving it a name. +Then, render a `Slot` component anywhere in your application, giving it a `name`. The `name` is either a `string` or a symbol. Symbol names are useful for slots that are supposed to be private, accessible only to clients that have access to the symbol value. -Any Fill will automatically occupy this Slot space, even if rendered elsewhere in the application. +Any `Fill` will render its UI in this `Slot` space, even if rendered elsewhere in the application. -You can either use the Fill component directly, or a wrapper component type as in the below example to abstract the slot name from consumer awareness. +You can either use the `Fill` component directly, or create a wrapper component (as in the following example) to hide the slot name from the consumer. ```jsx import { @@ -43,7 +43,7 @@ const MySlotFillProvider = () => { }; ``` -There is also `createSlotFill` helper method which was created to simplify the process of matching the corresponding `Slot` and `Fill` components: +There is also the `createSlotFill` helper method which was created to simplify the process of matching the corresponding `Slot` and `Fill` components: ```jsx const { Fill, Slot } = createSlotFill( 'Toolbar' ); @@ -59,18 +59,27 @@ const Toolbar = () => ( ## Props -The `SlotFillProvider` component does not accept any props. +The `SlotFillProvider` component does not accept any props (except `children`). Both `Slot` and `Fill` accept a `name` string prop, where a `Slot` with a given `name` will render the `children` of any associated `Fill`s. -`Slot` accepts a `bubblesVirtually` prop which changes the event bubbling behaviour: +`Slot` accepts a `bubblesVirtually` prop which changes the method how the `Fill` children are rendered. With `bubblesVirtually`, the `Fill` is rendered using a React portal. That affects the event bubbling and React context propagation behaviour: -- By default, events will bubble to their parents on the DOM hierarchy (native event bubbling) -- If `bubblesVirtually` is set to true, events will bubble to their virtual parent in the React elements hierarchy instead. +### `bubblesVirtually=false` -`Slot` with `bubblesVirtually` set to true also accept optional `className` and `style` props to add to the slot container. +- events will bubble to their parents on the DOM hierarchy (native event bubbling) +- the React elements inside the `Fill` will be rendered with React context of the `Slot` +- renders the `Fill` elements directly, inside a `Fragment`, with no wrapper DOM element -`Slot` **without** `bubblesVirtually` accepts an optional `children` function prop, which takes `fills` as a param. It allows you to perform additional processing and wrap `fills` conditionally. +### `bubblesVirtually=true` + +- events will bubble to their virtual (React) parent in the React elements hierarchy +- the React elements inside the `Fill` will keep the React context of the `Fill` and its parents +- renders a wrapper DOM element inside which the `Fill` elements are rendered (used as an argument for React `createPortal`) + +`Slot` with `bubblesVirtually=true` renders a wrapper DOM element (a `div` by default) and accepts additional props that customize this element, like `className` or `style`. You can also replace the `div` with another element by passing an `as` prop. + +`Slot` **without** `bubblesVirtually` accepts an optional `children` prop, which is a function that receives `fills` array as a param. It allows you to perform additional processing: render a placeholder when there are no fills, or render a wrapper only when there are fills. _Example_: @@ -90,7 +99,9 @@ const Toolbar = ( { isMobile } ) => ( ); ``` -Props can also be passed from a `Slot` to a `Fill` by using the prop `fillProps` on the `Slot`: +Additional information (props) can also be passed from a `Slot` to a `Fill` by a combination of: +1. Adding a `fillProps` prop to the `Slot`. +2. Passing a function as `children` to the `Fill`. This function will receive the `fillProps` as an argument. ```jsx const { Fill, Slot } = createSlotFill( 'Toolbar' ); diff --git a/packages/components/src/slot-fill/bubbles-virtually/fill.tsx b/packages/components/src/slot-fill/bubbles-virtually/fill.tsx index 3cfadbadc62c4b..ef7bc94ff540bd 100644 --- a/packages/components/src/slot-fill/bubbles-virtually/fill.tsx +++ b/packages/components/src/slot-fill/bubbles-virtually/fill.tsx @@ -1,50 +1,36 @@ /** * WordPress dependencies */ -import { useRef, useState, useEffect, createPortal } from '@wordpress/element'; +import { useObservableValue } from '@wordpress/compose'; +import { + useContext, + useRef, + useEffect, + createPortal, +} from '@wordpress/element'; /** * Internal dependencies */ -import useSlot from './use-slot'; +import SlotFillContext from './slot-fill-context'; import StyleProvider from '../../style-provider'; import type { FillComponentProps } from '../types'; -function useForceUpdate() { - const [ , setState ] = useState( {} ); - const mountedRef = useRef( true ); +export default function Fill( { name, children }: FillComponentProps ) { + const registry = useContext( SlotFillContext ); + const slot = useObservableValue( registry.slots, name ); + const instanceRef = useRef( {} ); + // We register fills so we can keep track of their existence. + // Slots can use the `useSlotFills` hook to know if there're already fills + // registered so they can choose to render themselves or not. useEffect( () => { - mountedRef.current = true; - return () => { - mountedRef.current = false; - }; - }, [] ); + const instance = instanceRef.current; + registry.registerFill( name, instance ); + return () => registry.unregisterFill( name, instance ); + }, [ registry, name ] ); - return () => { - if ( mountedRef.current ) { - setState( {} ); - } - }; -} - -export default function Fill( props: FillComponentProps ) { - const { name, children } = props; - const { registerFill, unregisterFill, ...slot } = useSlot( name ); - const rerender = useForceUpdate(); - const ref = useRef( { rerender } ); - - useEffect( () => { - // We register fills so we can keep track of their existence. - // Some Slot implementations need to know if there're already fills - // registered so they can choose to render themselves or not. - registerFill( ref ); - return () => { - unregisterFill( ref ); - }; - }, [ registerFill, unregisterFill ] ); - - if ( ! slot.ref || ! slot.ref.current ) { + if ( ! slot || ! slot.ref.current ) { return null; } diff --git a/packages/components/src/slot-fill/bubbles-virtually/slot-fill-provider.tsx b/packages/components/src/slot-fill/bubbles-virtually/slot-fill-provider.tsx index 4d68db6fd175ee..cf692700eef79c 100644 --- a/packages/components/src/slot-fill/bubbles-virtually/slot-fill-provider.tsx +++ b/packages/components/src/slot-fill/bubbles-virtually/slot-fill-provider.tsx @@ -23,26 +23,28 @@ function createSlotRegistry(): SlotFillBubblesVirtuallyContext { ref, fillProps ) => { - const slot = slots.get( name ); - - slots.set( name, { - ...slot, - ref: ref || slot?.ref, - fillProps: fillProps || slot?.fillProps || {}, - } ); + slots.set( name, { ref, fillProps } ); }; const unregisterSlot: SlotFillBubblesVirtuallyContext[ 'unregisterSlot' ] = ( name, ref ) => { + const slot = slots.get( name ); + if ( ! slot ) { + return; + } + // Make sure we're not unregistering a slot registered by another element // See https://github.com/WordPress/gutenberg/pull/19242#issuecomment-590295412 - if ( slots.get( name )?.ref === ref ) { - slots.delete( name ); + if ( slot.ref !== ref ) { + return; } + + slots.delete( name ); }; const updateSlot: SlotFillBubblesVirtuallyContext[ 'updateSlot' ] = ( name, + ref, fillProps ) => { const slot = slots.get( name ); @@ -50,16 +52,15 @@ function createSlotRegistry(): SlotFillBubblesVirtuallyContext { return; } - if ( isShallowEqual( slot.fillProps, fillProps ) ) { + if ( slot.ref !== ref ) { return; } - slot.fillProps = fillProps; - const slotFills = fills.get( name ); - if ( slotFills ) { - // Force update fills. - slotFills.forEach( ( fill ) => fill.current.rerender() ); + if ( isShallowEqual( slot.fillProps, fillProps ) ) { + return; } + + slots.set( name, { ref, fillProps } ); }; const registerFill: SlotFillBubblesVirtuallyContext[ 'registerFill' ] = ( @@ -69,20 +70,18 @@ function createSlotRegistry(): SlotFillBubblesVirtuallyContext { fills.set( name, [ ...( fills.get( name ) || [] ), ref ] ); }; - const unregisterFill: SlotFillBubblesVirtuallyContext[ 'registerFill' ] = ( - name, - ref - ) => { - const fillsForName = fills.get( name ); - if ( ! fillsForName ) { - return; - } + const unregisterFill: SlotFillBubblesVirtuallyContext[ 'unregisterFill' ] = + ( name, ref ) => { + const fillsForName = fills.get( name ); + if ( ! fillsForName ) { + return; + } - fills.set( - name, - fillsForName.filter( ( fillRef ) => fillRef !== ref ) - ); - }; + fills.set( + name, + fillsForName.filter( ( fillRef ) => fillRef !== ref ) + ); + }; return { slots, diff --git a/packages/components/src/slot-fill/bubbles-virtually/slot.tsx b/packages/components/src/slot-fill/bubbles-virtually/slot.tsx index 6ac2d51e1f857f..e65c055c410a6b 100644 --- a/packages/components/src/slot-fill/bubbles-virtually/slot.tsx +++ b/packages/components/src/slot-fill/bubbles-virtually/slot.tsx @@ -35,29 +35,30 @@ function Slot( as, // `children` is not allowed. However, if it is passed, // it will be displayed as is, so remove `children`. - // @ts-ignore children, ...restProps } = props; - const { registerSlot, unregisterSlot, ...registry } = - useContext( SlotFillContext ); + const registry = useContext( SlotFillContext ); + const ref = useRef< HTMLElement >( null ); + // We don't want to unregister and register the slot whenever + // `fillProps` change, which would cause the fill to be re-mounted. Instead, + // we can just update the slot (see hook below). + // For more context, see https://github.com/WordPress/gutenberg/pull/44403#discussion_r994415973 + const fillPropsRef = useRef( fillProps ); + useLayoutEffect( () => { + fillPropsRef.current = fillProps; + }, [ fillProps ] ); + useLayoutEffect( () => { - registerSlot( name, ref, fillProps ); - return () => { - unregisterSlot( name, ref ); - }; - // We don't want to unregister and register the slot whenever - // `fillProps` change, which would cause the fill to be re-mounted. Instead, - // we can just update the slot (see hook below). - // For more context, see https://github.com/WordPress/gutenberg/pull/44403#discussion_r994415973 - }, [ registerSlot, unregisterSlot, name ] ); - // fillProps may be an update that interacts with the layout, so we - // useLayoutEffect. + registry.registerSlot( name, ref, fillPropsRef.current ); + return () => registry.unregisterSlot( name, ref ); + }, [ registry, name ] ); + useLayoutEffect( () => { - registry.updateSlot( name, fillProps ); + registry.updateSlot( name, ref, fillPropsRef.current ); } ); return ( diff --git a/packages/components/src/slot-fill/bubbles-virtually/use-slot.ts b/packages/components/src/slot-fill/bubbles-virtually/use-slot.ts index d1d37e1d8e541c..cac57a024e4ee2 100644 --- a/packages/components/src/slot-fill/bubbles-virtually/use-slot.ts +++ b/packages/components/src/slot-fill/bubbles-virtually/use-slot.ts @@ -1,40 +1,17 @@ /** * WordPress dependencies */ -import { useMemo, useContext } from '@wordpress/element'; +import { useContext } from '@wordpress/element'; import { useObservableValue } from '@wordpress/compose'; /** * Internal dependencies */ import SlotFillContext from './slot-fill-context'; -import type { - SlotFillBubblesVirtuallyFillRef, - SlotFillBubblesVirtuallySlotRef, - FillProps, - SlotKey, -} from '../types'; +import type { SlotKey } from '../types'; export default function useSlot( name: SlotKey ) { const registry = useContext( SlotFillContext ); const slot = useObservableValue( registry.slots, name ); - - const api = useMemo( - () => ( { - updateSlot: ( fillProps: FillProps ) => - registry.updateSlot( name, fillProps ), - unregisterSlot: ( ref: SlotFillBubblesVirtuallySlotRef ) => - registry.unregisterSlot( name, ref ), - registerFill: ( ref: SlotFillBubblesVirtuallyFillRef ) => - registry.registerFill( name, ref ), - unregisterFill: ( ref: SlotFillBubblesVirtuallyFillRef ) => - registry.unregisterFill( name, ref ), - } ), - [ name, registry ] - ); - - return { - ...slot, - ...api, - }; + return { ...slot }; } diff --git a/packages/components/src/slot-fill/context.ts b/packages/components/src/slot-fill/context.ts index c4839462fbce0c..b1f0718180e9eb 100644 --- a/packages/components/src/slot-fill/context.ts +++ b/packages/components/src/slot-fill/context.ts @@ -1,20 +1,22 @@ /** * WordPress dependencies */ +import { observableMap } from '@wordpress/compose'; import { createContext } from '@wordpress/element'; + /** * Internal dependencies */ import type { BaseSlotFillContext } from './types'; const initialValue: BaseSlotFillContext = { + slots: observableMap(), + fills: observableMap(), registerSlot: () => {}, unregisterSlot: () => {}, registerFill: () => {}, unregisterFill: () => {}, - getSlot: () => undefined, - getFills: () => [], - subscribe: () => () => {}, + updateFill: () => {}, }; export const SlotFillContext = createContext( initialValue ); diff --git a/packages/components/src/slot-fill/fill.ts b/packages/components/src/slot-fill/fill.ts index 4134af25684b07..0bd1aec8fa3e0e 100644 --- a/packages/components/src/slot-fill/fill.ts +++ b/packages/components/src/slot-fill/fill.ts @@ -7,46 +7,26 @@ import { useContext, useLayoutEffect, useRef } from '@wordpress/element'; * Internal dependencies */ import SlotFillContext from './context'; -import useSlot from './use-slot'; import type { FillComponentProps } from './types'; export default function Fill( { name, children }: FillComponentProps ) { - const { registerFill, unregisterFill } = useContext( SlotFillContext ); - const slot = useSlot( name ); - - const ref = useRef( { - name, - children, - } ); + const registry = useContext( SlotFillContext ); + const instanceRef = useRef( {} ); + const childrenRef = useRef( children ); useLayoutEffect( () => { - const refValue = ref.current; - registerFill( name, refValue ); - return () => unregisterFill( name, refValue ); - // The useLayoutEffects here are written to fire at specific times, and introducing new dependencies could cause unexpected changes in behavior. - // We'll leave them as-is until a more detailed investigation/refactor can be performed. - }, [] ); + childrenRef.current = children; + }, [ children ] ); useLayoutEffect( () => { - ref.current.children = children; - if ( slot ) { - slot.forceUpdate(); - } - // The useLayoutEffects here are written to fire at specific times, and introducing new dependencies could cause unexpected changes in behavior. - // We'll leave them as-is until a more detailed investigation/refactor can be performed. - }, [ children ] ); + const instance = instanceRef.current; + registry.registerFill( name, instance, childrenRef.current ); + return () => registry.unregisterFill( name, instance ); + }, [ registry, name ] ); useLayoutEffect( () => { - if ( name === ref.current.name ) { - // Ignore initial effect. - return; - } - unregisterFill( ref.current.name, ref.current ); - ref.current.name = name; - registerFill( name, ref.current ); - // The useLayoutEffects here are written to fire at specific times, and introducing new dependencies could cause unexpected changes in behavior. - // We'll leave them as-is until a more detailed investigation/refactor can be performed. - }, [ name ] ); + registry.updateFill( name, instanceRef.current, childrenRef.current ); + } ); return null; } diff --git a/packages/components/src/slot-fill/index.tsx b/packages/components/src/slot-fill/index.tsx index 03ed33a67f13b6..caf97091b67ac8 100644 --- a/packages/components/src/slot-fill/index.tsx +++ b/packages/components/src/slot-fill/index.tsx @@ -84,17 +84,15 @@ export function createSlotFill( key: SlotKey ) { props: DistributiveOmit< SlotComponentProps, 'name' > ) => <Slot name={ key } { ...props } />; SlotComponent.displayName = `${ baseName }Slot`; + /** + * @deprecated 6.8.0 + * Please use `slotFill.name` instead of `slotFill.Slot.__unstableName`. + */ SlotComponent.__unstableName = key; return { + name: key, Fill: FillComponent, Slot: SlotComponent, }; } - -export const createPrivateSlotFill = ( name: string ) => { - const privateKey = Symbol( name ); - const privateSlotFill = createSlotFill( privateKey ); - - return { privateKey, ...privateSlotFill }; -}; diff --git a/packages/components/src/slot-fill/provider.tsx b/packages/components/src/slot-fill/provider.tsx index 6ed624bab67a3c..e5319bc7f33e44 100644 --- a/packages/components/src/slot-fill/provider.tsx +++ b/packages/components/src/slot-fill/provider.tsx @@ -1,7 +1,6 @@ /** * WordPress dependencies */ -import type { Component } from '@wordpress/element'; import { useState } from '@wordpress/element'; /** @@ -9,111 +8,102 @@ import { useState } from '@wordpress/element'; */ import SlotFillContext from './context'; import type { - FillComponentProps, + FillInstance, + FillChildren, + BaseSlotInstance, BaseSlotFillContext, - BaseSlotComponentProps, SlotFillProviderProps, SlotKey, } from './types'; +import { observableMap } from '@wordpress/compose'; function createSlotRegistry(): BaseSlotFillContext { - const slots: Record< SlotKey, Component< BaseSlotComponentProps > > = {}; - const fills: Record< SlotKey, FillComponentProps[] > = {}; - let listeners: Array< () => void > = []; - - function registerSlot( - name: SlotKey, - slot: Component< BaseSlotComponentProps > - ) { - const previousSlot = slots[ name ]; - slots[ name ] = slot; - triggerListeners(); - - // Sometimes the fills are registered after the initial render of slot - // But before the registerSlot call, we need to rerender the slot. - forceUpdateSlot( name ); - - // If a new instance of a slot is being mounted while another with the - // same name exists, force its update _after_ the new slot has been - // assigned into the instance, such that its own rendering of children - // will be empty (the new Slot will subsume all fills for this name). - if ( previousSlot ) { - previousSlot.forceUpdate(); - } - } - - function registerFill( name: SlotKey, instance: FillComponentProps ) { - fills[ name ] = [ ...( fills[ name ] || [] ), instance ]; - forceUpdateSlot( name ); + const slots = observableMap< SlotKey, BaseSlotInstance >(); + const fills = observableMap< + SlotKey, + { instance: FillInstance; children: FillChildren }[] + >(); + + function registerSlot( name: SlotKey, instance: BaseSlotInstance ) { + slots.set( name, instance ); } - function unregisterSlot( - name: SlotKey, - instance: Component< BaseSlotComponentProps > - ) { + function unregisterSlot( name: SlotKey, instance: BaseSlotInstance ) { // If a previous instance of a Slot by this name unmounts, do nothing, // as the slot and its fills should only be removed for the current // known instance. - if ( slots[ name ] !== instance ) { + if ( slots.get( name ) !== instance ) { return; } - delete slots[ name ]; - triggerListeners(); + slots.delete( name ); } - function unregisterFill( name: SlotKey, instance: FillComponentProps ) { - fills[ name ] = - fills[ name ]?.filter( ( fill ) => fill !== instance ) ?? []; - forceUpdateSlot( name ); + function registerFill( + name: SlotKey, + instance: FillInstance, + children: FillChildren + ) { + fills.set( name, [ + ...( fills.get( name ) || [] ), + { instance, children }, + ] ); } - function getSlot( - name: SlotKey - ): Component< BaseSlotComponentProps > | undefined { - return slots[ name ]; + function unregisterFill( name: SlotKey, instance: FillInstance ) { + const fillsForName = fills.get( name ); + if ( ! fillsForName ) { + return; + } + + fills.set( + name, + fillsForName.filter( ( fill ) => fill.instance !== instance ) + ); } - function getFills( + function updateFill( name: SlotKey, - slotInstance: Component< BaseSlotComponentProps > - ): FillComponentProps[] { - // Fills should only be returned for the current instance of the slot - // in which they occupy. - if ( slots[ name ] !== slotInstance ) { - return []; + instance: FillInstance, + children: FillChildren + ) { + const fillsForName = fills.get( name ); + if ( ! fillsForName ) { + return; } - return fills[ name ]; - } - - function forceUpdateSlot( name: SlotKey ) { - const slot = getSlot( name ); - if ( slot ) { - slot.forceUpdate(); + const fillForInstance = fillsForName.find( + ( f ) => f.instance === instance + ); + if ( ! fillForInstance ) { + return; } - } - - function triggerListeners() { - listeners.forEach( ( listener ) => listener() ); - } - function subscribe( listener: () => void ) { - listeners.push( listener ); + if ( fillForInstance.children === children ) { + return; + } - return () => { - listeners = listeners.filter( ( l ) => l !== listener ); - }; + fills.set( + name, + fillsForName.map( ( f ) => { + if ( f.instance === instance ) { + // Replace with new record with updated `children`. + return { instance, children }; + } + + return f; + } ) + ); } return { + slots, + fills, registerSlot, unregisterSlot, registerFill, unregisterFill, - getSlot, - getFills, - subscribe, + updateFill, }; } diff --git a/packages/components/src/slot-fill/slot.tsx b/packages/components/src/slot-fill/slot.tsx index 3fe2a549359260..c1182562672c0b 100644 --- a/packages/components/src/slot-fill/slot.tsx +++ b/packages/components/src/slot-fill/slot.tsx @@ -6,9 +6,12 @@ import type { ReactElement, ReactNode, Key } from 'react'; /** * WordPress dependencies */ +import { useObservableValue } from '@wordpress/compose'; import { + useContext, + useLayoutEffect, + useRef, Children, - Component, cloneElement, isEmptyElement, } from '@wordpress/element'; @@ -17,7 +20,7 @@ import { * Internal dependencies */ import SlotFillContext from './context'; -import type { BaseSlotComponentProps, SlotComponentProps } from './types'; +import type { SlotComponentProps } from './types'; /** * Whether the argument is a function. @@ -29,90 +32,63 @@ function isFunction( maybeFunc: any ): maybeFunc is Function { return typeof maybeFunc === 'function'; } -class SlotComponent extends Component< BaseSlotComponentProps > { - private isUnmounted: boolean; - - constructor( props: BaseSlotComponentProps ) { - super( props ); +function addKeysToChildren( children: ReactNode ) { + return Children.map( children, ( child, childIndex ) => { + if ( ! child || typeof child === 'string' ) { + return child; + } + let childKey: Key = childIndex; + if ( typeof child === 'object' && 'key' in child && child?.key ) { + childKey = child.key; + } - this.isUnmounted = false; - } + return cloneElement( child as ReactElement, { + key: childKey, + } ); + } ); +} - componentDidMount() { - const { registerSlot } = this.props; - this.isUnmounted = false; - registerSlot( this.props.name, this ); - } +function Slot( props: Omit< SlotComponentProps, 'bubblesVirtually' > ) { + const registry = useContext( SlotFillContext ); + const instanceRef = useRef( {} ); - componentWillUnmount() { - const { unregisterSlot } = this.props; - this.isUnmounted = true; - unregisterSlot( this.props.name, this ); - } + const { name, children, fillProps = {} } = props; - componentDidUpdate( prevProps: BaseSlotComponentProps ) { - const { name, unregisterSlot, registerSlot } = this.props; + useLayoutEffect( () => { + const instance = instanceRef.current; + registry.registerSlot( name, instance ); + return () => registry.unregisterSlot( name, instance ); + }, [ registry, name ] ); - if ( prevProps.name !== name ) { - unregisterSlot( prevProps.name, this ); - registerSlot( name, this ); - } - } + let fills = useObservableValue( registry.fills, name ) ?? []; + const currentSlot = useObservableValue( registry.slots, name ); - forceUpdate() { - if ( this.isUnmounted ) { - return; - } - super.forceUpdate(); + // Fills should only be rendered in the currently registered instance of the slot. + if ( currentSlot !== instanceRef.current ) { + fills = []; } - render() { - const { children, name, fillProps = {}, getFills } = this.props; - const fills: ReactNode[] = ( getFills( name, this ) ?? [] ) - .map( ( fill ) => { - const fillChildren = isFunction( fill.children ) - ? fill.children( fillProps ) - : fill.children; - return Children.map( fillChildren, ( child, childIndex ) => { - if ( ! child || typeof child === 'string' ) { - return child; - } - let childKey: Key = childIndex; - if ( - typeof child === 'object' && - 'key' in child && - child?.key - ) { - childKey = child.key; - } - - return cloneElement( child as ReactElement, { - key: childKey, - } ); - } ); - } ) - .filter( - // In some cases fills are rendered only when some conditions apply. - // This ensures that we only use non-empty fills when rendering, i.e., - // it allows us to render wrappers only when the fills are actually present. - ( element ) => ! isEmptyElement( element ) - ); - - return <>{ isFunction( children ) ? children( fills ) : fills }</>; - } + const renderedFills = fills + .map( ( fill ) => { + const fillChildren = isFunction( fill.children ) + ? fill.children( fillProps ) + : fill.children; + return addKeysToChildren( fillChildren ); + } ) + .filter( + // In some cases fills are rendered only when some conditions apply. + // This ensures that we only use non-empty fills when rendering, i.e., + // it allows us to render wrappers only when the fills are actually present. + ( element ) => ! isEmptyElement( element ) + ); + + return ( + <> + { isFunction( children ) + ? children( renderedFills ) + : renderedFills } + </> + ); } -const Slot = ( props: Omit< SlotComponentProps, 'bubblesVirtually' > ) => ( - <SlotFillContext.Consumer> - { ( { registerSlot, unregisterSlot, getFills } ) => ( - <SlotComponent - { ...props } - registerSlot={ registerSlot } - unregisterSlot={ unregisterSlot } - getFills={ getFills } - /> - ) } - </SlotFillContext.Consumer> -); - export default Slot; diff --git a/packages/components/src/slot-fill/stories/index.story.tsx b/packages/components/src/slot-fill/stories/index.story.tsx index bc6c4f57ad9ce1..2c74496e1eada0 100644 --- a/packages/components/src/slot-fill/stories/index.story.tsx +++ b/packages/components/src/slot-fill/stories/index.story.tsx @@ -20,9 +20,9 @@ const meta: Meta< typeof Slot > = { // @ts-expect-error - See https://github.com/storybookjs/storybook/issues/23170 subcomponents: { Fill, SlotFillProvider }, argTypes: { - name: { control: { type: null } }, + name: { control: false }, as: { control: { type: 'text' } }, - fillProps: { control: { type: null } }, + fillProps: { control: false }, }, parameters: { controls: { expanded: true }, diff --git a/packages/components/src/slot-fill/types.ts b/packages/components/src/slot-fill/types.ts index 1711e04cbb1f47..758f1c8257d548 100644 --- a/packages/components/src/slot-fill/types.ts +++ b/packages/components/src/slot-fill/types.ts @@ -1,7 +1,7 @@ /** * External dependencies */ -import type { Component, MutableRefObject, ReactNode, RefObject } from 'react'; +import type { ReactNode, RefObject } from 'react'; /** * WordPress dependencies @@ -84,6 +84,10 @@ export type SlotComponentProps = style?: never; } ); +export type FillChildren = + | ReactNode + | ( ( fillProps: FillProps ) => ReactNode ); + export type FillComponentProps = { /** * The name of the slot to fill into. @@ -93,7 +97,7 @@ export type FillComponentProps = { /** * Children elements or render function. */ - children?: ReactNode | ( ( fillProps: FillProps ) => ReactNode ); + children?: FillChildren; }; export type SlotFillProviderProps = { @@ -108,38 +112,18 @@ export type SlotFillProviderProps = { passthrough?: boolean; }; -export type SlotFillBubblesVirtuallySlotRef = RefObject< HTMLElement >; -export type SlotFillBubblesVirtuallyFillRef = MutableRefObject< { - rerender: () => void; -} >; +export type SlotRef = RefObject< HTMLElement >; +export type FillInstance = {}; +export type BaseSlotInstance = {}; export type SlotFillBubblesVirtuallyContext = { - slots: ObservableMap< - SlotKey, - { - ref: SlotFillBubblesVirtuallySlotRef; - fillProps: FillProps; - } - >; - fills: ObservableMap< SlotKey, SlotFillBubblesVirtuallyFillRef[] >; - registerSlot: ( - name: SlotKey, - ref: SlotFillBubblesVirtuallySlotRef, - fillProps: FillProps - ) => void; - unregisterSlot: ( - name: SlotKey, - ref: SlotFillBubblesVirtuallySlotRef - ) => void; - updateSlot: ( name: SlotKey, fillProps: FillProps ) => void; - registerFill: ( - name: SlotKey, - ref: SlotFillBubblesVirtuallyFillRef - ) => void; - unregisterFill: ( - name: SlotKey, - ref: SlotFillBubblesVirtuallyFillRef - ) => void; + slots: ObservableMap< SlotKey, { ref: SlotRef; fillProps: FillProps } >; + fills: ObservableMap< SlotKey, FillInstance[] >; + registerSlot: ( name: SlotKey, ref: SlotRef, fillProps: FillProps ) => void; + unregisterSlot: ( name: SlotKey, ref: SlotRef ) => void; + updateSlot: ( name: SlotKey, ref: SlotRef, fillProps: FillProps ) => void; + registerFill: ( name: SlotKey, instance: FillInstance ) => void; + unregisterFill: ( name: SlotKey, instance: FillInstance ) => void; /** * This helps the provider know if it's using the default context value or not. @@ -148,30 +132,22 @@ export type SlotFillBubblesVirtuallyContext = { }; export type BaseSlotFillContext = { - registerSlot: ( + slots: ObservableMap< SlotKey, BaseSlotInstance >; + fills: ObservableMap< + SlotKey, + { instance: FillInstance; children: FillChildren }[] + >; + registerSlot: ( name: SlotKey, slot: BaseSlotInstance ) => void; + unregisterSlot: ( name: SlotKey, slot: BaseSlotInstance ) => void; + registerFill: ( name: SlotKey, - slot: Component< BaseSlotComponentProps > + instance: FillInstance, + children: FillChildren ) => void; - unregisterSlot: ( + unregisterFill: ( name: SlotKey, instance: FillInstance ) => void; + updateFill: ( name: SlotKey, - slot: Component< BaseSlotComponentProps > + instance: FillInstance, + children: FillChildren ) => void; - registerFill: ( name: SlotKey, instance: FillComponentProps ) => void; - unregisterFill: ( name: SlotKey, instance: FillComponentProps ) => void; - getSlot: ( - name: SlotKey - ) => Component< BaseSlotComponentProps > | undefined; - getFills: ( - name: SlotKey, - slotInstance: Component< BaseSlotComponentProps > - ) => FillComponentProps[]; - subscribe: ( listener: () => void ) => () => void; }; - -export type BaseSlotComponentProps = Pick< - BaseSlotFillContext, - 'registerSlot' | 'unregisterSlot' | 'getFills' -> & - Omit< SlotComponentProps, 'bubblesVirtually' > & { - children?: ( fills: ReactNode ) => ReactNode; - }; diff --git a/packages/components/src/slot-fill/use-slot.ts b/packages/components/src/slot-fill/use-slot.ts deleted file mode 100644 index 4ab419be1ad2bd..00000000000000 --- a/packages/components/src/slot-fill/use-slot.ts +++ /dev/null @@ -1,27 +0,0 @@ -/** - * WordPress dependencies - */ -import { useContext, useSyncExternalStore } from '@wordpress/element'; - -/** - * Internal dependencies - */ -import SlotFillContext from './context'; -import type { SlotKey } from './types'; - -/** - * React hook returning the active slot given a name. - * - * @param name Slot name. - * @return Slot object. - */ -const useSlot = ( name: SlotKey ) => { - const { getSlot, subscribe } = useContext( SlotFillContext ); - return useSyncExternalStore( - subscribe, - () => getSlot( name ), - () => getSlot( name ) - ); -}; - -export default useSlot; diff --git a/packages/components/src/snackbar/stories/index.story.tsx b/packages/components/src/snackbar/stories/index.story.tsx index 9bd1dae42b71be..5c24044cc1ae68 100644 --- a/packages/components/src/snackbar/stories/index.story.tsx +++ b/packages/components/src/snackbar/stories/index.story.tsx @@ -19,17 +19,17 @@ const meta: Meta< typeof Snackbar > = { id: 'components-snackbar', component: Snackbar, argTypes: { - as: { control: { type: null } }, + as: { control: false }, onRemove: { action: 'onRemove', - control: { type: null }, + control: false, }, onDismiss: { action: 'onDismiss', - control: { type: null }, + control: false, }, listRef: { - control: { type: null }, + control: false, }, }, parameters: { diff --git a/packages/components/src/snackbar/stories/list.story.tsx b/packages/components/src/snackbar/stories/list.story.tsx index 1f93f374ec1745..69a4367c485759 100644 --- a/packages/components/src/snackbar/stories/list.story.tsx +++ b/packages/components/src/snackbar/stories/list.story.tsx @@ -18,10 +18,10 @@ const meta: Meta< typeof SnackbarList > = { id: 'components-snackbarlist', component: SnackbarList, argTypes: { - as: { control: { type: null } }, + as: { control: false }, onRemove: { action: 'onRemove', - control: { type: null }, + control: false, }, }, parameters: { diff --git a/packages/components/src/style.scss b/packages/components/src/style.scss index 70317f4a2d0e0b..368dec0f5e253d 100644 --- a/packages/components/src/style.scss +++ b/packages/components/src/style.scss @@ -10,6 +10,7 @@ // Components @import "./animate/style.scss"; @import "./autocomplete/style.scss"; +@import "./badge/styles.scss"; @import "./button-group/style.scss"; @import "./button/style.scss"; @import "./checkbox-control/style.scss"; diff --git a/packages/components/src/surface/stories/index.story.tsx b/packages/components/src/surface/stories/index.story.tsx index 7f6790d09c848e..1ef0c0d5637cb4 100644 --- a/packages/components/src/surface/stories/index.story.tsx +++ b/packages/components/src/surface/stories/index.story.tsx @@ -13,7 +13,7 @@ const meta: Meta< typeof Surface > = { component: Surface, title: 'Components (Experimental)/Surface', argTypes: { - children: { control: { type: null } }, + children: { control: false }, as: { control: { type: 'text' } }, }, parameters: { diff --git a/packages/components/src/tab-panel/index.tsx b/packages/components/src/tab-panel/index.tsx index be06b42fcd013f..ec4f33d875a381 100644 --- a/packages/components/src/tab-panel/index.tsx +++ b/packages/components/src/tab-panel/index.tsx @@ -2,7 +2,6 @@ * External dependencies */ import * as Ariakit from '@ariakit/react'; -import { useStoreState } from '@ariakit/react'; import clsx from 'clsx'; import type { ForwardedRef } from 'react'; @@ -125,7 +124,7 @@ const UnforwardedTabPanel = ( } ); const selectedTabName = extractTabName( - useStoreState( tabStore, 'selectedId' ) + Ariakit.useStoreState( tabStore, 'selectedId' ) ); const setTabStoreSelectedId = useCallback( diff --git a/packages/components/src/tab-panel/stories/index.story.tsx b/packages/components/src/tab-panel/stories/index.story.tsx index 57a3cc311f863c..8f40c61beb5239 100644 --- a/packages/components/src/tab-panel/stories/index.story.tsx +++ b/packages/components/src/tab-panel/stories/index.story.tsx @@ -2,6 +2,7 @@ * External dependencies */ import type { Meta, StoryFn } from '@storybook/react'; +import { fn } from '@storybook/test'; /** * WordPress dependencies @@ -22,6 +23,9 @@ const meta: Meta< typeof TabPanel > = { controls: { expanded: true }, docs: { canvas: { sourceState: 'shown' } }, }, + args: { + onSelect: fn(), + }, }; export default meta; diff --git a/packages/components/src/tab-panel/style.scss b/packages/components/src/tab-panel/style.scss index b54f7af1bf4ded..7e811b21b65b6e 100644 --- a/packages/components/src/tab-panel/style.scss +++ b/packages/components/src/tab-panel/style.scss @@ -40,8 +40,9 @@ border-radius: 0; // Animation - transition: all 0.1s linear; - @include reduce-motion("transition"); + @media not (prefers-reduced-motion) { + transition: all 0.1s linear; + } } // Active. @@ -68,8 +69,9 @@ border-radius: $radius-small; // Animation - transition: all 0.1s linear; - @include reduce-motion("transition"); + @media not (prefers-reduced-motion) { + transition: all 0.1s linear; + } } &:focus-visible::before { diff --git a/packages/components/src/tabs/README.md b/packages/components/src/tabs/README.md index 9c7e846046c904..7f5f3219adfd1e 100644 --- a/packages/components/src/tabs/README.md +++ b/packages/components/src/tabs/README.md @@ -1,254 +1,218 @@ # Tabs -<div class="callout callout-alert"> -This feature is still experimental. “Experimental” means this is an early implementation subject to drastic and breaking changes. -</div> - -Tabs is a collection of React components that combine to render an [ARIA-compliant tabs pattern](https://www.w3.org/WAI/ARIA/apg/patterns/tabs/). - -Tabs organizes content across different screens, data sets, and interactions. It has two sections: a list of tabs, and the view to show when tabs are chosen. - -## Development guidelines - -### Usage - -#### Uncontrolled Mode - -Tabs can be used in an uncontrolled mode, where the component manages its own state. In this mode, the `defaultTabId` prop can be used to set the initially selected tab. If this prop is not set, the first tab will be selected by default. In addition, in most cases where the currently active tab becomes disabled or otherwise unavailable, uncontrolled mode will automatically fall back to selecting the first available tab. - -```jsx -import { Tabs } from '@wordpress/components'; - -const onSelect = ( tabName ) => { - console.log( 'Selecting tab', tabName ); -}; - -const MyUncontrolledTabs = () => ( - <Tabs onSelect={ onSelect } defaultTabId="tab2"> - <Tabs.TabList> - <Tabs.Tab tabId="tab1" title="Tab 1"> - Tab 1 - </Tabs.Tab> - <Tabs.Tab tabId="tab2" title="Tab 2"> - Tab 2 - </Tabs.Tab> - <Tabs.Tab tabId="tab3" title="Tab 3"> - Tab 3 - </Tabs.Tab> - </Tabs.TabList> - <Tabs.TabPanel tabId="tab1"> - <p>Selected tab: Tab 1</p> - </Tabs.TabPanel> - <Tabs.TabPanel tabId="tab2"> - <p>Selected tab: Tab 2</p> - </Tabs.TabPanel> - <Tabs.TabPanel tabId="tab3"> - <p>Selected tab: Tab 3</p> - </Tabs.TabPanel> - </Tabs> - ); -``` - -#### Controlled Mode - -Tabs can also be used in a controlled mode, where the parent component specifies the `selectedTabId` and the `onSelect` props to control tab selection. In this mode, the `defaultTabId` prop will be ignored if it is provided. If the `selectedTabId` is `null`, no tab is selected. In this mode, if the currently selected tab becomes disabled or otherwise unavailable, the component will _not_ fall back to another available tab, leaving the controlling component in charge of implementing the desired logic. - -```jsx -import { Tabs } from '@wordpress/components'; - const [ selectedTabId, setSelectedTabId ] = useState< - string | undefined | null - >(); - -const onSelect = ( tabName ) => { - console.log( 'Selecting tab', tabName ); -}; - -const MyControlledTabs = () => ( - <Tabs - selectedTabId={ selectedTabId } - onSelect={ ( selectedId ) => { - setSelectedTabId( selectedId ); - onSelect( selectedId ); - } } - > - <Tabs.TabList> - <Tabs.Tab tabId="tab1" title="Tab 1"> - Tab 1 - </Tabs.Tab> - <Tabs.Tab tabId="tab2" title="Tab 2"> - Tab 2 - </Tabs.Tab> - <Tabs.Tab tabId="tab3" title="Tab 3"> - Tab 3 - </Tabs.Tab> - </Tabs.TabList> - <Tabs.TabPanel tabId="tab1"> - <p>Selected tab: Tab 1</p> - </Tabs.TabPanel> - <Tabs.TabPanel tabId="tab2"> - <p>Selected tab: Tab 2</p> - </Tabs.TabPanel> - <Tabs.TabPanel tabId="tab3"> - <p>Selected tab: Tab 3</p> - </Tabs.TabPanel> - </Tabs> - ); -``` - -### Components and Sub-components - -Tabs is comprised of four individual components: -- `Tabs`: a wrapper component and context provider. It is responsible for managing the state of the tabs and rendering the `TabList` and `TabPanels`. -- `TabList`: a wrapper component for the `Tab` components. It is responsible for rendering the list of tabs. -- `Tab`: renders a single tab. The currently active tab receives default styling that can be overridden with CSS targeting [aria-selected="true"]. -- `TabPanel`: renders the content to display for a single tab once that tab is selected. - -#### Tabs - -##### Props - -###### `children`: `React.ReactNode` - -The children elements, which should include one instance of the `Tabs.Tablist` component and as many instances of the `Tabs.TabPanel` components as there are `Tabs.Tab` components. - -- Required: Yes - -###### `selectOnMove`: `boolean` - -Determines if the tab should be selected when it receives focus. If set to `false`, the tab will only be selected upon clicking, not when using arrow keys to shift focus (manual tab activation). See the [official W3C docs](https://www.w3.org/WAI/ARIA/apg/patterns/tabpanel/) for more info. - -- Required: No -- Default: `true` - -###### `selectedTabId`: `string | null` +<!-- This file is generated automatically and cannot be edited directly. Make edits via TypeScript types and TSDocs. --> -The id of the tab whose panel is currently visible. +🔒 This component is locked as a [private API](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-private-apis/). We do not yet recommend using this outside of the Gutenberg project. -If left `undefined`, it will be automatically set to the first enabled tab, and the component assumes it is being used in "uncontrolled" mode. +<p class="callout callout-info">See the <a href="https://wordpress.github.io/gutenberg/?path=/docs/components-tabs--docs">WordPress Storybook</a> for more detailed, interactive documentation.</p> -Consequently, any value different than `undefined` will set the component in "controlled" mode. When in "controlled" mode, the `null` value will result in no tabs being selected, and the tablist becoming tabbable. +Tabs is a collection of React components that combine to render +an [ARIA-compliant tabs pattern](https://www.w3.org/WAI/ARIA/apg/patterns/tabs/). -- Required: No +Tabs organizes content across different screens, data sets, and interactions. +It has two sections: a list of tabs, and the view to show when a tab is chosen. -###### `defaultTabId`: `string | null` +`Tabs` itself is a wrapper component and context provider. +It is responsible for managing the state of the tabs, and rendering one instance of the `Tabs.TabList` component and one or more instances of the `Tab.TabPanel` component. -The id of the tab whose panel is currently visible. +## Props -If left `undefined`, it will be automatically set to the first enabled tab. If set to `null`, no tab will be selected, and the tablist will be tabbable. +### `activeTabId` -_Note: this prop will be overridden by the `selectedTabId` prop if it is provided (meaning the component will be used in "controlled" mode)._ + - Type: `string` + - Required: No -- Required: No +The current active tab `id`. The active tab is the tab element within the +tablist widget that has DOM focus. -###### `onSelect`: `( ( selectedId: string | null | undefined ) => void )` +- `null` represents the tablist (ie. the base composite element). Users + will be able to navigate out of it using arrow keys. +- If `activeTabId` is initially set to `null`, the base composite element + itself will have focus and users will be able to navigate to it using + arrow keys. -The function called when the `selectedTabId` changes. +### `children` -- Required: No -- Default: `noop` + - Type: `ReactNode` + - Required: Yes -###### `activeTabId`: `string | null` +The children elements, which should include one instance of the +`Tabs.Tablist` component and as many instances of the `Tabs.TabPanel` +components as there are `Tabs.Tab` components. -The current active tab `id`. The active tab is the tab element within the tablist widget that has DOM focus. +### `defaultTabId` -- `null` represents the tablist (ie. the base composite element). Users - will be able to navigate out of it using arrow keys; -- If `activeTabId` is initially set to `null`, the base composite element - itself will have focus and users will be able to navigate to it using - arrow keys. + - Type: `string` + - Required: No + +The id of the tab whose panel is currently visible. -- Required: No +If left `undefined`, it will be automatically set to the first enabled +tab. If set to `null`, no tab will be selected, and the tablist will be +tabbable. -###### `defaultActiveTabId`: `string | null` +Note: this prop will be overridden by the `selectedTabId` prop if it is +provided (meaning the component will be used in "controlled" mode). -The tab id that should be active by default when the composite widget is rendered. If `null`, the tablist element itself will have focus and users will be able to navigate to it using arrow keys. If `undefined`, the first enabled item will be focused. +### `defaultActiveTabId` -_Note: this prop will be overridden by the `activeTabId` prop if it is provided._ + - Type: `string` + - Required: No -- Required: No +The tab id that should be active by default when the composite widget is +rendered. If `null`, the tablist element itself will have focus +and users will be able to navigate to it using arrow keys. If `undefined`, +the first enabled item will be focused. -###### `onActiveTabIdChange`: `( ( activeId: string | null | undefined ) => void )` +Note: this prop will be overridden by the `activeTabId` prop if it is +provided. + +### `onSelect` + + - Type: `(selectedId: string) => void` + - Required: No The function called when the `selectedTabId` changes. -- Required: No -- Default: `noop` +### `onActiveTabIdChange` + + - Type: `(activeId: string) => void` + - Required: No + +A callback that gets called when the `activeTabId` state changes. -###### `orientation`: `'horizontal' | 'vertical' | 'both'` +### `orientation` -Defines the orientation of the tablist and determines which arrow keys can be used to move focus: + - Type: `"horizontal" | "vertical" | "both"` + - Required: No + - Default: `"horizontal"` -- `both`: all arrow keys work; -- `horizontal`: only left and right arrow keys work; +Defines the orientation of the tablist and determines which arrow keys +can be used to move focus: + +- `both`: all arrow keys work. +- `horizontal`: only left and right arrow keys work. - `vertical`: only up and down arrow keys work. -- Required: No -- Default: `horizontal` +### `selectOnMove` + + - Type: `boolean` + - Required: No + - Default: `true` + +Determines if the tab should be selected when it receives focus. If set to +`false`, the tab will only be selected upon clicking, not when using arrow +keys to shift focus (manual tab activation). See the [official W3C docs](https://www.w3.org/WAI/ARIA/apg/patterns/tabpanel/) +for more info. + +### `selectedTabId` + + - Type: `string` + - Required: No + +The id of the tab whose panel is currently visible. + +If left `undefined`, it will be automatically set to the first enabled +tab, and the component assumes it is being used in "uncontrolled" mode. -#### TabList +Consequently, any value different than `undefined` will set the component +in "controlled" mode. When in "controlled" mode, the `null` value will +result in no tabs being selected, and the tablist becoming tabbable. -##### Props +## Subcomponents -###### `children`: `React.ReactNode` +### Tabs.TabList -The children elements, which should include one or more instances of the `Tabs.Tab` component. +A wrapper component for the `Tab` components. -- Required: No +It is responsible for rendering the list of tabs. -#### Tab +#### Props -##### Props +##### `children` -###### `tabId`: `string` + - Type: `ReactNode` + - Required: Yes -The unique ID of the tab. It will be used to register the tab and match it to a corresponding `Tabs.TabPanel` component. If not provided, a unique ID will be automatically generated. +The children elements, which should include one or more instances of the +`Tabs.Tab` component. -- Required: Yes +### Tabs.Tab -###### `children`: `React.ReactNode` +Renders a single tab. + +The currently active tab receives default styling that can be +overridden with CSS targeting `[aria-selected="true"]`. + +#### Props + +##### `children` + + - Type: `ReactNode` + - Required: No The contents of the tab. -- Required: No +##### `disabled` -###### `disabled`: `boolean` + - Type: `boolean` + - Required: No + - Default: `false` -Determines if the tab should be disabled. Note that disabled tabs can still be accessed via the keyboard when navigating through the tablist. +Determines if the tab should be disabled. Note that disabled tabs can +still be accessed via the keyboard when navigating through the tablist. -- Required: No -- Default: `false` +##### `render` -###### `render`: `React.ReactNode` + - Type: `RenderProp<HTMLAttributes<any> & { ref?: Ref<any>; }> | ReactElement<any, string | JSXElementConstructor<any>>` + - Required: No -Allows the component to be rendered as a different HTML element or React component. The value can be a React element or a function that takes in the original component props and gives back a React element with the props merged. +Allows the component to be rendered as a different HTML element or React +component. The value can be a React element or a function that takes in the +original component props and gives back a React element with the props +merged. By default, the tab will be rendered as a `button` element. -- Required: No +##### `tabId` -#### TabPanel + - Type: `string` + - Required: Yes -##### Props +The unique ID of the tab. It will be used to register the tab and match +it to a corresponding `Tabs.TabPanel` component. -###### `children`: `React.ReactNode` +### Tabs.TabPanel -The contents of the tab panel. +Renders the content to display for a single tab once that tab is selected. -- Required: No +#### Props -###### `tabId`: `string` +##### `children` -The unique `id` of the `Tabs.Tab` component controlling this panel. This connection is used to assign the `aria-labelledby` attribute to the tab panel and to determine if the tab panel should be visible. + - Type: `ReactNode` + - Required: No -If not provided, this link is automatically established by matching the order of `Tabs.Tab` and `Tabs.TabPanel` elements in the DOM. +The contents of the tab panel. -- Required: Yes +##### `focusable` -###### `focusable`: `boolean` + - Type: `boolean` + - Required: No + - Default: `true` Determines whether or not the tabpanel element should be focusable. +If `false`, pressing the tab key will skip over the tabpanel, and instead +focus on the first focusable element in the panel (if there is one). + +##### `tabId` + + - Type: `string` + - Required: Yes -If `false`, pressing the tab key will skip over the tabpanel, and instead focus on the first focusable element in the panel (if there is one). +The unique `id` of the `Tabs.Tab` component controlling this panel. This +connection is used to assign the `aria-labelledby` attribute to the tab +panel and to determine if the tab panel should be visible. -- Required: No -- Default: `true` +If not provided, this link is automatically established by matching the +order of `Tabs.Tab` and `Tabs.TabPanel` elements in the DOM. diff --git a/packages/components/src/tabs/docs-manifest.json b/packages/components/src/tabs/docs-manifest.json new file mode 100644 index 00000000000000..fc24b177ef6163 --- /dev/null +++ b/packages/components/src/tabs/docs-manifest.json @@ -0,0 +1,22 @@ +{ + "$schema": "../../schemas/docs-manifest.json", + "displayName": "Tabs", + "filePath": "./index.tsx", + "subcomponents": [ + { + "displayName": "TabList", + "preferredDisplayName": "Tabs.TabList", + "filePath": "./tablist.tsx" + }, + { + "displayName": "Tab", + "preferredDisplayName": "Tabs.Tab", + "filePath": "./tab.tsx" + }, + { + "displayName": "TabPanel", + "preferredDisplayName": "Tabs.TabPanel", + "filePath": "./tabpanel.tsx" + } + ] +} diff --git a/packages/components/src/tabs/index.tsx b/packages/components/src/tabs/index.tsx index 819d259395daf8..2cbe487976c59e 100644 --- a/packages/components/src/tabs/index.tsx +++ b/packages/components/src/tabs/index.tsx @@ -36,11 +36,14 @@ function internalToExternalTabId( } /** - * Display one panel of content at a time with a tabbed interface, based on the - * WAI-ARIA Tabs Pattern⁠. + * Tabs is a collection of React components that combine to render + * an [ARIA-compliant tabs pattern](https://www.w3.org/WAI/ARIA/apg/patterns/tabs/). * - * @see https://www.w3.org/WAI/ARIA/apg/patterns/tabs/ - * ``` + * Tabs organizes content across different screens, data sets, and interactions. + * It has two sections: a list of tabs, and the view to show when a tab is chosen. + * + * `Tabs` itself is a wrapper component and context provider. + * It is responsible for managing the state of the tabs, and rendering one instance of the `Tabs.TabList` component and one or more instances of the `Tab.TabPanel` component. */ export const Tabs = Object.assign( function Tabs( { @@ -121,12 +124,26 @@ export const Tabs = Object.assign( ); }, { + /** + * Renders a single tab. + * + * The currently active tab receives default styling that can be + * overridden with CSS targeting `[aria-selected="true"]`. + */ Tab: Object.assign( Tab, { displayName: 'Tabs.Tab', } ), + /** + * A wrapper component for the `Tab` components. + * + * It is responsible for rendering the list of tabs. + */ TabList: Object.assign( TabList, { displayName: 'Tabs.TabList', } ), + /** + * Renders the content to display for a single tab once that tab is selected. + */ TabPanel: Object.assign( TabPanel, { displayName: 'Tabs.TabPanel', } ), diff --git a/packages/components/src/tabs/stories/best-practices.mdx b/packages/components/src/tabs/stories/best-practices.mdx new file mode 100644 index 00000000000000..a8bb9cf20a5f0e --- /dev/null +++ b/packages/components/src/tabs/stories/best-practices.mdx @@ -0,0 +1,99 @@ +import { Meta } from '@storybook/blocks'; + +import * as TabsStories from './index.story'; + +<Meta of={ TabsStories } name="Best Practices" /> + +# Tabs + +## Usage + +### Uncontrolled Mode + +Tabs can be used in an uncontrolled mode, where the component manages its own state. In this mode, the `defaultTabId` prop can be used to set the initially selected tab. If this prop is not set, the first tab will be selected by default. In addition, in most cases where the currently active tab becomes disabled or otherwise unavailable, uncontrolled mode will automatically fall back to selecting the first available tab. + +```jsx +import { Tabs } from '@wordpress/components'; + +const onSelect = ( tabName ) => { + console.log( 'Selecting tab', tabName ); +}; + +const MyUncontrolledTabs = () => ( + <Tabs onSelect={ onSelect } defaultTabId="tab2"> + <Tabs.TabList> + <Tabs.Tab tabId="tab1" title="Tab 1"> + Tab 1 + </Tabs.Tab> + <Tabs.Tab tabId="tab2" title="Tab 2"> + Tab 2 + </Tabs.Tab> + <Tabs.Tab tabId="tab3" title="Tab 3"> + Tab 3 + </Tabs.Tab> + </Tabs.TabList> + <Tabs.TabPanel tabId="tab1"> + <p>Selected tab: Tab 1</p> + </Tabs.TabPanel> + <Tabs.TabPanel tabId="tab2"> + <p>Selected tab: Tab 2</p> + </Tabs.TabPanel> + <Tabs.TabPanel tabId="tab3"> + <p>Selected tab: Tab 3</p> + </Tabs.TabPanel> + </Tabs> +); +``` + +### Controlled Mode + +Tabs can also be used in a controlled mode, where the parent component specifies the `selectedTabId` and the `onSelect` props to control tab selection. In this mode, the `defaultTabId` prop will be ignored if it is provided. If the `selectedTabId` is `null`, no tab is selected. In this mode, if the currently selected tab becomes disabled or otherwise unavailable, the component will _not_ fall back to another available tab, leaving the controlling component in charge of implementing the desired logic. + +```tsx +import { Tabs } from '@wordpress/components'; + +const [ selectedTabId, setSelectedTabId ] = useState< + string | undefined | null +>(); + +const onSelect = ( tabName ) => { + console.log( 'Selecting tab', tabName ); +}; + +const MyControlledTabs = () => ( + <Tabs + selectedTabId={ selectedTabId } + onSelect={ ( selectedId ) => { + setSelectedTabId( selectedId ); + onSelect( selectedId ); + } } + > + <Tabs.TabList> + <Tabs.Tab tabId="tab1" title="Tab 1"> + Tab 1 + </Tabs.Tab> + <Tabs.Tab tabId="tab2" title="Tab 2"> + Tab 2 + </Tabs.Tab> + <Tabs.Tab tabId="tab3" title="Tab 3"> + Tab 3 + </Tabs.Tab> + </Tabs.TabList> + <Tabs.TabPanel tabId="tab1"> + <p>Selected tab: Tab 1</p> + </Tabs.TabPanel> + <Tabs.TabPanel tabId="tab2"> + <p>Selected tab: Tab 2</p> + </Tabs.TabPanel> + <Tabs.TabPanel tabId="tab3"> + <p>Selected tab: Tab 3</p> + </Tabs.TabPanel> + </Tabs> +); +``` + +### Using `Tabs` with links + +The semantics implemented by the `Tabs` component don't align well with the semantics needed by a list of links. Furthermore, end users usually expect every link to be tabbable, while `Tabs.Tablist` is a [composite](https://w3c.github.io/aria/#composite) widget acting as a single tab stop. + +For these reasons, even if the `Tabs` component is fully extensible, we don't recommend using `Tabs` with links, and we don't currently provide any related Storybook example. diff --git a/packages/components/src/tabs/stories/index.story.tsx b/packages/components/src/tabs/stories/index.story.tsx index 5b2fd621bbb436..0502d6400a4f5c 100644 --- a/packages/components/src/tabs/stories/index.story.tsx +++ b/packages/components/src/tabs/stories/index.story.tsx @@ -2,6 +2,7 @@ * External dependencies */ import type { Meta, StoryFn } from '@storybook/react'; +import { fn } from '@storybook/test'; /** * WordPress dependencies @@ -14,7 +15,6 @@ import { useState } from '@wordpress/element'; */ import { Tabs } from '..'; import { Slot, Fill, Provider as SlotFillProvider } from '../../slot-fill'; -import DropdownMenu from '../../dropdown-menu'; import Button from '../../button'; import Tooltip from '../../tooltip'; import Icon from '../../icon'; @@ -39,6 +39,10 @@ const meta: Meta< typeof Tabs > = { controls: { expanded: true }, docs: { canvas: { sourceState: 'shown' } }, }, + args: { + onActiveTabIdChange: fn(), + onSelect: fn(), + }, }; export default meta; @@ -362,133 +366,3 @@ const CloseButtonTemplate: StoryFn< typeof Tabs > = ( props ) => { ); }; export const InsertCustomElements = CloseButtonTemplate.bind( {} ); - -const ControlledModeTemplate: StoryFn< typeof Tabs > = ( props ) => { - const [ selectedTabId, setSelectedTabId ] = useState< - string | undefined | null - >( props.selectedTabId ); - - return ( - <> - <Tabs - { ...props } - selectedTabId={ selectedTabId } - onSelect={ ( selectedId ) => { - setSelectedTabId( selectedId ); - props.onSelect?.( selectedId ); - } } - > - <Tabs.TabList> - <Tabs.Tab tabId="tab1">Tab 1</Tabs.Tab> - - <Tabs.Tab tabId="tab2">Tab 2</Tabs.Tab> - - <Tabs.Tab tabId="tab3">Tab 3</Tabs.Tab> - </Tabs.TabList> - <Tabs.TabPanel tabId="tab1"> - <p>Selected tab: Tab 1</p> - </Tabs.TabPanel> - <Tabs.TabPanel tabId="tab2"> - <p>Selected tab: Tab 2</p> - </Tabs.TabPanel> - <Tabs.TabPanel tabId="tab3"> - <p>Selected tab: Tab 3</p> - </Tabs.TabPanel> - </Tabs> - <div style={ { marginTop: '200px' } }> - <p>Select a tab:</p> - <DropdownMenu - controls={ [ - { - onClick: () => setSelectedTabId( 'tab1' ), - title: 'Tab 1', - isActive: selectedTabId === 'tab1', - }, - { - onClick: () => setSelectedTabId( 'tab2' ), - title: 'Tab 2', - isActive: selectedTabId === 'tab2', - }, - { - onClick: () => setSelectedTabId( 'tab3' ), - title: 'Tab 3', - isActive: selectedTabId === 'tab3', - }, - ] } - label="Choose a tab. The power is yours." - /> - </div> - </> - ); -}; - -export const ControlledMode = ControlledModeTemplate.bind( {} ); -ControlledMode.args = { - selectedTabId: 'tab3', -}; - -const TabBecomesDisabledTemplate: StoryFn< typeof Tabs > = ( props ) => { - const [ disableTab2, setDisableTab2 ] = useState( false ); - - return ( - <> - <Button - variant="primary" - onClick={ () => setDisableTab2( ! disableTab2 ) } - > - { disableTab2 ? 'Enable' : 'Disable' } Tab 2 - </Button> - <Tabs { ...props }> - <Tabs.TabList> - <Tabs.Tab tabId="tab1">Tab 1</Tabs.Tab> - <Tabs.Tab tabId="tab2" disabled={ disableTab2 }> - Tab 2 - </Tabs.Tab> - <Tabs.Tab tabId="tab3">Tab 3</Tabs.Tab> - </Tabs.TabList> - <Tabs.TabPanel tabId="tab1"> - <p>Selected tab: Tab 1</p> - </Tabs.TabPanel> - <Tabs.TabPanel tabId="tab2"> - <p>Selected tab: Tab 2</p> - </Tabs.TabPanel> - <Tabs.TabPanel tabId="tab3"> - <p>Selected tab: Tab 3</p> - </Tabs.TabPanel> - </Tabs> - </> - ); -}; -export const TabBecomesDisabled = TabBecomesDisabledTemplate.bind( {} ); - -const TabGetsRemovedTemplate: StoryFn< typeof Tabs > = ( props ) => { - const [ removeTab1, setRemoveTab1 ] = useState( false ); - - return ( - <> - <Button - variant="primary" - onClick={ () => setRemoveTab1( ! removeTab1 ) } - > - { removeTab1 ? 'Restore' : 'Remove' } Tab 1 - </Button> - <Tabs { ...props }> - <Tabs.TabList> - { ! removeTab1 && <Tabs.Tab tabId="tab1">Tab 1</Tabs.Tab> } - <Tabs.Tab tabId="tab2">Tab 2</Tabs.Tab> - <Tabs.Tab tabId="tab3">Tab 3</Tabs.Tab> - </Tabs.TabList> - <Tabs.TabPanel tabId="tab1"> - <p>Selected tab: Tab 1</p> - </Tabs.TabPanel> - <Tabs.TabPanel tabId="tab2"> - <p>Selected tab: Tab 2</p> - </Tabs.TabPanel> - <Tabs.TabPanel tabId="tab3"> - <p>Selected tab: Tab 3</p> - </Tabs.TabPanel> - </Tabs> - </> - ); -}; -export const TabGetsRemoved = TabGetsRemovedTemplate.bind( {} ); diff --git a/packages/components/src/tabs/tab.tsx b/packages/components/src/tabs/tab.tsx index 70f56e52ad2627..8226d0589f08c8 100644 --- a/packages/components/src/tabs/tab.tsx +++ b/packages/components/src/tabs/tab.tsx @@ -1,8 +1,3 @@ -/** - * External dependencies - */ -import * as Ariakit from '@ariakit/react'; - /** * WordPress dependencies */ @@ -29,18 +24,6 @@ export const Tab = forwardRef< >( function Tab( { children, tabId, disabled, render, ...otherProps }, ref ) { const { store, instanceId } = useTabsContext() ?? {}; - // If the active item is not connected, the tablist may end up in a state - // where none of the tabs are tabbable. In this case, we force all tabs to - // be tabbable, so that as soon as an item received focus, it becomes active - // and Tablist goes back to working as expected. - // eslint-disable-next-line @wordpress/no-unused-vars-before-return - const tabbable = Ariakit.useStoreState( store, ( state ) => { - return ( - state?.activeId !== null && - ! store?.item( state?.activeId )?.element?.isConnected - ); - } ); - if ( ! store ) { warning( '`Tabs.Tab` must be wrapped in a `Tabs` component.' ); return null; @@ -55,7 +38,6 @@ export const Tab = forwardRef< id={ instancedTabId } disabled={ disabled } render={ render } - tabbable={ tabbable } { ...otherProps } > <StyledTabChildren>{ children }</StyledTabChildren> diff --git a/packages/components/src/tabs/test/index.tsx b/packages/components/src/tabs/test/index.tsx index dcf64102c9fa67..fd9ceb38190a79 100644 --- a/packages/components/src/tabs/test/index.tsx +++ b/packages/components/src/tabs/test/index.tsx @@ -9,6 +9,7 @@ import { render } from '@ariakit/test/react'; * WordPress dependencies */ import { useEffect, useState } from '@wordpress/element'; +import { isRTL } from '@wordpress/i18n'; /** * Internal dependencies @@ -16,6 +17,16 @@ import { useEffect, useState } from '@wordpress/element'; import { Tabs } from '..'; import type { TabsProps } from '../types'; +// Setup mocking the `isRTL` function to test arrow key navigation behavior. +jest.mock( '@wordpress/i18n', () => { + const original = jest.requireActual( '@wordpress/i18n' ); + return { + ...original, + isRTL: jest.fn( () => false ), + }; +} ); +const mockedIsRTL = isRTL as jest.Mock; + type Tab = { tabId: string; title: string; @@ -50,6 +61,30 @@ const TABS: Tab[] = [ }, ]; +const TABS_WITH_ALPHA_DISABLED = TABS.map( ( tabObj ) => + tabObj.tabId === 'alpha' + ? { + ...tabObj, + tab: { + ...tabObj.tab, + disabled: true, + }, + } + : tabObj +); + +const TABS_WITH_BETA_DISABLED = TABS.map( ( tabObj ) => + tabObj.tabId === 'beta' + ? { + ...tabObj, + tab: { + ...tabObj.tab, + disabled: true, + }, + } + : tabObj +); + const TABS_WITH_DELTA: Tab[] = [ ...TABS, { @@ -141,11 +176,47 @@ const ControlledTabs = ( { ); }; -const getSelectedTab = async () => - await screen.findByRole( 'tab', { selected: true } ); - let originalGetClientRects: () => DOMRectList; +async function waitForComponentToBeInitializedWithSelectedTab( + selectedTabName: string | undefined +) { + if ( ! selectedTabName ) { + // Wait for the tablist to be tabbable as a mean to know + // that ariakit has finished initializing. + await waitFor( () => + expect( screen.getByRole( 'tablist' ) ).toHaveAttribute( + 'tabindex', + expect.stringMatching( /^(0|-1)$/ ) + ) + ); + // No initially selected tabs or tabpanels. + await waitFor( () => + expect( + screen.queryByRole( 'tab', { selected: true } ) + ).not.toBeInTheDocument() + ); + await waitFor( () => + expect( screen.queryByRole( 'tabpanel' ) ).not.toBeInTheDocument() + ); + } else { + // Waiting for a tab to be selected is a sign that the component + // has fully initialized. + expect( + await screen.findByRole( 'tab', { + selected: true, + name: selectedTabName, + } ) + ).toBeVisible(); + // The corresponding tabpanel is also shown. + expect( + screen.getByRole( 'tabpanel', { + name: selectedTabName, + } ) + ).toBeVisible(); + } +} + describe( 'Tabs', () => { beforeAll( () => { originalGetClientRects = window.HTMLElement.prototype.getClientRects; @@ -162,13 +233,16 @@ describe( 'Tabs', () => { window.HTMLElement.prototype.getClientRects = originalGetClientRects; } ); - describe( 'Accessibility and semantics', () => { - it( 'should use the correct aria attributes', async () => { + describe( 'Adherence to spec and basic behavior', () => { + it( 'should apply the correct roles, semantics and attributes', async () => { await render( <UncontrolledTabs tabs={ TABS } /> ); + // Alpha is automatically selected as the selected tab. + await waitForComponentToBeInitializedWithSelectedTab( 'Alpha' ); + const tabList = screen.getByRole( 'tablist' ); const allTabs = screen.getAllByRole( 'tab' ); - const selectedTabPanel = await screen.findByRole( 'tabpanel' ); + const allTabpanels = screen.getAllByRole( 'tabpanel' ); expect( tabList ).toBeVisible(); expect( tabList ).toHaveAttribute( @@ -178,133 +252,103 @@ describe( 'Tabs', () => { expect( allTabs ).toHaveLength( TABS.length ); - // The selected `tab` aria-controls the active `tabpanel`, - // which is `aria-labelledby` the selected `tab`. - expect( selectedTabPanel ).toBeVisible(); + // Only 1 tab panel is accessible — the one associated with the + // selected tab. The selected `tab` aria-controls the active + /// `tabpanel`, which is `aria-labelledby` the selected `tab`. + expect( allTabpanels ).toHaveLength( 1 ); + + expect( allTabpanels[ 0 ] ).toBeVisible(); expect( allTabs[ 0 ] ).toHaveAttribute( 'aria-controls', - selectedTabPanel.getAttribute( 'id' ) + allTabpanels[ 0 ].getAttribute( 'id' ) ); - expect( selectedTabPanel ).toHaveAttribute( + expect( allTabpanels[ 0 ] ).toHaveAttribute( 'aria-labelledby', allTabs[ 0 ].getAttribute( 'id' ) ); } ); - } ); - describe( 'Focus Behavior', () => { - it( 'should focus on the related TabPanel when pressing the Tab key', async () => { - await render( <UncontrolledTabs tabs={ TABS } /> ); - expect( await getSelectedTab() ).toHaveTextContent( 'Alpha' ); - - const selectedTabPanel = await screen.findByRole( 'tabpanel' ); - - // Tab should initially focus the first tab in the tablist, which - // is Alpha. - await press.Tab(); - expect( - await screen.findByRole( 'tab', { name: 'Alpha' } ) - ).toHaveFocus(); - - // By default the tabpanel should receive focus - await press.Tab(); - expect( selectedTabPanel ).toHaveFocus(); - } ); - it( 'should not focus on the related TabPanel when pressing the Tab key if `focusable: false` is set', async () => { - const TABS_WITH_ALPHA_FOCUSABLE_FALSE = TABS.map( ( tabObj ) => - tabObj.tabId === 'alpha' - ? { - ...tabObj, - content: ( - <> - Selected Tab: Alpha - <button>Alpha Button</button> - </> - ), - tabpanel: { focusable: false }, - } - : tabObj - ); + it( 'should associate each `tab` with the correct `tabpanel`, even if they are not rendered in the same order', async () => { + const TABS_WITH_DELTA_REVERSED = [ ...TABS_WITH_DELTA ].reverse(); await render( - <UncontrolledTabs tabs={ TABS_WITH_ALPHA_FOCUSABLE_FALSE } /> + <Tabs> + <Tabs.TabList> + { TABS_WITH_DELTA.map( ( tabObj ) => ( + <Tabs.Tab + key={ tabObj.tabId } + tabId={ tabObj.tabId } + className={ tabObj.tab.className } + disabled={ tabObj.tab.disabled } + > + { tabObj.title } + </Tabs.Tab> + ) ) } + </Tabs.TabList> + { TABS_WITH_DELTA_REVERSED.map( ( tabObj ) => ( + <Tabs.TabPanel + key={ tabObj.tabId } + tabId={ tabObj.tabId } + focusable={ tabObj.tabpanel?.focusable } + > + { tabObj.content } + </Tabs.TabPanel> + ) ) } + </Tabs> ); - expect( await getSelectedTab() ).toHaveTextContent( 'Alpha' ); - - const alphaButton = await screen.findByRole( 'button', { - name: /alpha button/i, - } ); + // Alpha is automatically selected as the selected tab. + await waitForComponentToBeInitializedWithSelectedTab( 'Alpha' ); - // Tab should initially focus the first tab in the tablist, which - // is Alpha. - await press.Tab(); + // Select Beta, make sure the correct tabpanel is rendered + await click( screen.getByRole( 'tab', { name: 'Beta' } ) ); expect( - await screen.findByRole( 'tab', { name: 'Alpha' } ) - ).toHaveFocus(); - // Because the alpha tabpanel is set to `focusable: false`, pressing - // the Tab key should focus the button, not the tabpanel - await press.Tab(); - expect( alphaButton ).toHaveFocus(); - } ); - - it( "should focus the first tab, even if disabled, when the current selected tab id doesn't match an existing one", async () => { - const TABS_WITH_ALPHA_DISABLED = TABS.map( ( tabObj ) => - tabObj.tabId === 'alpha' - ? { - ...tabObj, - tab: { - ...tabObj.tab, - disabled: true, - }, - } - : tabObj - ); - - await render( - <ControlledTabs - tabs={ TABS_WITH_ALPHA_DISABLED } - selectedTabId="non-existing-tab" - /> - ); - - // No tab should be selected i.e. it doesn't fall back to first tab. - await waitFor( () => - expect( - screen.queryByRole( 'tab', { selected: true } ) - ).not.toBeInTheDocument() - ); - - // No tabpanel should be rendered either - expect( screen.queryByRole( 'tabpanel' ) ).not.toBeInTheDocument(); - - await press.Tab(); + screen.getByRole( 'tab', { + selected: true, + name: 'Beta', + } ) + ).toBeVisible(); expect( - await screen.findByRole( 'tab', { name: 'Alpha' } ) - ).toHaveFocus(); + screen.getByRole( 'tabpanel', { + name: 'Beta', + } ) + ).toBeVisible(); - await press.ArrowRight(); + // Select Gamma, make sure the correct tabpanel is rendered + await click( screen.getByRole( 'tab', { name: 'Gamma' } ) ); expect( - await screen.findByRole( 'tab', { name: 'Beta' } ) - ).toHaveFocus(); - - await press.ArrowRight(); + screen.getByRole( 'tab', { + selected: true, + name: 'Gamma', + } ) + ).toBeVisible(); expect( - await screen.findByRole( 'tab', { name: 'Gamma' } ) - ).toHaveFocus(); + screen.getByRole( 'tabpanel', { + name: 'Gamma', + } ) + ).toBeVisible(); - await press.Tab(); - await press.ShiftTab(); + // Select Delta, make sure the correct tabpanel is rendered + await click( screen.getByRole( 'tab', { name: 'Delta' } ) ); + expect( + screen.getByRole( 'tab', { + selected: true, + name: 'Delta', + } ) + ).toBeVisible(); expect( - await screen.findByRole( 'tab', { name: 'Gamma' } ) - ).toHaveFocus(); + screen.getByRole( 'tabpanel', { + name: 'Delta', + } ) + ).toBeVisible(); } ); - } ); - describe( 'Tab Attributes', () => { it( "should apply the tab's `className` to the tab button", async () => { await render( <UncontrolledTabs tabs={ TABS } /> ); + // Alpha is automatically selected as the selected tab. + await waitForComponentToBeInitializedWithSelectedTab( 'Alpha' ); + expect( await screen.findByRole( 'tab', { name: 'Alpha' } ) ).toHaveClass( 'alpha-class' ); @@ -317,908 +361,1076 @@ describe( 'Tabs', () => { } ); } ); - describe( 'Tab Activation', () => { - it( 'defaults to automatic tab activation (pointer clicks)', async () => { + describe( 'pointer interactions', () => { + it( 'should select a tab when clicked', async () => { const mockOnSelect = jest.fn(); await render( <UncontrolledTabs tabs={ TABS } onSelect={ mockOnSelect } /> ); - // Alpha is the initially selected tab - expect( await getSelectedTab() ).toHaveTextContent( 'Alpha' ); - expect( - await screen.findByRole( 'tabpanel', { name: 'Alpha' } ) - ).toBeInTheDocument(); + // Alpha is automatically selected as the selected tab. + await waitForComponentToBeInitializedWithSelectedTab( 'Alpha' ); + + expect( mockOnSelect ).toHaveBeenCalledTimes( 1 ); expect( mockOnSelect ).toHaveBeenLastCalledWith( 'alpha' ); // Click on Beta, make sure beta is the selected tab await click( screen.getByRole( 'tab', { name: 'Beta' } ) ); - expect( await getSelectedTab() ).toHaveTextContent( 'Beta' ); expect( - screen.getByRole( 'tabpanel', { name: 'Beta' } ) - ).toBeInTheDocument(); + screen.getByRole( 'tab', { + selected: true, + name: 'Beta', + } ) + ).toBeVisible(); + expect( + screen.getByRole( 'tabpanel', { + name: 'Beta', + } ) + ).toBeVisible(); + + expect( mockOnSelect ).toHaveBeenCalledTimes( 2 ); expect( mockOnSelect ).toHaveBeenLastCalledWith( 'beta' ); - // Click on Alpha, make sure beta is the selected tab + // Click on Alpha, make sure alpha is the selected tab await click( screen.getByRole( 'tab', { name: 'Alpha' } ) ); - expect( await getSelectedTab() ).toHaveTextContent( 'Alpha' ); expect( - screen.getByRole( 'tabpanel', { name: 'Alpha' } ) - ).toBeInTheDocument(); - expect( mockOnSelect ).toHaveBeenLastCalledWith( 'alpha' ); - } ); - - it( 'defaults to automatic tab activation (arrow keys)', async () => { - const mockOnSelect = jest.fn(); - - await render( - <UncontrolledTabs tabs={ TABS } onSelect={ mockOnSelect } /> - ); - - expect( await getSelectedTab() ).toHaveTextContent( 'Alpha' ); - - // onSelect gets called on the initial render. It should be called - // with the first enabled tab, which is alpha. - expect( mockOnSelect ).toHaveBeenCalledTimes( 1 ); - expect( mockOnSelect ).toHaveBeenLastCalledWith( 'alpha' ); - - // Tab to focus the tablist. Make sure alpha is focused. - expect( await getSelectedTab() ).toHaveTextContent( 'Alpha' ); - expect( await getSelectedTab() ).not.toHaveFocus(); - await press.Tab(); - expect( await getSelectedTab() ).toHaveFocus(); - - // Navigate forward with arrow keys and make sure the Beta tab is - // selected automatically. - await press.ArrowRight(); - expect( await getSelectedTab() ).toHaveTextContent( 'Beta' ); - expect( await getSelectedTab() ).toHaveFocus(); - expect( mockOnSelect ).toHaveBeenCalledTimes( 2 ); - expect( mockOnSelect ).toHaveBeenLastCalledWith( 'beta' ); + screen.getByRole( 'tab', { + selected: true, + name: 'Alpha', + } ) + ).toBeVisible(); + expect( + screen.getByRole( 'tabpanel', { + name: 'Alpha', + } ) + ).toBeVisible(); - // Navigate backwards with arrow keys. Make sure alpha is - // selected automatically. - await press.ArrowLeft(); - expect( await getSelectedTab() ).toHaveTextContent( 'Alpha' ); - expect( await getSelectedTab() ).toHaveFocus(); expect( mockOnSelect ).toHaveBeenCalledTimes( 3 ); expect( mockOnSelect ).toHaveBeenLastCalledWith( 'alpha' ); } ); - it( 'wraps around the last/first tab when using arrow keys', async () => { + it( 'should not select a disabled tab when clicked', async () => { const mockOnSelect = jest.fn(); await render( - <UncontrolledTabs tabs={ TABS } onSelect={ mockOnSelect } /> + <UncontrolledTabs + tabs={ TABS_WITH_BETA_DISABLED } + onSelect={ mockOnSelect } + /> ); - expect( await getSelectedTab() ).toHaveTextContent( 'Alpha' ); - expect( await getSelectedTab() ).not.toHaveFocus(); + // Alpha is automatically selected as the selected tab. + await waitForComponentToBeInitializedWithSelectedTab( 'Alpha' ); - // onSelect gets called on the initial render. expect( mockOnSelect ).toHaveBeenCalledTimes( 1 ); expect( mockOnSelect ).toHaveBeenLastCalledWith( 'alpha' ); - // Tab to focus the tablist. Make sure Alpha is focused. - await press.Tab(); - expect( await getSelectedTab() ).toHaveFocus(); + // Clicking on Beta does not result in beta being selected + // because the tab is disabled. + await click( screen.getByRole( 'tab', { name: 'Beta' } ) ); - // Navigate backwards with arrow keys and make sure that the Gamma tab - // (the last tab) is selected automatically. - await press.ArrowLeft(); - expect( await getSelectedTab() ).toHaveTextContent( 'Gamma' ); - expect( await getSelectedTab() ).toHaveFocus(); - expect( mockOnSelect ).toHaveBeenCalledTimes( 2 ); - expect( mockOnSelect ).toHaveBeenLastCalledWith( 'gamma' ); + expect( + screen.getByRole( 'tab', { + selected: true, + name: 'Alpha', + } ) + ).toBeVisible(); + expect( + screen.getByRole( 'tabpanel', { + name: 'Alpha', + } ) + ).toBeVisible(); - // Navigate forward with arrow keys. Make sure alpha (the first tab) is - // selected automatically. - await press.ArrowRight(); - expect( await getSelectedTab() ).toHaveTextContent( 'Alpha' ); - expect( await getSelectedTab() ).toHaveFocus(); - expect( mockOnSelect ).toHaveBeenCalledTimes( 3 ); - expect( mockOnSelect ).toHaveBeenLastCalledWith( 'alpha' ); + expect( mockOnSelect ).toHaveBeenCalledTimes( 1 ); } ); + } ); - it( 'should not move tab selection when pressing the up/down arrow keys, unless the orientation is changed to `vertical`', async () => { - const mockOnSelect = jest.fn(); + describe( 'initial tab selection', () => { + describe( 'when a selected tab id is not specified', () => { + describe( 'when left `undefined` [Uncontrolled]', () => { + it( 'should choose the first tab as selected', async () => { + await render( <UncontrolledTabs tabs={ TABS } /> ); - const { rerender } = await render( - <UncontrolledTabs tabs={ TABS } onSelect={ mockOnSelect } /> - ); + // Alpha is automatically selected as the selected tab. + await waitForComponentToBeInitializedWithSelectedTab( + 'Alpha' + ); - expect( await getSelectedTab() ).toHaveTextContent( 'Alpha' ); - expect( await getSelectedTab() ).not.toHaveFocus(); + // Press tab. The selected tab (alpha) received focus. + await press.Tab(); + expect( + await screen.findByRole( 'tab', { + selected: true, + name: 'Alpha', + } ) + ).toHaveFocus(); + } ); + + it( 'should choose the first non-disabled tab if the first tab is disabled', async () => { + await render( + <UncontrolledTabs tabs={ TABS_WITH_ALPHA_DISABLED } /> + ); + + // Beta is automatically selected as the selected tab, since alpha is + // disabled. + await waitForComponentToBeInitializedWithSelectedTab( + 'Beta' + ); + + // Press tab. The selected tab (beta) received focus. The corresponding + // tabpanel is shown. + await press.Tab(); + expect( + await screen.findByRole( 'tab', { + selected: true, + name: 'Beta', + } ) + ).toHaveFocus(); + } ); + } ); + describe( 'when `null` [Controlled]', () => { + it( 'should not have a selected tab nor show any tabpanels, make the tablist tabbable and still allow selecting tabs', async () => { + await render( + <ControlledTabs tabs={ TABS } selectedTabId={ null } /> + ); + + // No initially selected tabs or tabpanels. + await waitForComponentToBeInitializedWithSelectedTab( + undefined + ); + + // Press tab. The tablist receives focus + await press.Tab(); + expect( + await screen.findByRole( 'tablist' ) + ).toHaveFocus(); - // onSelect gets called on the initial render. - expect( mockOnSelect ).toHaveBeenCalledTimes( 1 ); - expect( mockOnSelect ).toHaveBeenLastCalledWith( 'alpha' ); + // Press right arrow to select the first tab (alpha) and + // show the related tabpanel. + await press.ArrowRight(); + expect( + await screen.findByRole( 'tab', { + selected: true, + name: 'Alpha', + } ) + ).toHaveFocus(); + expect( + await screen.findByRole( 'tabpanel', { + name: 'Alpha', + } ) + ).toBeVisible(); + } ); + } ); + } ); - // Tab to focus the tablist. Make sure Alpha is focused. - await press.Tab(); - expect( await getSelectedTab() ).toHaveFocus(); + describe( 'when a selected tab id is specified', () => { + describe( 'through the `defaultTabId` prop [Uncontrolled]', () => { + it( 'should select the initial tab matching the `defaultTabId` prop', async () => { + await render( + <UncontrolledTabs tabs={ TABS } defaultTabId="beta" /> + ); + + // Beta is the initially selected tab + await waitForComponentToBeInitializedWithSelectedTab( + 'Beta' + ); + + // Press tab. The selected tab (beta) received focus. The corresponding + // tabpanel is shown. + await press.Tab(); + expect( + await screen.findByRole( 'tab', { + selected: true, + name: 'Beta', + } ) + ).toHaveFocus(); + } ); + + it( 'should select the initial tab matching the `defaultTabId` prop even if the tab is disabled', async () => { + await render( + <UncontrolledTabs + tabs={ TABS_WITH_BETA_DISABLED } + defaultTabId="beta" + /> + ); + + // Beta is automatically selected as the selected tab despite being + // disabled, respecting the `defaultTabId` prop. + await waitForComponentToBeInitializedWithSelectedTab( + 'Beta' + ); + + // Press tab. The selected tab (beta) received focus, since it is + // accessible despite being disabled. + await press.Tab(); + expect( + await screen.findByRole( 'tab', { + selected: true, + name: 'Beta', + } ) + ).toHaveFocus(); + } ); + + it( 'should not have a selected tab nor show any tabpanels, but allow tabbing to the first tab when `defaultTabId` prop does not match any known tab', async () => { + await render( + <UncontrolledTabs + tabs={ TABS } + defaultTabId="non-existing-tab" + /> + ); + + // No initially selected tabs or tabpanels, since the `defaultTabId` + // prop is not matching any known tabs. + await waitForComponentToBeInitializedWithSelectedTab( + undefined + ); + + // Press tab. The first tab receives focus, but it's + // not selected. + await press.Tab(); + expect( + screen.getByRole( 'tab', { name: 'Alpha' } ) + ).toHaveFocus(); + await waitFor( () => + expect( + screen.queryByRole( 'tab', { selected: true } ) + ).not.toBeInTheDocument() + ); + await waitFor( () => + expect( + screen.queryByRole( 'tabpanel' ) + ).not.toBeInTheDocument() + ); - // Press the arrow up key, nothing happens. - await press.ArrowUp(); - expect( await getSelectedTab() ).toHaveTextContent( 'Alpha' ); - expect( await getSelectedTab() ).toHaveFocus(); - expect( mockOnSelect ).toHaveBeenCalledTimes( 1 ); - expect( mockOnSelect ).toHaveBeenLastCalledWith( 'alpha' ); + // Press right arrow to select the next tab (beta) and + // show the related tabpanel. + await press.ArrowRight(); + expect( + await screen.findByRole( 'tab', { + selected: true, + name: 'Beta', + } ) + ).toHaveFocus(); + expect( + await screen.findByRole( 'tabpanel', { + name: 'Beta', + } ) + ).toBeVisible(); + } ); + + it( 'should not have a selected tab nor show any tabpanels, but allow tabbing to the first tab, even when disabled, when `defaultTabId` prop does not match any known tab', async () => { + await render( + <UncontrolledTabs + tabs={ TABS_WITH_ALPHA_DISABLED } + defaultTabId="non-existing-tab" + /> + ); + + // No initially selected tabs or tabpanels, since the `defaultTabId` + // prop is not matching any known tabs. + await waitForComponentToBeInitializedWithSelectedTab( + undefined + ); + + // Press tab. The first tab receives focus, but it's + // not selected. + await press.Tab(); + expect( + screen.getByRole( 'tab', { name: 'Alpha' } ) + ).toHaveFocus(); + await waitFor( () => + expect( + screen.queryByRole( 'tab', { selected: true } ) + ).not.toBeInTheDocument() + ); + await waitFor( () => + expect( + screen.queryByRole( 'tabpanel' ) + ).not.toBeInTheDocument() + ); - // Press the arrow down key, nothing happens - await press.ArrowDown(); - expect( await getSelectedTab() ).toHaveTextContent( 'Alpha' ); - expect( await getSelectedTab() ).toHaveFocus(); - expect( mockOnSelect ).toHaveBeenCalledTimes( 1 ); - expect( mockOnSelect ).toHaveBeenLastCalledWith( 'alpha' ); + // Press right arrow to select the next tab (beta) and + // show the related tabpanel. + await press.ArrowRight(); + expect( + await screen.findByRole( 'tab', { + selected: true, + name: 'Beta', + } ) + ).toHaveFocus(); + expect( + await screen.findByRole( 'tabpanel', { + name: 'Beta', + } ) + ).toBeVisible(); + } ); + + it( 'should ignore any changes to the `defaultTabId` prop after the first render', async () => { + const mockOnSelect = jest.fn(); + + const { rerender } = await render( + <UncontrolledTabs + tabs={ TABS } + defaultTabId="beta" + onSelect={ mockOnSelect } + /> + ); + + // Beta is the initially selected tab + await waitForComponentToBeInitializedWithSelectedTab( + 'Beta' + ); + + // Changing the defaultTabId prop to gamma should not have any effect. + await rerender( + <UncontrolledTabs + tabs={ TABS } + defaultTabId="gamma" + onSelect={ mockOnSelect } + /> + ); - // Change orientation to `vertical`. When the orientation is vertical, - // left/right arrow keys are replaced by up/down arrow keys. - await rerender( - <UncontrolledTabs - tabs={ TABS } - onSelect={ mockOnSelect } - orientation="vertical" - /> - ); + expect( + await screen.findByRole( 'tab', { + selected: true, + name: 'Beta', + } ) + ).toBeVisible(); + expect( + screen.getByRole( 'tabpanel', { + name: 'Beta', + } ) + ).toBeVisible(); - expect( screen.getByRole( 'tablist' ) ).toHaveAttribute( - 'aria-orientation', - 'vertical' - ); + expect( mockOnSelect ).not.toHaveBeenCalled(); + } ); + } ); - // Make sure alpha is still focused. - expect( await getSelectedTab() ).toHaveTextContent( 'Alpha' ); - expect( await getSelectedTab() ).toHaveFocus(); + describe( 'through the `selectedTabId` prop [Controlled]', () => { + describe( 'when the `selectedTabId` matches an existing tab', () => { + it( 'should choose the initial tab matching the `selectedTabId`', async () => { + await render( + <ControlledTabs + tabs={ TABS } + selectedTabId="beta" + /> + ); - // Navigate forward with arrow keys and make sure the Beta tab is - // selected automatically. - await press.ArrowDown(); - expect( await getSelectedTab() ).toHaveTextContent( 'Beta' ); - expect( await getSelectedTab() ).toHaveFocus(); - expect( mockOnSelect ).toHaveBeenCalledTimes( 2 ); - expect( mockOnSelect ).toHaveBeenLastCalledWith( 'beta' ); + // Beta is the initially selected tab + await waitForComponentToBeInitializedWithSelectedTab( + 'Beta' + ); - // Navigate backwards with arrow keys. Make sure alpha is - // selected automatically. - await press.ArrowUp(); - expect( await getSelectedTab() ).toHaveTextContent( 'Alpha' ); - expect( await getSelectedTab() ).toHaveFocus(); - expect( mockOnSelect ).toHaveBeenCalledTimes( 3 ); - expect( mockOnSelect ).toHaveBeenLastCalledWith( 'alpha' ); + // Press tab. The selected tab (beta) received focus, since it is + // accessible despite being disabled. + await press.Tab(); + expect( + await screen.findByRole( 'tab', { + selected: true, + name: 'Beta', + } ) + ).toHaveFocus(); + } ); - // Navigate backwards with arrow keys. Make sure alpha is - // selected automatically. - await press.ArrowUp(); - expect( await getSelectedTab() ).toHaveTextContent( 'Gamma' ); - expect( await getSelectedTab() ).toHaveFocus(); - expect( mockOnSelect ).toHaveBeenCalledTimes( 4 ); - expect( mockOnSelect ).toHaveBeenLastCalledWith( 'gamma' ); - - // Navigate backwards with arrow keys. Make sure alpha is - // selected automatically. - await press.ArrowDown(); - expect( await getSelectedTab() ).toHaveTextContent( 'Alpha' ); - expect( await getSelectedTab() ).toHaveFocus(); - expect( mockOnSelect ).toHaveBeenCalledTimes( 5 ); - expect( mockOnSelect ).toHaveBeenLastCalledWith( 'alpha' ); - } ); + it( 'should choose the initial tab matching the `selectedTabId` even if a `defaultTabId` is passed', async () => { + await render( + <ControlledTabs + tabs={ TABS } + defaultTabId="beta" + selectedTabId="gamma" + /> + ); - it( 'should move focus on a tab even if disabled with arrow key, but not with pointer clicks', async () => { - const mockOnSelect = jest.fn(); + // Gamma is the initially selected tab + await waitForComponentToBeInitializedWithSelectedTab( + 'Gamma' + ); - const TABS_WITH_DELTA_DISABLED = TABS_WITH_DELTA.map( ( tabObj ) => - tabObj.tabId === 'delta' - ? { - ...tabObj, - tab: { - ...tabObj.tab, - disabled: true, - }, - } - : tabObj - ); + // Press tab. The selected tab (gamma) received focus, since it is + // accessible despite being disabled. + await press.Tab(); + expect( + await screen.findByRole( 'tab', { + selected: true, + name: 'Gamma', + } ) + ).toHaveFocus(); + } ); - await render( - <UncontrolledTabs - tabs={ TABS_WITH_DELTA_DISABLED } - onSelect={ mockOnSelect } - /> - ); + it( 'should choose the initial tab matching the `selectedTabId` even if the tab is disabled', async () => { + await render( + <ControlledTabs + tabs={ TABS_WITH_BETA_DISABLED } + selectedTabId="beta" + /> + ); - expect( await getSelectedTab() ).toHaveTextContent( 'Alpha' ); - expect( await getSelectedTab() ).not.toHaveFocus(); + // Beta is the initially selected tab + await waitForComponentToBeInitializedWithSelectedTab( + 'Beta' + ); - // onSelect gets called on the initial render. - expect( mockOnSelect ).toHaveBeenCalledTimes( 1 ); - expect( mockOnSelect ).toHaveBeenLastCalledWith( 'alpha' ); + // Press tab. The selected tab (beta) received focus, since it is + // accessible despite being disabled. + await press.Tab(); + expect( + await screen.findByRole( 'tab', { + selected: true, + name: 'Beta', + } ) + ).toHaveFocus(); + } ); + } ); - // Tab to focus the tablist. Make sure Alpha is focused. - await press.Tab(); - expect( await getSelectedTab() ).toHaveFocus(); + describe( "when the `selectedTabId` doesn't match an existing tab", () => { + it( 'should not have a selected tab nor show any tabpanels, but allow tabbing to the first tab', async () => { + await render( + <ControlledTabs + tabs={ TABS } + selectedTabId="non-existing-tab" + /> + ); - // Confirm onSelect has not been re-called - expect( mockOnSelect ).toHaveBeenCalledTimes( 1 ); + // No initially selected tabs or tabpanels, since the `selectedTabId` + // prop is not matching any known tabs. + await waitForComponentToBeInitializedWithSelectedTab( + undefined + ); - // Press the right arrow key three times. Since the delta tab is disabled: - // - it won't be selected. The gamma tab will be selected instead, since - // it was the tab that was last selected before delta. Therefore, the - // `mockOnSelect` function gets called only twice (and not three times) - // - it will receive focus, when using arrow keys - await press.ArrowRight(); - await press.ArrowRight(); - await press.ArrowRight(); - expect( await getSelectedTab() ).toHaveTextContent( 'Gamma' ); - expect( - screen.getByRole( 'tab', { name: 'Delta' } ) - ).toHaveFocus(); - expect( mockOnSelect ).toHaveBeenCalledTimes( 3 ); - expect( mockOnSelect ).toHaveBeenLastCalledWith( 'gamma' ); - - // Navigate backwards with arrow keys. The gamma tab receives focus. - // The `mockOnSelect` callback doesn't fire, since the gamma tab was - // already selected. - await press.ArrowLeft(); - expect( await getSelectedTab() ).toHaveTextContent( 'Gamma' ); - expect( await getSelectedTab() ).toHaveFocus(); - expect( mockOnSelect ).toHaveBeenCalledTimes( 3 ); + // Press tab. The first tab receives focus, but it's + // not selected. + await press.Tab(); + expect( + screen.getByRole( 'tab', { name: 'Alpha' } ) + ).toHaveFocus(); + await waitFor( () => + expect( + screen.queryByRole( 'tab', { selected: true } ) + ).not.toBeInTheDocument() + ); + await waitFor( () => + expect( + screen.queryByRole( 'tabpanel' ) + ).not.toBeInTheDocument() + ); - // Click on the disabled tab. Compared to using arrow keys to move the - // focus, disabled tabs ignore pointer clicks — and therefore, they don't - // receive focus, nor they cause the `mockOnSelect` function to fire. - await click( screen.getByRole( 'tab', { name: 'Delta' } ) ); - expect( await getSelectedTab() ).toHaveTextContent( 'Gamma' ); - expect( await getSelectedTab() ).toHaveFocus(); - expect( mockOnSelect ).toHaveBeenCalledTimes( 3 ); - } ); + // Press right arrow to select the next tab (beta) and + // show the related tabpanel. + await press.ArrowRight(); + expect( + await screen.findByRole( 'tab', { + selected: true, + name: 'Beta', + } ) + ).toHaveFocus(); + expect( + await screen.findByRole( 'tabpanel', { + name: 'Beta', + } ) + ).toBeVisible(); + } ); - it( 'should not focus the next tab when the Tab key is pressed', async () => { - await render( <UncontrolledTabs tabs={ TABS } /> ); + it( 'should not have a selected tab nor show any tabpanels, but allow tabbing to the first tab even when disabled', async () => { + await render( + <ControlledTabs + tabs={ TABS_WITH_ALPHA_DISABLED } + selectedTabId="non-existing-tab" + /> + ); - expect( await getSelectedTab() ).toHaveTextContent( 'Alpha' ); - expect( await getSelectedTab() ).not.toHaveFocus(); + // No initially selected tabs or tabpanels, since the `selectedTabId` + // prop is not matching any known tabs. + await waitForComponentToBeInitializedWithSelectedTab( + undefined + ); - // Tab should initially focus the first tab in the tablist, which - // is Alpha. - await press.Tab(); - expect( - await screen.findByRole( 'tab', { name: 'Alpha' } ) - ).toHaveFocus(); + // Press tab. The first tab receives focus, but it's + // not selected. + await press.Tab(); + expect( + screen.getByRole( 'tab', { name: 'Alpha' } ) + ).toHaveFocus(); + await waitFor( () => + expect( + screen.queryByRole( 'tab', { selected: true } ) + ).not.toBeInTheDocument() + ); + await waitFor( () => + expect( + screen.queryByRole( 'tabpanel' ) + ).not.toBeInTheDocument() + ); - // Because all other tabs should have `tabindex=-1`, pressing Tab - // should NOT move the focus to the next tab, which is Beta. - // Instead, focus should go to the currently selected tabpanel (alpha). - await press.Tab(); - expect( - await screen.findByRole( 'tabpanel', { - name: 'Alpha', - } ) - ).toHaveFocus(); + // Press right arrow to select the next tab (beta) and + // show the related tabpanel. + await press.ArrowRight(); + expect( + await screen.findByRole( 'tab', { + selected: true, + name: 'Beta', + } ) + ).toHaveFocus(); + expect( + await screen.findByRole( 'tabpanel', { + name: 'Beta', + } ) + ).toBeVisible(); + } ); + } ); + } ); } ); + } ); - it( 'switches to manual tab activation when the `selectOnMove` prop is set to `false`', async () => { - const mockOnSelect = jest.fn(); + describe( 'keyboard interactions', () => { + describe.each( [ + [ 'Uncontrolled', UncontrolledTabs ], + [ 'Controlled', ControlledTabs ], + ] )( '[`%s`]', ( _mode, Component ) => { + it( 'should handle the tablist as one tab stop', async () => { + await render( <Component tabs={ TABS } /> ); - await render( - <UncontrolledTabs - tabs={ TABS } - onSelect={ mockOnSelect } - selectOnMove={ false } - /> - ); + // Alpha is automatically selected as the selected tab. + await waitForComponentToBeInitializedWithSelectedTab( 'Alpha' ); - expect( await getSelectedTab() ).toHaveTextContent( 'Alpha' ); - expect( await getSelectedTab() ).not.toHaveFocus(); + // Press tab. The selected tab (alpha) received focus. + await press.Tab(); + expect( + await screen.findByRole( 'tab', { + selected: true, + name: 'Alpha', + } ) + ).toHaveFocus(); - // onSelect gets called on the initial render. - expect( mockOnSelect ).toHaveBeenCalledTimes( 1 ); - expect( mockOnSelect ).toHaveBeenLastCalledWith( 'alpha' ); + // By default the tabpanel should receive focus + await press.Tab(); + expect( + await screen.findByRole( 'tabpanel', { + name: 'Alpha', + } ) + ).toHaveFocus(); + } ); - // Click on Alpha and make sure it is selected. - // onSelect shouldn't fire since the selected tab didn't change. - await click( screen.getByRole( 'tab', { name: 'Alpha' } ) ); - expect( - await screen.findByRole( 'tab', { name: 'Alpha' } ) - ).toHaveFocus(); - expect( mockOnSelect ).toHaveBeenCalledTimes( 1 ); - expect( mockOnSelect ).toHaveBeenLastCalledWith( 'alpha' ); - - // Navigate forward with arrow keys. Make sure Beta is focused, but - // that the tab selection happens only when pressing the spacebar - // or enter key. onSelect shouldn't fire since the selected tab - // didn't change. - await press.ArrowRight(); - expect( - await screen.findByRole( 'tab', { name: 'Beta' } ) - ).toHaveFocus(); - expect( mockOnSelect ).toHaveBeenCalledTimes( 1 ); - - await press.Enter(); - expect( mockOnSelect ).toHaveBeenCalledTimes( 2 ); - expect( mockOnSelect ).toHaveBeenLastCalledWith( 'beta' ); + it( 'should not focus the tabpanel container when its `focusable` property is set to `false`', async () => { + await render( + <Component + tabs={ TABS.map( ( tabObj ) => + tabObj.tabId === 'alpha' + ? { + ...tabObj, + content: ( + <> + Selected Tab: Alpha + <button>Alpha Button</button> + </> + ), + tabpanel: { focusable: false }, + } + : tabObj + ) } + /> + ); - // Navigate forward with arrow keys. Make sure Gamma (last tab) is - // focused, but that tab selection happens only when pressing the - // spacebar or enter key. onSelect shouldn't fire since the selected - // tab didn't change. - await press.ArrowRight(); - expect( - await screen.findByRole( 'tab', { name: 'Gamma' } ) - ).toHaveFocus(); - expect( mockOnSelect ).toHaveBeenCalledTimes( 2 ); - expect( - screen.getByRole( 'tab', { name: 'Gamma' } ) - ).toHaveFocus(); + // Alpha is automatically selected as the selected tab. + await waitForComponentToBeInitializedWithSelectedTab( 'Alpha' ); - await press.Space(); - expect( mockOnSelect ).toHaveBeenCalledTimes( 3 ); - expect( mockOnSelect ).toHaveBeenLastCalledWith( 'gamma' ); - } ); - } ); - describe( 'Uncontrolled mode', () => { - describe( 'Without `defaultTabId` prop', () => { - it( 'should render first tab', async () => { - await render( <UncontrolledTabs tabs={ TABS } /> ); + // Tab should initially focus the first tab in the tablist, which + // is Alpha. + await press.Tab(); + expect( + await screen.findByRole( 'tab', { + selected: true, + name: 'Alpha', + } ) + ).toHaveFocus(); - expect( await getSelectedTab() ).toHaveTextContent( 'Alpha' ); + // In this case, the tabpanel container is skipped and focus is + // moved directly to its contents + await press.Tab(); expect( - await screen.findByRole( 'tabpanel', { name: 'Alpha' } ) - ).toBeInTheDocument(); + await screen.findByRole( 'button', { + name: 'Alpha Button', + } ) + ).toHaveFocus(); } ); - it( 'should not have a selected tab if the currently selected tab is removed', async () => { - const { rerender } = await render( - <UncontrolledTabs tabs={ TABS } /> - ); - expect( await getSelectedTab() ).toHaveTextContent( 'Alpha' ); - expect( await getSelectedTab() ).not.toHaveFocus(); + it( 'should select tabs in the tablist when using the left and right arrow keys by default (automatic tab activation)', async () => { + const mockOnSelect = jest.fn(); - // Tab to focus the tablist. Make sure Alpha is focused. - await press.Tab(); - expect( await getSelectedTab() ).toHaveFocus(); + await render( + <Component tabs={ TABS } onSelect={ mockOnSelect } /> + ); - // Remove first item from `TABS` array - await rerender( <UncontrolledTabs tabs={ TABS.slice( 1 ) } /> ); + // Alpha is automatically selected as the selected tab. + await waitForComponentToBeInitializedWithSelectedTab( 'Alpha' ); - // No tab should be selected i.e. it doesn't fall back to first tab. - await waitFor( () => - expect( - screen.queryByRole( 'tab', { selected: true } ) - ).not.toBeInTheDocument() - ); + expect( mockOnSelect ).toHaveBeenCalledTimes( 1 ); + expect( mockOnSelect ).toHaveBeenLastCalledWith( 'alpha' ); - // No tabpanel should be rendered either + // Focus the tablist (and the selected tab, alpha) + // Tab should initially focus the first tab in the tablist, which + // is Alpha. + await press.Tab(); expect( - screen.queryByRole( 'tabpanel' ) - ).not.toBeInTheDocument(); - } ); - } ); + await screen.findByRole( 'tab', { + selected: true, + name: 'Alpha', + } ) + ).toHaveFocus(); - describe( 'With `defaultTabId`', () => { - it( 'should render the tab set by `defaultTabId` prop', async () => { - await render( - <UncontrolledTabs tabs={ TABS } defaultTabId="beta" /> - ); + // Press the right arrow key to select the beta tab + await press.ArrowRight(); - expect( await getSelectedTab() ).toHaveTextContent( 'Beta' ); - } ); + expect( + screen.getByRole( 'tab', { + selected: true, + name: 'Beta', + } ) + ).toHaveFocus(); + expect( + screen.getByRole( 'tabpanel', { + name: 'Beta', + } ) + ).toBeVisible(); - it( 'should not select a tab when `defaultTabId` does not match any known tab', async () => { - await render( - <UncontrolledTabs - tabs={ TABS } - defaultTabId="does-not-exist" - /> - ); + expect( mockOnSelect ).toHaveBeenCalledTimes( 2 ); + expect( mockOnSelect ).toHaveBeenLastCalledWith( 'beta' ); - // No tab should be selected i.e. it doesn't fall back to first tab. - expect( - screen.queryByRole( 'tab', { selected: true } ) - ).not.toBeInTheDocument(); + // Press the right arrow key to select the gamma tab + await press.ArrowRight(); - // No tabpanel should be rendered either expect( - screen.queryByRole( 'tabpanel' ) - ).not.toBeInTheDocument(); - } ); - it( 'should not change tabs when defaultTabId is changed', async () => { - const { rerender } = await render( - <UncontrolledTabs tabs={ TABS } defaultTabId="beta" /> - ); + screen.getByRole( 'tab', { + selected: true, + name: 'Gamma', + } ) + ).toHaveFocus(); + expect( + screen.getByRole( 'tabpanel', { + name: 'Gamma', + } ) + ).toBeVisible(); - expect( await getSelectedTab() ).toHaveTextContent( 'Beta' ); + expect( mockOnSelect ).toHaveBeenCalledTimes( 3 ); + expect( mockOnSelect ).toHaveBeenLastCalledWith( 'gamma' ); - await rerender( - <UncontrolledTabs tabs={ TABS } defaultTabId="alpha" /> - ); + // Press the left arrow key to select the beta tab + await press.ArrowLeft(); - expect( await getSelectedTab() ).toHaveTextContent( 'Beta' ); + expect( + screen.getByRole( 'tab', { + selected: true, + name: 'Beta', + } ) + ).toHaveFocus(); + expect( + screen.getByRole( 'tabpanel', { + name: 'Beta', + } ) + ).toBeVisible(); + + expect( mockOnSelect ).toHaveBeenCalledTimes( 4 ); + expect( mockOnSelect ).toHaveBeenLastCalledWith( 'beta' ); } ); - it( 'should not have any selected tabs if the currently selected tab is removed, even if a tab is matching the defaultTabId', async () => { + it( 'should not automatically select tabs in the tablist when pressing the left and right arrow keys if the `selectOnMove` prop is set to `false` (manual tab activation)', async () => { const mockOnSelect = jest.fn(); - const { rerender } = await render( - <UncontrolledTabs + await render( + <Component tabs={ TABS } - defaultTabId="gamma" onSelect={ mockOnSelect } + selectOnMove={ false } /> ); - expect( await getSelectedTab() ).toHaveTextContent( 'Gamma' ); + // Alpha is automatically selected as the selected tab. + await waitForComponentToBeInitializedWithSelectedTab( 'Alpha' ); - await click( screen.getByRole( 'tab', { name: 'Alpha' } ) ); - expect( await getSelectedTab() ).toHaveTextContent( 'Alpha' ); + expect( mockOnSelect ).toHaveBeenCalledTimes( 1 ); expect( mockOnSelect ).toHaveBeenLastCalledWith( 'alpha' ); - await rerender( - <UncontrolledTabs - tabs={ TABS.slice( 1 ) } - defaultTabId="gamma" - onSelect={ mockOnSelect } - /> - ); + // Focus the tablist (and the selected tab, alpha) + // Tab should initially focus the first tab in the tablist, which + // is Alpha. + await press.Tab(); + expect( + await screen.findByRole( 'tab', { + selected: true, + name: 'Alpha', + } ) + ).toHaveFocus(); - // No tab should be selected i.e. it doesn't fall back to first tab. - await waitFor( () => - expect( - screen.queryByRole( 'tab', { selected: true } ) - ).not.toBeInTheDocument() - ); + // Press the right arrow key to move focus to the beta tab, + // but without selecting it + await press.ArrowRight(); + + expect( + screen.getByRole( 'tab', { + selected: false, + name: 'Beta', + } ) + ).toHaveFocus(); + expect( + await screen.findByRole( 'tab', { + selected: true, + name: 'Alpha', + } ) + ).toBeVisible(); + expect( + screen.getByRole( 'tabpanel', { + name: 'Alpha', + } ) + ).toBeVisible(); + + expect( mockOnSelect ).toHaveBeenCalledTimes( 1 ); - // No tabpanel should be rendered either + // Press the space key to click the beta tab, and select it. + // The same should be true with any other mean of clicking the tab button + // (ie. mouse click, enter key). + await press.Space(); + + expect( + screen.getByRole( 'tab', { + selected: true, + name: 'Beta', + } ) + ).toHaveFocus(); expect( - screen.queryByRole( 'tabpanel' ) - ).not.toBeInTheDocument(); + screen.getByRole( 'tabpanel', { + name: 'Beta', + } ) + ).toBeVisible(); + + expect( mockOnSelect ).toHaveBeenCalledTimes( 2 ); + expect( mockOnSelect ).toHaveBeenLastCalledWith( 'beta' ); } ); - it( 'should keep the currently selected tab even if it becomes disabled', async () => { + it( 'should not select tabs in the tablist when using the up and down arrow keys, unless the `orientation` prop is set to `vertical`', async () => { const mockOnSelect = jest.fn(); const { rerender } = await render( - <UncontrolledTabs - tabs={ TABS } - defaultTabId="gamma" - onSelect={ mockOnSelect } - /> + <Component tabs={ TABS } onSelect={ mockOnSelect } /> ); - expect( await getSelectedTab() ).toHaveTextContent( 'Gamma' ); - - await click( screen.getByRole( 'tab', { name: 'Alpha' } ) ); - expect( await getSelectedTab() ).toHaveTextContent( 'Alpha' ); + // Alpha is automatically selected as the selected tab. + await waitForComponentToBeInitializedWithSelectedTab( 'Alpha' ); expect( mockOnSelect ).toHaveBeenCalledTimes( 1 ); expect( mockOnSelect ).toHaveBeenLastCalledWith( 'alpha' ); - const TABS_WITH_ALPHA_DISABLED = TABS.map( ( tabObj ) => - tabObj.tabId === 'alpha' - ? { - ...tabObj, - tab: { - ...tabObj.tab, - disabled: true, - }, - } - : tabObj - ); + // Focus the tablist (and the selected tab, alpha) + // Tab should initially focus the first tab in the tablist, which + // is Alpha. + await press.Tab(); + expect( + await screen.findByRole( 'tab', { + selected: true, + name: 'Alpha', + } ) + ).toHaveFocus(); - await rerender( - <UncontrolledTabs - tabs={ TABS_WITH_ALPHA_DISABLED } - defaultTabId="gamma" - onSelect={ mockOnSelect } - /> - ); + // Press the up arrow key, but the focused/selected tab does not change. + await press.ArrowUp(); + + expect( + screen.getByRole( 'tab', { + selected: true, + name: 'Alpha', + } ) + ).toHaveFocus(); + expect( + screen.getByRole( 'tabpanel', { + name: 'Alpha', + } ) + ).toBeVisible(); - expect( await getSelectedTab() ).toHaveTextContent( 'Alpha' ); expect( mockOnSelect ).toHaveBeenCalledTimes( 1 ); - } ); - it( 'should have no active tabs when the tab associated to `defaultTabId` is removed while being the active tab', async () => { - const { rerender } = await render( - <UncontrolledTabs tabs={ TABS } defaultTabId="gamma" /> - ); + // Press the down arrow key, but the focused/selected tab does not change. + await press.ArrowDown(); + + expect( + screen.getByRole( 'tab', { + selected: true, + name: 'Alpha', + } ) + ).toHaveFocus(); + expect( + screen.getByRole( 'tabpanel', { + name: 'Alpha', + } ) + ).toBeVisible(); - expect( await getSelectedTab() ).toHaveTextContent( 'Gamma' ); + expect( mockOnSelect ).toHaveBeenCalledTimes( 1 ); - // Remove gamma + // Change the orientation to "vertical" and rerender the component. await rerender( - <UncontrolledTabs - tabs={ TABS.slice( 0, 2 ) } - defaultTabId="gamma" + <Component + tabs={ TABS } + onSelect={ mockOnSelect } + orientation="vertical" /> ); - expect( screen.getAllByRole( 'tab' ) ).toHaveLength( 2 ); - // No tab should be selected i.e. it doesn't fall back to first tab. + // Pressing the down arrow key now selects the next tab (beta). + await press.ArrowDown(); + expect( - screen.queryByRole( 'tab', { selected: true } ) - ).not.toBeInTheDocument(); - // No tabpanel should be rendered either + screen.getByRole( 'tab', { + selected: true, + name: 'Beta', + } ) + ).toHaveFocus(); expect( - screen.queryByRole( 'tabpanel' ) - ).not.toBeInTheDocument(); - } ); + screen.getByRole( 'tabpanel', { + name: 'Beta', + } ) + ).toBeVisible(); - it( 'waits for the tab with the `defaultTabId` to be present in the `tabs` array before selecting it', async () => { - const { rerender } = await render( - <UncontrolledTabs tabs={ TABS } defaultTabId="delta" /> - ); + expect( mockOnSelect ).toHaveBeenCalledTimes( 2 ); + expect( mockOnSelect ).toHaveBeenLastCalledWith( 'beta' ); - // No tab should be selected i.e. it doesn't fall back to first tab. - await waitFor( () => - expect( - screen.queryByRole( 'tab', { selected: true } ) - ).not.toBeInTheDocument() - ); + // Pressing the up arrow key now selects the previous tab (alpha). + await press.ArrowUp(); - // No tabpanel should be rendered either expect( - screen.queryByRole( 'tabpanel' ) - ).not.toBeInTheDocument(); - - await rerender( - <UncontrolledTabs - tabs={ TABS_WITH_DELTA } - defaultTabId="delta" - /> - ); + screen.getByRole( 'tab', { + selected: true, + name: 'Alpha', + } ) + ).toHaveFocus(); + expect( + screen.getByRole( 'tabpanel', { + name: 'Alpha', + } ) + ).toBeVisible(); - expect( await getSelectedTab() ).toHaveTextContent( 'Delta' ); + expect( mockOnSelect ).toHaveBeenCalledTimes( 3 ); + expect( mockOnSelect ).toHaveBeenLastCalledWith( 'alpha' ); } ); - } ); - describe( 'Disabled tab', () => { - it( 'should disable the tab when `disabled` is `true`', async () => { + it( 'should loop tab focus at the end of the tablist when using arrow keys', async () => { const mockOnSelect = jest.fn(); - const TABS_WITH_DELTA_DISABLED = TABS_WITH_DELTA.map( - ( tabObj ) => - tabObj.tabId === 'delta' - ? { - ...tabObj, - tab: { - ...tabObj.tab, - disabled: true, - }, - } - : tabObj - ); - await render( - <UncontrolledTabs - tabs={ TABS_WITH_DELTA_DISABLED } - onSelect={ mockOnSelect } - /> + <Component tabs={ TABS } onSelect={ mockOnSelect } /> ); - expect( await getSelectedTab() ).toHaveTextContent( 'Alpha' ); + // Alpha is automatically selected as the selected tab. + await waitForComponentToBeInitializedWithSelectedTab( 'Alpha' ); - expect( - screen.getByRole( 'tab', { name: 'Delta' } ) - ).toHaveAttribute( 'aria-disabled', 'true' ); - - // onSelect gets called on the initial render. expect( mockOnSelect ).toHaveBeenCalledTimes( 1 ); expect( mockOnSelect ).toHaveBeenLastCalledWith( 'alpha' ); - // Move focus to the tablist, make sure alpha is focused. + // Focus the tablist (and the selected tab, alpha) + // Tab should initially focus the first tab in the tablist, which + // is Alpha. await press.Tab(); expect( - screen.getByRole( 'tab', { name: 'Alpha' } ) + await screen.findByRole( 'tab', { + selected: true, + name: 'Alpha', + } ) ).toHaveFocus(); - // onSelect should not be called since the disabled tab is - // highlighted, but not selected. + // Press the left arrow key to loop around and select the gamma tab await press.ArrowLeft(); - expect( mockOnSelect ).toHaveBeenCalledTimes( 1 ); - // Delta (which is disabled) has focus expect( - screen.getByRole( 'tab', { name: 'Delta' } ) + screen.getByRole( 'tab', { + selected: true, + name: 'Gamma', + } ) ).toHaveFocus(); + expect( + screen.getByRole( 'tabpanel', { + name: 'Gamma', + } ) + ).toBeVisible(); - // Alpha retains the selection, even if it's not focused. - expect( await getSelectedTab() ).toHaveTextContent( 'Alpha' ); - } ); - - it( 'should select first enabled tab when the initial tab is disabled', async () => { - const TABS_WITH_ALPHA_DISABLED = TABS.map( ( tabObj ) => - tabObj.tabId === 'alpha' - ? { - ...tabObj, - tab: { - ...tabObj.tab, - disabled: true, - }, - } - : tabObj - ); - - const { rerender } = await render( - <UncontrolledTabs tabs={ TABS_WITH_ALPHA_DISABLED } /> - ); + expect( mockOnSelect ).toHaveBeenCalledTimes( 2 ); + expect( mockOnSelect ).toHaveBeenLastCalledWith( 'gamma' ); - // As alpha (first tab) is disabled, - // the first enabled tab should be beta. - expect( await getSelectedTab() ).toHaveTextContent( 'Beta' ); + // Press the right arrow key to loop around and select the alpha tab + await press.ArrowRight(); - // Re-enable all tabs - await rerender( <UncontrolledTabs tabs={ TABS } /> ); + expect( + screen.getByRole( 'tab', { + selected: true, + name: 'Alpha', + } ) + ).toHaveFocus(); + expect( + screen.getByRole( 'tabpanel', { + name: 'Alpha', + } ) + ).toBeVisible(); - // Even if the initial tab becomes enabled again, the selected - // tab doesn't change. - expect( await getSelectedTab() ).toHaveTextContent( 'Beta' ); + expect( mockOnSelect ).toHaveBeenCalledTimes( 3 ); + expect( mockOnSelect ).toHaveBeenLastCalledWith( 'alpha' ); } ); - it( 'should select the tab associated to `defaultTabId` even if the tab is disabled', async () => { - const TABS_ONLY_GAMMA_ENABLED = TABS.map( ( tabObj ) => - tabObj.tabId !== 'gamma' - ? { - ...tabObj, - tab: { - ...tabObj.tab, - disabled: true, - }, - } - : tabObj - ); - const { rerender } = await render( - <UncontrolledTabs - tabs={ TABS_ONLY_GAMMA_ENABLED } - defaultTabId="beta" - /> - ); + // TODO: mock writing direction to RTL + it( 'should swap the left and right arrow keys when selecting tabs if the writing direction is set to RTL', async () => { + // For this test only, mock the writing direction to RTL. + mockedIsRTL.mockImplementation( () => true ); - // As alpha (first tab), and beta (the initial tab), are both - // disabled the first enabled tab should be gamma. - expect( await getSelectedTab() ).toHaveTextContent( 'Beta' ); + const mockOnSelect = jest.fn(); - // Re-enable all tabs - await rerender( - <UncontrolledTabs tabs={ TABS } defaultTabId="beta" /> + await render( + <Component tabs={ TABS } onSelect={ mockOnSelect } /> ); - // Even if the initial tab becomes enabled again, the selected tab doesn't - // change. - expect( await getSelectedTab() ).toHaveTextContent( 'Beta' ); - } ); + // Alpha is automatically selected as the selected tab. + await waitForComponentToBeInitializedWithSelectedTab( 'Alpha' ); - it( 'should keep the currently tab as selected even when it becomes disabled', async () => { - const mockOnSelect = jest.fn(); - const { rerender } = await render( - <UncontrolledTabs tabs={ TABS } onSelect={ mockOnSelect } /> - ); - - expect( await getSelectedTab() ).toHaveTextContent( 'Alpha' ); expect( mockOnSelect ).toHaveBeenCalledTimes( 1 ); expect( mockOnSelect ).toHaveBeenLastCalledWith( 'alpha' ); - const TABS_WITH_ALPHA_DISABLED = TABS.map( ( tabObj ) => - tabObj.tabId === 'alpha' - ? { - ...tabObj, - tab: { - ...tabObj.tab, - disabled: true, - }, - } - : tabObj - ); - - // Disable alpha - await rerender( - <UncontrolledTabs - tabs={ TABS_WITH_ALPHA_DISABLED } - onSelect={ mockOnSelect } - /> - ); + // Focus the tablist (and the selected tab, alpha) + // Tab should initially focus the first tab in the tablist, which + // is Alpha. + await press.Tab(); + expect( + await screen.findByRole( 'tab', { + selected: true, + name: 'Alpha', + } ) + ).toHaveFocus(); - expect( await getSelectedTab() ).toHaveTextContent( 'Alpha' ); - expect( mockOnSelect ).toHaveBeenCalledTimes( 1 ); + // Press the left arrow key to select the beta tab + await press.ArrowLeft(); - // Re-enable all tabs - await rerender( - <UncontrolledTabs tabs={ TABS } onSelect={ mockOnSelect } /> - ); + expect( + screen.getByRole( 'tab', { + selected: true, + name: 'Beta', + } ) + ).toHaveFocus(); + expect( + screen.getByRole( 'tabpanel', { + name: 'Beta', + } ) + ).toBeVisible(); - expect( await getSelectedTab() ).toHaveTextContent( 'Alpha' ); - expect( mockOnSelect ).toHaveBeenCalledTimes( 1 ); - } ); + expect( mockOnSelect ).toHaveBeenCalledTimes( 2 ); + expect( mockOnSelect ).toHaveBeenLastCalledWith( 'beta' ); - it( 'should select the tab associated to `defaultTabId` even when disabled', async () => { - const mockOnSelect = jest.fn(); + // Press the left arrow key to select the gamma tab + await press.ArrowLeft(); - const { rerender } = await render( - <UncontrolledTabs - tabs={ TABS } - onSelect={ mockOnSelect } - defaultTabId="gamma" - /> - ); + expect( + screen.getByRole( 'tab', { + selected: true, + name: 'Gamma', + } ) + ).toHaveFocus(); + expect( + screen.getByRole( 'tabpanel', { + name: 'Gamma', + } ) + ).toBeVisible(); - expect( await getSelectedTab() ).toHaveTextContent( 'Gamma' ); - - const TABS_WITH_GAMMA_DISABLED = TABS.map( ( tabObj ) => - tabObj.tabId === 'gamma' - ? { - ...tabObj, - tab: { - ...tabObj.tab, - disabled: true, - }, - } - : tabObj - ); + expect( mockOnSelect ).toHaveBeenCalledTimes( 3 ); + expect( mockOnSelect ).toHaveBeenLastCalledWith( 'gamma' ); - // Disable gamma - await rerender( - <UncontrolledTabs - tabs={ TABS_WITH_GAMMA_DISABLED } - onSelect={ mockOnSelect } - defaultTabId="gamma" - /> - ); + // Press the right arrow key to select the beta tab + await press.ArrowRight(); - expect( await getSelectedTab() ).toHaveTextContent( 'Gamma' ); + expect( + screen.getByRole( 'tab', { + selected: true, + name: 'Beta', + } ) + ).toHaveFocus(); + expect( + screen.getByRole( 'tabpanel', { + name: 'Beta', + } ) + ).toBeVisible(); - // Re-enable all tabs - await rerender( - <UncontrolledTabs - tabs={ TABS } - onSelect={ mockOnSelect } - defaultTabId="gamma" - /> - ); + expect( mockOnSelect ).toHaveBeenCalledTimes( 4 ); + expect( mockOnSelect ).toHaveBeenLastCalledWith( 'beta' ); - // Confirm that alpha is still selected, and that onSelect has - // not been called again. - expect( await getSelectedTab() ).toHaveTextContent( 'Gamma' ); - expect( mockOnSelect ).not.toHaveBeenCalled(); + // Restore the original implementation of the isRTL function. + mockedIsRTL.mockRestore(); } ); - } ); - } ); - - describe( 'Controlled mode', () => { - it( 'should render the tab specified by the `selectedTabId` prop', async () => { - await render( - <ControlledTabs tabs={ TABS } selectedTabId="beta" /> - ); - expect( await getSelectedTab() ).toHaveTextContent( 'Beta' ); - expect( - await screen.findByRole( 'tabpanel', { name: 'Beta' } ) - ).toBeInTheDocument(); - } ); - it( 'should render the specified `selectedTabId`, and ignore the `defaultTabId` prop', async () => { - await render( - <ControlledTabs - tabs={ TABS } - selectedTabId="gamma" - defaultTabId="beta" - /> - ); - - expect( await getSelectedTab() ).toHaveTextContent( 'Gamma' ); - } ); - it( 'should not have a selected tab if `selectedTabId` does not match any known tab', async () => { - await render( - <ControlledTabs - tabs={ TABS_WITH_DELTA } - selectedTabId="does-not-exist" - /> - ); - - expect( - screen.queryByRole( 'tab', { selected: true } ) - ).not.toBeInTheDocument(); + it( 'should focus tabs in the tablist even if disabled', async () => { + const mockOnSelect = jest.fn(); - // No tabpanel should be rendered either - expect( screen.queryByRole( 'tabpanel' ) ).not.toBeInTheDocument(); - } ); - it( 'should not have a selected tab if the active tab is removed, but should select a tab that gets added if it matches the selectedTabId', async () => { - const { rerender } = await render( - <ControlledTabs tabs={ TABS } selectedTabId="beta" /> - ); + await render( + <Component + tabs={ TABS_WITH_BETA_DISABLED } + onSelect={ mockOnSelect } + /> + ); - expect( await getSelectedTab() ).toHaveTextContent( 'Beta' ); + // Alpha is automatically selected as the selected tab. + await waitForComponentToBeInitializedWithSelectedTab( 'Alpha' ); - // Remove beta - await rerender( - <ControlledTabs - tabs={ TABS.filter( ( tab ) => tab.tabId !== 'beta' ) } - selectedTabId="beta" - /> - ); - - expect( screen.getAllByRole( 'tab' ) ).toHaveLength( 2 ); + expect( mockOnSelect ).toHaveBeenCalledTimes( 1 ); + expect( mockOnSelect ).toHaveBeenLastCalledWith( 'alpha' ); - // No tab should be selected i.e. it doesn't fall back to first tab. - // `waitFor` is needed here to prevent testing library from - // throwing a 'not wrapped in `act()`' error. - await waitFor( () => + // Focus the tablist (and the selected tab, alpha) + // Tab should initially focus the first tab in the tablist, which + // is Alpha. + await press.Tab(); expect( - screen.queryByRole( 'tab', { selected: true } ) - ).not.toBeInTheDocument() - ); - - // No tabpanel should be rendered either - expect( screen.queryByRole( 'tabpanel' ) ).not.toBeInTheDocument(); - - // Restore beta - await rerender( - <ControlledTabs tabs={ TABS } selectedTabId="beta" /> - ); - - expect( await getSelectedTab() ).toHaveTextContent( 'Beta' ); - } ); - - describe( 'Disabled tab', () => { - it( 'should `selectedTabId` refers to a disabled tab', async () => { - const TABS_WITH_DELTA_WITH_BETA_DISABLED = TABS_WITH_DELTA.map( - ( tabObj ) => - tabObj.tabId === 'beta' - ? { - ...tabObj, - tab: { - ...tabObj.tab, - disabled: true, - }, - } - : tabObj - ); - - await render( - <ControlledTabs - tabs={ TABS_WITH_DELTA_WITH_BETA_DISABLED } - selectedTabId="beta" - /> - ); + await screen.findByRole( 'tab', { + selected: true, + name: 'Alpha', + } ) + ).toHaveFocus(); - expect( await getSelectedTab() ).toHaveTextContent( 'Beta' ); - } ); - it( 'should keep the currently selected tab as selected even when it becomes disabled', async () => { - const { rerender } = await render( - <ControlledTabs tabs={ TABS } selectedTabId="beta" /> - ); + // Pressing the right arrow key moves focus to the beta tab, but alpha + // remains the selected tab because beta is disabled. + await press.ArrowRight(); - expect( await getSelectedTab() ).toHaveTextContent( 'Beta' ); - - const TABS_WITH_BETA_DISABLED = TABS.map( ( tabObj ) => - tabObj.tabId === 'beta' - ? { - ...tabObj, - tab: { - ...tabObj.tab, - disabled: true, - }, - } - : tabObj - ); + expect( + screen.getByRole( 'tab', { + selected: false, + name: 'Beta', + } ) + ).toHaveFocus(); + expect( + screen.getByRole( 'tab', { + selected: true, + name: 'Alpha', + } ) + ).toBeVisible(); + expect( + screen.getByRole( 'tabpanel', { + name: 'Alpha', + } ) + ).toBeVisible(); - await rerender( - <ControlledTabs - tabs={ TABS_WITH_BETA_DISABLED } - selectedTabId="beta" - /> - ); + expect( mockOnSelect ).toHaveBeenCalledTimes( 1 ); - expect( await getSelectedTab() ).toHaveTextContent( 'Beta' ); + // Press the right arrow key to select the gamma tab + await press.ArrowRight(); - // re-enable all tabs - await rerender( - <ControlledTabs tabs={ TABS } selectedTabId="beta" /> - ); + expect( + screen.getByRole( 'tab', { + selected: true, + name: 'Gamma', + } ) + ).toHaveFocus(); + expect( + screen.getByRole( 'tabpanel', { + name: 'Gamma', + } ) + ).toBeVisible(); - expect( await getSelectedTab() ).toHaveTextContent( 'Beta' ); + expect( mockOnSelect ).toHaveBeenCalledTimes( 2 ); + expect( mockOnSelect ).toHaveBeenLastCalledWith( 'gamma' ); } ); } ); - describe( 'When `selectedId` is changed by the controlling component', () => { + + describe( 'When `selectedId` is changed by the controlling component [Controlled]', () => { describe.each( [ true, false ] )( 'and `selectOnMove` is %s', ( selectOnMove ) => { @@ -1231,17 +1443,18 @@ describe( 'Tabs', () => { /> ); - expect( await getSelectedTab() ).toHaveTextContent( + // Beta is the selected tab. + await waitForComponentToBeInitializedWithSelectedTab( 'Beta' ); // Tab key should focus the currently selected tab, which is Beta. await press.Tab(); - expect( await getSelectedTab() ).toHaveTextContent( - 'Beta' - ); expect( - screen.getByRole( 'tab', { name: 'Beta' } ) + screen.getByRole( 'tab', { + selected: true, + name: 'Beta', + } ) ).toHaveFocus(); await rerender( @@ -1253,17 +1466,28 @@ describe( 'Tabs', () => { ); // When the selected tab is changed, focus should not be changed. - expect( await getSelectedTab() ).toHaveTextContent( - 'Gamma' - ); expect( - screen.getByRole( 'tab', { name: 'Beta' } ) + screen.getByRole( 'tab', { + selected: true, + name: 'Gamma', + } ) + ).toBeVisible(); + expect( + screen.getByRole( 'tab', { + selected: false, + name: 'Beta', + } ) ).toHaveFocus(); - // Arrow keys should move focus to the next tab, which is Gamma - await press.ArrowRight(); + // Arrow left should move focus to the previous tab (alpha). + // The alpha tab should be always focused, and should be selected + // when the `selectOnMove` prop is set to `true`. + await press.ArrowLeft(); expect( - screen.getByRole( 'tab', { name: 'Gamma' } ) + screen.getByRole( 'tab', { + selected: selectOnMove, + name: 'Alpha', + } ) ).toHaveFocus(); } ); @@ -1279,20 +1503,22 @@ describe( 'Tabs', () => { </> ); - expect( await getSelectedTab() ).toHaveTextContent( + // Beta is the selected tab. + await waitForComponentToBeInitializedWithSelectedTab( 'Beta' ); // Tab key should focus the currently selected tab, which is Beta. await press.Tab(); await press.Tab(); - expect( await getSelectedTab() ).toHaveTextContent( - 'Beta' - ); expect( - screen.getByRole( 'tab', { name: 'Beta' } ) + screen.getByRole( 'tab', { + selected: true, + name: 'Beta', + } ) ).toHaveFocus(); + // Change the selected tab to gamma via a controlled update. await rerender( <> <button>Focus me</button> @@ -1305,12 +1531,17 @@ describe( 'Tabs', () => { ); // When the selected tab is changed, it should not automatically receive focus. - expect( await getSelectedTab() ).toHaveTextContent( - 'Gamma' - ); - expect( - screen.getByRole( 'tab', { name: 'Beta' } ) + screen.getByRole( 'tab', { + selected: true, + name: 'Gamma', + } ) + ).toBeVisible(); + expect( + screen.getByRole( 'tab', { + selected: false, + name: 'Beta', + } ) ).toHaveFocus(); // Press shift+tab, move focus to the button before Tabs @@ -1336,125 +1567,439 @@ describe( 'Tabs', () => { } ); } ); + } ); - describe( 'When `selectOnMove` is `true`', () => { - it( 'should automatically select a newly focused tab', async () => { - await render( - <ControlledTabs tabs={ TABS } selectedTabId="beta" /> - ); + describe( 'miscellaneous runtime changes', () => { + describe( 'removing a tab', () => { + describe( 'with no explicitly set initial tab', () => { + it( 'should not select a new tab when the selected tab is removed', async () => { + const mockOnSelect = jest.fn(); - expect( await getSelectedTab() ).toHaveTextContent( 'Beta' ); + const { rerender } = await render( + <UncontrolledTabs + tabs={ TABS } + onSelect={ mockOnSelect } + /> + ); - await press.Tab(); + // Alpha is automatically selected as the selected tab. + await waitForComponentToBeInitializedWithSelectedTab( + 'Alpha' + ); - // Tab key should focus the currently selected tab, which is Beta. - expect( await getSelectedTab() ).toHaveTextContent( 'Beta' ); - expect( await getSelectedTab() ).toHaveFocus(); + expect( mockOnSelect ).toHaveBeenCalledTimes( 1 ); + expect( mockOnSelect ).toHaveBeenLastCalledWith( 'alpha' ); - // Arrow keys should select and move focus to the next tab. - await press.ArrowRight(); - expect( await getSelectedTab() ).toHaveTextContent( 'Gamma' ); - expect( await getSelectedTab() ).toHaveFocus(); + // Select gamma + await click( screen.getByRole( 'tab', { name: 'Gamma' } ) ); + + expect( + screen.getByRole( 'tab', { + selected: true, + name: 'Gamma', + } ) + ).toHaveFocus(); + expect( + screen.getByRole( 'tabpanel', { + name: 'Gamma', + } ) + ).toBeVisible(); + + expect( mockOnSelect ).toHaveBeenCalledTimes( 2 ); + expect( mockOnSelect ).toHaveBeenLastCalledWith( 'gamma' ); + + // Remove gamma + await rerender( + <UncontrolledTabs + tabs={ TABS.slice( 0, 2 ) } + onSelect={ mockOnSelect } + /> + ); + + expect( screen.getAllByRole( 'tab' ) ).toHaveLength( 2 ); + + // No tab should be selected i.e. it doesn't fall back to gamma, + // even if it matches the `defaultTabId` prop. + expect( + screen.queryByRole( 'tab', { selected: true } ) + ).not.toBeInTheDocument(); + // No tabpanel should be rendered either + expect( + screen.queryByRole( 'tabpanel' ) + ).not.toBeInTheDocument(); + + expect( mockOnSelect ).toHaveBeenCalledTimes( 2 ); + } ); } ); + + describe.each( [ + [ 'defaultTabId', 'Uncontrolled', UncontrolledTabs ], + [ 'selectedTabId', 'Controlled', ControlledTabs ], + ] )( + 'when using the `%s` prop [%s]', + ( propName, _mode, Component ) => { + it( 'should not select a new tab when the selected tab is removed', async () => { + const mockOnSelect = jest.fn(); + + const initialComponentProps = { + tabs: TABS, + [ propName ]: 'gamma', + onSelect: mockOnSelect, + }; + + const { rerender } = await render( + <Component { ...initialComponentProps } /> + ); + + // Gamma is the selected tab. + await waitForComponentToBeInitializedWithSelectedTab( + 'Gamma' + ); + + // Remove gamma + await rerender( + <Component + { ...initialComponentProps } + tabs={ TABS.slice( 0, 2 ) } + /> + ); + + expect( screen.getAllByRole( 'tab' ) ).toHaveLength( + 2 + ); + // No tab should be selected i.e. it doesn't fall back to first tab. + expect( + screen.queryByRole( 'tab', { selected: true } ) + ).not.toBeInTheDocument(); + // No tabpanel should be rendered either + expect( + screen.queryByRole( 'tabpanel' ) + ).not.toBeInTheDocument(); + + // Re-add gamma. Gamma becomes selected again. + await rerender( + <Component { ...initialComponentProps } /> + ); + + expect( screen.getAllByRole( 'tab' ) ).toHaveLength( + TABS.length + ); + + expect( + screen.getByRole( 'tab', { + selected: true, + name: 'Gamma', + } ) + ).toBeVisible(); + expect( + screen.getByRole( 'tabpanel', { + name: 'Gamma', + } ) + ).toBeVisible(); + + expect( mockOnSelect ).not.toHaveBeenCalled(); + } ); + + it( `should not select the tab matching the \`${ propName }\` prop as a fallback when the selected tab is removed`, async () => { + const mockOnSelect = jest.fn(); + + const initialComponentProps = { + tabs: TABS, + [ propName ]: 'gamma', + onSelect: mockOnSelect, + }; + + const { rerender } = await render( + <Component { ...initialComponentProps } /> + ); + + // Gamma is the selected tab. + await waitForComponentToBeInitializedWithSelectedTab( + 'Gamma' + ); + + // Select alpha + await click( + screen.getByRole( 'tab', { name: 'Alpha' } ) + ); + + expect( + screen.getByRole( 'tab', { + selected: true, + name: 'Alpha', + } ) + ).toHaveFocus(); + expect( + screen.getByRole( 'tabpanel', { + name: 'Alpha', + } ) + ).toBeVisible(); + + expect( mockOnSelect ).toHaveBeenCalledTimes( 1 ); + expect( mockOnSelect ).toHaveBeenLastCalledWith( + 'alpha' + ); + + // Remove alpha + await rerender( + <Component + { ...initialComponentProps } + tabs={ TABS.slice( 1 ) } + /> + ); + + expect( screen.getAllByRole( 'tab' ) ).toHaveLength( + 2 + ); + + // No tab should be selected i.e. it doesn't fall back to gamma, + // even if it matches the `defaultTabId` prop. + expect( + screen.queryByRole( 'tab', { selected: true } ) + ).not.toBeInTheDocument(); + // No tabpanel should be rendered either + expect( + screen.queryByRole( 'tabpanel' ) + ).not.toBeInTheDocument(); + + // Re-add alpha. Alpha becomes selected again. + await rerender( + <Component { ...initialComponentProps } /> + ); + + expect( screen.getAllByRole( 'tab' ) ).toHaveLength( + TABS.length + ); + + expect( + screen.getByRole( 'tab', { + selected: true, + name: 'Alpha', + } ) + ).toBeVisible(); + expect( + screen.getByRole( 'tabpanel', { + name: 'Alpha', + } ) + ).toBeVisible(); + + expect( mockOnSelect ).toHaveBeenCalledTimes( 1 ); + } ); + } + ); } ); - describe( 'When `selectOnMove` is `false`', () => { - it( 'should apply focus without automatically changing the selected tab', async () => { - await render( - <ControlledTabs - tabs={ TABS } - selectedTabId="beta" - selectOnMove={ false } - /> - ); - expect( await getSelectedTab() ).toHaveTextContent( 'Beta' ); + describe( 'adding a tab', () => { + describe.each( [ + [ 'defaultTabId', 'Uncontrolled', UncontrolledTabs ], + [ 'selectedTabId', 'Controlled', ControlledTabs ], + ] )( + 'when using the `%s` prop [%s]', + ( propName, _mode, Component ) => { + it( `should select a newly added tab if it matches the \`${ propName }\` prop`, async () => { + const mockOnSelect = jest.fn(); + + const initialComponentProps = { + tabs: TABS, + [ propName ]: 'delta', + onSelect: mockOnSelect, + }; - // Tab key should focus the currently selected tab, which is Beta. - await press.Tab(); - await waitFor( async () => - expect( - await screen.findByRole( 'tab', { name: 'Beta' } ) - ).toHaveFocus() - ); + const { rerender } = await render( + <Component { ...initialComponentProps } /> + ); - // Arrow key should move focus but not automatically change the selected tab. - await press.ArrowRight(); - expect( - screen.getByRole( 'tab', { name: 'Gamma' } ) - ).toHaveFocus(); - expect( await getSelectedTab() ).toHaveTextContent( 'Beta' ); + // No initially selected tabs or tabpanels, since the `defaultTabId` + // prop is not matching any known tabs. + await waitForComponentToBeInitializedWithSelectedTab( + undefined + ); - // Pressing the spacebar should select the focused tab. - await press.Space(); - expect( await getSelectedTab() ).toHaveTextContent( 'Gamma' ); + expect( mockOnSelect ).not.toHaveBeenCalled(); - // Arrow key should move focus but not automatically change the selected tab. - await press.ArrowRight(); - expect( - screen.getByRole( 'tab', { name: 'Alpha' } ) - ).toHaveFocus(); - expect( await getSelectedTab() ).toHaveTextContent( 'Gamma' ); + // Re-render with beta disabled. + await rerender( + <Component + { ...initialComponentProps } + tabs={ TABS_WITH_DELTA } + /> + ); - // Pressing the enter/return should select the focused tab. - await press.Enter(); - expect( await getSelectedTab() ).toHaveTextContent( 'Alpha' ); - } ); + // Delta becomes selected + expect( + screen.getByRole( 'tab', { + selected: true, + name: 'Delta', + } ) + ).toBeVisible(); + expect( + screen.getByRole( 'tabpanel', { + name: 'Delta', + } ) + ).toBeVisible(); + + expect( mockOnSelect ).not.toHaveBeenCalled(); + } ); + } + ); } ); - } ); - it( 'should associate each `Tab` with the correct `TabPanel`, even if they are not rendered in the same order', async () => { - const TABS_WITH_DELTA_REVERSED = [ ...TABS_WITH_DELTA ].reverse(); - - await render( - <Tabs> - <Tabs.TabList> - { TABS_WITH_DELTA.map( ( tabObj ) => ( - <Tabs.Tab - key={ tabObj.tabId } - tabId={ tabObj.tabId } - className={ tabObj.tab.className } - disabled={ tabObj.tab.disabled } - > - { tabObj.title } - </Tabs.Tab> - ) ) } - </Tabs.TabList> - { TABS_WITH_DELTA_REVERSED.map( ( tabObj ) => ( - <Tabs.TabPanel - key={ tabObj.tabId } - tabId={ tabObj.tabId } - focusable={ tabObj.tabpanel?.focusable } - > - { tabObj.content } - </Tabs.TabPanel> - ) ) } - </Tabs> - ); + describe( 'a tab becomes disabled', () => { + describe.each( [ + [ 'defaultTabId', 'Uncontrolled', UncontrolledTabs ], + [ 'selectedTabId', 'Controlled', ControlledTabs ], + ] )( + 'when using the `%s` prop [%s]', + ( propName, _mode, Component ) => { + it( `should keep the initial tab matching the \`${ propName }\` prop as selected even if it becomes disabled`, async () => { + const mockOnSelect = jest.fn(); + + const initialComponentProps = { + tabs: TABS, + [ propName ]: 'beta', + onSelect: mockOnSelect, + }; - // Alpha is the initially selected tab,and should render the correct tabpanel - expect( await getSelectedTab() ).toHaveTextContent( 'Alpha' ); - expect( screen.getByRole( 'tabpanel' ) ).toHaveTextContent( - 'Selected tab: Alpha' - ); + const { rerender } = await render( + <Component { ...initialComponentProps } /> + ); - // Select Beta, make sure the correct tabpanel is rendered - await click( screen.getByRole( 'tab', { name: 'Beta' } ) ); - expect( await getSelectedTab() ).toHaveTextContent( 'Beta' ); - expect( screen.getByRole( 'tabpanel' ) ).toHaveTextContent( - 'Selected tab: Beta' - ); + // Beta is the selected tab. + await waitForComponentToBeInitializedWithSelectedTab( + 'Beta' + ); - // Select Gamma, make sure the correct tabpanel is rendered - await click( screen.getByRole( 'tab', { name: 'Gamma' } ) ); - expect( await getSelectedTab() ).toHaveTextContent( 'Gamma' ); - expect( screen.getByRole( 'tabpanel' ) ).toHaveTextContent( - 'Selected tab: Gamma' - ); + expect( mockOnSelect ).not.toHaveBeenCalled(); - // Select Delta, make sure the correct tabpanel is rendered - await click( screen.getByRole( 'tab', { name: 'Delta' } ) ); - expect( await getSelectedTab() ).toHaveTextContent( 'Delta' ); - expect( screen.getByRole( 'tabpanel' ) ).toHaveTextContent( - 'Selected tab: Delta' - ); + // Re-render with beta disabled. + await rerender( + <Component + { ...initialComponentProps } + tabs={ TABS_WITH_BETA_DISABLED } + /> + ); + + // Beta continues to be selected and focused, even if it is disabled. + expect( + screen.getByRole( 'tab', { + selected: true, + name: 'Beta', + } ) + ).toBeVisible(); + expect( + screen.getByRole( 'tabpanel', { + name: 'Beta', + } ) + ).toBeVisible(); + + // Re-enable beta. + await rerender( + <Component { ...initialComponentProps } /> + ); + + // Beta continues to be selected and focused. + expect( + screen.getByRole( 'tab', { + selected: true, + name: 'Beta', + } ) + ).toBeVisible(); + expect( + screen.getByRole( 'tabpanel', { + name: 'Beta', + } ) + ).toBeVisible(); + + expect( mockOnSelect ).not.toHaveBeenCalled(); + } ); + + it( 'should keep the current tab selected by the user as selected even if it becomes disabled', async () => { + const mockOnSelect = jest.fn(); + + const { rerender } = await render( + <Component + tabs={ TABS } + onSelect={ mockOnSelect } + /> + ); + + // Alpha is automatically selected as the selected tab. + await waitForComponentToBeInitializedWithSelectedTab( + 'Alpha' + ); + + expect( mockOnSelect ).toHaveBeenCalledTimes( 1 ); + expect( mockOnSelect ).toHaveBeenLastCalledWith( + 'alpha' + ); + + // Click on beta tab, beta becomes selected. + await click( + screen.getByRole( 'tab', { name: 'Beta' } ) + ); + + expect( + screen.getByRole( 'tab', { + selected: true, + name: 'Beta', + } ) + ).toBeVisible(); + expect( + screen.getByRole( 'tabpanel', { + name: 'Beta', + } ) + ).toBeVisible(); + + expect( mockOnSelect ).toHaveBeenCalledTimes( 2 ); + expect( mockOnSelect ).toHaveBeenLastCalledWith( + 'beta' + ); + + // Re-render with beta disabled. + await rerender( + <Component + tabs={ TABS_WITH_BETA_DISABLED } + onSelect={ mockOnSelect } + /> + ); + + // Beta continues to be selected, even if it is disabled. + expect( + screen.getByRole( 'tab', { + selected: true, + name: 'Beta', + } ) + ).toHaveFocus(); + expect( + screen.getByRole( 'tabpanel', { + name: 'Beta', + } ) + ).toBeVisible(); + + // Re-enable beta. + await rerender( + <Component + tabs={ TABS } + onSelect={ mockOnSelect } + /> + ); + + // Beta continues to be selected and focused. + expect( + screen.getByRole( 'tab', { + selected: true, + name: 'Beta', + } ) + ).toBeVisible(); + expect( + screen.getByRole( 'tabpanel', { + name: 'Beta', + } ) + ).toBeVisible(); + + expect( mockOnSelect ).toHaveBeenCalledTimes( 2 ); + } ); + } + ); + } ); } ); } ); diff --git a/packages/components/src/tabs/types.ts b/packages/components/src/tabs/types.ts index 959a82509a05d6..7ef0f919322c04 100644 --- a/packages/components/src/tabs/types.ts +++ b/packages/components/src/tabs/types.ts @@ -22,18 +22,16 @@ export type TabsProps = { * `Tabs.Tablist` component and as many instances of the `Tabs.TabPanel` * components as there are `Tabs.Tab` components. */ - children: Ariakit.TabProps[ 'children' ]; + children: Ariakit.TabProviderProps[ 'children' ]; /** * Determines if the tab should be selected when it receives focus. If set to * `false`, the tab will only be selected upon clicking, not when using arrow - * keys to shift focus (manual tab activation). See the official W3C docs + * keys to shift focus (manual tab activation). See the [official W3C docs](https://www.w3.org/WAI/ARIA/apg/patterns/tabpanel/) * for more info. * * @default true - * - * @see https://www.w3.org/WAI/ARIA/apg/patterns/tabpanel/ */ - selectOnMove?: Ariakit.TabStoreProps[ 'selectOnMove' ]; + selectOnMove?: Ariakit.TabProviderProps[ 'selectOnMove' ]; /** * The id of the tab whose panel is currently visible. * @@ -44,7 +42,7 @@ export type TabsProps = { * in "controlled" mode. When in "controlled" mode, the `null` value will * result in no tabs being selected, and the tablist becoming tabbable. */ - selectedTabId?: Ariakit.TabStoreProps[ 'selectedId' ]; + selectedTabId?: Ariakit.TabProviderProps[ 'selectedId' ]; /** * The id of the tab whose panel is currently visible. * @@ -55,21 +53,22 @@ export type TabsProps = { * Note: this prop will be overridden by the `selectedTabId` prop if it is * provided (meaning the component will be used in "controlled" mode). */ - defaultTabId?: Ariakit.TabStoreProps[ 'defaultSelectedId' ]; + defaultTabId?: Ariakit.TabProviderProps[ 'defaultSelectedId' ]; /** * The function called when the `selectedTabId` changes. */ - onSelect?: Ariakit.TabStoreProps[ 'setSelectedId' ]; + onSelect?: Ariakit.TabProviderProps[ 'setSelectedId' ]; /** * The current active tab `id`. The active tab is the tab element within the * tablist widget that has DOM focus. + * * - `null` represents the tablist (ie. the base composite element). Users * will be able to navigate out of it using arrow keys. * - If `activeTabId` is initially set to `null`, the base composite element * itself will have focus and users will be able to navigate to it using - * arrow keys.activeTabId + * arrow keys. */ - activeTabId?: Ariakit.TabStoreProps[ 'activeId' ]; + activeTabId?: Ariakit.TabProviderProps[ 'activeId' ]; /** * The tab id that should be active by default when the composite widget is * rendered. If `null`, the tablist element itself will have focus @@ -79,21 +78,22 @@ export type TabsProps = { * Note: this prop will be overridden by the `activeTabId` prop if it is * provided. */ - defaultActiveTabId?: Ariakit.TabStoreProps[ 'defaultActiveId' ]; + defaultActiveTabId?: Ariakit.TabProviderProps[ 'defaultActiveId' ]; /** * A callback that gets called when the `activeTabId` state changes. */ - onActiveTabIdChange?: Ariakit.TabStoreProps[ 'setActiveId' ]; + onActiveTabIdChange?: Ariakit.TabProviderProps[ 'setActiveId' ]; /** * Defines the orientation of the tablist and determines which arrow keys * can be used to move focus: + * * - `both`: all arrow keys work. * - `horizontal`: only left and right arrow keys work. * - `vertical`: only up and down arrow keys work. * * @default "horizontal" */ - orientation?: Ariakit.TabStoreProps[ 'orientation' ]; + orientation?: Ariakit.TabProviderProps[ 'orientation' ]; }; export type TabListProps = { @@ -105,7 +105,6 @@ export type TabListProps = { }; // TODO: consider prop name changes (tabId, selectedTabId) -// switch to auto-generated README // compound technique export type TabProps = { diff --git a/packages/components/src/text-control/README.md b/packages/components/src/text-control/README.md index 0b6e2d4ebc684e..80f3145a4325b3 100644 --- a/packages/components/src/text-control/README.md +++ b/packages/components/src/text-control/README.md @@ -63,6 +63,7 @@ const MyTextControl = () => { return ( <TextControl __nextHasNoMarginBottom + __next40pxDefaultSize label="Additional CSS Class" value={ className } onChange={ ( value ) => setClassName( value ) } diff --git a/packages/components/src/text-control/index.tsx b/packages/components/src/text-control/index.tsx index ea2d2c17bb9cf6..83881542fe7b7b 100644 --- a/packages/components/src/text-control/index.tsx +++ b/packages/components/src/text-control/index.tsx @@ -16,6 +16,7 @@ import { forwardRef } from '@wordpress/element'; import BaseControl from '../base-control'; import type { WordPressComponentProps } from '../context'; import type { TextControlProps } from './types'; +import { maybeWarnDeprecated36pxSize } from '../utils/deprecated-36px-size'; function UnforwardedTextControl( props: WordPressComponentProps< TextControlProps, 'input', false >, @@ -38,6 +39,12 @@ function UnforwardedTextControl( const onChangeValue = ( event: ChangeEvent< HTMLInputElement > ) => onChange( event.target.value ); + maybeWarnDeprecated36pxSize( { + componentName: 'TextControl', + size: undefined, + __next40pxDefaultSize, + } ); + return ( <BaseControl __nextHasNoMarginBottom={ __nextHasNoMarginBottom } @@ -77,6 +84,7 @@ function UnforwardedTextControl( * return ( * <TextControl * __nextHasNoMarginBottom + * __next40pxDefaultSize * label="Additional CSS Class" * value={ className } * onChange={ ( value ) => setClassName( value ) } diff --git a/packages/components/src/text-control/stories/index.story.tsx b/packages/components/src/text-control/stories/index.story.tsx index bebdb2caf75f63..fe7fb538805da3 100644 --- a/packages/components/src/text-control/stories/index.story.tsx +++ b/packages/components/src/text-control/stories/index.story.tsx @@ -15,12 +15,13 @@ import TextControl from '..'; const meta: Meta< typeof TextControl > = { component: TextControl, - title: 'Components/TextControl', + title: 'Components/Selection & Input/Common/TextControl', + id: 'components-textcontrol', argTypes: { help: { control: { type: 'text' } }, label: { control: { type: 'text' } }, onChange: { action: 'onChange' }, - value: { control: { type: null } }, + value: { control: false }, }, parameters: { controls: { @@ -54,6 +55,7 @@ export const Default: StoryFn< typeof TextControl > = DefaultTemplate.bind( ); Default.args = { __nextHasNoMarginBottom: true, + __next40pxDefaultSize: true, }; export const WithLabelAndHelpText: StoryFn< typeof TextControl > = diff --git a/packages/components/src/text-control/test/text-control.tsx b/packages/components/src/text-control/test/text-control.tsx index 19b17cae443614..7eb3a82d2fb5f5 100644 --- a/packages/components/src/text-control/test/text-control.tsx +++ b/packages/components/src/text-control/test/text-control.tsx @@ -9,7 +9,13 @@ import { render, screen } from '@testing-library/react'; import _TextControl from '..'; const TextControl = ( props: React.ComponentProps< typeof _TextControl > ) => { - return <_TextControl { ...props } __nextHasNoMarginBottom />; + return ( + <_TextControl + { ...props } + __nextHasNoMarginBottom + __next40pxDefaultSize + /> + ); }; const noop = () => {}; diff --git a/packages/components/src/text-highlight/test/index.tsx b/packages/components/src/text-highlight/test/index.tsx index 2d71f3e98b1358..bb2b08a169fbe8 100644 --- a/packages/components/src/text-highlight/test/index.tsx +++ b/packages/components/src/text-highlight/test/index.tsx @@ -20,7 +20,7 @@ const defaultText = describe( 'TextHighlight', () => { describe( 'Basic rendering', () => { it.each( [ [ 'Gutenberg' ], [ 'media' ] ] )( - 'should highlight the singular occurance of the text "%s" in the text if it exists', + 'should highlight the singular occurrence of the text "%s" in the text if it exists', ( highlight ) => { const { container } = render( <TextHighlight @@ -39,7 +39,7 @@ describe( 'TextHighlight', () => { } ); - it( 'should highlight multiple occurances of the string every time it exists in the text', () => { + it( 'should highlight multiple occurrences of the string every time it exists in the text', () => { const highlight = 'edit'; const { container } = render( @@ -55,7 +55,7 @@ describe( 'TextHighlight', () => { } ); } ); - it( 'should highlight occurances of a string regardless of capitalisation', () => { + it( 'should highlight occurrences of a string regardless of capitalisation', () => { // Note that `The` occurs twice in the default text, once in // lowercase and once capitalized. const highlight = 'The'; diff --git a/packages/components/src/text/README.md b/packages/components/src/text/README.md index 46bd6a5f10de77..ef06f63e950f0e 100644 --- a/packages/components/src/text/README.md +++ b/packages/components/src/text/README.md @@ -156,7 +156,7 @@ Adjusts all text line-height based on the typography system. **Type**: `number` -Clamps the text content to the specifiec `numberOfLines`, adding the `ellipsis` at the end. +Clamps the text content to the specific `numberOfLines`, adding the `ellipsis` at the end. ### optimizeReadabilityFor diff --git a/packages/components/src/text/hook.ts b/packages/components/src/text/hook.ts index a447b2ce5133be..76314686eb963b 100644 --- a/packages/components/src/text/hook.ts +++ b/packages/components/src/text/hook.ts @@ -104,6 +104,7 @@ export default function useText( const isOptimalTextColorDark = getOptimalTextShade( optimizeReadabilityFor ) === 'dark'; + // Should not use theme colors sx.optimalTextColor = isOptimalTextColorDark ? css( { color: COLORS.gray[ 900 ] } ) : css( { color: COLORS.white } ); diff --git a/packages/components/src/text/stories/index.story.tsx b/packages/components/src/text/stories/index.story.tsx index 92a2c7eb9be3e3..18e2c219460852 100644 --- a/packages/components/src/text/stories/index.story.tsx +++ b/packages/components/src/text/stories/index.story.tsx @@ -49,7 +49,7 @@ Truncate.args = { facilisis dictum tortor, eu tincidunt justo scelerisque tincidunt. Duis semper dui id augue malesuada, ut feugiat nisi aliquam. Vestibulum venenatis diam sem, finibus dictum massa semper in. Nulla -facilisi. Nunc vulputate faucibus diam, in lobortis arcu ornare vel. +facilities. Nunc vulputate faucibus diam, in lobortis arcu ornare vel. In dignissim nunc sed facilisis finibus. Etiam imperdiet mattis arcu, sed rutrum sapien blandit gravida. Aenean sollicitudin neque eget enim blandit, sit amet rutrum leo vehicula. Nunc malesuada @@ -68,7 +68,7 @@ Highlight.args = { facilisis dictum tortor, eu tincidunt justo scelerisque tincidunt. Duis semper dui id augue malesuada, ut feugiat nisi aliquam. Vestibulum venenatis diam sem, finibus dictum massa semper in. Nulla -facilisi. Nunc vulputate faucibus diam, in lobortis arcu ornare vel. +facilities. Nunc vulputate faucibus diam, in lobortis arcu ornare vel. In dignissim nunc sed facilisis finibus. Etiam imperdiet mattis arcu, sed rutrum sapien blandit gravida. Aenean sollicitudin neque eget enim blandit, sit amet rutrum leo vehicula. Nunc malesuada diff --git a/packages/components/src/text/styles.ts b/packages/components/src/text/styles.ts index e777ed4f0941de..7d3b70e2ab2390 100644 --- a/packages/components/src/text/styles.ts +++ b/packages/components/src/text/styles.ts @@ -9,7 +9,7 @@ import { css } from '@emotion/react'; import { COLORS, CONFIG } from '../utils'; export const Text = css` - color: ${ COLORS.gray[ 900 ] }; + color: ${ COLORS.theme.foreground }; line-height: ${ CONFIG.fontLineHeightBase }; margin: 0; text-wrap: balance; /* Fallback for Safari. */ diff --git a/packages/components/src/text/test/__snapshots__/index.tsx.snap b/packages/components/src/text/test/__snapshots__/index.tsx.snap index 1b98c0853ac549..caa876cb24dc78 100644 --- a/packages/components/src/text/test/__snapshots__/index.tsx.snap +++ b/packages/components/src/text/test/__snapshots__/index.tsx.snap @@ -6,7 +6,7 @@ Snapshot Diff: + Base styles @@ -3,8 +3,9 @@ - "color": "#1e1e1e", + "color": "var(--wp-components-color-foreground, #1e1e1e)", "font-size": "calc((13 / 13) * 13px)", "font-weight": "normal", "line-height": "1.4", @@ -19,7 +19,7 @@ Snapshot Diff: exports[`Text should render highlighted words with highlightCaseSensitive 1`] = ` .emotion-0 { - color: #1e1e1e; + color: var(--wp-components-color-foreground, #1e1e1e); line-height: 1.4; margin: 0; text-wrap: balance; @@ -52,7 +52,7 @@ exports[`Text should render highlighted words with highlightCaseSensitive 1`] = exports[`Text snapshot tests should render correctly 1`] = ` .emotion-0 { - color: #1e1e1e; + color: var(--wp-components-color-foreground, #1e1e1e); line-height: 1.4; margin: 0; text-wrap: balance; diff --git a/packages/components/src/text/utils.ts b/packages/components/src/text/utils.ts index bcf7bff9c36ab7..1c081ce85869d2 100644 --- a/packages/components/src/text/utils.ts +++ b/packages/components/src/text/utils.ts @@ -27,7 +27,7 @@ import { createElement } from '@wordpress/element'; * @property {string | Record<string, unknown>} [highlightClassName=''] Classname to apply to highlighted text or a Record of classnames to apply to given text (which should be the key). * @property {import('react').AllHTMLAttributes<HTMLDivElement>['style']} [highlightStyle={}] Styles to apply to highlighted text. * @property {keyof JSX.IntrinsicElements} [highlightTag='mark'] Tag to use for the highlighted text. - * @property {import('highlight-words-core').FindAllArgs['sanitize']} [sanitize] Custom `santize` function to pass to `highlight-words-core`. + * @property {import('highlight-words-core').FindAllArgs['sanitize']} [sanitize] Custom `sanitize` function to pass to `highlight-words-core`. * @property {string[]} [searchWords=[]] Words to search for and highlight. * @property {string} [unhighlightClassName=''] Classname to apply to unhighlighted text. * @property {import('react').AllHTMLAttributes<HTMLDivElement>['style']} [unhighlightStyle] Style to apply to unhighlighted text. diff --git a/packages/components/src/textarea-control/stories/index.story.tsx b/packages/components/src/textarea-control/stories/index.story.tsx index c303883a92c5d7..3160e0bfe68f31 100644 --- a/packages/components/src/textarea-control/stories/index.story.tsx +++ b/packages/components/src/textarea-control/stories/index.story.tsx @@ -21,7 +21,7 @@ const meta: Meta< typeof TextareaControl > = { onChange: { action: 'onChange' }, label: { control: { type: 'text' } }, help: { control: { type: 'text' } }, - value: { control: { type: null } }, + value: { control: false }, }, parameters: { controls: { diff --git a/packages/components/src/theme/stories/index.story.tsx b/packages/components/src/theme/stories/index.story.tsx index 67eec72533ff3f..8ef87cbe8ddb41 100644 --- a/packages/components/src/theme/stories/index.story.tsx +++ b/packages/components/src/theme/stories/index.story.tsx @@ -37,7 +37,7 @@ export const Default = Template.bind( {} ); Default.args = {}; export const Nested: StoryFn< typeof Theme > = ( args ) => ( - <Theme accent="tomato"> + <Theme accent="crimson"> <Button variant="primary">Outer theme (hardcoded)</Button> <Theme { ...args }> diff --git a/packages/components/src/toggle-control/stories/index.story.tsx b/packages/components/src/toggle-control/stories/index.story.tsx index b9db0474bc7603..6511655810066f 100644 --- a/packages/components/src/toggle-control/stories/index.story.tsx +++ b/packages/components/src/toggle-control/stories/index.story.tsx @@ -18,7 +18,7 @@ const meta: Meta< typeof ToggleControl > = { id: 'components-togglecontrol', component: ToggleControl, argTypes: { - checked: { control: { type: null } }, + checked: { control: false }, help: { control: { type: 'text' } }, label: { control: { type: 'text' } }, onChange: { action: 'onChange' }, diff --git a/packages/components/src/toggle-group-control/stories/index.story.tsx b/packages/components/src/toggle-group-control/stories/index.story.tsx index afdfa457f66348..0f3c0a299617af 100644 --- a/packages/components/src/toggle-group-control/stories/index.story.tsx +++ b/packages/components/src/toggle-group-control/stories/index.story.tsx @@ -32,7 +32,7 @@ const meta: Meta< typeof ToggleGroupControl > = { argTypes: { help: { control: { type: 'text' } }, onChange: { action: 'onChange' }, - value: { control: { type: null } }, + value: { control: false }, }, parameters: { controls: { expanded: true }, @@ -51,6 +51,7 @@ const Template: StoryFn< typeof ToggleGroupControl > = ( { return ( <ToggleGroupControl __nextHasNoMarginBottom + __next40pxDefaultSize { ...props } onChange={ ( ...changeArgs ) => { setValue( ...changeArgs ); diff --git a/packages/components/src/toggle-group-control/test/__snapshots__/index.tsx.snap b/packages/components/src/toggle-group-control/test/__snapshots__/index.tsx.snap index 832c6d7cb7a8c8..18837ae79a325a 100644 --- a/packages/components/src/toggle-group-control/test/__snapshots__/index.tsx.snap +++ b/packages/components/src/toggle-group-control/test/__snapshots__/index.tsx.snap @@ -44,8 +44,8 @@ exports[`ToggleGroupControl controlled should render correctly with icons 1`] = display: inline-flex; min-width: 0; position: relative; - min-height: 36px; - padding: 2px; + min-height: 40px; + padding: 3px; } .emotion-8:hover { @@ -72,7 +72,7 @@ exports[`ToggleGroupControl controlled should render correctly with icons 1`] = content: ''; position: absolute; pointer-events: none; - background: #1e1e1e; + background: var(--wp-components-color-foreground, #1e1e1e); outline: 2px solid transparent; outline-offset: -3px; --antialiasing-factor: 100; @@ -134,7 +134,7 @@ exports[`ToggleGroupControl controlled should render correctly with icons 1`] = background: transparent; border: none; border-radius: 1px; - color: #757575; + color: var(--wp-components-color-gray-700, #757575); fill: currentColor; cursor: pointer; display: -webkit-box; @@ -158,12 +158,12 @@ exports[`ToggleGroupControl controlled should render correctly with icons 1`] = user-select: none; width: 100%; z-index: 2; - color: #1e1e1e; - height: 30px; + color: var(--wp-components-color-foreground, #1e1e1e); + height: 32px; aspect-ratio: 1; padding-left: 0; padding-right: 0; - color: #fff; + color: var(--wp-components-color-foreground-inverted, #fff); } @media not ( prefers-reduced-motion ) { @@ -183,7 +183,7 @@ exports[`ToggleGroupControl controlled should render correctly with icons 1`] = } .emotion-12:active { - background: #fff; + background: var(--wp-components-color-background, #fff); } .emotion-12:active { @@ -211,7 +211,7 @@ exports[`ToggleGroupControl controlled should render correctly with icons 1`] = background: transparent; border: none; border-radius: 1px; - color: #757575; + color: var(--wp-components-color-gray-700, #757575); fill: currentColor; cursor: pointer; display: -webkit-box; @@ -235,8 +235,8 @@ exports[`ToggleGroupControl controlled should render correctly with icons 1`] = user-select: none; width: 100%; z-index: 2; - color: #1e1e1e; - height: 30px; + color: var(--wp-components-color-foreground, #1e1e1e); + height: 32px; aspect-ratio: 1; padding-left: 0; padding-right: 0; @@ -259,7 +259,7 @@ exports[`ToggleGroupControl controlled should render correctly with icons 1`] = } .emotion-17:active { - background: #fff; + background: var(--wp-components-color-background, #fff); } <div> @@ -357,7 +357,7 @@ exports[`ToggleGroupControl controlled should render correctly with icons 1`] = </div> </div> <button - class="components-button" + class="components-button is-next-40px-default-size" type="button" > Reset @@ -409,8 +409,8 @@ exports[`ToggleGroupControl controlled should render correctly with text options display: inline-flex; min-width: 0; position: relative; - min-height: 36px; - padding: 2px; + min-height: 40px; + padding: 3px; } .emotion-8:hover { @@ -437,7 +437,7 @@ exports[`ToggleGroupControl controlled should render correctly with text options content: ''; position: absolute; pointer-events: none; - background: #1e1e1e; + background: var(--wp-components-color-foreground, #1e1e1e); outline: 2px solid transparent; outline-offset: -3px; --antialiasing-factor: 100; @@ -499,7 +499,7 @@ exports[`ToggleGroupControl controlled should render correctly with text options background: transparent; border: none; border-radius: 1px; - color: #757575; + color: var(--wp-components-color-gray-700, #757575); fill: currentColor; cursor: pointer; display: -webkit-box; @@ -542,7 +542,7 @@ exports[`ToggleGroupControl controlled should render correctly with text options } .emotion-12:active { - background: #fff; + background: var(--wp-components-color-background, #fff); } .emotion-13 { @@ -626,7 +626,7 @@ exports[`ToggleGroupControl controlled should render correctly with text options </div> </div> <button - class="components-button" + class="components-button is-next-40px-default-size" type="button" > Reset @@ -678,8 +678,8 @@ exports[`ToggleGroupControl uncontrolled should render correctly with icons 1`] display: inline-flex; min-width: 0; position: relative; - min-height: 36px; - padding: 2px; + min-height: 40px; + padding: 3px; } .emotion-8:hover { @@ -706,7 +706,7 @@ exports[`ToggleGroupControl uncontrolled should render correctly with icons 1`] content: ''; position: absolute; pointer-events: none; - background: #1e1e1e; + background: var(--wp-components-color-foreground, #1e1e1e); outline: 2px solid transparent; outline-offset: -3px; --antialiasing-factor: 100; @@ -768,7 +768,7 @@ exports[`ToggleGroupControl uncontrolled should render correctly with icons 1`] background: transparent; border: none; border-radius: 1px; - color: #757575; + color: var(--wp-components-color-gray-700, #757575); fill: currentColor; cursor: pointer; display: -webkit-box; @@ -792,12 +792,12 @@ exports[`ToggleGroupControl uncontrolled should render correctly with icons 1`] user-select: none; width: 100%; z-index: 2; - color: #1e1e1e; - height: 30px; + color: var(--wp-components-color-foreground, #1e1e1e); + height: 32px; aspect-ratio: 1; padding-left: 0; padding-right: 0; - color: #fff; + color: var(--wp-components-color-foreground-inverted, #fff); } @media not ( prefers-reduced-motion ) { @@ -817,7 +817,7 @@ exports[`ToggleGroupControl uncontrolled should render correctly with icons 1`] } .emotion-12:active { - background: #fff; + background: var(--wp-components-color-background, #fff); } .emotion-12:active { @@ -845,7 +845,7 @@ exports[`ToggleGroupControl uncontrolled should render correctly with icons 1`] background: transparent; border: none; border-radius: 1px; - color: #757575; + color: var(--wp-components-color-gray-700, #757575); fill: currentColor; cursor: pointer; display: -webkit-box; @@ -869,8 +869,8 @@ exports[`ToggleGroupControl uncontrolled should render correctly with icons 1`] user-select: none; width: 100%; z-index: 2; - color: #1e1e1e; - height: 30px; + color: var(--wp-components-color-foreground, #1e1e1e); + height: 32px; aspect-ratio: 1; padding-left: 0; padding-right: 0; @@ -893,7 +893,7 @@ exports[`ToggleGroupControl uncontrolled should render correctly with icons 1`] } .emotion-17:active { - background: #fff; + background: var(--wp-components-color-background, #fff); } <div> @@ -1037,8 +1037,8 @@ exports[`ToggleGroupControl uncontrolled should render correctly with text optio display: inline-flex; min-width: 0; position: relative; - min-height: 36px; - padding: 2px; + min-height: 40px; + padding: 3px; } .emotion-8:hover { @@ -1065,7 +1065,7 @@ exports[`ToggleGroupControl uncontrolled should render correctly with text optio content: ''; position: absolute; pointer-events: none; - background: #1e1e1e; + background: var(--wp-components-color-foreground, #1e1e1e); outline: 2px solid transparent; outline-offset: -3px; --antialiasing-factor: 100; @@ -1127,7 +1127,7 @@ exports[`ToggleGroupControl uncontrolled should render correctly with text optio background: transparent; border: none; border-radius: 1px; - color: #757575; + color: var(--wp-components-color-gray-700, #757575); fill: currentColor; cursor: pointer; display: -webkit-box; @@ -1170,7 +1170,7 @@ exports[`ToggleGroupControl uncontrolled should render correctly with text optio } .emotion-12:active { - background: #fff; + background: var(--wp-components-color-background, #fff); } .emotion-13 { diff --git a/packages/components/src/toggle-group-control/test/index.tsx b/packages/components/src/toggle-group-control/test/index.tsx index 168e8f498958b5..28928a9735a378 100644 --- a/packages/components/src/toggle-group-control/test/index.tsx +++ b/packages/components/src/toggle-group-control/test/index.tsx @@ -28,7 +28,13 @@ const hoverOutside = async () => { }; const ToggleGroupControl = ( props: ToggleGroupControlProps ) => { - return <_ToggleGroupControl { ...props } __nextHasNoMarginBottom />; + return ( + <_ToggleGroupControl + { ...props } + __nextHasNoMarginBottom + __next40pxDefaultSize + /> + ); }; const ControlledToggleGroupControl = ( { @@ -51,9 +57,15 @@ const ControlledToggleGroupControl = ( { } } value={ value } /> - <Button onClick={ () => setValue( undefined ) }>Reset</Button> + <Button + onClick={ () => setValue( undefined ) } + __next40pxDefaultSize + > + Reset + </Button> { extraButtonOptions?.map( ( obj ) => ( <Button + __next40pxDefaultSize key={ obj.value } onClick={ () => setValue( obj.value ) } > diff --git a/packages/components/src/toggle-group-control/toggle-group-control-option-base/styles.ts b/packages/components/src/toggle-group-control/toggle-group-control-option-base/styles.ts index c0248f9b3f7f22..a53eced1219db4 100644 --- a/packages/components/src/toggle-group-control/toggle-group-control-option-base/styles.ts +++ b/packages/components/src/toggle-group-control/toggle-group-control-option-base/styles.ts @@ -38,7 +38,7 @@ export const buttonView = ( { background: transparent; border: none; border-radius: ${ CONFIG.radiusXSmall }; - color: ${ COLORS.gray[ 700 ] }; + color: ${ COLORS.theme.gray[ 700 ] }; fill: currentColor; cursor: pointer; display: flex; @@ -70,7 +70,7 @@ export const buttonView = ( { } &:active { - background: ${ CONFIG.controlBackgroundColor }; + background: ${ COLORS.ui.background }; } ${ isDeselectable && deselectable } @@ -79,7 +79,7 @@ export const buttonView = ( { `; const pressed = css` - color: ${ COLORS.white }; + color: ${ COLORS.theme.foregroundInverted }; &:active { background: transparent; @@ -87,11 +87,11 @@ const pressed = css` `; const deselectable = css` - color: ${ COLORS.gray[ 900 ] }; + color: ${ COLORS.theme.foreground }; &:focus { box-shadow: - inset 0 0 0 1px ${ COLORS.white }, + inset 0 0 0 1px ${ COLORS.ui.background }, 0 0 0 ${ CONFIG.borderWidthFocus } ${ COLORS.theme.accent }; outline: 2px solid transparent; } @@ -112,7 +112,7 @@ const isIconStyles = ( { }; return css` - color: ${ COLORS.gray[ 900 ] }; + color: ${ COLORS.theme.foreground }; height: ${ iconButtonSizes[ size ] }; aspect-ratio: 1; padding-left: 0; diff --git a/packages/components/src/toggle-group-control/toggle-group-control-option-icon/README.md b/packages/components/src/toggle-group-control/toggle-group-control-option-icon/README.md index a0e3a44cf74607..1ee82b26a91998 100644 --- a/packages/components/src/toggle-group-control/toggle-group-control-option-icon/README.md +++ b/packages/components/src/toggle-group-control/toggle-group-control-option-icon/README.md @@ -17,7 +17,7 @@ import { formatLowercase, formatUppercase } from '@wordpress/icons'; function Example() { return ( - <ToggleGroupControl __nextHasNoMarginBottom> + <ToggleGroupControl __nextHasNoMarginBottom __next40pxDefaultSize> <ToggleGroupControlOptionIcon value="uppercase" icon={ formatUppercase } diff --git a/packages/components/src/toggle-group-control/toggle-group-control-option-icon/component.tsx b/packages/components/src/toggle-group-control/toggle-group-control-option-icon/component.tsx index 352b4acee71844..d1840cba5595d9 100644 --- a/packages/components/src/toggle-group-control/toggle-group-control-option-icon/component.tsx +++ b/packages/components/src/toggle-group-control/toggle-group-control-option-icon/component.tsx @@ -52,7 +52,7 @@ function UnforwardedToggleGroupControlOptionIcon( * * function Example() { * return ( - * <ToggleGroupControl __nextHasNoMarginBottom> + * <ToggleGroupControl __nextHasNoMarginBottom __next40pxDefaultSize> * <ToggleGroupControlOptionIcon * value="uppercase" * label="Uppercase" diff --git a/packages/components/src/toggle-group-control/toggle-group-control-option/README.md b/packages/components/src/toggle-group-control/toggle-group-control-option/README.md index 2e7c55bd688361..411857d260a311 100644 --- a/packages/components/src/toggle-group-control/toggle-group-control-option/README.md +++ b/packages/components/src/toggle-group-control/toggle-group-control-option/README.md @@ -6,7 +6,6 @@ This feature is still experimental. “Experimental” means this is an early im `ToggleGroupControlOption` is a form component and is meant to be used as a child of [`ToggleGroupControl`](/packages/components/src/toggle-group-control/toggle-group-control/README.md). - ## Usage ```js @@ -22,6 +21,7 @@ function Example() { value="vertical" isBlock __nextHasNoMarginBottom + __next40pxDefaultSize > <ToggleGroupControlOption value="horizontal" diff --git a/packages/components/src/toggle-group-control/toggle-group-control-option/component.tsx b/packages/components/src/toggle-group-control/toggle-group-control-option/component.tsx index b71d37e81cf25f..8632d92fc00578 100644 --- a/packages/components/src/toggle-group-control/toggle-group-control-option/component.tsx +++ b/packages/components/src/toggle-group-control/toggle-group-control-option/component.tsx @@ -53,6 +53,7 @@ function UnforwardedToggleGroupControlOption( * value="vertical" * isBlock * __nextHasNoMarginBottom + * __next40pxDefaultSize * > * <ToggleGroupControlOption value="horizontal" label="Horizontal" /> * <ToggleGroupControlOption value="vertical" label="Vertical" /> diff --git a/packages/components/src/toggle-group-control/toggle-group-control/README.md b/packages/components/src/toggle-group-control/toggle-group-control/README.md index ca5c5d14eb6b5a..841d474c148d45 100644 --- a/packages/components/src/toggle-group-control/toggle-group-control/README.md +++ b/packages/components/src/toggle-group-control/toggle-group-control/README.md @@ -25,6 +25,7 @@ function Example() { value="vertical" isBlock __nextHasNoMarginBottom + __next40pxDefaultSize > <ToggleGroupControlOption value="horizontal" label="Horizontal" /> <ToggleGroupControlOption value="vertical" label="Vertical" /> @@ -100,4 +101,4 @@ Start opting into the larger default height that will become the default size in Start opting into the new margin-free styles that will become the default in a future version. - Required: No -- Default: `false` \ No newline at end of file +- Default: `false` diff --git a/packages/components/src/toggle-group-control/toggle-group-control/as-radio-group.tsx b/packages/components/src/toggle-group-control/toggle-group-control/as-radio-group.tsx index 0166728dbafba4..56fb5faca56385 100644 --- a/packages/components/src/toggle-group-control/toggle-group-control/as-radio-group.tsx +++ b/packages/components/src/toggle-group-control/toggle-group-control/as-radio-group.tsx @@ -3,7 +3,6 @@ */ import type { ForwardedRef } from 'react'; import * as Ariakit from '@ariakit/react'; -import { useStoreState } from '@ariakit/react'; /** * WordPress dependencies @@ -70,7 +69,7 @@ function UnforwardedToggleGroupControlAsRadioGroup( rtl: isRTL(), } ); - const selectedValue = useStoreState( radio, 'value' ); + const selectedValue = Ariakit.useStoreState( radio, 'value' ); const setValue = radio.setValue; // Ensures that the active id is also reset after the value is "reset" by the consumer. diff --git a/packages/components/src/toggle-group-control/toggle-group-control/component.tsx b/packages/components/src/toggle-group-control/toggle-group-control/component.tsx index 0c3cadf210d84a..9f3427e95a6017 100644 --- a/packages/components/src/toggle-group-control/toggle-group-control/component.tsx +++ b/packages/components/src/toggle-group-control/toggle-group-control/component.tsx @@ -23,6 +23,7 @@ import { ToggleGroupControlAsButtonGroup } from './as-button-group'; import { useTrackElementOffsetRect } from '../../utils/element-rect'; import { useMergeRefs } from '@wordpress/compose'; import { useAnimatedOffsetRect } from '../../utils/hooks/use-animated-offset-rect'; +import { maybeWarnDeprecated36pxSize } from '../../utils/deprecated-36px-size'; function UnconnectedToggleGroupControl( props: WordPressComponentProps< ToggleGroupControlProps, 'div', false >, @@ -31,6 +32,7 @@ function UnconnectedToggleGroupControl( const { __nextHasNoMarginBottom = false, __next40pxDefaultSize = false, + __shouldNotWarnDeprecated36pxSize, className, isAdaptiveWidth = false, isBlock = false, @@ -81,6 +83,13 @@ function UnconnectedToggleGroupControl( ? ToggleGroupControlAsButtonGroup : ToggleGroupControlAsRadioGroup; + maybeWarnDeprecated36pxSize( { + componentName: 'ToggleGroupControl', + size, + __next40pxDefaultSize, + __shouldNotWarnDeprecated36pxSize, + } ); + return ( <BaseControl help={ help } @@ -135,6 +144,7 @@ function UnconnectedToggleGroupControl( * value="vertical" * isBlock * __nextHasNoMarginBottom + * __next40pxDefaultSize * > * <ToggleGroupControlOption value="horizontal" label="Horizontal" /> * <ToggleGroupControlOption value="vertical" label="Vertical" /> diff --git a/packages/components/src/toggle-group-control/toggle-group-control/styles.ts b/packages/components/src/toggle-group-control/toggle-group-control/styles.ts index bb6efe476b2b2c..8376b66a5a86cf 100644 --- a/packages/components/src/toggle-group-control/toggle-group-control/styles.ts +++ b/packages/components/src/toggle-group-control/toggle-group-control/styles.ts @@ -39,7 +39,7 @@ export const toggleGroupControl = ( { content: ''; position: absolute; pointer-events: none; - background: ${ COLORS.gray[ 900 ] }; + background: ${ COLORS.theme.foreground }; // Windows High Contrast mode will show this outline, but not the box-shadow. outline: 2px solid transparent; diff --git a/packages/components/src/toggle-group-control/types.ts b/packages/components/src/toggle-group-control/types.ts index 463d8d26e64410..cfa9d00080467c 100644 --- a/packages/components/src/toggle-group-control/types.ts +++ b/packages/components/src/toggle-group-control/types.ts @@ -128,6 +128,13 @@ export type ToggleGroupControlProps = Pick< * @default false */ __next40pxDefaultSize?: boolean; + /** + * Do not throw a warning for the deprecated 36px default size. + * For internal components of other components that already throw the warning. + * + * @ignore + */ + __shouldNotWarnDeprecated36pxSize?: boolean; }; export type ToggleGroupControlContextProps = { diff --git a/packages/components/src/toolbar/stories/index.story.tsx b/packages/components/src/toolbar/stories/index.story.tsx index e4fb3b07e1c904..8590c1ec8a2c6b 100644 --- a/packages/components/src/toolbar/stories/index.story.tsx +++ b/packages/components/src/toolbar/stories/index.story.tsx @@ -51,7 +51,7 @@ const meta: Meta< typeof Toolbar > = { ToolbarDropdownMenu, }, argTypes: { - children: { control: { type: null } }, + children: { control: false }, variant: { options: [ undefined, 'unstyled' ], control: { type: 'radio' }, diff --git a/packages/components/src/toolbar/toolbar-button/index.tsx b/packages/components/src/toolbar/toolbar-button/index.tsx index bb591ff7b521c6..ae1f54acbfc14d 100644 --- a/packages/components/src/toolbar/toolbar-button/index.tsx +++ b/packages/components/src/toolbar/toolbar-button/index.tsx @@ -54,6 +54,7 @@ function UnforwardedToolbarButton( <Button ref={ ref } icon={ restProps.icon } + size="compact" label={ title } shortcut={ restProps.shortcut } data-subscript={ restProps.subscript } @@ -97,6 +98,7 @@ function UnforwardedToolbarButton( > { ( toolbarItemProps ) => ( <Button + size="compact" label={ title } isPressed={ isActive } { ...toolbarItemProps } diff --git a/packages/components/src/toolbar/toolbar/style.scss b/packages/components/src/toolbar/toolbar/style.scss index c0cabacb84c77e..b53df6303e0fbf 100644 --- a/packages/components/src/toolbar/toolbar/style.scss +++ b/packages/components/src/toolbar/toolbar/style.scss @@ -56,9 +56,10 @@ z-index: -1; // Animate in. - animation: components-button__appear-animation 0.1s ease; - animation-fill-mode: forwards; - @include reduce-motion("animation"); + @media not (prefers-reduced-motion) { + animation: components-button__appear-animation 0.1s ease; + animation-fill-mode: forwards; + } } svg { diff --git a/packages/components/src/tools-panel/stories/index.story.tsx b/packages/components/src/tools-panel/stories/index.story.tsx index 459932c9d22d7a..76735c845c3ea6 100644 --- a/packages/components/src/tools-panel/stories/index.story.tsx +++ b/packages/components/src/tools-panel/stories/index.story.tsx @@ -31,9 +31,9 @@ const meta: Meta< typeof ToolsPanel > = { // @ts-expect-error - See https://github.com/storybookjs/storybook/issues/23170 subcomponents: { ToolsPanelItem }, argTypes: { - as: { control: { type: null } }, - children: { control: { type: null } }, - panelId: { control: { type: null } }, + as: { control: false }, + children: { control: false }, + panelId: { control: false }, resetAll: { action: 'resetAll' }, }, parameters: { @@ -74,6 +74,7 @@ export const Default: StoryFn< typeof ToolsPanel > = ( { isShownByDefault > <UnitControl + __next40pxDefaultSize label="Width" value={ width } onChange={ ( next ) => setWidth( next ) } @@ -86,6 +87,7 @@ export const Default: StoryFn< typeof ToolsPanel > = ( { isShownByDefault > <UnitControl + __next40pxDefaultSize label="Height" value={ height } onChange={ ( next ) => setHeight( next ) } @@ -98,6 +100,7 @@ export const Default: StoryFn< typeof ToolsPanel > = ( { isShownByDefault > <UnitControl + __next40pxDefaultSize label="Minimum height" value={ minHeight } onChange={ ( next ) => setMinHeight( next ) } @@ -110,6 +113,7 @@ export const Default: StoryFn< typeof ToolsPanel > = ( { > <ToggleGroupControl __nextHasNoMarginBottom + __next40pxDefaultSize label="Scale" value={ scale } onChange={ ( next ) => setScale( next ) } @@ -166,6 +170,7 @@ export const WithNonToolsPanelItems: StoryFn< typeof ToolsPanel > = ( { isShownByDefault > <UnitControl + __next40pxDefaultSize label="Width" value={ width } onChange={ ( next ) => setWidth( next ) } @@ -178,6 +183,7 @@ export const WithNonToolsPanelItems: StoryFn< typeof ToolsPanel > = ( { isShownByDefault > <UnitControl + __next40pxDefaultSize label="Height" value={ height } onChange={ ( next ) => setHeight( next ) } @@ -236,6 +242,7 @@ export const WithOptionalItemsPlusIcon: StoryFn< typeof ToolsPanel > = ( { } > <UnitControl + __next40pxDefaultSize label="Minimum width" value={ minWidth } onChange={ ( next ) => setMinWidth( next ) } @@ -248,6 +255,7 @@ export const WithOptionalItemsPlusIcon: StoryFn< typeof ToolsPanel > = ( { isShownByDefault={ false } > <UnitControl + __next40pxDefaultSize label="Width" value={ width } onChange={ ( next ) => setWidth( next ) } @@ -260,6 +268,7 @@ export const WithOptionalItemsPlusIcon: StoryFn< typeof ToolsPanel > = ( { isShownByDefault={ false } > <UnitControl + __next40pxDefaultSize label="Height" value={ height } onChange={ ( next ) => setHeight( next ) } @@ -340,6 +349,7 @@ export const WithSlotFillItems: StoryFn< typeof ToolsPanel > = ( { panelId={ panelId } > <UnitControl + __next40pxDefaultSize label="Injected Width" value={ width } onChange={ ( next ) => @@ -355,6 +365,7 @@ export const WithSlotFillItems: StoryFn< typeof ToolsPanel > = ( { panelId={ panelId } > <UnitControl + __next40pxDefaultSize label="Injected Height" value={ height } onChange={ ( next ) => @@ -440,6 +451,7 @@ export const WithConditionalDefaultControl: StoryFn< typeof ToolsPanel > = ( { isShownByDefault > <UnitControl + __next40pxDefaultSize label="Injected Height" value={ height } onChange={ ( next ) => @@ -457,6 +469,7 @@ export const WithConditionalDefaultControl: StoryFn< typeof ToolsPanel > = ( { > <ToggleGroupControl __nextHasNoMarginBottom + __next40pxDefaultSize label="Scale" value={ scale } onChange={ ( next ) => @@ -539,6 +552,7 @@ export const WithConditionallyRenderedControl: StoryFn< isShownByDefault > <UnitControl + __next40pxDefaultSize label="Injected Height" value={ height } onChange={ ( next ) => @@ -559,6 +573,7 @@ export const WithConditionallyRenderedControl: StoryFn< > <ToggleGroupControl __nextHasNoMarginBottom + __next40pxDefaultSize label="Scale" value={ scale } onChange={ ( next ) => diff --git a/packages/components/src/tools-panel/test/index.tsx b/packages/components/src/tools-panel/test/index.tsx index 8d2c4f17a8b170..68cf72233121e0 100644 --- a/packages/components/src/tools-panel/test/index.tsx +++ b/packages/components/src/tools-panel/test/index.tsx @@ -154,7 +154,6 @@ const renderWrappedItemInPanel = () => { const renderPanel = () => { return render( <ToolsPanel { ...defaultProps }> - { false && <div>Hidden</div> } <ToolsPanelItem { ...controlProps }> <div>Example control</div> </ToolsPanelItem> @@ -236,22 +235,6 @@ describe( 'ToolsPanel', () => { it( 'should not render panel menu when there are no panel items', () => { render( <ToolsPanel { ...defaultProps }> - { false && ( - <ToolsPanelItem - label="Not rendered 1" - hasValue={ () => false } - > - Should not show - </ToolsPanelItem> - ) } - { false && ( - <ToolsPanelItem - label="Not rendered 2" - hasValue={ () => false } - > - Not shown either - </ToolsPanelItem> - ) } <span>Visible but insignificant</span> </ToolsPanel> ); diff --git a/packages/components/src/tools-panel/tools-panel/README.md b/packages/components/src/tools-panel/tools-panel/README.md index 1daa7537335e1c..b5e6860e2bd072 100644 --- a/packages/components/src/tools-panel/tools-panel/README.md +++ b/packages/components/src/tools-panel/tools-panel/README.md @@ -101,6 +101,7 @@ export function DimensionPanel() { isShownByDefault > <UnitControl + __next40pxDefaultSize label={ __( 'Height' ) } onChange={ setHeight } value={ height } @@ -113,6 +114,7 @@ export function DimensionPanel() { isShownByDefault > <UnitControl + __next40pxDefaultSize label={ __( 'Width' ) } onChange={ setWidth } value={ width } @@ -124,6 +126,7 @@ export function DimensionPanel() { onDeselect={ () => setPadding( undefined ) } > <BoxControl + __next40pxDefaultSize label={ __( 'Padding' ) } onChange={ setPadding } values={ padding } @@ -136,6 +139,7 @@ export function DimensionPanel() { onDeselect={ () => setMargin( undefined ) } > <BoxControl + __next40pxDefaultSize label={ __( 'Margin' ) } onChange={ setMargin } values={ margin } diff --git a/packages/components/src/tools-panel/tools-panel/component.tsx b/packages/components/src/tools-panel/tools-panel/component.tsx index 4e01e39ffffb43..c094d593dd4c24 100644 --- a/packages/components/src/tools-panel/tools-panel/component.tsx +++ b/packages/components/src/tools-panel/tools-panel/component.tsx @@ -75,6 +75,7 @@ const UnconnectedToolsPanel = ( * onDeselect={ () => setHeight() } * > * <UnitControl + * __next40pxDefaultSize * label={ __( 'Height' ) } * onChange={ setHeight } * value={ height } @@ -86,6 +87,7 @@ const UnconnectedToolsPanel = ( * onDeselect={ () => setWidth() } * > * <UnitControl + * __next40pxDefaultSize * label={ __( 'Width' ) } * onChange={ setWidth } * value={ width } diff --git a/packages/components/src/tooltip/index.tsx b/packages/components/src/tooltip/index.tsx index ce94daf67bfaba..b7184579ceca91 100644 --- a/packages/components/src/tooltip/index.tsx +++ b/packages/components/src/tooltip/index.tsx @@ -2,7 +2,6 @@ * External dependencies */ import * as Ariakit from '@ariakit/react'; -import { useStoreState } from '@ariakit/react'; import clsx from 'clsx'; /** @@ -94,7 +93,7 @@ function UnforwardedTooltip( placement: computedPlacement, showTimeout: delay, } ); - const mounted = useStoreState( tooltipStore, 'mounted' ); + const mounted = Ariakit.useStoreState( tooltipStore, 'mounted' ); if ( isNestedInTooltip ) { return isOnlyChild ? ( diff --git a/packages/components/src/tooltip/stories/index.story.tsx b/packages/components/src/tooltip/stories/index.story.tsx index 4bddba0ff7b666..e3d21525dd345d 100644 --- a/packages/components/src/tooltip/stories/index.story.tsx +++ b/packages/components/src/tooltip/stories/index.story.tsx @@ -19,7 +19,7 @@ const meta: Meta< typeof Tooltip > = { id: 'components-tooltip', component: Tooltip, argTypes: { - children: { control: { type: null } }, + children: { control: false }, position: { control: { type: 'select' }, options: [ diff --git a/packages/components/src/tree-grid/cell.tsx b/packages/components/src/tree-grid/cell.tsx index a9883eb0512266..08261abe10313a 100644 --- a/packages/components/src/tree-grid/cell.tsx +++ b/packages/components/src/tree-grid/cell.tsx @@ -21,7 +21,11 @@ function UnforwardedTreeGridCell( return ( <td { ...props } role="gridcell"> { withoutGridItem ? ( - <>{ children }</> + <> + { typeof children === 'function' + ? children( { ...props, ref } ) + : children } + </> ) : ( <TreeGridItem ref={ ref }>{ children }</TreeGridItem> ) } diff --git a/packages/components/src/tree-grid/stories/index.story.tsx b/packages/components/src/tree-grid/stories/index.story.tsx index 5a1ed95e1fd627..e113103897dbdb 100644 --- a/packages/components/src/tree-grid/stories/index.story.tsx +++ b/packages/components/src/tree-grid/stories/index.story.tsx @@ -22,7 +22,7 @@ const meta: Meta< typeof TreeGrid > = { // @ts-expect-error - See https://github.com/storybookjs/storybook/issues/23170 subcomponents: { TreeGridRow, TreeGridCell }, argTypes: { - children: { control: { type: null } }, + children: { control: false }, }, parameters: { actions: { argTypesRegex: '^on.*' }, @@ -111,6 +111,7 @@ const Rows = ( { label="Description" hideLabelFromVision placeholder="Description" + __next40pxDefaultSize { ...props } /> ) } @@ -121,6 +122,7 @@ const Rows = ( { label="Notes" hideLabelFromVision placeholder="Notes" + __next40pxDefaultSize { ...props } /> ) } diff --git a/packages/components/src/tree-grid/types.ts b/packages/components/src/tree-grid/types.ts index 31d04882d1a815..f4946ba58ca8a0 100644 --- a/packages/components/src/tree-grid/types.ts +++ b/packages/components/src/tree-grid/types.ts @@ -36,7 +36,7 @@ export type TreeGridRowProps = { type RovingTabIndexItemPassThruProps = { ref: React.ForwardedRef< any >; tabIndex?: number; - onFocus: React.FocusEventHandler< any >; + onFocus?: React.FocusEventHandler< any >; [ key: string ]: any; }; diff --git a/packages/components/src/tree-select/README.md b/packages/components/src/tree-select/README.md index 3d26488478bd0c..d2f73443a2a880 100644 --- a/packages/components/src/tree-select/README.md +++ b/packages/components/src/tree-select/README.md @@ -1,10 +1,10 @@ # TreeSelect -TreeSelect component is used to generate select input fields. +<!-- This file is generated automatically and cannot be edited directly. Make edits via TypeScript types and TSDocs. --> -## Usage +<p class="callout callout-info">See the <a href="https://wordpress.github.io/gutenberg/?path=/docs/components-treeselect--docs">WordPress Storybook</a> for more detailed, interactive documentation.</p> -Render a user interface to select the parent page in a hierarchy of pages: +Generates a hierarchical select input. ```jsx import { useState } from 'react'; @@ -15,7 +15,8 @@ const MyTreeSelect = () => { return ( <TreeSelect - __nextHasNoMarginBottom + __nextHasNoMarginBottom + __next40pxDefaultSize label="Parent page" noOptionLabel="No parent page" onChange={ ( newPage ) => setPage( newPage ) } @@ -53,48 +54,163 @@ const MyTreeSelect = () => { ## Props -The set of props accepted by the component will be specified below. -Props not included in this set will be applied to the SelectControl component being used. +### `__next40pxDefaultSize` -### label + - Type: `boolean` + - Required: No + - Default: `false` + +Start opting into the larger default height that will become the default size in a future version. + +### `__nextHasNoMarginBottom` + + - Type: `boolean` + - Required: No + - Default: `false` + +Start opting into the new margin-free styles that will become the default in a future version. + +### `children` + + - Type: `ReactNode` + - Required: No + +As an alternative to the `options` prop, `optgroup`s and `options` can be +passed in as `children` for more customizability. + +### `disabled` + + - Type: `boolean` + - Required: No + - Default: `false` + +If true, the `input` will be disabled. + +### `hideLabelFromVision` + + - Type: `boolean` + - Required: No + - Default: `false` + +If true, the label will only be visible to screen readers. + +### `help` + + - Type: `ReactNode` + - Required: No + +Additional description for the control. + +Only use for meaningful description or instructions for the control. An element containing the description will be programmatically associated to the BaseControl by the means of an `aria-describedby` attribute. + +### `label` + + - Type: `ReactNode` + - Required: No If this property is added, a label will be generated using label property as the content. -- Type: `String` -- Required: No +### `labelPosition` + + - Type: `"top" | "bottom" | "side" | "edge"` + - Required: No + - Default: `'top'` + +The position of the label. + +### `noOptionLabel` -### noOptionLabel + - Type: `string` + - Required: No If this property is added, an option will be added with this label to represent empty selection. -- Type: `String` -- Required: No +### `onChange` -### onChange + - Type: `(value: string, extra?: { event?: ChangeEvent<HTMLSelectElement>; }) => void` + - Required: No -A function that receives the id of the new node element that is being selected. +A function that receives the value of the new option that is being selected as input. -- Type: `function` -- Required: Yes +### `options` -### selectedId + - Type: `readonly ({ label: string; value: string; } & Omit<OptionHTMLAttributes<HTMLOptionElement>, "label" | "value">)[]` + - Required: No + +An array of option property objects to be rendered, +each with a `label` and `value` property, as well as any other +`<option>` attributes. + +### `prefix` + + - Type: `ReactNode` + - Required: No + +Renders an element on the left side of the input. + +By default, the prefix is aligned with the edge of the input border, with no padding. +If you want to apply standard padding in accordance with the size variant, wrap the element in +the provided `<InputControlPrefixWrapper>` component. + +```jsx +import { + __experimentalInputControl as InputControl, + __experimentalInputControlPrefixWrapper as InputControlPrefixWrapper, +} from '@wordpress/components'; + +<InputControl + prefix={<InputControlPrefixWrapper>@</InputControlPrefixWrapper>} +/> +``` + +### `selectedId` + + - Type: `string` + - Required: No The id of the currently selected node. -- Type: `string` | `string[]` -- Required: No +### `size` -### tree + - Type: `"default" | "small" | "compact" | "__unstable-large"` + - Required: No + - Default: `'default'` -An array containing the tree objects with the possible nodes the user can select. +Adjusts the size of the input. -- Type: `Object[]` -- Required: No +### `suffix` -#### __nextHasNoMarginBottom + - Type: `ReactNode` + - Required: No -Start opting into the new margin-free styles that will become the default in a future version. +Renders an element on the right side of the input. + +By default, the suffix is aligned with the edge of the input border, with no padding. +If you want to apply standard padding in accordance with the size variant, wrap the element in +the provided `<InputControlSuffixWrapper>` component. + +```jsx +import { + __experimentalInputControl as InputControl, + __experimentalInputControlSuffixWrapper as InputControlSuffixWrapper, +} from '@wordpress/components'; + +<InputControl + suffix={<InputControlSuffixWrapper>%</InputControlSuffixWrapper>} +/> +``` + +### `tree` + + - Type: `Tree[]` + - Required: No + +An array containing the tree objects with the possible nodes the user can select. + +### `variant` + + - Type: `"default" | "minimal"` + - Required: No + - Default: `'default'` -- Type: `Boolean` -- Required: No -- Default: `false` +The style variant of the control. diff --git a/packages/components/src/tree-select/docs-manifest.json b/packages/components/src/tree-select/docs-manifest.json new file mode 100644 index 00000000000000..0e74d71d309e10 --- /dev/null +++ b/packages/components/src/tree-select/docs-manifest.json @@ -0,0 +1,5 @@ +{ + "$schema": "../../schemas/docs-manifest.json", + "displayName": "TreeSelect", + "filePath": "./index.tsx" +} diff --git a/packages/components/src/tree-select/index.tsx b/packages/components/src/tree-select/index.tsx index bd92807bff4cc9..66116576361623 100644 --- a/packages/components/src/tree-select/index.tsx +++ b/packages/components/src/tree-select/index.tsx @@ -11,6 +11,7 @@ import { SelectControl } from '../select-control'; import type { TreeSelectProps, Tree, Truthy } from './types'; import { useDeprecated36pxDefaultSizeProp } from '../utils/use-deprecated-props'; import { ContextSystemProvider } from '../context'; +import { maybeWarnDeprecated36pxSize } from '../utils/deprecated-36px-size'; const CONTEXT_VALUE = { BaseControl: { @@ -35,11 +36,11 @@ function getSelectOptions( } /** - * TreeSelect component is used to generate select input fields. + * Generates a hierarchical select input. * * ```jsx + * import { useState } from 'react'; * import { TreeSelect } from '@wordpress/components'; - * import { useState } from '@wordpress/element'; * * const MyTreeSelect = () => { * const [ page, setPage ] = useState( 'p21' ); @@ -47,6 +48,7 @@ function getSelectOptions( * return ( * <TreeSelect * __nextHasNoMarginBottom + * __next40pxDefaultSize * label="Parent page" * noOptionLabel="No parent page" * onChange={ ( newPage ) => setPage( newPage ) } @@ -99,9 +101,16 @@ export function TreeSelect( props: TreeSelectProps ) { ].filter( < T, >( option: T ): option is Truthy< T > => !! option ); }, [ noOptionLabel, tree ] ); + maybeWarnDeprecated36pxSize( { + componentName: 'TreeSelect', + size: restProps.size, + __next40pxDefaultSize: restProps.__next40pxDefaultSize, + } ); + return ( <ContextSystemProvider value={ CONTEXT_VALUE }> <SelectControl + __shouldNotWarnDeprecated36pxSize { ...{ label, options, onChange } } value={ selectedId } { ...restProps } diff --git a/packages/components/src/tree-select/stories/index.story.tsx b/packages/components/src/tree-select/stories/index.story.tsx index 703147dc145344..0ef8f44b790db0 100644 --- a/packages/components/src/tree-select/stories/index.story.tsx +++ b/packages/components/src/tree-select/stories/index.story.tsx @@ -22,7 +22,7 @@ const meta: Meta< typeof TreeSelect > = { label: { control: { type: 'text' } }, prefix: { control: { type: 'text' } }, suffix: { control: { type: 'text' } }, - selectedId: { control: { type: null } }, + selectedId: { control: false }, }, parameters: { controls: { @@ -50,6 +50,7 @@ const TreeSelectWithState: StoryFn< typeof TreeSelect > = ( props ) => { export const Default = TreeSelectWithState.bind( {} ); Default.args = { __nextHasNoMarginBottom: true, + __next40pxDefaultSize: true, label: 'Label Text', noOptionLabel: 'No parent page', help: 'Help text to explain the select control.', diff --git a/packages/components/src/tree-select/types.ts b/packages/components/src/tree-select/types.ts index da90ece3a658e8..59e8e173fab02f 100644 --- a/packages/components/src/tree-select/types.ts +++ b/packages/components/src/tree-select/types.ts @@ -16,11 +16,18 @@ export interface Tree { // `TreeSelect` inherits props from `SelectControl`, but only // in single selection mode (ie. when the `multiple` prop is not defined). export interface TreeSelectProps - extends Omit< SelectControlSingleSelectionProps, 'value' | 'multiple' > { + extends Omit< + SelectControlSingleSelectionProps, + 'value' | 'multiple' | 'onChange' + > { /** * If this property is added, an option will be added with this label to represent empty selection. */ noOptionLabel?: string; + /** + * A function that receives the value of the new option that is being selected as input. + */ + onChange?: SelectControlSingleSelectionProps[ 'onChange' ]; /** * An array containing the tree objects with the possible nodes the user can select. */ diff --git a/packages/components/src/unit-control/README.md b/packages/components/src/unit-control/README.md index dd54de80357d8e..65d9e4b74062dd 100644 --- a/packages/components/src/unit-control/README.md +++ b/packages/components/src/unit-control/README.md @@ -15,7 +15,7 @@ import { __experimentalUnitControl as UnitControl } from '@wordpress/components' const Example = () => { const [ value, setValue ] = useState( '10px' ); - return <UnitControl onChange={ setValue } value={ value } />; + return <UnitControl __next40pxDefaultSize onChange={ setValue } value={ value } />; }; ``` @@ -128,7 +128,7 @@ const Example = () => { ]; return ( - <UnitControl onChange={ setValue } value={ value } units={ units } /> + <UnitControl __next40pxDefaultSize onChange={ setValue } value={ value } units={ units } /> ); }; ``` @@ -143,7 +143,7 @@ For example, a `value` of `50%` will set the current unit to `%`. Example: ```jsx -<UnitControl value="50%" /> +<UnitControl __next40pxDefaultSize value="50%" /> ``` - Required: No diff --git a/packages/components/src/unit-control/index.tsx b/packages/components/src/unit-control/index.tsx index 2dd08cc155225f..65e1e56cda3b3b 100644 --- a/packages/components/src/unit-control/index.tsx +++ b/packages/components/src/unit-control/index.tsx @@ -27,6 +27,7 @@ import { useControlledState } from '../utils/hooks'; import { escapeRegExp } from '../utils/strings'; import type { UnitControlProps, UnitControlOnChangeCallback } from './types'; import { useDeprecated36pxDefaultSizeProp } from '../utils/use-deprecated-props'; +import { maybeWarnDeprecated36pxSize } from '../utils/deprecated-36px-size'; function UnforwardedUnitControl( unitControlProps: WordPressComponentProps< @@ -55,9 +56,17 @@ function UnforwardedUnitControl( units: unitsProp = CSS_UNITS, value: valueProp, onFocus: onFocusProp, + __shouldNotWarnDeprecated36pxSize, ...props } = useDeprecated36pxDefaultSizeProp( unitControlProps ); + maybeWarnDeprecated36pxSize( { + componentName: 'UnitControl', + __next40pxDefaultSize: props.__next40pxDefaultSize, + size, + __shouldNotWarnDeprecated36pxSize, + } ); + if ( 'unit' in unitControlProps ) { deprecated( 'UnitControl unit prop', { since: '5.6', @@ -215,6 +224,7 @@ function UnforwardedUnitControl( return ( <ValueInput { ...props } + __shouldNotWarnDeprecated36pxSize autoComplete={ autoComplete } className={ classes } disabled={ disabled } @@ -246,7 +256,7 @@ function UnforwardedUnitControl( * const Example = () => { * const [ value, setValue ] = useState( '10px' ); * - * return <UnitControl onChange={ setValue } value={ value } />; + * return <UnitControl __next40pxDefaultSize onChange={ setValue } value={ value } />; * }; * ``` */ diff --git a/packages/components/src/unit-control/stories/index.story.tsx b/packages/components/src/unit-control/stories/index.story.tsx index de8f476e26e5c7..3a9da70ad5095f 100644 --- a/packages/components/src/unit-control/stories/index.story.tsx +++ b/packages/components/src/unit-control/stories/index.story.tsx @@ -20,11 +20,11 @@ const meta: Meta< typeof UnitControl > = { id: 'components-experimental-unitcontrol', argTypes: { __unstableInputWidth: { control: { type: 'text' } }, - __unstableStateReducer: { control: { type: null } }, - onChange: { control: { type: null } }, - onUnitChange: { control: { type: null } }, + __unstableStateReducer: { control: false }, + onChange: { control: false }, + onUnitChange: { control: false }, prefix: { control: { type: 'text' } }, - value: { control: { type: null } }, + value: { control: false }, }, parameters: { actions: { argTypesRegex: '^on.*' }, @@ -59,6 +59,7 @@ export const Default: StoryFn< typeof UnitControl > = DefaultTemplate.bind( ); Default.args = { label: 'Label', + __next40pxDefaultSize: true, }; /** diff --git a/packages/components/src/unit-control/test/index.tsx b/packages/components/src/unit-control/test/index.tsx index d91498d46478b3..ad98d57cae6405 100644 --- a/packages/components/src/unit-control/test/index.tsx +++ b/packages/components/src/unit-control/test/index.tsx @@ -12,9 +12,13 @@ import { useState } from '@wordpress/element'; /** * Internal dependencies */ -import UnitControl from '..'; +import _UnitControl from '..'; import { CSS_UNITS, parseQuantityAndUnitFromRawValue } from '../utils'; +const UnitControl = ( props: React.ComponentProps< typeof _UnitControl > ) => ( + <_UnitControl __next40pxDefaultSize { ...props } /> +); + const getInput = ( { isInputTypeText = false, }: { diff --git a/packages/components/src/unit-control/types.ts b/packages/components/src/unit-control/types.ts index 9164502668a2b0..891945b422862a 100644 --- a/packages/components/src/unit-control/types.ts +++ b/packages/components/src/unit-control/types.ts @@ -107,4 +107,11 @@ export type UnitControlProps = Pick< InputControlProps, 'size' > & * Callback when either the quantity or the unit inputs gains focus. */ onFocus?: FocusEventHandler< HTMLInputElement | HTMLSelectElement >; + /** + * Do not throw a warning for the deprecated 36px default size. + * For internal components of other components that already throw the warning. + * + * @ignore + */ + __shouldNotWarnDeprecated36pxSize?: boolean; }; diff --git a/packages/components/src/utils/colors-values.js b/packages/components/src/utils/colors-values.js index 1ad528d13f0108..439d464f1460b1 100644 --- a/packages/components/src/utils/colors-values.js +++ b/packages/components/src/utils/colors-values.js @@ -75,6 +75,9 @@ export const COLORS = Object.freeze( { * @deprecated Use semantic aliases in `COLORS.ui` or theme-ready variables in `COLORS.theme.gray`. */ gray: GRAY, // TODO: Stop exporting this when everything is migrated to `theme` or `ui` + /** + * @deprecated Prefer theme-ready variables in `COLORS.theme`. + */ white, alert: ALERT, /** diff --git a/packages/components/src/utils/config-values.js b/packages/components/src/utils/config-values.js index 1bc3945f9b3b16..13b704540e9c4c 100644 --- a/packages/components/src/utils/config-values.js +++ b/packages/components/src/utils/config-values.js @@ -12,7 +12,6 @@ const CONTROL_PROPS = { controlPaddingXSmall: 8, controlPaddingXLarge: 12 * 1.3334, // TODO: Deprecate - controlBackgroundColor: COLORS.white, controlBoxShadowFocus: `0 0 0 0.5px ${ COLORS.theme.accent }`, controlHeight: CONTROL_HEIGHT, controlHeightXSmall: `calc( ${ CONTROL_HEIGHT } * 0.6 )`, diff --git a/packages/components/src/utils/deprecated-36px-size.ts b/packages/components/src/utils/deprecated-36px-size.ts index be5baa2515637c..0f87c2cd270c73 100644 --- a/packages/components/src/utils/deprecated-36px-size.ts +++ b/packages/components/src/utils/deprecated-36px-size.ts @@ -7,12 +7,15 @@ export function maybeWarnDeprecated36pxSize( { componentName, __next40pxDefaultSize, size, + __shouldNotWarnDeprecated36pxSize, }: { componentName: string; __next40pxDefaultSize: boolean | undefined; size: string | undefined; + __shouldNotWarnDeprecated36pxSize?: boolean; } ) { if ( + __shouldNotWarnDeprecated36pxSize || __next40pxDefaultSize || ( size !== undefined && size !== 'default' ) ) { diff --git a/packages/components/src/view/stories/index.story.tsx b/packages/components/src/view/stories/index.story.tsx index 324825059deb20..ee4bb71b8a243f 100644 --- a/packages/components/src/view/stories/index.story.tsx +++ b/packages/components/src/view/stories/index.story.tsx @@ -12,7 +12,7 @@ const meta: Meta< typeof View > = { component: View, title: 'Components (Experimental)/View', argTypes: { - as: { control: { type: null } }, + as: { control: false }, children: { control: { type: 'text' } }, }, parameters: { diff --git a/packages/components/src/visually-hidden/stories/index.story.tsx b/packages/components/src/visually-hidden/stories/index.story.tsx index 6ebcf9a2e949ce..1c79718ddf6e04 100644 --- a/packages/components/src/visually-hidden/stories/index.story.tsx +++ b/packages/components/src/visually-hidden/stories/index.story.tsx @@ -13,7 +13,7 @@ const meta: Meta< typeof VisuallyHidden > = { title: 'Components/Typography/VisuallyHidden', id: 'components-visuallyhidden', argTypes: { - children: { control: { type: null } }, + children: { control: false }, as: { control: { type: 'text' } }, }, parameters: { diff --git a/packages/components/src/z-stack/stories/index.story.tsx b/packages/components/src/z-stack/stories/index.story.tsx index 46a364bc520f32..53c7d950e75b1b 100644 --- a/packages/components/src/z-stack/stories/index.story.tsx +++ b/packages/components/src/z-stack/stories/index.story.tsx @@ -16,7 +16,7 @@ const meta: Meta< typeof ZStack > = { title: 'Components (Experimental)/ZStack', argTypes: { as: { control: { type: 'text' } }, - children: { control: { type: null } }, + children: { control: false }, }, parameters: { controls: { diff --git a/packages/components/tsconfig.json b/packages/components/tsconfig.json index 2033a6f43fede6..09bfef2c53b076 100644 --- a/packages/components/tsconfig.json +++ b/packages/components/tsconfig.json @@ -2,8 +2,6 @@ "$schema": "https://json.schemastore.org/tsconfig.json", "extends": "../../tsconfig.base.json", "compilerOptions": { - "rootDir": "src", - "declarationDir": "build-types", "types": [ "gutenberg-env", "gutenberg-test-env", @@ -31,7 +29,6 @@ { "path": "../rich-text" }, { "path": "../warning" } ], - "include": [ "src/**/*" ], "exclude": [ "src/**/*.android.js", "src/**/*.ios.js", diff --git a/packages/compose/CHANGELOG.md b/packages/compose/CHANGELOG.md index 8f8bedfde15d4a..9b6d54aac474c7 100644 --- a/packages/compose/CHANGELOG.md +++ b/packages/compose/CHANGELOG.md @@ -2,6 +2,16 @@ ## Unreleased +## 7.16.0 (2025-01-15) + +## 7.15.0 (2025-01-02) + +## 7.14.0 (2024-12-11) + +## 7.13.0 (2024-11-27) + +## 7.12.0 (2024-11-16) + ## 7.11.0 (2024-10-30) ## 7.10.0 (2024-10-16) diff --git a/packages/compose/package.json b/packages/compose/package.json index 68b00b24298d87..77851f50a5823f 100644 --- a/packages/compose/package.json +++ b/packages/compose/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/compose", - "version": "7.11.0", + "version": "7.16.0", "description": "WordPress higher-order components (HOCs).", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -33,13 +33,13 @@ "dependencies": { "@babel/runtime": "7.25.7", "@types/mousetrap": "^1.6.8", - "@wordpress/deprecated": "*", - "@wordpress/dom": "*", - "@wordpress/element": "*", - "@wordpress/is-shallow-equal": "*", - "@wordpress/keycodes": "*", - "@wordpress/priority-queue": "*", - "@wordpress/undo-manager": "*", + "@wordpress/deprecated": "file:../deprecated", + "@wordpress/dom": "file:../dom", + "@wordpress/element": "file:../element", + "@wordpress/is-shallow-equal": "file:../is-shallow-equal", + "@wordpress/keycodes": "file:../keycodes", + "@wordpress/priority-queue": "file:../priority-queue", + "@wordpress/undo-manager": "file:../undo-manager", "change-case": "^4.1.2", "clipboard": "^2.0.11", "mousetrap": "^1.6.5", diff --git a/packages/compose/src/hooks/use-async-list/index.ts b/packages/compose/src/hooks/use-async-list/index.ts index ff53bc7b9eb99d..bc085dd49b8183 100644 --- a/packages/compose/src/hooks/use-async-list/index.ts +++ b/packages/compose/src/hooks/use-async-list/index.ts @@ -13,7 +13,7 @@ type AsyncListConfig = { * * @param list New array. * @param state Current state. - * @return First items present iin state. + * @return First items present in state. */ function getFirstItemsPresentInState< T >( list: T[], state: T[] ): T[] { const firstItems = []; diff --git a/packages/compose/src/hooks/use-focus-return/index.js b/packages/compose/src/hooks/use-focus-return/index.js index 2cd93b279cd318..36dc7560669652 100644 --- a/packages/compose/src/hooks/use-focus-return/index.js +++ b/packages/compose/src/hooks/use-focus-return/index.js @@ -48,7 +48,13 @@ function useFocusReturn( onFocusReturn ) { return; } - focusedBeforeMount.current = node.ownerDocument.activeElement; + const activeDocument = + node.ownerDocument.activeElement instanceof + window.HTMLIFrameElement + ? node.ownerDocument.activeElement.contentDocument + : node.ownerDocument; + + focusedBeforeMount.current = activeDocument?.activeElement ?? null; } else if ( focusedBeforeMount.current ) { const isFocused = ref.current?.contains( ref.current?.ownerDocument.activeElement diff --git a/packages/compose/src/hooks/use-merge-refs/test/index.js b/packages/compose/src/hooks/use-merge-refs/test/index.js index 4c883ff97c6082..3744dbf9f6b16a 100644 --- a/packages/compose/src/hooks/use-merge-refs/test/index.js +++ b/packages/compose/src/hooks/use-merge-refs/test/index.js @@ -152,7 +152,7 @@ describe( 'useMergeRefs', () => { rerender( <MergedRefs count={ 2 } /> ); - // After a second render with a dependency change, expect the inital + // After a second render with a dependency change, expect the initial // callback function to be called with null and the new callback // function to be called with the original node. Note that for callback // one no dependencies have changed. @@ -235,7 +235,7 @@ describe( 'useMergeRefs', () => { rerender( <MergedRefs tagName="button" count={ 1 } /> ); - // After a third render with a dependency change, expect the inital + // After a third render with a dependency change, expect the initial // callback function to be called with null and the new callback // function to be called with the new element. Note that for callback // one no dependencies have changed. diff --git a/packages/compose/src/hooks/use-warn-on-change/index.js b/packages/compose/src/hooks/use-warn-on-change/index.js index 2da51db01d7b68..e8a865902d70b7 100644 --- a/packages/compose/src/hooks/use-warn-on-change/index.js +++ b/packages/compose/src/hooks/use-warn-on-change/index.js @@ -3,7 +3,7 @@ */ import usePrevious from '../use-previous'; -// Disable reason: Object and object are distinctly different types in TypeScript and we mean the lowercase object in thise case +// Disable reason: Object and object are distinctly different types in TypeScript and we mean the lowercase object in this case // but eslint wants to force us to use `Object`. See https://stackoverflow.com/questions/49464634/difference-between-object-and-object-in-typescript /* eslint-disable jsdoc/check-types */ /** diff --git a/packages/core-commands/CHANGELOG.md b/packages/core-commands/CHANGELOG.md index b98e0c55e24ff5..c93d3b2647d520 100644 --- a/packages/core-commands/CHANGELOG.md +++ b/packages/core-commands/CHANGELOG.md @@ -2,6 +2,16 @@ ## Unreleased +## 1.16.0 (2025-01-15) + +## 1.15.0 (2025-01-02) + +## 1.14.0 (2024-12-11) + +## 1.13.0 (2024-11-27) + +## 1.12.0 (2024-11-16) + ## 1.11.0 (2024-10-30) ## 1.10.0 (2024-10-16) diff --git a/packages/core-commands/package.json b/packages/core-commands/package.json index db2fb900865c80..bb215e46c85932 100644 --- a/packages/core-commands/package.json +++ b/packages/core-commands/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/core-commands", - "version": "1.11.0", + "version": "1.16.0", "description": "WordPress core reusable commands.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -29,19 +29,19 @@ "sideEffects": false, "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/block-editor": "*", - "@wordpress/commands": "*", - "@wordpress/compose": "*", - "@wordpress/core-data": "*", - "@wordpress/data": "*", - "@wordpress/element": "*", - "@wordpress/html-entities": "*", - "@wordpress/i18n": "*", - "@wordpress/icons": "*", - "@wordpress/notices": "*", - "@wordpress/private-apis": "*", - "@wordpress/router": "*", - "@wordpress/url": "*" + "@wordpress/block-editor": "file:../block-editor", + "@wordpress/commands": "file:../commands", + "@wordpress/compose": "file:../compose", + "@wordpress/core-data": "file:../core-data", + "@wordpress/data": "file:../data", + "@wordpress/element": "file:../element", + "@wordpress/html-entities": "file:../html-entities", + "@wordpress/i18n": "file:../i18n", + "@wordpress/icons": "file:../icons", + "@wordpress/notices": "file:../notices", + "@wordpress/private-apis": "file:../private-apis", + "@wordpress/router": "file:../router", + "@wordpress/url": "file:../url" }, "peerDependencies": { "react": "^18.0.0", diff --git a/packages/core-commands/src/admin-navigation-commands.js b/packages/core-commands/src/admin-navigation-commands.js index 8a8167bb29b820..f82faa05baae4c 100644 --- a/packages/core-commands/src/admin-navigation-commands.js +++ b/packages/core-commands/src/admin-navigation-commands.js @@ -44,11 +44,7 @@ const getAddNewPageCommand = () => } ); if ( page?.id ) { - history.push( { - postId: page.id, - postType: 'page', - canvas: 'edit', - } ); + history.navigate( `/page/${ page.id }?canvas=edit` ); } } catch ( error ) { const errorMessage = diff --git a/packages/core-commands/src/site-editor-navigation-commands.js b/packages/core-commands/src/site-editor-navigation-commands.js index 2785d809d41e03..c1b12a84d4d61a 100644 --- a/packages/core-commands/src/site-editor-navigation-commands.js +++ b/packages/core-commands/src/site-editor-navigation-commands.js @@ -136,19 +136,18 @@ const getNavigationCommandLoaderPerPostType = ( postType ) => return { ...command, callback: ( { close } ) => { - const args = { - postType, - postId: record.id, - canvas: 'edit', - }; - const targetUrl = addQueryArgs( - 'site-editor.php', - args - ); if ( isSiteEditor ) { - history.push( args ); + history.navigate( + `/${ postType }/${ record.id }?canvas=edit` + ); } else { - document.location = targetUrl; + document.location = addQueryArgs( + 'site-editor.php', + { + p: `/${ postType }/${ record.id }`, + canvas: 'edit', + } + ); } close(); }, @@ -220,19 +219,18 @@ const getNavigationCommandLoaderPerTemplate = ( templateType ) => : __( '(no title)' ), icon: icons[ templateType ], callback: ( { close } ) => { - const args = { - postType: templateType, - postId: record.id, - canvas: 'edit', - }; - const targetUrl = addQueryArgs( - 'site-editor.php', - args - ); if ( isSiteEditor ) { - history.push( args ); + history.navigate( + `/${ templateType }/${ record.id }?canvas=edit` + ); } else { - document.location = targetUrl; + document.location = addQueryArgs( + 'site-editor.php', + { + p: `/${ templateType }/${ record.id }`, + canvas: 'edit', + } + ); } close(); }, @@ -249,18 +247,19 @@ const getNavigationCommandLoaderPerTemplate = ( templateType ) => label: __( 'Template parts' ), icon: symbolFilled, callback: ( { close } ) => { - const args = { - postType: 'wp_template_part', - categoryId: 'all-parts', - }; - const targetUrl = addQueryArgs( - 'site-editor.php', - args - ); if ( isSiteEditor ) { - history.push( args ); + history.navigate( + '/pattern?postType=wp_template_part&categoryId=all-parts' + ); } else { - document.location = targetUrl; + document.location = addQueryArgs( + 'site-editor.php', + { + p: '/pattern', + postType: 'wp_template_part', + categoryId: 'all-parts', + } + ); } close(); }, @@ -303,17 +302,15 @@ const getSiteEditorBasicNavigationCommands = () => label: __( 'Navigation' ), icon: navigation, callback: ( { close } ) => { - const args = { - postType: 'wp_navigation', - }; - const targetUrl = addQueryArgs( - 'site-editor.php', - args - ); if ( isSiteEditor ) { - history.push( args ); + history.navigate( '/navigation' ); } else { - document.location = targetUrl; + document.location = addQueryArgs( + 'site-editor.php', + { + p: '/navigation', + } + ); } close(); }, @@ -324,17 +321,15 @@ const getSiteEditorBasicNavigationCommands = () => label: __( 'Styles' ), icon: styles, callback: ( { close } ) => { - const args = { - path: '/wp_global_styles', - }; - const targetUrl = addQueryArgs( - 'site-editor.php', - args - ); if ( isSiteEditor ) { - history.push( args ); + history.navigate( '/styles' ); } else { - document.location = targetUrl; + document.location = addQueryArgs( + 'site-editor.php', + { + p: '/styles', + } + ); } close(); }, @@ -345,17 +340,15 @@ const getSiteEditorBasicNavigationCommands = () => label: __( 'Pages' ), icon: page, callback: ( { close } ) => { - const args = { - postType: 'page', - }; - const targetUrl = addQueryArgs( - 'site-editor.php', - args - ); if ( isSiteEditor ) { - history.push( args ); + history.navigate( '/page' ); } else { - document.location = targetUrl; + document.location = addQueryArgs( + 'site-editor.php', + { + p: '/page', + } + ); } close(); }, @@ -366,17 +359,15 @@ const getSiteEditorBasicNavigationCommands = () => label: __( 'Templates' ), icon: layout, callback: ( { close } ) => { - const args = { - postType: 'wp_template', - }; - const targetUrl = addQueryArgs( - 'site-editor.php', - args - ); if ( isSiteEditor ) { - history.push( args ); + history.navigate( '/template' ); } else { - document.location = targetUrl; + document.location = addQueryArgs( + 'site-editor.php', + { + p: '/template', + } + ); } close(); }, @@ -389,17 +380,15 @@ const getSiteEditorBasicNavigationCommands = () => icon: symbol, callback: ( { close } ) => { if ( canCreateTemplate ) { - const args = { - postType: 'wp_block', - }; - const targetUrl = addQueryArgs( - 'site-editor.php', - args - ); if ( isSiteEditor ) { - history.push( args ); + history.navigate( '/pattern' ); } else { - document.location = targetUrl; + document.location = addQueryArgs( + 'site-editor.php', + { + p: '/pattern', + } + ); } close(); } else { diff --git a/packages/core-data/CHANGELOG.md b/packages/core-data/CHANGELOG.md index 31b44fa32dd0a4..235d3644c46dc7 100644 --- a/packages/core-data/CHANGELOG.md +++ b/packages/core-data/CHANGELOG.md @@ -2,6 +2,16 @@ ## Unreleased +## 7.16.0 (2025-01-15) + +## 7.15.0 (2025-01-02) + +## 7.14.0 (2024-12-11) + +## 7.13.0 (2024-11-27) + +## 7.12.0 (2024-11-16) + ## 7.11.0 (2024-10-30) ## 7.10.0 (2024-10-16) diff --git a/packages/core-data/README.md b/packages/core-data/README.md index 079f95ddbfc7a6..ec7db6a2a5736f 100644 --- a/packages/core-data/README.md +++ b/packages/core-data/README.md @@ -288,7 +288,7 @@ _Returns_ ### redo -Action triggered to redo the last undoed edit to an entity record, if any. +Action triggered to redo the last undone edit to an entity record, if any. ### saveEditedEntityRecord @@ -581,7 +581,7 @@ _Parameters_ - _state_ `State`: State tree - _kind_ `string`: Entity kind. - _name_ `string`: Entity name. -- _key_ `EntityRecordKey`: Record's key +- _key_ `EntityRecordKey`: Optional record's key. If requesting a global record (e.g. site settings), the key can be omitted. If requesting a specific item, the key must always be included. - _query_ `GetRecordsHttpQuery`: Optional query. If requesting specific fields, fields must always include the ID. For valid query parameters see the [Reference](https://developer.wordpress.org/rest-api/reference/) in the REST API Handbook and select the entity kind. Then see the arguments available "Retrieve a [Entity kind]". _Returns_ @@ -1083,6 +1083,8 @@ function PageRenameForm( { id } ) { return ( <form onSubmit={ onRename }> <TextControl + __nextHasNoMarginBottom + __next40pxDefaultSize label={ __( 'Name' ) } value={ page.editedRecord.title } onChange={ setTitle } diff --git a/packages/core-data/package.json b/packages/core-data/package.json index a7216e931a70ca..c3d25fc183cdf5 100644 --- a/packages/core-data/package.json +++ b/packages/core-data/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/core-data", - "version": "7.11.0", + "version": "7.16.0", "description": "Access to and manipulation of core WordPress entities.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -33,22 +33,22 @@ ], "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/api-fetch": "*", - "@wordpress/block-editor": "*", - "@wordpress/blocks": "*", - "@wordpress/compose": "*", - "@wordpress/data": "*", - "@wordpress/deprecated": "*", - "@wordpress/element": "*", - "@wordpress/html-entities": "*", - "@wordpress/i18n": "*", - "@wordpress/is-shallow-equal": "*", - "@wordpress/private-apis": "*", - "@wordpress/rich-text": "*", - "@wordpress/sync": "*", - "@wordpress/undo-manager": "*", - "@wordpress/url": "*", - "@wordpress/warning": "*", + "@wordpress/api-fetch": "file:../api-fetch", + "@wordpress/block-editor": "file:../block-editor", + "@wordpress/blocks": "file:../blocks", + "@wordpress/compose": "file:../compose", + "@wordpress/data": "file:../data", + "@wordpress/deprecated": "file:../deprecated", + "@wordpress/element": "file:../element", + "@wordpress/html-entities": "file:../html-entities", + "@wordpress/i18n": "file:../i18n", + "@wordpress/is-shallow-equal": "file:../is-shallow-equal", + "@wordpress/private-apis": "file:../private-apis", + "@wordpress/rich-text": "file:../rich-text", + "@wordpress/sync": "file:../sync", + "@wordpress/undo-manager": "file:../undo-manager", + "@wordpress/url": "file:../url", + "@wordpress/warning": "file:../warning", "change-case": "^4.1.2", "equivalent-key-map": "^0.2.2", "fast-deep-equal": "^3.1.3", diff --git a/packages/core-data/src/actions.js b/packages/core-data/src/actions.js index 13cbba39e11765..275cfccdb7823a 100644 --- a/packages/core-data/src/actions.js +++ b/packages/core-data/src/actions.js @@ -450,7 +450,7 @@ export const undo = }; /** - * Action triggered to redo the last undoed + * Action triggered to redo the last undone * edit to an entity record, if any. */ export const redo = diff --git a/packages/core-data/src/batch/create-batch.js b/packages/core-data/src/batch/create-batch.js index dd2c3b74188c13..73e07b140b652d 100644 --- a/packages/core-data/src/batch/create-batch.js +++ b/packages/core-data/src/batch/create-batch.js @@ -48,7 +48,7 @@ export default function createBatch( processor = defaultProcessor ) { * rejected when the input is processed by `batch.run()`. * * You may also pass a thunk which allows inputs to be added - * asychronously. + * asynchronously. * * ``` * // Both are allowed: diff --git a/packages/core-data/src/dynamic-entities.ts b/packages/core-data/src/dynamic-entities.ts new file mode 100644 index 00000000000000..51b579cb0cfcf0 --- /dev/null +++ b/packages/core-data/src/dynamic-entities.ts @@ -0,0 +1,111 @@ +/** + * Internal dependencies + */ +import type { GetRecordsHttpQuery, State } from './selectors'; +import type * as ET from './entity-types'; + +export type WPEntityTypes< C extends ET.Context = 'edit' > = { + Comment: ET.Comment< C >; + GlobalStyles: ET.GlobalStylesRevision< C >; + Media: ET.Attachment< C >; + Menu: ET.NavMenu< C >; + MenuItem: ET.NavMenuItem< C >; + MenuLocation: ET.MenuLocation< C >; + Plugin: ET.Plugin< C >; + PostType: ET.Type< C >; + Revision: ET.PostRevision< C >; + Sidebar: ET.Sidebar< C >; + Site: ET.Settings< C >; + Status: ET.PostStatusObject< C >; + Taxonomy: ET.Taxonomy< C >; + Theme: ET.Theme< C >; + UnstableBase: ET.UnstableBase< C >; + User: ET.User< C >; + Widget: ET.Widget< C >; + WidgetType: ET.WidgetType< C >; +}; + +/** + * A simple utility that pluralizes a string. + * Converts: + * - "post" to "posts" + * - "taxonomy" to "taxonomies" + * - "media" to "mediaItems" + * - "status" to "statuses" + * + * It does not pluralize "GlobalStyles" due to lack of clarity about it at time of writing. + */ +type PluralizeEntity< T extends string > = T extends 'GlobalStyles' + ? never + : T extends 'Media' + ? 'MediaItems' + : T extends 'Status' + ? 'Statuses' + : T extends `${ infer U }y` + ? `${ U }ies` + : `${ T }s`; + +/** + * A simple utility that singularizes a string. + * + * Converts: + * - "posts" to "post" + * - "taxonomies" to "taxonomy" + * - "mediaItems" to "media" + * - "statuses" to "status" + */ +type SingularizeEntity< T extends string > = T extends 'MediaItems' + ? 'Media' + : T extends 'Statuses' + ? 'Status' + : T extends `${ infer U }ies` + ? `${ U }y` + : T extends `${ infer U }s` + ? U + : T; + +export type SingularGetters = { + [ Key in `get${ keyof WPEntityTypes }` ]: ( + state: State, + id: number | string, + query?: GetRecordsHttpQuery + ) => WPEntityTypes[ Key extends `get${ infer E }` ? E : never ] | undefined; +}; + +export type PluralGetters = { + [ Key in `get${ PluralizeEntity< keyof WPEntityTypes > }` ]: ( + state: State, + query?: GetRecordsHttpQuery + ) => Array< + WPEntityTypes[ Key extends `get${ infer E }` + ? SingularizeEntity< E > + : never ] + > | null; +}; + +type ActionOptions = { + throwOnError?: boolean; +}; + +type DeleteRecordsHttpQuery = Record< string, any >; + +export type SaveActions = { + [ Key in `save${ keyof WPEntityTypes }` ]: ( + data: Partial< + WPEntityTypes[ Key extends `save${ infer E }` ? E : never ] + >, + options?: ActionOptions + ) => Promise< void >; +}; + +export type DeleteActions = { + [ Key in `delete${ keyof WPEntityTypes }` ]: ( + id: number | string, + query?: DeleteRecordsHttpQuery, + options?: ActionOptions + ) => Promise< void >; +}; + +export let dynamicActions: SaveActions & DeleteActions; + +export let dynamicSelectors: SingularGetters & PluralGetters; diff --git a/packages/core-data/src/entities.js b/packages/core-data/src/entities.js index 3c10676750a2ab..2730cdf3d64bf4 100644 --- a/packages/core-data/src/entities.js +++ b/packages/core-data/src/entities.js @@ -31,6 +31,8 @@ export const rootEntitiesConfig = [ 'site_icon_url', 'site_logo', 'timezone_string', + 'default_template_part_areas', + 'default_template_types', 'url', ].join( ',' ), }, diff --git a/packages/core-data/src/entity-types/base.ts b/packages/core-data/src/entity-types/base.ts new file mode 100644 index 00000000000000..79a3039ad140dc --- /dev/null +++ b/packages/core-data/src/entity-types/base.ts @@ -0,0 +1,84 @@ +/** + * Internal dependencies + */ +import type { Context, OmitNevers } from './helpers'; +import type { BaseEntityRecords as _BaseEntityRecords } from './base-entity-records'; + +export type TemplatePartArea = { + area: string; + label: string; + icon: string; + description: string; +}; + +export type TemplateType = { + title: string; + description: string; + slug: string; +}; + +declare module './base-entity-records' { + export namespace BaseEntityRecords { + /* eslint-disable-next-line @typescript-eslint/no-unused-vars */ + export interface Base< C extends Context > { + /** + * Site description. + */ + description: string; + + /** + * GMT offset for the site. + */ + gmt_offset: string; + + /** + * Home URL. + */ + home: string; + + /** + * Site title + */ + name: string; + + /** + * Site icon ID. + */ + site_icon?: number; + + /** + * Site icon URL. + */ + site_icon_url: string; + + /** + * Site logo ID. + */ + site_logo?: number; + + /** + * Site timezone string. + */ + timezone_string: string; + + /** + * Site URL. + */ + url: string; + + /** + * Default template part areas. + */ + default_template_part_areas?: Array< TemplatePartArea >; + + /** + * Default template types + */ + default_template_types?: Array< TemplateType >; + } + } +} + +export type Base< C extends Context = 'edit' > = OmitNevers< + _BaseEntityRecords.Base< C > +>; diff --git a/packages/core-data/src/entity-types/index.ts b/packages/core-data/src/entity-types/index.ts index 0e601137cbcb6c..68087a74005b2c 100644 --- a/packages/core-data/src/entity-types/index.ts +++ b/packages/core-data/src/entity-types/index.ts @@ -3,6 +3,7 @@ */ import type { Context, Updatable } from './helpers'; import type { Attachment } from './attachment'; +import type { Base, TemplatePartArea, TemplateType } from './base'; import type { Comment } from './comment'; import type { GlobalStylesRevision } from './global-styles-revision'; import type { MenuLocation } from './menu-location'; @@ -11,6 +12,7 @@ import type { NavMenuItem } from './nav-menu-item'; import type { Page } from './page'; import type { Plugin } from './plugin'; import type { Post } from './post'; +import type { PostStatusObject } from './post-status'; import type { PostRevision } from './post-revision'; import type { Settings } from './settings'; import type { Sidebar } from './sidebar'; @@ -27,6 +29,7 @@ export type { BaseEntityRecords } from './base-entity-records'; export type { Attachment, + Base as UnstableBase, Comment, Context, GlobalStylesRevision, @@ -37,13 +40,16 @@ export type { Plugin, Post, PostRevision, + PostStatusObject, Settings, Sidebar, Taxonomy, + TemplatePartArea, + TemplateType, Theme, + Type, Updatable, User, - Type, Widget, WidgetType, WpTemplate, @@ -84,6 +90,7 @@ export type { */ export interface PerPackageEntityRecords< C extends Context > { core: + | Base< C > | Attachment< C > | Comment< C > | GlobalStylesRevision< C > @@ -93,6 +100,7 @@ export interface PerPackageEntityRecords< C extends Context > { | Page< C > | Plugin< C > | Post< C > + | PostStatusObject< C > | PostRevision< C > | Settings< C > | Sidebar< C > diff --git a/packages/core-data/src/entity-types/post-status.ts b/packages/core-data/src/entity-types/post-status.ts new file mode 100644 index 00000000000000..92360dfdc17a60 --- /dev/null +++ b/packages/core-data/src/entity-types/post-status.ts @@ -0,0 +1,56 @@ +/** + * Internal dependencies + */ +import type { Context, OmitNevers } from './helpers'; +import type { BaseEntityRecords as _BaseEntityRecords } from './base-entity-records'; + +declare module './base-entity-records' { + export namespace BaseEntityRecords { + /* eslint-disable-next-line @typescript-eslint/no-unused-vars */ + export interface PostStatusObject< C extends Context > { + /** + * The title for the status. + */ + name: string; + + /** + * Whether posts with this status should be private. + */ + private: boolean; + + /** + * Whether posts with this status should be protected. + */ + protected: boolean; + + /** + * Whether posts of this status should be shown in the front end of the site. + */ + public: boolean; + + /** + * Whether posts with this status should be publicly-queryable. + */ + queryable: boolean; + + /** + * Whether to include posts in the edit listing for their post type. + */ + show_in_list: boolean; + + /** + * An alphanumeric identifier for the status. + */ + slug: string; + + /** + * Whether posts of this status may have floating published dates. + */ + date_floating: boolean; + } + } +} + +export type PostStatusObject< C extends Context = 'edit' > = OmitNevers< + _BaseEntityRecords.Type< C > +>; diff --git a/packages/core-data/src/fetch/__experimental-fetch-link-suggestions.ts b/packages/core-data/src/fetch/__experimental-fetch-link-suggestions.ts index e1a166ee272dbe..29012197589035 100644 --- a/packages/core-data/src/fetch/__experimental-fetch-link-suggestions.ts +++ b/packages/core-data/src/fetch/__experimental-fetch-link-suggestions.ts @@ -270,12 +270,29 @@ export function sortResults( results: SearchResult[], search: string ) { for ( const result of results ) { if ( result.title ) { const titleTokens = tokenize( result.title ); - const matchingTokens = titleTokens.filter( ( titleToken ) => - searchTokens.some( ( searchToken ) => - titleToken.includes( searchToken ) + const exactMatchingTokens = titleTokens.filter( ( titleToken ) => + searchTokens.some( + ( searchToken ) => titleToken === searchToken ) ); - scores[ result.id ] = matchingTokens.length / titleTokens.length; + const subMatchingTokens = titleTokens.filter( ( titleToken ) => + searchTokens.some( + ( searchToken ) => + titleToken !== searchToken && + titleToken.includes( searchToken ) + ) + ); + + // The score is a combination of exact matches and sub-matches. + // More weight is given to exact matches, as they are more relevant (e.g. "cat" vs "caterpillar"). + // Diving by the total number of tokens in the title normalizes the score and skews + // the results towards shorter titles. + const exactMatchScore = + ( exactMatchingTokens.length / titleTokens.length ) * 10; + + const subMatchScore = subMatchingTokens.length / titleTokens.length; + + scores[ result.id ] = exactMatchScore + subMatchScore; } else { scores[ result.id ] = 0; } diff --git a/packages/core-data/src/fetch/__experimental-fetch-url-data.js b/packages/core-data/src/fetch/__experimental-fetch-url-data.js index effb0566691dfe..003cc0ebf74ebb 100644 --- a/packages/core-data/src/fetch/__experimental-fetch-url-data.js +++ b/packages/core-data/src/fetch/__experimental-fetch-url-data.js @@ -29,7 +29,7 @@ const CACHE = new Map(); * * @async * @param {string} url the URL to request details from. - * @param {Object?} options any options to pass to the underlying fetch. + * @param {?Object} options any options to pass to the underlying fetch. * @example * ```js * import { __experimentalFetchUrlData as fetchUrlData } from '@wordpress/core-data'; diff --git a/packages/core-data/src/fetch/test/__experimental-fetch-link-suggestions.js b/packages/core-data/src/fetch/test/__experimental-fetch-link-suggestions.js index 6878c74332c3d7..ad0014ff86ecb8 100644 --- a/packages/core-data/src/fetch/test/__experimental-fetch-link-suggestions.js +++ b/packages/core-data/src/fetch/test/__experimental-fetch-link-suggestions.js @@ -393,6 +393,43 @@ describe( 'sortResults', () => { 6, ] ); } ); + + it( 'orders results to prefer direct matches over sub matches', () => { + const results = [ + { + id: 1, + title: 'News', + url: 'http://wordpress.local/news/', + type: 'page', + kind: 'post-type', + }, + { + id: 2, + title: 'Newspaper', + url: 'http://wordpress.local/newspaper/', + type: 'page', + kind: 'post-type', + }, + { + id: 3, + title: 'News Flash News', + url: 'http://wordpress.local/news-flash-news/', + type: 'page', + kind: 'post-type', + }, + { + id: 4, + title: 'News', + url: 'http://wordpress.local/news-2/', + type: 'page', + kind: 'post-type', + }, + ]; + const order = sortResults( results, 'News' ).map( + ( result ) => result.id + ); + expect( order ).toEqual( [ 1, 4, 3, 2 ] ); + } ); } ); describe( 'tokenize', () => { diff --git a/packages/core-data/src/hooks/test/use-query-select.js b/packages/core-data/src/hooks/test/use-query-select.js index 894851f79a9c78..873b897fff4e7d 100644 --- a/packages/core-data/src/hooks/test/use-query-select.js +++ b/packages/core-data/src/hooks/test/use-query-select.js @@ -17,9 +17,16 @@ import { render, screen, waitFor } from '@testing-library/react'; */ import useQuerySelect from '../use-query-select'; +/* eslint-disable @wordpress/wp-global-usage */ describe( 'useQuerySelect', () => { + const initialScriptDebug = globalThis.SCRIPT_DEBUG; let registry; + beforeAll( () => { + // Do not run hook in development mode; it will call `mapSelect` an extra time. + globalThis.SCRIPT_DEBUG = false; + } ); + beforeEach( () => { registry = createRegistry(); registry.registerStore( 'testStore', { @@ -31,6 +38,10 @@ describe( 'useQuerySelect', () => { } ); } ); + afterAll( () => { + globalThis.SCRIPT_DEBUG = initialScriptDebug; + } ); + const getTestComponent = ( mapSelectSpy, dependencyKey ) => ( props ) => { const dependencies = props[ dependencyKey ]; mapSelectSpy.mockImplementation( ( select ) => ( { @@ -188,3 +199,4 @@ describe( 'useQuerySelect', () => { ); } ); } ); +/* eslint-enable @wordpress/wp-global-usage */ diff --git a/packages/core-data/src/hooks/use-entity-block-editor.js b/packages/core-data/src/hooks/use-entity-block-editor.js index df213898659e7d..99171c6e15c695 100644 --- a/packages/core-data/src/hooks/use-entity-block-editor.js +++ b/packages/core-data/src/hooks/use-entity-block-editor.js @@ -69,7 +69,7 @@ export default function useEntityBlockEditor( kind, name, { id: _id } = {} ) { } // If there's an edit, cache the parsed blocks by the edit. - // If not, cache by the original enity record. + // If not, cache by the original entity record. const edits = getEntityRecordEdits( kind, name, id ); const isUnedited = ! edits || ! Object.keys( edits ).length; const cackeKey = isUnedited ? getEntityRecord( kind, name, id ) : edits; diff --git a/packages/core-data/src/hooks/use-entity-record.ts b/packages/core-data/src/hooks/use-entity-record.ts index 60228893e5102e..e3535f0e982ccd 100644 --- a/packages/core-data/src/hooks/use-entity-record.ts +++ b/packages/core-data/src/hooks/use-entity-record.ts @@ -126,6 +126,8 @@ const EMPTY_OBJECT = {}; * return ( * <form onSubmit={ onRename }> * <TextControl + * __nextHasNoMarginBottom + * __next40pxDefaultSize * label={ __( 'Name' ) } * value={ page.editedRecord.title } * onChange={ setTitle } diff --git a/packages/core-data/src/index.js b/packages/core-data/src/index.js index 99507a914f377b..db0fc854961332 100644 --- a/packages/core-data/src/index.js +++ b/packages/core-data/src/index.js @@ -20,6 +20,7 @@ import { } from './entities'; import { STORE_NAME } from './name'; import { unlock } from './lock-unlock'; +import { dynamicActions, dynamicSelectors } from './dynamic-entities'; // The entity selectors/resolvers and actions are shortcuts to their generic equivalents // (getEntityRecord, getEntityRecords, updateEntityRecord, updateEntityRecords) @@ -68,8 +69,17 @@ const entityActions = entitiesConfig.reduce( ( result, entity ) => { const storeConfig = () => ( { reducer, - actions: { ...actions, ...entityActions, ...createLocksActions() }, - selectors: { ...selectors, ...entitySelectors }, + actions: { + ...dynamicActions, + ...actions, + ...entityActions, + ...createLocksActions(), + }, + selectors: { + ...dynamicSelectors, + ...selectors, + ...entitySelectors, + }, resolvers: { ...resolvers, ...entityResolvers }, } ); diff --git a/packages/core-data/src/private-selectors.ts b/packages/core-data/src/private-selectors.ts index b2f6fa7def9858..fb0401509694ef 100644 --- a/packages/core-data/src/private-selectors.ts +++ b/packages/core-data/src/private-selectors.ts @@ -6,8 +6,14 @@ import { createSelector, createRegistrySelector } from '@wordpress/data'; /** * Internal dependencies */ -import type { State } from './selectors'; +import { + canUser, + getDefaultTemplateId, + getEntityRecord, + type State, +} from './selectors'; import { STORE_NAME } from './name'; +import { unlock } from './lock-unlock'; type EntityRecordKey = string | number; @@ -56,7 +62,12 @@ export const getBlockPatternsForPostType = createRegistrySelector( */ export const getEntityRecordsPermissions = createRegistrySelector( ( select ) => createSelector( - ( state: State, kind: string, name: string, ids: string[] ) => { + ( + state: State, + kind: string, + name: string, + ids: string | string[] + ) => { const normalizedIds = Array.isArray( ids ) ? ids : [ ids ]; return normalizedIds.map( ( id ) => ( { delete: select( STORE_NAME ).canUser( 'delete', { @@ -105,3 +116,166 @@ export function getEntityRecordPermissions( export function getRegisteredPostMeta( state: State, postType: string ) { return state.registeredPostMeta?.[ postType ] ?? {}; } + +function normalizePageId( value: number | string | undefined ): string | null { + if ( ! value || ! [ 'number', 'string' ].includes( typeof value ) ) { + return null; + } + + // We also need to check if it's not zero (`'0'`). + if ( Number( value ) === 0 ) { + return null; + } + + return value.toString(); +} + +interface SiteData { + show_on_front?: string; + page_on_front?: string | number; + page_for_posts?: string | number; +} + +export const getHomePage = createRegistrySelector( ( select ) => + createSelector( + () => { + const canReadSiteData = select( STORE_NAME ).canUser( 'read', { + kind: 'root', + name: 'site', + } ); + if ( ! canReadSiteData ) { + return null; + } + const siteData = select( STORE_NAME ).getEntityRecord( + 'root', + 'site' + ) as SiteData | undefined; + if ( ! siteData ) { + return null; + } + const homepageId = + siteData?.show_on_front === 'page' + ? normalizePageId( siteData.page_on_front ) + : null; + if ( homepageId ) { + return { postType: 'page', postId: homepageId }; + } + const frontPageTemplateId = select( + STORE_NAME + ).getDefaultTemplateId( { + slug: 'front-page', + } ); + return { postType: 'wp_template', postId: frontPageTemplateId }; + }, + ( state ) => [ + canUser( state, 'read', { + kind: 'root', + name: 'site', + } ) && getEntityRecord( state, 'root', 'site' ), + getDefaultTemplateId( state, { + slug: 'front-page', + } ), + ] + ) +); + +export const getPostsPageId = createRegistrySelector( ( select ) => () => { + const canReadSiteData = select( STORE_NAME ).canUser( 'read', { + kind: 'root', + name: 'site', + } ); + if ( ! canReadSiteData ) { + return null; + } + const siteData = select( STORE_NAME ).getEntityRecord( 'root', 'site' ) as + | SiteData + | undefined; + return siteData?.show_on_front === 'page' + ? normalizePageId( siteData.page_for_posts ) + : null; +} ); + +export const getTemplateId = createRegistrySelector( + ( select ) => ( state, postType, postId ) => { + const homepage = unlock( select( STORE_NAME ) ).getHomePage(); + + if ( ! homepage ) { + return; + } + + // For the front page, we always use the front page template if existing. + if ( + postType === 'page' && + postType === homepage?.postType && + postId.toString() === homepage?.postId + ) { + // The /lookup endpoint cannot currently handle a lookup + // when a page is set as the front page, so specifically in + // that case, we want to check if there is a front page + // template, and instead of falling back to the home + // template, we want to fall back to the page template. + const templates = select( STORE_NAME ).getEntityRecords( + 'postType', + 'wp_template', + { + per_page: -1, + } + ); + if ( ! templates ) { + return; + } + const id = templates.find( ( { slug } ) => slug === 'front-page' ) + ?.id; + if ( id ) { + return id; + } + // If no front page template is found, continue with the + // logic below (fetching the page template). + } + + const editedEntity = select( STORE_NAME ).getEditedEntityRecord( + 'postType', + postType, + postId + ); + if ( ! editedEntity ) { + return; + } + const postsPageId = unlock( select( STORE_NAME ) ).getPostsPageId(); + // Check if the current page is the posts page. + if ( postType === 'page' && postsPageId === postId.toString() ) { + return select( STORE_NAME ).getDefaultTemplateId( { + slug: 'home', + } ); + } + // First see if the post/page has an assigned template and fetch it. + const currentTemplateSlug = editedEntity.template; + if ( currentTemplateSlug ) { + const currentTemplate = select( STORE_NAME ) + .getEntityRecords( 'postType', 'wp_template', { + per_page: -1, + } ) + ?.find( ( { slug } ) => slug === currentTemplateSlug ); + if ( currentTemplate ) { + return currentTemplate.id; + } + } + // If no template is assigned, use the default template. + let slugToCheck; + // In `draft` status we might not have a slug available, so we use the `single` + // post type templates slug(ex page, single-post, single-product etc..). + // Pages do not need the `single` prefix in the slug to be prioritized + // through template hierarchy. + if ( editedEntity.slug ) { + slugToCheck = + postType === 'page' + ? `${ postType }-${ editedEntity.slug }` + : `single-${ postType }-${ editedEntity.slug }`; + } else { + slugToCheck = postType === 'page' ? 'page' : `single-${ postType }`; + } + return select( STORE_NAME ).getDefaultTemplateId( { + slug: slugToCheck, + } ); + } +); diff --git a/packages/core-data/src/resolvers.js b/packages/core-data/src/resolvers.js index a35403c0493460..173cfbd8833965 100644 --- a/packages/core-data/src/resolvers.js +++ b/packages/core-data/src/resolvers.js @@ -105,7 +105,7 @@ export const getEntityRecord = } ); - // Boostraps the edited document as well (and load from peers). + // Bootstraps the edited document as well (and load from peers). await getSyncProvider().bootstrap( entityConfig.syncObjectType + '--edit', objectId, @@ -226,7 +226,7 @@ export const getEditedEntityRecord = forwardResolver( 'getEntityRecord' ); * * @param {string} kind Entity kind. * @param {string} name Entity name. - * @param {Object?} query Query Object. If requesting specific fields, fields + * @param {?Object} query Query Object. If requesting specific fields, fields * must always include the ID. */ export const getEntityRecords = @@ -580,8 +580,15 @@ export const canUserEditEntityRecord = export const getAutosaves = ( postType, postId ) => async ( { dispatch, resolveSelect } ) => { - const { rest_base: restBase, rest_namespace: restNamespace = 'wp/v2' } = - await resolveSelect.getPostType( postType ); + const { + rest_base: restBase, + rest_namespace: restNamespace = 'wp/v2', + supports, + } = await resolveSelect.getPostType( postType ); + if ( ! supports?.autosave ) { + return; + } + const autosaves = await apiFetch( { path: `/${ restNamespace }/${ restBase }/${ postId }/autosaves?context=edit`, } ); diff --git a/packages/core-data/src/selectors.ts b/packages/core-data/src/selectors.ts index 7ea8c2f7f26d53..c31ebc04254640 100644 --- a/packages/core-data/src/selectors.ts +++ b/packages/core-data/src/selectors.ts @@ -113,7 +113,7 @@ type Optional< T > = T | undefined; /** * HTTP Query parameters sent with the API request to fetch the entity records. */ -type GetRecordsHttpQuery = Record< string, any >; +export type GetRecordsHttpQuery = Record< string, any >; /** * Arguments for EntityRecord selectors. @@ -310,7 +310,7 @@ export interface GetEntityRecord { state: State, kind: string, name: string, - key: EntityRecordKey, + key?: EntityRecordKey, query?: GetRecordsHttpQuery ): EntityRecord | undefined; @@ -321,7 +321,7 @@ export interface GetEntityRecord { >( kind: string, name: string, - key: EntityRecordKey, + key?: EntityRecordKey, query?: GetRecordsHttpQuery ) => EntityRecord | undefined; __unstableNormalizeArgs?: ( args: EntityRecordArgs ) => EntityRecordArgs; @@ -335,7 +335,7 @@ export interface GetEntityRecord { * @param state State tree * @param kind Entity kind. * @param name Entity name. - * @param key Record's key + * @param key Optional record's key. If requesting a global record (e.g. site settings), the key can be omitted. If requesting a specific item, the key must always be included. * @param query Optional query. If requesting specific * fields, fields must always include the ID. For valid query parameters see the [Reference](https://developer.wordpress.org/rest-api/reference/) in the REST API Handbook and select the entity kind. Then see the arguments available "Retrieve a [Entity kind]". * @@ -350,7 +350,7 @@ export const getEntityRecord = createSelector( state: State, kind: string, name: string, - key: EntityRecordKey, + key?: EntityRecordKey, query?: GetRecordsHttpQuery ): EntityRecord | undefined => { const queriedState = diff --git a/packages/core-data/src/test/resolvers.js b/packages/core-data/src/test/resolvers.js index 472056a4dfdc9b..c10b8257b3d02b 100644 --- a/packages/core-data/src/test/resolvers.js +++ b/packages/core-data/src/test/resolvers.js @@ -726,7 +726,10 @@ describe( 'getAutosaves', () => { const postType = 'post'; const postId = 1; const restBase = 'posts'; - const postEntityConfig = { rest_base: restBase }; + const postEntityConfig = { + rest_base: restBase, + supports: { autosave: true }, + }; triggerFetch.mockImplementation( () => SUCCESSFUL_RESPONSE ); const dispatch = Object.assign( jest.fn(), { @@ -750,7 +753,10 @@ describe( 'getAutosaves', () => { const postType = 'post'; const postId = 1; const restBase = 'posts'; - const postEntityConfig = { rest_base: restBase }; + const postEntityConfig = { + rest_base: restBase, + supports: { autosave: true }, + }; triggerFetch.mockImplementation( () => [] ); const dispatch = Object.assign( jest.fn(), { diff --git a/packages/core-data/tsconfig.json b/packages/core-data/tsconfig.json index 26602d82ab0c01..57c9d208e4c689 100644 --- a/packages/core-data/tsconfig.json +++ b/packages/core-data/tsconfig.json @@ -2,8 +2,6 @@ "$schema": "https://json.schemastore.org/tsconfig.json", "extends": "../../tsconfig.base.json", "compilerOptions": { - "rootDir": "src", - "declarationDir": "build-types", "checkJs": false, "noImplicitAny": false }, @@ -23,6 +21,5 @@ { "path": "../undo-manager" }, { "path": "../url" }, { "path": "../warning" } - ], - "include": [ "src/**/*" ] + ] } diff --git a/packages/create-block-interactive-template/package.json b/packages/create-block-interactive-template/package.json index bd34bed4d2e256..62a306de6e7d93 100644 --- a/packages/create-block-interactive-template/package.json +++ b/packages/create-block-interactive-template/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/create-block-interactive-template", - "version": "2.11.0", + "version": "2.16.0", "description": "Template for @wordpress/create-block to create interactive blocks with the Interactivity API.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/create-block-interactive-template/plugin-templates/$slug.php.mustache b/packages/create-block-interactive-template/plugin-templates/$slug.php.mustache index 48469aa7d0d931..cb7f45b7207946 100644 --- a/packages/create-block-interactive-template/plugin-templates/$slug.php.mustache +++ b/packages/create-block-interactive-template/plugin-templates/$slug.php.mustache @@ -8,8 +8,12 @@ * Description: {{description}} {{/description}} * Version: {{version}} - * Requires at least: 6.6 - * Requires PHP: 7.2 +{{#requiresAtLeast}} + * Requires at least: {{requiresAtLeast}} +{{/requiresAtLeast}} +{{#requiresPHP}} + * Requires PHP: {{requiresPHP}} +{{/requiresPHP}} {{#author}} * Author: {{author}} {{/author}} diff --git a/packages/create-block-interactive-template/plugin-templates/readme.txt.mustache b/packages/create-block-interactive-template/plugin-templates/readme.txt.mustache index c3abf5ae4ec024..19a4c8e78587b6 100644 --- a/packages/create-block-interactive-template/plugin-templates/readme.txt.mustache +++ b/packages/create-block-interactive-template/plugin-templates/readme.txt.mustache @@ -3,7 +3,9 @@ Contributors: {{author}} {{/author}} Tags: block -Tested up to: 6.6 +{{#testedUpTo}} +Tested up to: {{testedUpTo}} +{{/testedUpTo}} Stable tag: {{version}} {{#license}} License: {{license}} diff --git a/packages/create-block-tutorial-template/CHANGELOG.md b/packages/create-block-tutorial-template/CHANGELOG.md index dd043522c27a6b..f985fb3cbaea82 100644 --- a/packages/create-block-tutorial-template/CHANGELOG.md +++ b/packages/create-block-tutorial-template/CHANGELOG.md @@ -2,6 +2,16 @@ ## Unreleased +## 4.16.0 (2025-01-15) + +## 4.15.0 (2025-01-02) + +## 4.14.0 (2024-12-11) + +## 4.13.0 (2024-11-27) + +## 4.12.0 (2024-11-16) + ## 4.11.0 (2024-10-30) ## 4.10.0 (2024-10-16) diff --git a/packages/create-block-tutorial-template/package.json b/packages/create-block-tutorial-template/package.json index c2ea39fad8ba74..0b02cb36f9c4de 100644 --- a/packages/create-block-tutorial-template/package.json +++ b/packages/create-block-tutorial-template/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/create-block-tutorial-template", - "version": "4.11.0", + "version": "4.16.0", "description": "This is a template for @wordpress/create-block that creates an example 'Copyright Date' block. This block is used in the official WordPress block development Quick Start Guide.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/create-block-tutorial-template/plugin-templates/$slug.php.mustache b/packages/create-block-tutorial-template/plugin-templates/$slug.php.mustache index 7ce4be3f7cc739..49959fb5b2f691 100644 --- a/packages/create-block-tutorial-template/plugin-templates/$slug.php.mustache +++ b/packages/create-block-tutorial-template/plugin-templates/$slug.php.mustache @@ -8,8 +8,12 @@ * Description: {{description}} {{/description}} * Version: {{version}} - * Requires at least: 6.6 - * Requires PHP: 7.2 +{{#requiresAtLeast}} + * Requires at least: {{requiresAtLeast}} +{{/requiresAtLeast}} +{{#requiresPHP}} + * Requires PHP: {{requiresPHP}} +{{/requiresPHP}} {{#author}} * Author: {{author}} {{/author}} diff --git a/packages/create-block-tutorial-template/plugin-templates/readme.txt.mustache b/packages/create-block-tutorial-template/plugin-templates/readme.txt.mustache index c3abf5ae4ec024..19a4c8e78587b6 100644 --- a/packages/create-block-tutorial-template/plugin-templates/readme.txt.mustache +++ b/packages/create-block-tutorial-template/plugin-templates/readme.txt.mustache @@ -3,7 +3,9 @@ Contributors: {{author}} {{/author}} Tags: block -Tested up to: 6.6 +{{#testedUpTo}} +Tested up to: {{testedUpTo}} +{{/testedUpTo}} Stable tag: {{version}} {{#license}} License: {{license}} diff --git a/packages/create-block/CHANGELOG.md b/packages/create-block/CHANGELOG.md index c961882082541c..dd0fb85b2f0d90 100644 --- a/packages/create-block/CHANGELOG.md +++ b/packages/create-block/CHANGELOG.md @@ -2,6 +2,30 @@ ## Unreleased +## 4.59.0 (2025-01-15) + +## 4.58.0 (2025-01-02) + +### Enhancement + +- Add support for custom `textdomain` property for the scaffolded block ([#57197](https://github.com/WordPress/gutenberg/pull/57197)). +- Allow external templates to customize additional plugin header and readme fields: "Requires at least", "Requires PHP", and "Tested up to" ([#68193](https://github.com/WordPress/gutenberg/pull/68193)) +- Update the default template to scaffold a block in its subfolder to make it easier to update to multiple blocks in a single plugin ([#68175](https://github.com/WordPress/gutenberg/pull/68175)). + +### Internal + +- Refactored the code to use new API introduced together with `@inquirer/prompts` instead of legacy `inquirer` package ([#67877](https://github.com/WordPress/gutenberg/pull/67877)). + +## 4.57.0 (2024-12-11) + +### Internal + +- The bundled `rimraf` dependency has been updated from `^3.0.2` to `^5.0.10` ([#67708](https://github.com/WordPress/gutenberg/pull/67708)). + +## 4.56.0 (2024-11-27) + +## 4.55.0 (2024-11-16) + ## 4.54.0 (2024-10-30) ### Enhancement @@ -458,7 +482,7 @@ ### Internal -- Relocated npm packge from `create-wordpress-block` to `@wordpress/create-block` ([#19773](https://github.com/WordPress/gutenberg/pull/19773)). +- Relocated npm package from `create-wordpress-block` to `@wordpress/create-block` ([#19773](https://github.com/WordPress/gutenberg/pull/19773)). ## 0.5.0 (2020-01-08) diff --git a/packages/create-block/docs/external-template.md b/packages/create-block/docs/external-template.md index 45c3cba8c9271d..d840896f266f30 100644 --- a/packages/create-block/docs/external-template.md +++ b/packages/create-block/docs/external-template.md @@ -76,10 +76,13 @@ The following configurable variables are used with the template files. Template - `npmDevDependencies` (default: `[]`) – the list of remote npm packages to be installed in the project with [`npm install --save-dev`](https://docs.npmjs.com/cli/v8/commands/npm-install) when `wpScripts` is enabled. - `customPackageJSON` (no default) - allows definition of additional properties for the generated package.json file. -**Plugin header fields** ([learn more](https://developer.wordpress.org/plugins/plugin-basics/header-requirements/)): +**Plugin header and readme fields** (learn more about [header requirements](https://developer.wordpress.org/plugins/plugin-basics/header-requirements/) and [readmes](https://developer.wordpress.org/plugins/wordpress-org/how-your-readme-txt-works/)): - `pluginURI` (no default) – the home page of the plugin. - `version` (default: `'0.1.0'`) – the current version number of the plugin. +- `requiresAtLeast` (default: `'6.7'`) – the lowest WordPress version that the plugin will work on. +- `requiresPHP` (default: `'7.4'`) – the minimum required PHP version for use with this plugin. +- `testedUpTo` (default: `'6.7'`) – the highest WordPress version that the plugin has been tested against. - `author` (default: `'The WordPress Contributors'`) – the name of the plugin author(s). - `license` (default: `'GPL-2.0-or-later'`) – the short name of the plugin’s license. - `licenseURI` (default: `'https://www.gnu.org/licenses/gpl-2.0.html'`) – a link to the full text of the license. @@ -97,6 +100,7 @@ The following configurable variables are used with the template files. Template - `description` (no default) – a short description for your block. - `dashicon` (no default) – an icon property thats makes it easier to identify a block ([available values](https://developer.wordpress.org/resource/dashicons/)). - `category` (default: `'widgets'`) – blocks are grouped into categories to help users browse and discover them. The categories provided by core are `text`, `media`, `design`, `widgets`, `theme`, and `embed`. +- `textdomain` (defaults to the `slug` value) – the text domain used to make strings translatable ([more info](https://developer.wordpress.org/plugins/internationalization/how-to-internationalize-your-plugin/#text-domains)). - `attributes` (no default) – block attributes ([more details](https://developer.wordpress.org/block-editor/developers/block-api/block-attributes/)). - `supports` (no default) – optional block extended support features ([more details](https://developer.wordpress.org/block-editor/developers/block-api/block-supports/). - `editorScript` (default: `'file:./index.js'`) – an editor script definition. diff --git a/packages/create-block/lib/check-system-requirements.js b/packages/create-block/lib/check-system-requirements.js index 4a88d167d437c7..152931bc191410 100644 --- a/packages/create-block/lib/check-system-requirements.js +++ b/packages/create-block/lib/check-system-requirements.js @@ -1,7 +1,7 @@ /** * External dependencies */ -const inquirer = require( 'inquirer' ); +const { confirm } = require( '@inquirer/prompts' ); const checkSync = require( 'check-node-version' ); const tools = require( 'check-node-version/tools' ); const { promisify } = require( 'util' ); @@ -34,14 +34,10 @@ async function checkSystemRequirements( engines ) { log.error( 'The program may not complete correctly if you continue.' ); log.info( '' ); - const { yesContinue } = await inquirer.prompt( [ - { - type: 'confirm', - name: 'yesContinue', - message: 'Are you sure you want to continue anyway?', - default: false, - }, - ] ); + const yesContinue = await confirm( { + message: 'Are you sure you want to continue anyway?', + default: false, + } ); if ( ! yesContinue ) { log.error( 'Cancelled.' ); diff --git a/packages/create-block/lib/index.js b/packages/create-block/lib/index.js index da08bcd4ab1dc7..ccc2e91b106e20 100644 --- a/packages/create-block/lib/index.js +++ b/packages/create-block/lib/index.js @@ -1,7 +1,7 @@ /** * External dependencies */ -const inquirer = require( 'inquirer' ); +const { confirm, select } = require( '@inquirer/prompts' ); const { capitalCase } = require( 'change-case' ); const program = require( 'commander' ); @@ -14,9 +14,9 @@ const log = require( './log' ); const { engines, version } = require( '../package.json' ); const scaffold = require( './scaffold' ); const { - getPluginTemplate, getDefaultValues, - getPrompts, + getProjectTemplate, + runPrompts, } = require( './templates' ); const commandName = `wp-create-block`; @@ -79,11 +79,13 @@ program targetDir, } ) => { - await checkSystemRequirements( engines ); try { - const pluginTemplate = await getPluginTemplate( templateName ); + await checkSystemRequirements( engines ); + + const projectTemplate = + await getProjectTemplate( templateName ); const availableVariants = Object.keys( - pluginTemplate.variants + projectTemplate.variants ); if ( variant && ! availableVariants.includes( variant ) ) { if ( ! availableVariants.length ) { @@ -113,7 +115,7 @@ program if ( slug ) { const defaultValues = getDefaultValues( - pluginTemplate, + projectTemplate, variant ); const answers = { @@ -123,7 +125,7 @@ program title: capitalCase( slug ), ...optionsValues, }; - await scaffold( pluginTemplate, answers ); + await scaffold( projectTemplate, answers ); } else { log.info( '' ); log.info( @@ -133,25 +135,22 @@ program ); if ( ! variant && availableVariants.length > 1 ) { - const result = await inquirer.prompt( { - type: 'list', - name: 'variant', + variant = await select( { message: 'The template variant to use for this block:', - choices: availableVariants, + choices: availableVariants.map( ( value ) => ( { + value, + } ) ), } ); - variant = result.variant; } const defaultValues = getDefaultValues( - pluginTemplate, + projectTemplate, variant ); - const filterOptionsProvided = ( { name } ) => - ! Object.keys( optionsValues ).includes( name ); - const blockPrompts = getPrompts( - pluginTemplate, + const blockAnswers = await runPrompts( + projectTemplate, [ 'slug', 'namespace', @@ -159,45 +158,36 @@ program 'description', 'dashicon', 'category', - ], - variant - ).filter( filterOptionsProvided ); - const blockAnswers = await inquirer.prompt( blockPrompts ); - - const pluginAnswers = plugin - ? await inquirer - .prompt( { - type: 'confirm', - name: 'configurePlugin', - message: - 'Do you want to customize the WordPress plugin?', - default: false, - } ) - .then( async ( { configurePlugin } ) => { - if ( ! configurePlugin ) { - return {}; - } + ! plugin && 'textdomain', + ].filter( Boolean ), + variant, + optionsValues + ); - const pluginPrompts = getPrompts( - pluginTemplate, - [ - 'pluginURI', - 'version', - 'author', - 'license', - 'licenseURI', - 'domainPath', - 'updateURI', - ], - variant - ).filter( filterOptionsProvided ); - const result = - await inquirer.prompt( pluginPrompts ); - return result; - } ) - : {}; + const pluginAnswers = + plugin && + ( await confirm( { + message: + 'Do you want to customize the WordPress plugin?', + default: false, + } ) ) + ? await runPrompts( + projectTemplate, + [ + 'pluginURI', + 'version', + 'author', + 'license', + 'licenseURI', + 'domainPath', + 'updateURI', + ], + variant, + optionsValues + ) + : {}; - await scaffold( pluginTemplate, { + await scaffold( projectTemplate, { ...defaultValues, ...optionsValues, variant, @@ -209,6 +199,9 @@ program if ( error instanceof CLIError ) { log.error( error.message ); process.exit( 1 ); + } else if ( error.name === 'ExitPromptError' ) { + log.info( 'Cancelled.' ); + process.exit( 1 ); } else { throw error; } diff --git a/packages/create-block/lib/prompts.js b/packages/create-block/lib/prompts.js index 12da9f892b80e6..88bdaf22635d36 100644 --- a/packages/create-block/lib/prompts.js +++ b/packages/create-block/lib/prompts.js @@ -11,7 +11,6 @@ const upperFirst = ( [ firstLetter, ...rest ] ) => // Block metadata. const slug = { type: 'input', - name: 'slug', message: 'The block slug used for identification (also the output folder name):', validate( input ) { @@ -25,7 +24,6 @@ const slug = { const namespace = { type: 'input', - name: 'namespace', message: 'The internal namespace for the block name (something unique for your products):', validate( input ) { @@ -39,25 +37,22 @@ const namespace = { const title = { type: 'input', - name: 'title', message: 'The display title for your block:', - filter( input ) { + transformer( input ) { return input && upperFirst( input ); }, }; const description = { type: 'input', - name: 'description', message: 'The short description for your block (optional):', - filter( input ) { + transformer( input ) { return input && upperFirst( input ); }, }; const dashicon = { type: 'input', - name: 'dashicon', message: 'The dashicon to make it easier to identify your block (optional):', validate( input ) { @@ -67,29 +62,41 @@ const dashicon = { return true; }, - filter( input ) { + transformer( input ) { return input && input.replace( /dashicon(s)?-/, '' ); }, }; const category = { - type: 'list', - name: 'category', + type: 'select', message: 'The category name to help users browse and discover your block:', - choices: [ 'text', 'media', 'design', 'widgets', 'theme', 'embed' ], + choices: [ 'text', 'media', 'design', 'widgets', 'theme', 'embed' ].map( + ( value ) => ( { value } ) + ), +}; + +const textdomain = { + type: 'input', + message: + 'The text domain used to make strings translatable in the block (optional):', + validate( input ) { + if ( input.length && ! /^[a-z][a-z0-9\-]*$/.test( input ) ) { + return 'Invalid text domain specified. Text domain can contain only lowercase alphanumeric characters or dashes, and start with a letter.'; + } + + return true; + }, }; // Plugin header fields. const pluginURI = { type: 'input', - name: 'pluginURI', message: 'The home page of the plugin (optional). Unique URL outside of WordPress.org:', }; const version = { type: 'input', - name: 'version', message: 'The current version number of the plugin:', validate( input ) { // Regular expression was copied from https://semver.org. @@ -105,32 +112,27 @@ const version = { const author = { type: 'input', - name: 'author', message: 'The name of the plugin author (optional). Multiple authors may be listed using commas:', }; const license = { type: 'input', - name: 'license', message: 'The short name of the plugin’s license (optional):', }; const licenseURI = { type: 'input', - name: 'licenseURI', message: 'A link to the full text of the license (optional):', }; const domainPath = { type: 'input', - name: 'domainPath', message: 'A custom domain path for the translations (optional):', }; const updateURI = { type: 'input', - name: 'updateURI', message: 'A custom update URI for the plugin (optional):', }; @@ -141,6 +143,7 @@ module.exports = { description, dashicon, category, + textdomain, pluginURI, version, author, diff --git a/packages/create-block/lib/scaffold.js b/packages/create-block/lib/scaffold.js index 73b9f549908867..44812e3d5954d6 100644 --- a/packages/create-block/lib/scaffold.js +++ b/packages/create-block/lib/scaffold.js @@ -26,6 +26,7 @@ module.exports = async ( description, dashicon, category, + textdomain, attributes, supports, author, @@ -35,6 +36,9 @@ module.exports = async ( domainPath, updateURI, version, + requiresAtLeast, + requiresPHP, + testedUpTo, wpScripts, wpEnv, npmDependencies, @@ -57,13 +61,12 @@ module.exports = async ( } ) => { slug = slug.toLowerCase(); - namespace = namespace.toLowerCase(); const rootDirectory = join( process.cwd(), targetDir || slug ); const transformedValues = transformer( { $schema, apiVersion, plugin, - namespace, + namespace: namespace.toLowerCase(), slug, title, description, @@ -78,12 +81,15 @@ module.exports = async ( domainPath, updateURI, version, + requiresAtLeast, + requiresPHP, + testedUpTo, wpScripts, wpEnv, npmDependencies, npmDevDependencies, customScripts, - folderName, + folderName: folderName.replace( /\$slug/g, slug ), editorScript, editorStyle, style, @@ -95,7 +101,7 @@ module.exports = async ( customPackageJSON, customBlockJSON, example, - textdomain: slug, + textdomain: textdomain || slug, rootDirectory, } ); diff --git a/packages/create-block/lib/templates.js b/packages/create-block/lib/templates.js index 4e70ee66fd3a40..3604ac99a35eac 100644 --- a/packages/create-block/lib/templates.js +++ b/packages/create-block/lib/templates.js @@ -1,6 +1,7 @@ /** * External dependencies */ +const inquirer = require( '@inquirer/prompts' ); const { command } = require( 'execa' ); const glob = require( 'fast-glob' ); const { resolve } = require( 'path' ); @@ -58,6 +59,7 @@ const predefinedPluginTemplates = { }, viewScript: 'file:./view.js', example: {}, + folderName: './src/$slug', }, variants: { static: {}, @@ -157,7 +159,7 @@ const configToTemplate = async ( { }; }; -const getPluginTemplate = async ( templateName ) => { +const getProjectTemplate = async ( templateName ) => { if ( predefinedPluginTemplates[ templateName ] ) { return await configToTemplate( predefinedPluginTemplates[ templateName ] @@ -224,16 +226,20 @@ const getPluginTemplate = async ( templateName ) => { } }; -const getDefaultValues = ( pluginTemplate, variant ) => { +const getDefaultValues = ( projectTemplate, variant ) => { return { $schema: 'https://schemas.wp.org/trunk/block.json', apiVersion: 3, namespace: 'create-block', category: 'widgets', + textdomain: '', author: 'The WordPress Contributors', license: 'GPL-2.0-or-later', licenseURI: 'https://www.gnu.org/licenses/gpl-2.0.html', version: '0.1.0', + requiresAtLeast: '6.7', + requiresPHP: '7.4', + testedUpTo: '6.7', wpScripts: true, customScripts: {}, wpEnv: false, @@ -243,20 +249,33 @@ const getDefaultValues = ( pluginTemplate, variant ) => { editorStyle: 'file:./index.css', style: 'file:./style-index.css', transformer: ( view ) => view, - ...pluginTemplate.defaultValues, - ...pluginTemplate.variants?.[ variant ], - variantVars: getVariantVars( pluginTemplate.variants, variant ), + ...projectTemplate.defaultValues, + ...projectTemplate.variants?.[ variant ], + variantVars: getVariantVars( projectTemplate.variants, variant ), }; }; -const getPrompts = ( pluginTemplate, keys, variant ) => { - const defaultValues = getDefaultValues( pluginTemplate, variant ); - return keys.map( ( promptName ) => { - return { - ...prompts[ promptName ], +const runPrompts = async ( + projectTemplate, + promptNames, + variant, + optionsValues +) => { + const defaultValues = getDefaultValues( projectTemplate, variant ); + const result = {}; + for ( const promptName of promptNames ) { + if ( Object.keys( optionsValues ).includes( promptName ) ) { + continue; + } + + const { type, ...config } = prompts[ promptName ]; + result[ promptName ] = await inquirer[ type ]( { + ...config, default: defaultValues[ promptName ], - }; - } ); + } ); + } + + return result; }; const getVariantVars = ( variants, variant ) => { @@ -277,7 +296,7 @@ const getVariantVars = ( variants, variant ) => { }; module.exports = { - getPluginTemplate, getDefaultValues, - getPrompts, + getProjectTemplate, + runPrompts, }; diff --git a/packages/create-block/lib/templates/es5/$slug.php.mustache b/packages/create-block/lib/templates/es5/$slug.php.mustache index 825fd1bfd8b5aa..5beb2ca06712c9 100644 --- a/packages/create-block/lib/templates/es5/$slug.php.mustache +++ b/packages/create-block/lib/templates/es5/$slug.php.mustache @@ -7,9 +7,13 @@ {{#description}} * Description: {{description}} {{/description}} - * Requires at least: 6.6 - * Requires PHP: 7.2 * Version: {{version}} +{{#requiresAtLeast}} + * Requires at least: {{requiresAtLeast}} +{{/requiresAtLeast}} +{{#requiresPHP}} + * Requires PHP: {{requiresPHP}} +{{/requiresPHP}} {{#author}} * Author: {{author}} {{/author}} diff --git a/packages/create-block/lib/templates/es5/readme.txt.mustache b/packages/create-block/lib/templates/es5/readme.txt.mustache index c3abf5ae4ec024..19a4c8e78587b6 100644 --- a/packages/create-block/lib/templates/es5/readme.txt.mustache +++ b/packages/create-block/lib/templates/es5/readme.txt.mustache @@ -3,7 +3,9 @@ Contributors: {{author}} {{/author}} Tags: block -Tested up to: 6.6 +{{#testedUpTo}} +Tested up to: {{testedUpTo}} +{{/testedUpTo}} Stable tag: {{version}} {{#license}} License: {{license}} diff --git a/packages/create-block/lib/templates/plugin/$slug.php.mustache b/packages/create-block/lib/templates/plugin/$slug.php.mustache index 75666af3a850b2..e229560a655c36 100644 --- a/packages/create-block/lib/templates/plugin/$slug.php.mustache +++ b/packages/create-block/lib/templates/plugin/$slug.php.mustache @@ -7,9 +7,13 @@ {{#description}} * Description: {{description}} {{/description}} - * Requires at least: 6.6 - * Requires PHP: 7.2 * Version: {{version}} +{{#requiresAtLeast}} + * Requires at least: {{requiresAtLeast}} +{{/requiresAtLeast}} +{{#requiresPHP}} + * Requires PHP: {{requiresPHP}} +{{/requiresPHP}} {{#author}} * Author: {{author}} {{/author}} @@ -42,6 +46,6 @@ if ( ! defined( 'ABSPATH' ) ) { * @see https://developer.wordpress.org/reference/functions/register_block_type/ */ function {{namespaceSnakeCase}}_{{slugSnakeCase}}_block_init() { - register_block_type( __DIR__ . '/build' ); + register_block_type( __DIR__ . '/build/{{slug}}' ); } add_action( 'init', '{{namespaceSnakeCase}}_{{slugSnakeCase}}_block_init' ); diff --git a/packages/create-block/lib/templates/plugin/readme.txt.mustache b/packages/create-block/lib/templates/plugin/readme.txt.mustache index c3abf5ae4ec024..19a4c8e78587b6 100644 --- a/packages/create-block/lib/templates/plugin/readme.txt.mustache +++ b/packages/create-block/lib/templates/plugin/readme.txt.mustache @@ -3,7 +3,9 @@ Contributors: {{author}} {{/author}} Tags: block -Tested up to: 6.6 +{{#testedUpTo}} +Tested up to: {{testedUpTo}} +{{/testedUpTo}} Stable tag: {{version}} {{#license}} License: {{license}} diff --git a/packages/create-block/package.json b/packages/create-block/package.json index dbb8e56ea72151..10a1a0b8829375 100644 --- a/packages/create-block/package.json +++ b/packages/create-block/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/create-block", - "version": "4.54.0", + "version": "4.59.0", "description": "Generates PHP, JS and CSS code for registering a block for a WordPress plugin.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -31,18 +31,18 @@ "wp-create-block": "./index.js" }, "dependencies": { - "@wordpress/lazy-import": "*", + "@inquirer/prompts": "^7.2.0", + "@wordpress/lazy-import": "file:../lazy-import", "chalk": "^4.0.0", "change-case": "^4.1.2", "check-node-version": "^4.1.0", "commander": "^9.2.0", "execa": "^4.0.2", "fast-glob": "^3.2.7", - "inquirer": "^7.1.0", "make-dir": "^3.0.0", "mustache": "^4.0.0", "npm-package-arg": "^8.1.5", - "rimraf": "^3.0.2", + "rimraf": "^5.0.10", "write-pkg": "^4.0.0" }, "publishConfig": { diff --git a/packages/customize-widgets/CHANGELOG.md b/packages/customize-widgets/CHANGELOG.md index b36ed481749ea9..38a9ff31781dc9 100644 --- a/packages/customize-widgets/CHANGELOG.md +++ b/packages/customize-widgets/CHANGELOG.md @@ -2,6 +2,16 @@ ## Unreleased +## 5.16.0 (2025-01-15) + +## 5.15.0 (2025-01-02) + +## 5.14.0 (2024-12-11) + +## 5.13.0 (2024-11-27) + +## 5.12.0 (2024-11-16) + ## 5.11.0 (2024-10-30) ## 5.10.0 (2024-10-16) diff --git a/packages/customize-widgets/package.json b/packages/customize-widgets/package.json index 41b3a7bd463aa3..f0659343560734 100644 --- a/packages/customize-widgets/package.json +++ b/packages/customize-widgets/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/customize-widgets", - "version": "5.11.0", + "version": "5.16.0", "description": "Widgets blocks in Customizer Module for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -26,27 +26,26 @@ "wpScript": true, "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/block-editor": "*", - "@wordpress/block-library": "*", - "@wordpress/blocks": "*", - "@wordpress/components": "*", - "@wordpress/compose": "*", - "@wordpress/core-data": "*", - "@wordpress/data": "*", - "@wordpress/dom": "*", - "@wordpress/editor": "*", - "@wordpress/element": "*", - "@wordpress/hooks": "*", - "@wordpress/i18n": "*", - "@wordpress/icons": "*", - "@wordpress/interface": "*", - "@wordpress/is-shallow-equal": "*", - "@wordpress/keyboard-shortcuts": "*", - "@wordpress/keycodes": "*", - "@wordpress/media-utils": "*", - "@wordpress/preferences": "*", - "@wordpress/private-apis": "*", - "@wordpress/widgets": "*", + "@wordpress/block-editor": "file:../block-editor", + "@wordpress/block-library": "file:../block-library", + "@wordpress/blocks": "file:../blocks", + "@wordpress/components": "file:../components", + "@wordpress/compose": "file:../compose", + "@wordpress/core-data": "file:../core-data", + "@wordpress/data": "file:../data", + "@wordpress/dom": "file:../dom", + "@wordpress/element": "file:../element", + "@wordpress/hooks": "file:../hooks", + "@wordpress/i18n": "file:../i18n", + "@wordpress/icons": "file:../icons", + "@wordpress/interface": "file:../interface", + "@wordpress/is-shallow-equal": "file:../is-shallow-equal", + "@wordpress/keyboard-shortcuts": "file:../keyboard-shortcuts", + "@wordpress/keycodes": "file:../keycodes", + "@wordpress/media-utils": "file:../media-utils", + "@wordpress/preferences": "file:../preferences", + "@wordpress/private-apis": "file:../private-apis", + "@wordpress/widgets": "file:../widgets", "clsx": "^2.1.1", "fast-deep-equal": "^3.1.3" }, diff --git a/packages/customize-widgets/src/components/header/style.scss b/packages/customize-widgets/src/components/header/style.scss index 5c3f37a0bf0d42..51699957cae065 100644 --- a/packages/customize-widgets/src/components/header/style.scss +++ b/packages/customize-widgets/src/components/header/style.scss @@ -33,16 +33,26 @@ border-radius: $radius-small; color: $white; padding: 0; - min-width: $grid-unit-30; - height: $grid-unit-30; + min-width: $grid-unit-40; + height: $grid-unit-40; margin: $grid-unit-15 0 $grid-unit-15 auto; &::before { content: none; } + svg { + @media not (prefers-reduced-motion) { + transition: transform cubic-bezier(0.165, 0.84, 0.44, 1) 0.2s; + } + } + &.is-pressed { background: $gray-900; + + svg { + transform: rotate(45deg); + } } } diff --git a/packages/customize-widgets/src/components/sidebar-block-editor/index.js b/packages/customize-widgets/src/components/sidebar-block-editor/index.js index d3c78a3d6d012a..be132e9cedd82e 100644 --- a/packages/customize-widgets/src/components/sidebar-block-editor/index.js +++ b/packages/customize-widgets/src/components/sidebar-block-editor/index.js @@ -87,6 +87,7 @@ export default function SidebarBlockEditor( { mediaUpload: mediaUploadBlockEditor, hasFixedToolbar: isFixedToolbarActive || ! isMediumViewport, keepCaretInsideBlock, + editorTool: 'edit', __unstableHasCustomAppender: true, }; }, [ diff --git a/packages/customize-widgets/src/index.js b/packages/customize-widgets/src/index.js index df96d645ee7007..5de010fa8bd37e 100644 --- a/packages/customize-widgets/src/index.js +++ b/packages/customize-widgets/src/index.js @@ -17,7 +17,6 @@ import { store as blocksStore, } from '@wordpress/blocks'; import { dispatch } from '@wordpress/data'; -import { privateApis as editorPrivateApis } from '@wordpress/editor'; import { store as preferencesStore } from '@wordpress/preferences'; /** @@ -27,7 +26,6 @@ import CustomizeWidgets from './components/customize-widgets'; import getSidebarSection from './controls/sidebar-section'; import getSidebarControl from './controls/sidebar-control'; import './filters'; -import { unlock } from './lock-unlock'; const { wp } = window; @@ -39,8 +37,6 @@ const DISABLED_BLOCKS = [ ]; const ENABLE_EXPERIMENTAL_FSE_BLOCKS = false; -const { registerCoreBlockBindingsSources } = unlock( editorPrivateApis ); - /** * Initializes the widgets block editor in the customizer. * @@ -64,7 +60,6 @@ export function initialize( editorName, blockEditorSettings ) { ); } ); registerCoreBlocks( coreBlocks ); - registerCoreBlockBindingsSources(); registerLegacyWidgetBlock(); if ( globalThis.IS_GUTENBERG_PLUGIN ) { __experimentalRegisterExperimentalCoreBlocks( { diff --git a/packages/data-controls/CHANGELOG.md b/packages/data-controls/CHANGELOG.md index c536d850f8c733..96e2448a911ced 100644 --- a/packages/data-controls/CHANGELOG.md +++ b/packages/data-controls/CHANGELOG.md @@ -2,6 +2,16 @@ ## Unreleased +## 4.16.0 (2025-01-15) + +## 4.15.0 (2025-01-02) + +## 4.14.0 (2024-12-11) + +## 4.13.0 (2024-11-27) + +## 4.12.0 (2024-11-16) + ## 4.11.0 (2024-10-30) ## 4.10.0 (2024-10-16) diff --git a/packages/data-controls/package.json b/packages/data-controls/package.json index e4898ff1d0e1ab..5f025c5c889b57 100644 --- a/packages/data-controls/package.json +++ b/packages/data-controls/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/data-controls", - "version": "4.11.0", + "version": "4.16.0", "description": "A set of common controls for the @wordpress/data api.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -30,9 +30,9 @@ "types": "build-types", "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/api-fetch": "*", - "@wordpress/data": "*", - "@wordpress/deprecated": "*" + "@wordpress/api-fetch": "file:../api-fetch", + "@wordpress/data": "file:../data", + "@wordpress/deprecated": "file:../deprecated" }, "peerDependencies": { "react": "^18.0.0" diff --git a/packages/data-controls/tsconfig.json b/packages/data-controls/tsconfig.json index 5ccc6045880d4a..faa13b152672b6 100644 --- a/packages/data-controls/tsconfig.json +++ b/packages/data-controls/tsconfig.json @@ -2,14 +2,11 @@ "$schema": "https://json.schemastore.org/tsconfig.json", "extends": "../../tsconfig.base.json", "compilerOptions": { - "rootDir": "src", - "declarationDir": "build-types", "types": [ "gutenberg-env" ] }, "references": [ { "path": "../api-fetch" }, { "path": "../data" }, { "path": "../deprecated" } - ], - "include": [ "src/**/*" ] + ] } diff --git a/packages/data/CHANGELOG.md b/packages/data/CHANGELOG.md index 9ceb1521e6cd6d..6a2de21f115504 100644 --- a/packages/data/CHANGELOG.md +++ b/packages/data/CHANGELOG.md @@ -2,10 +2,20 @@ ## Unreleased +## 10.16.0 (2025-01-15) + +## 10.15.0 (2025-01-02) + +## 10.14.0 (2024-12-11) + +## 10.13.0 (2024-11-27) + ### Enhancements - Upgrade `redux` dependency to `^5.0.1` ([#66966](https://github.com/WordPress/gutenberg/pull/66966)) +## 10.12.0 (2024-11-16) + ## 10.11.0 (2024-10-30) ## 10.10.0 (2024-10-16) diff --git a/packages/data/README.md b/packages/data/README.md index 25dd75820fb5db..00105722bd04fb 100644 --- a/packages/data/README.md +++ b/packages/data/README.md @@ -42,13 +42,6 @@ const actions = { discountPercent, }; }, - - fetchFromAPI( path ) { - return { - type: 'FETCH_FROM_API', - path, - }; - }, }; const store = createReduxStore( 'my-shop', { @@ -84,17 +77,11 @@ const store = createReduxStore( 'my-shop', { }, }, - controls: { - FETCH_FROM_API( action ) { - return apiFetch( { path: action.path } ); - }, - }, - resolvers: { - *getPrice( item ) { + getPrice: ( item ) => async ({ dispatch }) => { { const path = '/wp/v2/prices/' + item; - const price = yield actions.fetchFromAPI( path ); - return actions.setPrice( item, price ); + const price = await apiFetch( { path } ); + dispatch.setPrice( item, price ); }, }, } ); @@ -133,13 +120,21 @@ A **resolver** is a side-effect for a selector. If your selector result may need The `resolvers` option should be passed as an object where each key is the name of the selector to act upon, the value a function which receives the same arguments passed to the selector, excluding the state argument. It can then dispatch as necessary to fulfill the requirements of the selector, taking advantage of the fact that most data consumers will subscribe to subsequent state changes (by `subscribe` or `withSelect`). -#### `controls` +Resolvers, in combination with [thunks](https://github.com/WordPress/gutenberg/blob/trunk/docs/how-to-guides/thunks.md#thunks-can-be-async), can be used to implement asynchronous data flows for your store. -A **control** defines the execution flow behavior associated with a specific action type. This can be particularly useful in implementing asynchronous data flows for your store. By defining your action creator or resolvers as a generator which yields specific controlled action types, the execution will proceed as defined by the control handler. +#### `controls` (deprecated) -The `controls` option should be passed as an object where each key is the name of the action type to act upon, the value a function which receives the original action object. It should returns either a promise which is to resolve when evaluation of the action should continue, or a value. The value or resolved promise value is assigned on the return value of the yield assignment. If the control handler returns undefined, the execution is not continued. +To handle asynchronous data flows, it is recommended to use [thunks](https://github.com/WordPress/gutenberg/blob/trunk/docs/how-to-guides/thunks.md#thunks-can-be-async) instead of `controls`. -Refer to the [documentation of `@wordpress/redux-routine`](https://github.com/WordPress/gutenberg/tree/HEAD/packages/redux-routine/README.md) for more information. +<details> + <summary>View <em>controls</em> explanation</summary> +<br> +A <em>control</em> defines the execution flow behavior associated with a specific action type. Before <a href="https://github.com/WordPress/gutenberg/blob/trunk/docs/how-to-guides/thunks.md#thunks-can-be-async">thunks</a>, controls were used to implement asynchronous data flows for your store. By defining your action creator or resolvers as a generator which yields specific controlled action types, the execution will proceed as defined by the control handler. +<br><br> +The <em>controls</em> option should be passed as an object where each key is the name of the action type to act upon, the value a function which receives the original action object. It should returns either a promise which is to resolve when evaluation of the action should continue, or a value. The value or resolved promise value is assigned on the return value of the yield assignment. If the control handler returns undefined, the execution is not continued. +<br><br> +Refer to the <a href="https://github.com/WordPress/gutenberg/tree/HEAD/packages/redux-routine/README.md">documentation of <em>@wordpress/redux-routine</em></a> for more information. +</details> #### `initialState` @@ -262,7 +257,7 @@ The data module shares many of the same [core principles](https://redux.js.org/i The [higher-order components](#higher-order-components) were created to complement this distinction. The intention with splitting `withSelect` and `withDispatch` — where in React Redux they are combined under `connect` as `mapStateToProps` and `mapDispatchToProps` arguments — is to more accurately reflect that dispatch is not dependent upon a subscription to state changes, and to allow for state-derived values to be used in `withDispatch` (via [higher-order component composition](https://github.com/WordPress/gutenberg/tree/HEAD/packages/compose/README.md)). -The data module also has built-in solutions for handling asynchronous side-effects, through [resolvers](#resolvers) and [controls](#controls). These differ slightly from [standard redux async solutions](https://redux.js.org/advanced/async-actions) like [`redux-thunk`](https://github.com/gaearon/redux-thunk) or [`redux-saga`](https://redux-saga.js.org/). +The data module also has built-in solutions for handling asynchronous side-effects, through [resolvers](#resolvers) and [thunks](https://github.com/WordPress/gutenberg/blob/trunk/docs/how-to-guides/thunks.md#thunks-can-be-async). These differ slightly from [standard redux async solutions](https://redux.js.org/advanced/async-actions) like [`redux-thunk`](https://github.com/gaearon/redux-thunk) or [`redux-saga`](https://redux-saga.js.org/). Specific implementation differences from Redux and React Redux: @@ -397,7 +392,7 @@ Creates a new store registry, given an optional object of initial store configur _Parameters_ - _storeConfigs_ `Object`: Initial store configurations. -- _parent_ `Object?`: Parent registry. +- _parent_ `?Object`: Parent registry. _Returns_ @@ -423,11 +418,11 @@ When registering a control created with `createRegistryControl` with a store, th _Parameters_ -- _registryControl_ `Function`: Function receiving a registry object and returning a control. +- _registryControl_ `T & { isRegistryControl?: boolean; }`: Function receiving a registry object and returning a control. _Returns_ -- `Function`: Registry control that can be registered with a store. +- Registry control that can be registered with a store. ### createRegistrySelector @@ -476,11 +471,11 @@ with a store. _Parameters_ -- _registrySelector_ `Function`: Function receiving a registry `select` function and returning a state selector. +- _registrySelector_ `( select: ) => Selector`: Function receiving a registry `select` function and returning a state selector. _Returns_ -- `Function`: Registry selector that can be registered with a store. +- `RegistrySelector< Selector >`: Registry selector that can be registered with a store. ### createSelector @@ -490,15 +485,6 @@ _Related_ - The documentation for the `rememo` package from which the `createSelector` function is reexported. -_Parameters_ - -- _selector_ `Function`: Selector function that calculates a value from state and parameters. -- _getDependants_ `Function`: Function that returns an array of "dependant" objects. - -_Returns_ - -- `Function`: A memoized version of `selector` that caches the calculated return values. - ### dispatch Given a store descriptor, returns an object of the store's action creators. Calling an action creator will cause it to be dispatched, updating the state value accordingly. diff --git a/packages/data/package.json b/packages/data/package.json index 1117a8373ff121..33ac259a931c81 100644 --- a/packages/data/package.json +++ b/packages/data/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/data", - "version": "10.11.0", + "version": "10.16.0", "description": "Data module for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -31,13 +31,13 @@ "sideEffects": false, "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/compose": "*", - "@wordpress/deprecated": "*", - "@wordpress/element": "*", - "@wordpress/is-shallow-equal": "*", - "@wordpress/priority-queue": "*", - "@wordpress/private-apis": "*", - "@wordpress/redux-routine": "*", + "@wordpress/compose": "file:../compose", + "@wordpress/deprecated": "file:../deprecated", + "@wordpress/element": "file:../element", + "@wordpress/is-shallow-equal": "file:../is-shallow-equal", + "@wordpress/priority-queue": "file:../priority-queue", + "@wordpress/private-apis": "file:../private-apis", + "@wordpress/redux-routine": "file:../redux-routine", "deepmerge": "^4.3.0", "equivalent-key-map": "^0.2.2", "is-plain-object": "^5.0.0", diff --git a/packages/data/src/components/use-select/index.js b/packages/data/src/components/use-select/index.js index 15a3c88d2d5543..ea4869bb4e7a92 100644 --- a/packages/data/src/components/use-select/index.js +++ b/packages/data/src/components/use-select/index.js @@ -19,6 +19,25 @@ import useAsyncMode from '../async-mode-provider/use-async-mode'; const renderQueue = createQueue(); +function warnOnUnstableReference( a, b ) { + if ( ! a || ! b ) { + return; + } + + const keys = + typeof a === 'object' && typeof b === 'object' + ? Object.keys( a ).filter( ( k ) => a[ k ] !== b[ k ] ) + : []; + + // eslint-disable-next-line no-console + console.warn( + 'The `useSelect` hook returns different values when called with the same state and parameters.\n' + + 'This can lead to unnecessary re-renders and performance issues if not fixed.\n\n' + + 'Non-equal value keys: %s\n\n', + keys.join( ', ' ) + ); +} + /** * @typedef {import('../../types').StoreDescriptor<C>} StoreDescriptor * @template {import('../../types').AnyConfig} C @@ -155,14 +174,11 @@ function Store( registry, suspense ) { listeningStores ); - if ( process.env.NODE_ENV === 'development' ) { + if ( globalThis.SCRIPT_DEBUG ) { if ( ! didWarnUnstableReference ) { const secondMapResult = mapSelect( select, registry ); if ( ! isShallowEqual( mapResult, secondMapResult ) ) { - // eslint-disable-next-line no-console - console.warn( - `The 'useSelect' hook returns different values when called with the same state and parameters. This can lead to unnecessary rerenders.` - ); + warnOnUnstableReference( mapResult, secondMapResult ); didWarnUnstableReference = true; } } diff --git a/packages/data/src/components/use-select/test/index.js b/packages/data/src/components/use-select/test/index.js index 3320f7d4638a59..6bb47d3c68e9ed 100644 --- a/packages/data/src/components/use-select/test/index.js +++ b/packages/data/src/components/use-select/test/index.js @@ -32,12 +32,24 @@ function counterStore( initialCount = 0, step = 1 ) { }; } +/* eslint-disable @wordpress/wp-global-usage */ describe( 'useSelect', () => { + const initialScriptDebug = globalThis.SCRIPT_DEBUG; let registry; + + beforeAll( () => { + // Do not run hook in development mode; it will call `mapSelect` an extra time. + globalThis.SCRIPT_DEBUG = false; + } ); + beforeEach( () => { registry = createRegistry(); } ); + afterAll( () => { + globalThis.SCRIPT_DEBUG = initialScriptDebug; + } ); + it( 'passes the relevant data to the component', () => { registry.registerStore( 'testStore', { reducer: () => ( { foo: 'bar' } ), @@ -1257,3 +1269,4 @@ describe( 'useSelect', () => { } ); } ); } ); +/* eslint-enable @wordpress/wp-global-usage */ diff --git a/packages/data/src/components/with-dispatch/test/index.js b/packages/data/src/components/with-dispatch/test/index.js index 6bcda99ba9c0f0..a58b4af3c5588d 100644 --- a/packages/data/src/components/with-dispatch/test/index.js +++ b/packages/data/src/components/with-dispatch/test/index.js @@ -77,7 +77,7 @@ describe( 'withDispatch', () => { ); // Function value reference should not have changed in props update. - // The spy method is only called during inital render. + // The spy method is only called during initial render. expect( ButtonSpy ).toHaveBeenCalledTimes( 1 ); await user.click( screen.getByRole( 'button' ) ); diff --git a/packages/data/src/components/with-select/test/index.js b/packages/data/src/components/with-select/test/index.js index fe798354cba20d..9e01bb17cbb7e1 100644 --- a/packages/data/src/components/with-select/test/index.js +++ b/packages/data/src/components/with-select/test/index.js @@ -18,7 +18,19 @@ import withDispatch from '../../with-dispatch'; import { createRegistry } from '../../../registry'; import { RegistryProvider } from '../../registry-provider'; +/* eslint-disable @wordpress/wp-global-usage */ describe( 'withSelect', () => { + const initialScriptDebug = globalThis.SCRIPT_DEBUG; + + beforeAll( () => { + // Do not run HOC in development mode; it will call `mapSelect` an extra time. + globalThis.SCRIPT_DEBUG = false; + } ); + + afterAll( () => { + globalThis.SCRIPT_DEBUG = initialScriptDebug; + } ); + it( 'passes the relevant data to the component', () => { const registry = createRegistry(); registry.registerStore( 'reactReducer', { @@ -615,3 +627,4 @@ describe( 'withSelect', () => { expect( screen.getByRole( 'status' ) ).toHaveTextContent( 'second' ); } ); } ); +/* eslint-enable @wordpress/wp-global-usage */ diff --git a/packages/data/src/create-selector.js b/packages/data/src/create-selector.ts similarity index 55% rename from packages/data/src/create-selector.js rename to packages/data/src/create-selector.ts index 069932e8007e28..bfb7a1d283733f 100644 --- a/packages/data/src/create-selector.js +++ b/packages/data/src/create-selector.ts @@ -3,9 +3,5 @@ * and the selector parameters, and recomputes the values only when any of them changes. * * @see The documentation for the `rememo` package from which the `createSelector` function is reexported. - * - * @param {Function} selector Selector function that calculates a value from state and parameters. - * @param {Function} getDependants Function that returns an array of "dependant" objects. - * @return {Function} A memoized version of `selector` that caches the calculated return values. */ export { default as createSelector } from 'rememo'; diff --git a/packages/data/src/factory.js b/packages/data/src/factory.ts similarity index 75% rename from packages/data/src/factory.js rename to packages/data/src/factory.ts index be4ef8cf673c5e..8218fd2cdb07db 100644 --- a/packages/data/src/factory.js +++ b/packages/data/src/factory.ts @@ -1,3 +1,14 @@ +/** + * Internal dependencies + */ +import type { select as globalSelect } from './select'; + +type RegistrySelector< Selector extends ( ...args: any[] ) => any > = { + ( ...args: Parameters< Selector > ): ReturnType< Selector >; + isRegistrySelector?: boolean; + registry?: any; +}; + /** * Creates a selector function that takes additional curried argument with the * registry `select` function. While a regular selector has signature @@ -33,17 +44,21 @@ * registry as argument. The registry binding happens automatically when registering the selector * with a store. * - * @param {Function} registrySelector Function receiving a registry `select` - * function and returning a state selector. + * @param registrySelector Function receiving a registry `select` + * function and returning a state selector. * - * @return {Function} Registry selector that can be registered with a store. + * @return Registry selector that can be registered with a store. */ -export function createRegistrySelector( registrySelector ) { +export function createRegistrySelector< + Selector extends ( ...args: any[] ) => any, +>( + registrySelector: ( select: typeof globalSelect ) => Selector +): RegistrySelector< Selector > { const selectorsByRegistry = new WeakMap(); // Create a selector function that is bound to the registry referenced by `selector.registry` // and that has the same API as a regular selector. Binding it in such a way makes it // possible to call the selector directly from another selector. - const wrappedSelector = ( ...args ) => { + const wrappedSelector: RegistrySelector< Selector > = ( ...args ) => { let selector = selectorsByRegistry.get( wrappedSelector.registry ); // We want to make sure the cache persists even when new registry // instances are created. For example patterns create their own editors @@ -60,8 +75,6 @@ export function createRegistrySelector( registrySelector ) { * Flag indicating that the selector is a registry selector that needs the correct registry * reference to be assigned to `selector.registry` to make it work correctly. * be mapped as a registry selector. - * - * @type {boolean} */ wrappedSelector.isRegistrySelector = true; @@ -84,11 +97,13 @@ export function createRegistrySelector( registrySelector ) { * When registering a control created with `createRegistryControl` with a store, the store * knows which calling convention to use when executing the control. * - * @param {Function} registryControl Function receiving a registry object and returning a control. + * @param registryControl Function receiving a registry object and returning a control. * - * @return {Function} Registry control that can be registered with a store. + * @return Registry control that can be registered with a store. */ -export function createRegistryControl( registryControl ) { +export function createRegistryControl< T extends ( ...args: any ) => any >( + registryControl: T & { isRegistryControl?: boolean } +) { registryControl.isRegistryControl = true; return registryControl; diff --git a/packages/data/src/redux-store/index.js b/packages/data/src/redux-store/index.js index 979c3127b9ed52..6c20c8cb2bb3ec 100644 --- a/packages/data/src/redux-store/index.js +++ b/packages/data/src/redux-store/index.js @@ -250,7 +250,7 @@ export default function createReduxStore( key, options ) { }; // Expose normalization method on the bound selector - // in order that it can be called when fullfilling + // in order that it can be called when fulfilling // the resolver. boundSelector.__unstableNormalizeArgs = selector.__unstableNormalizeArgs; diff --git a/packages/data/src/redux-store/test/index.js b/packages/data/src/redux-store/test/index.js index 1251051c4c9d9a..1365ceab0d5a8d 100644 --- a/packages/data/src/redux-store/test/index.js +++ b/packages/data/src/redux-store/test/index.js @@ -311,7 +311,7 @@ describe( 'normalizing args', () => { // Needs to be called twice: // 1. When the selector is called. - // 2. When the resolver is fullfilled. + // 2. When the resolver is fulfilled. expect( normalizingFunction ).toHaveBeenCalledTimes( 2 ); } ); diff --git a/packages/data/src/registry.js b/packages/data/src/registry.js index 3e7a8fdd8b5a07..8db8bfbbbb702d 100644 --- a/packages/data/src/registry.js +++ b/packages/data/src/registry.js @@ -49,7 +49,7 @@ function getStoreName( storeNameOrDescriptor ) { * configurations. * * @param {Object} storeConfigs Initial store configurations. - * @param {Object?} parent Parent registry. + * @param {?Object} parent Parent registry. * * @return {WPDataRegistry} Data registry. */ diff --git a/packages/data/tsconfig.json b/packages/data/tsconfig.json index 2bfc881dc6216e..b73eca0d342f04 100644 --- a/packages/data/tsconfig.json +++ b/packages/data/tsconfig.json @@ -2,8 +2,6 @@ "$schema": "https://json.schemastore.org/tsconfig.json", "extends": "../../tsconfig.base.json", "compilerOptions": { - "rootDir": "src", - "declarationDir": "build-types", "checkJs": false }, "references": [ @@ -14,6 +12,5 @@ { "path": "../is-shallow-equal" }, { "path": "../priority-queue" }, { "path": "../redux-routine" } - ], - "include": [ "src/**/*" ] + ] } diff --git a/packages/dataviews/CHANGELOG.md b/packages/dataviews/CHANGELOG.md index 60cdad73b32efe..4342621cc74050 100644 --- a/packages/dataviews/CHANGELOG.md +++ b/packages/dataviews/CHANGELOG.md @@ -2,6 +2,48 @@ ## Unreleased +## 4.12.0 (2025-01-15) + +## 4.11.0 (2025-01-02) + +### Bug Fixes + +- Fixed commonjs export ([#67962](https://github.com/WordPress/gutenberg/pull/67962)) + +### Features + +- Add support for hierarchical visualization of data. `DataViews` gets a new prop `getItemLevel` that should return the hierarchical level of the item. The view can use `view.showLevels` to display the levels. It's up to the consumer data source to prepare this information. + +## 4.10.0 (2024-12-11) + +### Breaking Changes + +- Support showing or hiding title, media and description fields ([#67477](https://github.com/WordPress/gutenberg/pull/67477)). +- Unify the `title`, `media` and `description` fields for the different layouts. So instead of the previous `view.layout.mediaField`, `view.layout.primaryField` and `view.layout.columnFields`, all the layouts now support these three fields with the following config ([#67477](https://github.com/WordPress/gutenberg/pull/67477)): + +```js +const view = { + type: 'table', + titleField: 'title', + mediaField: 'media', + descriptionField: 'description', + fields: [ 'author', 'date' ], +}; +``` + +### Internal + +- Upgraded `@ariakit/react` (v0.4.13) and `@ariakit/test` (v0.4.5) ([#65907](https://github.com/WordPress/gutenberg/pull/65907)). +- Upgraded `@ariakit/react` (v0.4.15) and `@ariakit/test` (v0.4.7) ([#67404](https://github.com/WordPress/gutenberg/pull/67404)). + +## 4.9.0 (2024-11-27) + +### Bug Fixes + +- Fix focus loss when removing all filters or resetting ([#67003](https://github.com/WordPress/gutenberg/pull/67003)). + +## 4.8.0 (2024-11-16) + ## 4.7.0 (2024-10-30) ## 4.6.0 (2024-10-16) @@ -23,8 +65,7 @@ ## Internal - The "move left/move right" controls in the table layout (popup displayed on cliking header) are always visible. ([#64646](https://github.com/WordPress/gutenberg/pull/64646)). Before this, its visibility depending on filters, enableSorting, and enableHiding. -- Filters no longer display the elements' description. ([#64674](https://github.com/WordPress/gutenberg/pull/64674)) - +- Filters no longer display the elements' description. ([#64674](https://github.com/WordPress/gutenberg/pull/64674)) ## Enhancements diff --git a/packages/dataviews/README.md b/packages/dataviews/README.md index 621e3c7ba71ce2..741d25971f5341 100644 --- a/packages/dataviews/README.md +++ b/packages/dataviews/README.md @@ -1,9 +1,9 @@ # The `@wordpress/dataviews` package -The DataViews package offers two React components and a few utilites to work with a list of data: +The DataViews package offers two React components and a few utilities to work with a list of data: -- `DataViews`: to render the dataset using different types of layouts (table, grid, list) and interaction capabilities (search, filters, sorting, etc.). -- `DataForm`: to edit the items of the dataset. +- `DataViews`: to render the dataset using different types of layouts (table, grid, list) and interaction capabilities (search, filters, sorting, etc.). +- `DataForm`: to edit the items of the dataset. ## Installation @@ -15,19 +15,23 @@ npm install @wordpress/dataviews --save ## `DataViews` -<div class="callout callout-info">At <a href="https://wordpress.github.io/gutenberg/">WordPress Gutenberg's Storybook</a> there's and <a href="https://wordpress.github.io/gutenberg/?path=/docs/dataviews-dataviews--docs">example implementation of the Dataviews component</a>.</div> +<div class="callout callout-info">At <a href="https://wordpress.github.io/gutenberg/">WordPress Gutenberg's Storybook</a> there's an <a href="https://wordpress.github.io/gutenberg/?path=/docs/dataviews-dataviews--docs">example implementation of the Dataviews component</a>.</div> + +**Important note** If you're trying to use the `DataViews` component in a WordPress plugin or theme and you're building your scripts using the `@wordpress/scripts` package, you need to import the components from `@wordpress/dataviews/wp` instead of `@wordpress/dataviews`. ### Usage The `DataViews` component receives data and some other configuration to render the dataset. It'll call the `onChangeView` callback every time the user has interacted with the dataset in some way (sorted, filtered, changed layout, etc.): -![DataViews flow](https://developer.wordpress.org/files/2024/09/368600071-20aa078f-7c3d-406d-8dd0-8b764addd22a.png "DataViews flow") +![DataViews flow](https://developer.wordpress.org/files/2024/09/368600071-20aa078f-7c3d-406d-8dd0-8b764addd22a.png 'DataViews flow') Example: ```jsx const Example = () => { - const onChangeView = () => { /* React to user changes. */ } + const onChangeView = () => { + /* React to user changes. */ + }; return ( <DataViews @@ -43,7 +47,6 @@ const Example = () => { }; ``` - ### Properties #### `data`: `Object[]` @@ -66,13 +69,13 @@ const data = [ ]; ``` -The data can come from anywhere, from a static JSON file to a dynamic source like a HTTP Request. It's the consumer's responsiblity to query the data source appropiately and update the dataset based on the user's choices for sorting, filtering, etc. +The data can come from anywhere, from a static JSON file to a dynamic source like a HTTP Request. It's the consumer's responsibility to query the data source appropriately and update the dataset based on the user's choices for sorting, filtering, etc. Each record should have an `id` that identifies them uniquely. If they don't, the consumer should provide the `getItemId` property to `DataViews`: a function that returns an unique identifier for the record. #### `getItemId`: `function` -Function that receives an item and returns an unique identifier for it. +A function that receives an item and returns a unique identifier for it. It's optional. The field will get a default implementation by `DataViews` that returns the value of the `item[ id ]`. @@ -85,6 +88,19 @@ Example: } ``` +#### `getItemLevel`: `function` + +A function that receives an item and returns its hierarchical level. It's optional, but this property must be passed for DataViews to display the hierarchical levels of the data if `view.showLevels` is true. + +Example: + +```js +// Example implementation +{ + getItemLevel={ ( item ) => item.level } +} +``` + #### `fields`: `Object[]` The fields describe the visible items for each record in the dataset and how they behave (how to sort them, display them, etc.). See "Fields API" for a description of every property. @@ -163,6 +179,7 @@ const view = { field: 'date', direction: 'desc', }, + titleField: 'title', fields: [ 'author', 'status' ], layout: {}, }; @@ -183,54 +200,28 @@ Properties: - `field`: the field used for sorting the dataset. - `direction`: the direction to use for sorting, one of `asc` or `desc`. -- `fields`: a list of field `id` that are visible in the UI and the specific order in which they are displayed. +- `titleField`: The id of the field representing the title of the record. +- `mediaField`: The id of the field representing the media of the record. +- `descriptionField`: The id of the field representing the description of the record. +- `showTitle`: Whether the title should be shown in the UI. `true` by default. +- `showMedia`: Whether the media should be shown in the UI. `true` by default. +- `showDescription`: Whether the description should be shown in the UI. `true` by default. +- `showLevels`: Whether to display the hierarchical levels for the data. `false` by default. See related `getItemLevel` DataView prop. +- `fields`: a list of remaining field `id` that are visible in the UI and the specific order in which they are displayed. - `layout`: config that is specific to a particular layout type. ##### Properties of `layout` -| Properties of `layout` | Table | Grid | List | -| --------------------------------------------------------------------------------------------------------------- | ----- | ---- | ---- | -| `primaryField`: the field's `id` to be highlighted in each layout. It's not hidable. | ✓ | ✓ | ✓ | -| `mediaField`: the field's `id` to be used for rendering each card's media. It's not hiddable. | | ✓ | ✓ | -| `columnFields`: a list of field's `id` to render vertically stacked instead of horizontally (the default). | | ✓ | | -| `badgeFields`: a list of field's `id` to render without label and styled as badges. | | ✓ | | -| `combinedFields`: a list of "virtual" fields that are made by combining others. See "Combining fields" section. | ✓ | | | -| `styles`: additional `width`, `maxWidth`, `minWidth` styles for each field column. | ✓ | | | - -##### Combining fields - -The `table` layout has the ability to create "virtual" fields that are made out by combining existing ones. - -Each "virtual field", has to provide an `id` and `label` (optionally a `header` instead), which have the same meaning as any other field. - -Additionally, they need to provide: - -- `children`: a list of field's `id` to combine -- `direction`: how should they be stacked, `vertical` or `horizontal` - -For example, this is how you'd define a `site` field which is a combination of a `title` and `description` fields, which are not displayed: - -```js -{ - fields: [ 'site', 'status' ], - layout: { - combinedFields: [ - { - id: 'site', - label: 'Site', - children: [ 'title', 'description' ], - direction: 'vertical', - } - ] - } -} -``` +| Properties of `layout` | Table | Grid | List | +| ----------------------------------------------------------------------------------- | ----- | ---- | ---- | +| `badgeFields`: a list of field's `id` to render without label and styled as badges. | | ✓ | | +| `styles`: additional `width`, `maxWidth`, `minWidth` styles for each field column. | ✓ | | | #### `onChangeView`: `function` Callback executed when the view has changed. It receives the new view object as a parameter. -The view is a representation of the visible state of the dataset: what type of layout is used to display it (table, grid, etc.), how the dataset is filtered, how it is sorted or paginated. It's the consumer's responsibility to use the view config to query the data provider and make sure the user decisions (sort, pagination, filters, etc.) are respected. +The view is a representation of the visible state of the dataset: what type of layout is used to display it (table, grid, etc.), how the dataset is filtered, and how it is sorted or paginated. The consumer is responsible for using the view config to query the data provider and ensure the user decisions (sort, pagination, filters, etc.) are respected. The following example shows how a view object is used to query the WordPress REST API via the entities abstraction. The same can be done with any other data provider. @@ -253,6 +244,7 @@ function MyCustomPageTable() { value: [ 'publish', 'draft' ], }, ], + titleField: 'title', fields: [ 'author', 'status' ], layout: {}, } ); @@ -293,23 +285,54 @@ function MyCustomPageTable() { #### `actions`: `Object[]` -Collection of operations that can be performed upon each record. +A list of actions that can be performed on the dataset. See "Actions API" for more details. -Each action is an object with the following properties: +Example: -- `id`: string, required. Unique identifier of the action. For example, `move-to-trash`. -- `label`: string|function, required. User facing description of the action. For example, `Move to Trash`. It can also take a function that takes the selected items as a parameter and returns a string: this can be useful to provide a dynamic label based on the selection. -- `isPrimary`: boolean, optional. Whether the action should be listed inline (primary) or in hidden in the more actions menu (secondary). -- `icon`: SVG element. Icon to show for primary actions. It's required for a primary action, otherwise the action would be considered secondary. -- `isEligible`: function, optional. Whether the action can be performed for a given record. If not present, the action is considered to be eligible for all items. It takes the given record as input. -- `isDestructive`: boolean, optional. Whether the action can delete data, in which case the UI would communicate it via red color. -- `supportsBulk`: Whether the action can be used as a bulk action. False by default. -- `disabled`: Whether the action is disabled. False by default. -- `context`: where this action would be visible. One of `list`, `single`. -- `callback`: function, required unless `RenderModal` is provided. Callback function that takes as input the list of items to operate with, and performs the required action. -- `RenderModal`: ReactElement, optional. If an action requires that some UI be rendered in a modal, it can provide a component which takes as input the the list of `items` to operate with, `closeModal` function, and `onActionPerformed` function. When this prop is provided, the `callback` property is ignored. -- `hideModalHeader`: boolean, optional. This property is used in combination with `RenderModal` and controls the visibility of the modal's header. If the action renders a modal and doesn't hide the header, the action's label is going to be used in the modal's header. -- `modalHeader`: string, optional. The header of the modal. +```js +const actions = [ + { + id: 'view', + label: 'View', + isPrimary: true, + icon: <Icon icon={ view } />, + isEligible: ( item ) => item.status === 'published' + callback: ( items ) => { + console.log( 'Viewing item:', items[0] ); + }, + }, + { + id: 'edit', + label: 'Edit', + icon: <Icon icon={ edit } />, + supportsBulk: true, + callback: ( items ) => { + console.log( 'Editing items:', items ); + } + }, + { + id: 'delete', + label: 'Delete', + isDestructive: true, + supportsBulk: true, + RenderModal: ( { items, closeModal, onActionPerformed } ) => ( + <div> + <p>Are you sure you want to delete { items.length } item(s)?</p> + <Button + variant="primary" + onClick={() => { + console.log( 'Deleting items:', items ); + onActionPerformed(); + closeModal(); + }} + > + Confirm Delete + </Button> + </div> + ) + } +]; +``` #### `paginationInfo`: `Object` @@ -330,39 +353,40 @@ Whether the data is loading. `false` by default. #### `defaultLayouts`: `Record< string, view >` -This property provides layout information about the view types that are active. If empty, enables all layout types (see "Layout Types") with empty layout data. +This property provides layout information about active view types. If empty, this enables all layout types (see "Layout Types") with empty layout data. For example, this is how you'd enable only the table view type: ```js const defaultLayouts = { table: { - layout: { - primaryField: 'my-key', - }, + showMedia: false, + }, + grid: { + showMedia: true, }, }; ``` -The `defaultLayouts` property should be an object that includes properties named `table`, `grid`, or `list`. Each of these properties should contain a `layout` property, which holds the configuration for each specific layout type. Check "Properties of layout" for the full list of properties available for each layout's configuration +The `defaultLayouts` property should be an object that includes properties named `table`, `grid`, or `list`. These properties are applied to the view object each time the user switches to the corresponding layout. #### `selection`: `string[]` The list of selected items' ids. -If `selection` and `onChangeSelection` are provided, the `DataViews` component behaves as a controlled component, otherwise, it behaves like an uncontrolled component. +If `selection` and `onChangeSelection` are provided, the `DataViews` component behaves like a controlled component. Otherwise, it behaves like an uncontrolled component. #### `onChangeSelection`: `function` -Callback that signals the user selected one of more items. It receives the list of selected items' ids as a parameter. +Callback that signals the user selected one of more items. It receives the list of selected items' IDs as a parameter. -If `selection` and `onChangeSelection` are provided, the `DataViews` component behaves as a controlled component, otherwise, it behaves like an uncontrolled component. +If `selection` and `onChangeSelection` are provided, the `DataViews` component behaves like a controlled component. Otherwise, it behaves like an uncontrolled component. -### `isItemClickable`: `function` +#### `isItemClickable`: `function` -A function that determines if a media field or a primary field are clickable. It receives an item as an argument and returns a boolean value indicating whether the item can be clicked. +A function that determines if a media field or a primary field is clickable. It receives an item as an argument and returns a boolean value indicating whether the item can be clicked. -### `onClickItem`: `function` +#### `onClickItem`: `function` A callback function that is triggered when a user clicks on a media field or primary field. This function is currently implemented only in the `grid` and `list` views. @@ -387,8 +411,8 @@ const Example = () => { form={ form } onChange={ onChange } /> - ) -} + ); +}; ``` ### Properties @@ -397,7 +421,7 @@ const Example = () => { A single item to be edited. -It can be think of as a single record coming from the `data` property of `DataViews` — though it doesn't need to be. It can be totally separated or a mix of records if your app supports bulk editing. +It can be thought of as a single record coming from the `data` property of `DataViews` — though it doesn't need to be. It can be totally separated or a mix of records if your app supports bulk editing. #### `fields`: `Object[]` @@ -431,8 +455,30 @@ const fields = [ #### `form`: `Object[]` -- `type`: either `regular` or `panel`. -- `fields`: a list of fields ids that should be rendered. +- `type`: either `regular` or `panel`. +- `labelPosition`: either `side`, `top`, or `none`. +- `fields`: a list of fields ids that should be rendered. Field ids can also be defined as an object and allow you to define a `layout`, `labelPosition` or `children` if displaying combined fields. See "Form Field API" for a description of every property. + +Example: + +```js +const form = { + type: 'panel', + fields: [ + 'title', + 'data', + { + id: 'status', + label: 'Status & Visibility', + children: [ 'status', 'password' ], + }, + { + id: 'featured_media', + layout: 'regular', + }, + ], +}; +``` #### `onChange`: `function` @@ -463,10 +509,10 @@ const onChange = ( edits ) => { return ( <DataForm - data={data} - fields={fields} - form={form} - onChange={onChange} + data={ data } + fields={ fields } + form={ form } + onChange={ onChange } /> ); ``` @@ -479,107 +525,274 @@ Utility to apply the view config (filters, search, sorting, and pagination) to a Parameters: -- `data`: the dataset, as described in the "data" property of DataViews. -- `view`: the view config, as described in the "view" property of DataViews. -- `fields`: the fields config, as described in the "fields" property of DataViews. +- `data`: the dataset, as described in the "data" property of DataViews. +- `view`: the view config, as described in the "view" property of DataViews. +- `fields`: the fields config, as described in the "fields" property of DataViews. Returns an object containing: -- `data`: the new dataset, with the view config applied. -- `paginationInfo`: object containing the following properties: - - `totalItems`: total number of items for the current view config. - - `totalPages`: total number of pages for the current view config. +- `data`: the new dataset, with the view config applied. +- `paginationInfo`: object containing the following properties: + - `totalItems`: total number of items for the current view config. + - `totalPages`: total number of pages for the current view config. ### `isItemValid` -Utility to determine whether or not the given item's value is valid according to the current fields and form config. +Utility is used to determine whether or not the given item's value is valid according to the current fields and form configuration. Parameters: -- `item`: the item, as described in the "data" property of DataForm. -- `fields`: the fields config, as described in the "fields" property of DataForm. -- `form`: the form config, as described in the "form" property of DataForm. +- `item`: the item, as described in the "data" property of DataForm. +- `fields`: the fields config, as described in the "fields" property of DataForm. +- `form`: the form config, as described in the "form" property of DataForm. Returns a boolean indicating if the item is valid (true) or not (false). +## Actions API + +### `id` + +The unique identifier of the action. + +- Type: `string` +- Required +- Example: `move-to-trash` + +### `label` + +The user facing description of the action. + +- Type: `string | function` +- Required +- Example: + +```js +{ + label: Move to Trash +} +``` + +or + +```js +{ + label: ( items ) => ( items.length > 1 ? 'Delete items' : 'Delete item' ); +} +``` + +### `isPrimary` + +Whether the action should be displayed inline (primary) or only displayed in the "More actions" menu (secondary). + +- Type: `boolean` +- Optional + +### `icon` + +Icon to show for primary actions. + +- Type: SVG element +- Required for primary actions, optional for secondary actions. + +### `isEligible` + +Function that determines whether the action can be performed for a given record. + +- Type: `function` +- Optional. If not present, action is considered eligible for all items. +- Example: + +```js +{ + isEligible: ( item ) => item.status === 'published'; +} +``` + +### `isDestructive` + +Whether the action can delete data, in which case the UI communicates it via a red color. + +- Type: `boolean` +- Optional + +### `supportsBulk` + +Whether the action can operate over multiple items at once. + +- Type: `boolean` +- Optional +- Default: `false` + +### `disabled` + +Whether the action is disabled. + +- Type: `boolean` +- Optional +- Default: `false` + +### `context` + +Where this action would be visible. + +- Type: `string` +- Optional +- One of: `list`, `single` + +### `callback` + +Function that performs the required action. + +- Type: `function` +- Either `callback` or `RenderModal` must be provided. If `RenderModal` is provided, `callback` will be ignored +- Example: + +```js +{ + callback: ( items, { onActionPerformed } ) => { + // Perform action. + onActionPerformed?.( items ); + }; +} +``` + +### `RenderModal` + +Component to render UI in a modal for the action. + +- Type: `ReactElement` +- Either `callback` or `RenderModal` must be provided. If `RenderModal` is provided, `callback` will be ignored. +- Example: + +```jsx +{ + RenderModal: ( { items, closeModal, onActionPerformed } ) => { + const onSubmit = ( event ) => { + event.preventDefault(); + // Perform action. + closeModal?.(); + onActionPerformed?.( items ); + }; + return ( + <form onSubmit={ onSubmit }> + <p>Modal UI</p> + <HStack> + <Button variant="tertiary" onClick={ closeModal }> + Cancel + </Button> + <Button variant="primary" type="submit"> + Submit + </Button> + </HStack> + </form> + ); + }; +} +``` + +### `hideModalHeader` + +Controls visibility of the modal's header when using `RenderModal`. + +- Type: `boolean` +- Optional +- When false and using `RenderModal`, the action's label is used in modal header + +### `modalHeader` + +The header text to show in the modal. + +- Type: `string` +- Optional + ## Fields API ### `id` The unique identifier of the field. -- Type: `string`. -- Required. +- Type: `string`. +- Required. Example: ```js -{ id: 'field_id' } +{ + id: 'field_id'; +} ``` ### `type` Field type. One of `text`, `integer`, `datetime`. -If a field declares a `type`, it gets default implementations for the `sort`, `isValid`, and `Edit` functions. They will overriden if the field provides its own. +If a field declares a `type`, it gets default implementations for the `sort`, `isValid`, and `Edit` functions if no other values are specified. -- Type: `string`. -- Optional. +- Type: `string`. +- Optional. Example: ```js -{ type: 'text' } +{ + type: 'text'; +} ``` ### `label` The field's name. This will be used across the UI. -- Type: `string`. -- Optional. -- Defaults to the `id` value. +- Type: `string`. +- Optional. +- Defaults to the `id` value. Example: ```js -{ label: 'Title' } +{ + label: 'Title'; +} ``` ### `header` React component used by the layouts to display the field name — useful to add icons, etc. It's complementary to the `label` property. -- Type: React component. -- Optional. -- Defaults to the `label` value. -- Props: none. -- Returns a React element that represents the field's name. +- Type: React component. +- Optional. +- Defaults to the `label` value. +- Props: none. +- Returns a React element that represents the field's name. Example: ```js { - header: () => { /* Returns a react element. */ } + header: () => { + /* Returns a react element. */ + }; } ``` ### `getValue` -React component that returns the value of a field. This value is used in sorting the fields, or when filtering. +React component that returns the value of a field. This value is used to sort or filter the fields. -- Type: React component. -- Optional. -- Defaults to `item[ id ]`. -- Props: - - `item` value to be processed. -- Returns a value that represents the field. +- Type: React component. +- Optional. +- Defaults to `item[ id ]`. +- Props: + - `item` value to be processed. +- Returns a value that represents the field. Example: ```js { - getValue: ( { item } ) => { /* The field's value. */ }; + getValue: ( { item } ) => { + /* The field's value. */ + }; } ``` @@ -587,18 +800,20 @@ Example: React component that renders the field. This is used by the layouts. -- Type: React component. -- Optional. -- Defaults to `getValue`. -- Props - - `item` value to be processed. -- Returns a React element that represents the field's value. +- Type: React component. +- Optional. +- Defaults to `getValue`. +- Props + - `item` value to be processed. +- Returns a React element that represents the field's value. Example: ```js { - render: ( { item} ) => { /* React element to be displayed. */ } + render: ( { item } ) => { + /* React element to be displayed. */ + }; } ``` @@ -606,26 +821,21 @@ Example: React component that renders the control to edit the field. -- Type: React component | `string`. If it's a string, it needs to be one of `text`, `integer`, `datetime`, `radio`, `select`. -- Required by DataForm. Optional if the field provided a `type`. -- Props: - - `data`: the item to be processed - - `field`: the field definition - - `onChange`: the callback with the updates - - `hideLabelFromVision`: boolean representing if the label should be hidden -- Returns a React element to edit the field's value. +- Type: React component | `string`. If it's a string, it needs to be one of `text`, `integer`, `datetime`, `radio`, `select`. +- Required by DataForm. Optional if the field provided a `type`. +- Props: + - `data`: the item to be processed + - `field`: the field definition + - `onChange`: the callback with the updates + - `hideLabelFromVision`: boolean representing if the label should be hidden +- Returns a React element to edit the field's value. Example: ```js // A custom control defined by the field. { - Edit: ( { - data, - field, - onChange, - hideLabelFromVision - } ) => { + Edit: ( { data, field, onChange, hideLabelFromVision } ) => { const value = field.getValue( { item: data } ); return ( @@ -635,14 +845,14 @@ Example: hideLabelFromVision /> ); - } + }; } ``` ```js // Use one of the core controls. { - Edit: 'radio' + Edit: 'radio'; } ``` @@ -650,7 +860,7 @@ Example: // Edit is optional when field's type is present. // The field will use the default Edit function for text. { - type: 'text' + type: 'text'; } ``` @@ -667,16 +877,16 @@ Example: Function to sort the records. -- Type: `function`. -- Optional. -- Args - - `a`: the first item to compare - - `b`: the second item to compare - - `direction`: either `asc` (ascending) or `desc` (descending) -- Returns a number where: - - a negative value indicates that `a` should come before `b` - - a positive value indicates that `a` should come after `b` - - 0 indicates that `a` and `b` are considered equal +- Type: `function`. +- Optional. +- Args + - `a`: the first item to compare + - `b`: the second item to compare + - `direction`: either `asc` (ascending) or `desc` (descending) +- Returns a number where: + - a negative value indicates that `a` should come before `b` + - a positive value indicates that `a` should come after `b` + - 0 indicates that `a` and `b` are considered equal Example: @@ -687,7 +897,7 @@ Example: return direction === 'asc' ? a.localeCompare( b ) : b.localeCompare( a ); - } + }; } ``` @@ -695,7 +905,7 @@ Example: // If field type is provided, // the field gets a default sort function. { - type: 'number' + type: 'number'; } ``` @@ -703,8 +913,10 @@ Example: // Even if a field type is provided, // fields can override the default sort function assigned for that type. { - type: 'number' - sort: ( a, b, direction ) => { /* Custom sort */ } + type: 'number'; + sort: ( a, b, direction ) => { + /* Custom sort */ + }; } ``` @@ -712,13 +924,13 @@ Example: Function to validate a field's value. -- Type: function. -- Optional. -- Args - - `item`: the data to validate - - `context`: an object containing the following props: - - `elements`: the elements defined by the field -- Returns a boolean, indicating if the field is valid or not. +- Type: function. +- Optional. +- Args + - `item`: the data to validate + - `context`: an object containing the following props: + - `elements`: the elements defined by the field +- Returns a boolean, indicating if the field is valid or not. Example: @@ -727,7 +939,7 @@ Example: { isValid: ( item, context ) => { return !! item; - } + }; } ``` @@ -752,18 +964,20 @@ Example: Function that indicates if the field should be visible. -- Type: `function`. -- Optional. -- Args - - `item`: the data to be processed -- Returns a `boolean` indicating if the field should be visible (`true`) or not (`false`). +- Type: `function`. +- Optional. +- Args + - `item`: the data to be processed +- Returns a `boolean` indicating if the field should be visible (`true`) or not (`false`). Example: ```js // Custom isVisible function. { - isVisible: ( item ) => { /* Custom implementation. */ } + isVisible: ( item ) => { + /* Custom implementation. */ + }; } ``` @@ -771,54 +985,60 @@ Example: Boolean indicating if the field is sortable. -- Type: `boolean`. -- Optional. -- Defaults to `true`. +- Type: `boolean`. +- Optional. +- Defaults to `true`. Example: ```js -{ enableSorting: true } +{ + enableSorting: true; +} ``` ### `enableHiding` Boolean indicating if the field can be hidden. -- Type: `boolean`. -- Optional. -- Defaults to `true`. +- Type: `boolean`. +- Optional. +- Defaults to `true`. Example: ```js -{ enableHiding: true } +{ + enableHiding: true; +} ``` ### `enableGlobalSearch` Boolean indicating if the field is searchable. -- Type: `boolean`. -- Optional. -- Defaults to `false`. +- Type: `boolean`. +- Optional. +- Defaults to `false`. Example: ```js -{ enableGlobalSearch: true } +{ + enableGlobalSearch: true; +} ``` ### `elements` -List of valid values for a field. If provided, it creates a DataViews' filter for the field. DataForm's edit control will use these values as well (see `Edit` field property). +List of valid values for a field. If provided, it creates a DataViews' filter for the field. DataForm's edit control will also use these values. (See `Edit` field property.) -- Type: `array` of objects. -- Optional. -- Each object can have the following properties: - - `value`: required, the value to match against the field's value. - - `label`: required, the name to display to users. - - `description`: optional, a longer description of the item. +- Type: `array` of objects. +- Optional. +- Each object can have the following properties: + - `value`: the value to match against the field's value. (Required) + - `label`: the name to display to users. (Required) + - `description`: optional, a longer description of the item. Example: @@ -829,7 +1049,7 @@ Example: { value: '2', label: 'Product B' }, { value: '3', label: 'Product C' }, { value: '4', label: 'Product D' }, - ] + ]; } ``` @@ -837,11 +1057,11 @@ Example: Configuration of the filters. -- Type: `object`. -- Optional. -- Properties: - - `operators`: the list of operators supported by the field. See "operators" below. By default, a filter will support the `isAny` and `isNone` multi-selection operators. - - `isPrimary`: boolean, optional. Indicates if the filter is primary. A primary filter is always visible and is not listed in the "Add filter" component, except for the list layout where it behaves like a secondary filter. +- Type: `object`. +- Optional. +- Properties: + - `operators`: the list of operators supported by the field. See "operators" below. A filter will support the `isAny` and `isNone` multi-selection operators by default. + - `isPrimary`: boolean, optional. Indicates if the filter is primary. A primary filter is always visible and is not listed in the "Add filter" component, except for the list layout where it behaves like a secondary filter. Operators: @@ -854,7 +1074,7 @@ Operators: | `isAll` | Multiple items | `AND`. The item's field has all of the values in the list. | Category is all: Book, Review, Science Fiction | | `isNotAll` | Multiple items | `NOT AND`. The item's field doesn't have all of the values in the list. | Category is not all: Book, Review, Science Fiction | -`is` and `isNot` are single-selection operators, while `isAny`, `isNone`, `isAll`, and `isNotALl` are multi-selection. By default, a filter with no operators declared will support the `isAny` and `isNone` multi-selection operators. A filter cannot mix single-selection & multi-selection operators; if a single-selection operator is present in the list of valid operators, the multi-selection ones will be discarded and the filter won't allow selecting more than one item. +`is` and `isNot` are single-selection operators, while `isAny`, `isNone`, `isAll`, and `isNotALl` are multi-selection. A filter with no operators declared will support the `isAny` and `isNone` multi-selection operators by default. A filter cannot mix single-selection & multi-selection operators; if a single-selection operator is present in the list of valid operators, the multi-selection ones will be discarded, and the filter won't allow selecting more than one item. Example: @@ -862,7 +1082,7 @@ Example: // Set a filter as primary. { filterBy: { - isPrimary: true + isPrimary: true; } } ``` @@ -871,7 +1091,7 @@ Example: // Configure a filter as single-selection. { filterBy: { - operators: [ `is`, `isNot` ] + operators: [ `is`, `isNot` ]; } } ``` @@ -880,11 +1100,91 @@ Example: // Configure a filter as multi-selection with all the options. { filterBy: { - operators: [ `isAny`, `isNone`, `isAll`, `isNotAll` ] + operators: [ `isAny`, `isNone`, `isAll`, `isNotAll` ]; } } ``` +## Form Field API + +### `id` + +The unique identifier of the field. + +- Type: `string`. +- Required. + +Example: + +```js +{ + id: 'field_id'; +} +``` + +### `layout` + +The same as the `form.type`, either `regular` or `panel` only for the individual field. It defaults to `form.type`. + +- Type: `string`. + +Example: + +```js +{ + id: 'field_id', + layout: 'regular' +} +``` + +### `labelPosition` + +The same as the `form.labelPosition`, either `side`, `top`, or `none` for the individual field. It defaults to `form.labelPosition`. + +- Type: `string`. + +Example: + +```js +{ + id: 'field_id', + labelPosition: 'none' +} +``` + +### `label` + +The label used when displaying a combined field, this requires the use of `children` as well. + +- Type: `string`. + +Example: + +```js +{ + id: 'field_id', + label: 'Combined Field', + children: [ 'field1', 'field2' ] +} +``` + +### `children` + +Groups a set of fields defined within children. For example if you want to display multiple fields within the Panel dropdown you can use children ( see example ). + +- Type: `Array< string | FormField >`. + +Example: + +```js +{ + id: 'status', + layout: 'panel', + label: 'Combined Field', + children: [ 'field1', 'field2' ], +} +``` + ## Contributing to this package This is an individual package that's part of the Gutenberg project. The project is organized as a monorepo. It's made up of multiple self-contained software packages, each with a specific purpose. The packages in this monorepo are published to [npm](https://www.npmjs.com/) and used by [WordPress](https://make.wordpress.org/core/) as well as other software projects. diff --git a/packages/dataviews/build.js b/packages/dataviews/build.js new file mode 100644 index 00000000000000..16146632d0359e --- /dev/null +++ b/packages/dataviews/build.js @@ -0,0 +1,40 @@ +/** + * External dependencies + */ +// eslint-disable-next-line import/no-extraneous-dependencies +const esbuild = require( 'esbuild' ); + +const wpExternals = { + name: 'wordpress-externals', + setup( build ) { + build.onResolve( + { filter: /^@wordpress\/(data|hooks|i18n|date)(\/|$)/ }, + ( args ) => { + // Don't bundle WordPress signleton packages + return { path: args.path, external: true }; + } + ); + build.onResolve( { filter: /^@wordpress\// }, () => { + // Bundle WordPress packages + return { external: false }; + } ); + build.onResolve( { filter: /^\.[\.\/]/ }, () => { + // Bundle relative paths + return { external: false }; + } ); + build.onResolve( { filter: /.+/ }, ( args ) => { + // Mark everything else as external + return { path: args.path, external: true }; + } ); + }, +}; + +esbuild.build( { + entryPoints: [ 'src/index.ts' ], + bundle: true, + outdir: 'build-wp', + plugins: [ wpExternals ], + jsx: 'automatic', + logLevel: 'info', + format: 'esm', +} ); diff --git a/packages/dataviews/package.json b/packages/dataviews/package.json index df30fea1a1c714..a0bb5cbf28a3ee 100644 --- a/packages/dataviews/package.json +++ b/packages/dataviews/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/dataviews", - "version": "4.7.0", + "version": "4.12.0", "description": "DataViews is a component that provides an API to render datasets using different types of layouts (table, grid, list, etc.).", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -24,22 +24,37 @@ }, "main": "build/index.js", "module": "build-module/index.js", + "exports": { + ".": { + "types": "./build-types/index.d.ts", + "import": "./build-module/index.js", + "default": "./build/index.js" + }, + "./wp": { + "types": "./build-types/index.d.ts", + "default": "./build-wp/index.js" + }, + "./package.json": { + "default": "./package.json" + }, + "./build-style/": "./build-style/" + }, "react-native": "src/index", "wpScript": true, "types": "build-types", "sideEffects": false, "dependencies": { - "@ariakit/react": "^0.4.10", + "@ariakit/react": "^0.4.15", "@babel/runtime": "7.25.7", - "@wordpress/components": "*", - "@wordpress/compose": "*", - "@wordpress/data": "*", - "@wordpress/element": "*", - "@wordpress/i18n": "*", - "@wordpress/icons": "*", - "@wordpress/primitives": "*", - "@wordpress/private-apis": "*", - "@wordpress/warning": "*", + "@wordpress/components": "file:../components", + "@wordpress/compose": "file:../compose", + "@wordpress/data": "file:../data", + "@wordpress/element": "file:../element", + "@wordpress/i18n": "file:../i18n", + "@wordpress/icons": "file:../icons", + "@wordpress/primitives": "file:../primitives", + "@wordpress/private-apis": "file:../private-apis", + "@wordpress/warning": "file:../warning", "clsx": "^2.1.1", "remove-accents": "^0.5.0" }, @@ -48,5 +63,8 @@ }, "publishConfig": { "access": "public" + }, + "scripts": { + "build:wp": "node build" } } diff --git a/packages/dataviews/src/components/dataform-combined-edit/index.tsx b/packages/dataviews/src/components/dataform-combined-edit/index.tsx deleted file mode 100644 index 90a92ac861bdd1..00000000000000 --- a/packages/dataviews/src/components/dataform-combined-edit/index.tsx +++ /dev/null @@ -1,69 +0,0 @@ -/** - * WordPress dependencies - */ -import { - __experimentalHStack as HStack, - __experimentalVStack as VStack, - __experimentalHeading as Heading, - __experimentalSpacer as Spacer, -} from '@wordpress/components'; - -/** - * Internal dependencies - */ -import type { DataFormCombinedEditProps, NormalizedField } from '../../types'; -import FormFieldVisibility from '../form-field-visibility'; - -function Header( { title }: { title: string } ) { - return ( - <VStack className="dataforms-layouts__dropdown-header" spacing={ 4 }> - <HStack alignment="center"> - <Heading level={ 2 } size={ 13 }> - { title } - </Heading> - <Spacer /> - </HStack> - </VStack> - ); -} - -function DataFormCombinedEdit< Item >( { - field, - data, - onChange, - hideLabelFromVision, -}: DataFormCombinedEditProps< Item > ) { - const className = 'dataforms-combined-edit'; - const visibleChildren = ( field.children ?? [] ) - .map( ( fieldId ) => field.fields.find( ( { id } ) => id === fieldId ) ) - .filter( - ( childField ): childField is NormalizedField< Item > => - !! childField - ); - const children = visibleChildren.map( ( child ) => { - return ( - <FormFieldVisibility key={ child.id } data={ data } field={ child }> - <div className="dataforms-combined-edit__field"> - <child.Edit - data={ data } - field={ child } - onChange={ onChange } - /> - </div> - </FormFieldVisibility> - ); - } ); - - const Stack = field.direction === 'horizontal' ? HStack : VStack; - - return ( - <> - { ! hideLabelFromVision && <Header title={ field.label } /> } - <Stack spacing={ 4 } className={ className } as="fieldset"> - { children } - </Stack> - </> - ); -} - -export default DataFormCombinedEdit; diff --git a/packages/dataviews/src/components/dataform-combined-edit/style.scss b/packages/dataviews/src/components/dataform-combined-edit/style.scss deleted file mode 100644 index 97e052ed897989..00000000000000 --- a/packages/dataviews/src/components/dataform-combined-edit/style.scss +++ /dev/null @@ -1,16 +0,0 @@ -.dataforms-layouts-panel__field-dropdown { - .dataforms-combined-edit { - border: none; - padding: 0; - } -} - -.dataforms-combined-edit { - &__field { - flex: 1 1 auto; - } - - p.components-base-control__help:has(.components-checkbox-control__help) { - margin-top: $grid-unit-05; - } -} diff --git a/packages/dataviews/src/components/dataform-context/index.tsx b/packages/dataviews/src/components/dataform-context/index.tsx new file mode 100644 index 00000000000000..72fbf7e0f42ab6 --- /dev/null +++ b/packages/dataviews/src/components/dataform-context/index.tsx @@ -0,0 +1,30 @@ +/** + * WordPress dependencies + */ +import { createContext } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import type { NormalizedField } from '../../types'; + +type DataFormContextType< Item > = { + fields: NormalizedField< Item >[]; +}; + +const DataFormContext = createContext< DataFormContextType< any > >( { + fields: [], +} ); + +export function DataFormProvider< Item >( { + fields, + children, +}: React.PropsWithChildren< { fields: NormalizedField< Item >[] } > ) { + return ( + <DataFormContext.Provider value={ { fields } }> + { children } + </DataFormContext.Provider> + ); +} + +export default DataFormContext; diff --git a/packages/dataviews/src/components/dataform/index.tsx b/packages/dataviews/src/components/dataform/index.tsx index 58f0bf06afb414..b359ddba74381e 100644 --- a/packages/dataviews/src/components/dataform/index.tsx +++ b/packages/dataviews/src/components/dataform/index.tsx @@ -1,17 +1,34 @@ +/** + * WordPress dependencies + */ +import { useMemo } from '@wordpress/element'; + /** * Internal dependencies */ import type { DataFormProps } from '../../types'; -import { getFormLayout } from '../../dataforms-layouts'; +import { DataFormProvider } from '../dataform-context'; +import { normalizeFields } from '../../normalize-fields'; +import { DataFormLayout } from '../../dataforms-layouts/data-form-layout'; export default function DataForm< Item >( { + data, form, - ...props + fields, + onChange, }: DataFormProps< Item > ) { - const layout = getFormLayout( form.type ?? 'regular' ); - if ( ! layout ) { + const normalizedFields = useMemo( + () => normalizeFields( fields ), + [ fields ] + ); + + if ( ! form.fields ) { return null; } - return <layout.component form={ form } { ...props } />; + return ( + <DataFormProvider fields={ normalizedFields }> + <DataFormLayout data={ data } form={ form } onChange={ onChange } /> + </DataFormProvider> + ); } diff --git a/packages/dataviews/src/components/dataform/stories/index.story.tsx b/packages/dataviews/src/components/dataform/stories/index.story.tsx index b59d79063200bf..ecad2af43fb84b 100644 --- a/packages/dataviews/src/components/dataform/stories/index.story.tsx +++ b/packages/dataviews/src/components/dataform/stories/index.story.tsx @@ -1,13 +1,14 @@ /** * WordPress dependencies */ -import { useState } from '@wordpress/element'; +import { useMemo, useState } from '@wordpress/element'; +import { ToggleControl } from '@wordpress/components'; /** * Internal dependencies */ import DataForm from '../index'; -import type { CombinedFormField, Field } from '../../../types'; +import type { Field, Form } from '../../../types'; type SamplePost = { title: string; @@ -27,8 +28,13 @@ const meta = { type: { control: { type: 'select' }, description: - 'Chooses the layout of the form. "regular" is the default layout.', - options: [ 'regular', 'panel' ], + 'Chooses the default layout of each field. "regular" is the default layout.', + options: [ 'default', 'regular', 'panel' ], + }, + labelPosition: { + control: { type: 'select' }, + description: 'Chooses the label position of the layout.', + options: [ 'default', 'top', 'side', 'none' ], }, }, }; @@ -97,9 +103,33 @@ const fields = [ return item.status !== 'private'; }, }, + { + id: 'sticky', + label: 'Sticky', + type: 'integer', + Edit: ( { field, onChange, data, hideLabelFromVision } ) => { + const { id, getValue } = field; + return ( + <ToggleControl + __nextHasNoMarginBottom + label={ hideLabelFromVision ? '' : field.label } + checked={ getValue( { item: data } ) } + onChange={ () => + onChange( { [ id ]: ! getValue( { item: data } ) } ) + } + /> + ); + }, + }, ] as Field< SamplePost >[]; -export const Default = ( { type }: { type: 'panel' | 'regular' } ) => { +export const Default = ( { + type, + labelPosition, +}: { + type: 'default' | 'regular' | 'panel'; + labelPosition: 'default' | 'top' | 'side' | 'none'; +} ) => { const [ post, setPost ] = useState( { title: 'Hello, World!', order: 2, @@ -108,29 +138,36 @@ export const Default = ( { type }: { type: 'panel' | 'regular' } ) => { reviewer: 'fulano', date: '2021-01-01T12:00:00', birthdate: '1950-02-23T12:00:00', + sticky: false, } ); - const form = { - fields: [ - 'title', - 'order', - 'author', - 'reviewer', - 'status', - 'password', - 'date', - 'birthdate', - ], - }; + const form = useMemo( + () => ( { + type, + labelPosition, + fields: [ + 'title', + 'order', + { + id: 'sticky', + layout: 'regular', + labelPosition: 'side', + }, + 'author', + 'reviewer', + 'password', + 'date', + 'birthdate', + ], + } ), + [ type, labelPosition ] + ) as Form; return ( <DataForm< SamplePost > data={ post } fields={ fields } - form={ { - ...form, - type, - } } + form={ form } onChange={ ( edits ) => setPost( ( prev ) => ( { ...prev, @@ -142,40 +179,45 @@ export const Default = ( { type }: { type: 'panel' | 'regular' } ) => { }; const CombinedFieldsComponent = ( { - type = 'regular', - combinedFieldDirection = 'vertical', + type, + labelPosition, }: { - type: 'panel' | 'regular'; - combinedFieldDirection: 'vertical' | 'horizontal'; + type: 'default' | 'regular' | 'panel'; + labelPosition: 'default' | 'top' | 'side' | 'none'; } ) => { - const [ post, setPost ] = useState( { + const [ post, setPost ] = useState< SamplePost >( { title: 'Hello, World!', order: 2, author: 1, status: 'draft', + reviewer: 'fulano', + date: '2021-01-01T12:00:00', + birthdate: '1950-02-23T12:00:00', } ); - const form = { - fields: [ 'title', 'status_and_visibility', 'order', 'author' ], - combinedFields: [ - { - id: 'status_and_visibility', - label: 'Status & Visibility', - children: [ 'status', 'password' ], - direction: combinedFieldDirection, - render: ( { item } ) => item.status, - }, - ] as CombinedFormField< any >[], - }; + const form = useMemo( + () => ( { + type, + labelPosition, + fields: [ + 'title', + { + id: 'status', + label: 'Status & Visibility', + children: [ 'status', 'password' ], + }, + 'order', + 'author', + ], + } ), + [ type, labelPosition ] + ) as Form; return ( - <DataForm + <DataForm< SamplePost > data={ post } fields={ fields } - form={ { - ...form, - type, - } } + form={ form } onChange={ ( edits ) => setPost( ( prev ) => ( { ...prev, @@ -191,11 +233,8 @@ export const CombinedFields = { render: CombinedFieldsComponent, argTypes: { ...meta.argTypes, - combinedFieldDirection: { - control: { type: 'select' }, - description: - 'Chooses the direction of the combined field. "vertical" is the default layout.', - options: [ 'vertical', 'horizontal' ], - }, + }, + args: { + type: 'panel', }, }; diff --git a/packages/dataviews/src/components/dataviews-bulk-actions/index.tsx b/packages/dataviews/src/components/dataviews-bulk-actions/index.tsx index 92a3fe85f67e74..86f0bb6db0ba84 100644 --- a/packages/dataviews/src/components/dataviews-bulk-actions/index.tsx +++ b/packages/dataviews/src/components/dataviews-bulk-actions/index.tsx @@ -1,3 +1,8 @@ +/** + * External dependencies + */ +import type { ReactElement } from 'react'; + /** * WordPress dependencies */ @@ -15,11 +20,46 @@ import { closeSmall } from '@wordpress/icons'; * Internal dependencies */ import DataViewsContext from '../dataviews-context'; -import { ActionWithModal } from '../dataviews-item-actions'; -import type { Action } from '../../types'; +import { ActionModal } from '../dataviews-item-actions'; +import type { Action, ActionModal as ActionModalType } from '../../types'; import type { SetSelection } from '../../private-types'; import type { ActionTriggerProps } from '../dataviews-item-actions'; +interface ActionWithModalProps< Item > { + action: ActionModalType< Item >; + items: Item[]; + ActionTriggerComponent: ( + props: ActionTriggerProps< Item > + ) => ReactElement; +} + +function ActionWithModal< Item >( { + action, + items, + ActionTriggerComponent, +}: ActionWithModalProps< Item > ) { + const [ isModalOpen, setIsModalOpen ] = useState( false ); + const actionTriggerProps = { + action, + onClick: () => { + setIsModalOpen( true ); + }, + items, + }; + return ( + <> + <ActionTriggerComponent { ...actionTriggerProps } /> + { isModalOpen && ( + <ActionModal + action={ action } + items={ items } + closeModal={ () => setIsModalOpen( false ) } + /> + ) } + </> + ); +} + export function useHasAPossibleBulkAction< Item >( actions: Action< Item >[], item: Item @@ -160,7 +200,7 @@ function ActionButton< Item >( { key={ action.id } action={ action } items={ selectedEligibleItems } - ActionTrigger={ ActionTrigger } + ActionTriggerComponent={ ActionTrigger } /> ); } diff --git a/packages/dataviews/src/components/dataviews-context/index.ts b/packages/dataviews/src/components/dataviews-context/index.ts index 87acade73bc819..992048f9097064 100644 --- a/packages/dataviews/src/components/dataviews-context/index.ts +++ b/packages/dataviews/src/components/dataviews-context/index.ts @@ -26,9 +26,10 @@ type DataViewsContextType< Item > = { openedFilter: string | null; setOpenedFilter: ( openedFilter: string | null ) => void; getItemId: ( item: Item ) => string; - onClickItem: ( item: Item ) => void; + getItemLevel?: ( item: Item ) => number; + onClickItem?: ( item: Item ) => void; isItemClickable: ( item: Item ) => boolean; - density: number; + containerWidth: number; }; const DataViewsContext = createContext< DataViewsContextType< any > >( { @@ -45,9 +46,8 @@ const DataViewsContext = createContext< DataViewsContextType< any > >( { setOpenedFilter: () => {}, openedFilter: null, getItemId: ( item ) => item.id, - onClickItem: () => {}, - isItemClickable: () => false, - density: 0, + isItemClickable: () => true, + containerWidth: 0, } ); export default DataViewsContext; diff --git a/packages/dataviews/src/components/dataviews-filters/add-filter.tsx b/packages/dataviews/src/components/dataviews-filters/add-filter.tsx index 94aebb71ea5874..3921fd88eaaa29 100644 --- a/packages/dataviews/src/components/dataviews-filters/add-filter.tsx +++ b/packages/dataviews/src/components/dataviews-filters/add-filter.tsx @@ -33,37 +33,40 @@ export function AddFilterMenu( { view, onChangeView, setOpenedFilter, - trigger, + triggerProps, }: AddFilterProps & { - trigger: React.ReactNode; + triggerProps: React.ComponentProps< typeof Menu.TriggerButton >; } ) { const inactiveFilters = filters.filter( ( filter ) => ! filter.isVisible ); return ( - <Menu trigger={ trigger }> - { inactiveFilters.map( ( filter ) => { - return ( - <Menu.Item - key={ filter.field } - onClick={ () => { - setOpenedFilter( filter.field ); - onChangeView( { - ...view, - page: 1, - filters: [ - ...( view.filters || [] ), - { - field: filter.field, - value: undefined, - operator: filter.operators[ 0 ], - }, - ], - } ); - } } - > - <Menu.ItemLabel>{ filter.name }</Menu.ItemLabel> - </Menu.Item> - ); - } ) } + <Menu> + <Menu.TriggerButton { ...triggerProps } /> + <Menu.Popover> + { inactiveFilters.map( ( filter ) => { + return ( + <Menu.Item + key={ filter.field } + onClick={ () => { + setOpenedFilter( filter.field ); + onChangeView( { + ...view, + page: 1, + filters: [ + ...( view.filters || [] ), + { + field: filter.field, + value: undefined, + operator: filter.operators[ 0 ], + }, + ], + } ); + } } + > + <Menu.ItemLabel>{ filter.name }</Menu.ItemLabel> + </Menu.Item> + ); + } ) } + </Menu.Popover> </Menu> ); } @@ -78,18 +81,19 @@ function AddFilter( const inactiveFilters = filters.filter( ( filter ) => ! filter.isVisible ); return ( <AddFilterMenu - trigger={ - <Button - accessibleWhenDisabled - size="compact" - className="dataviews-filters-button" - variant="tertiary" - disabled={ ! inactiveFilters.length } - ref={ ref } - > - { __( 'Add filter' ) } - </Button> - } + triggerProps={ { + render: ( + <Button + accessibleWhenDisabled + size="compact" + className="dataviews-filters-button" + variant="tertiary" + disabled={ ! inactiveFilters.length } + ref={ ref } + /> + ), + children: __( 'Add filter' ), + } } { ...{ filters, view, onChangeView, setOpenedFilter } } /> ); diff --git a/packages/dataviews/src/components/dataviews-filters/index.tsx b/packages/dataviews/src/components/dataviews-filters/index.tsx index 9722844cf4a141..180e17d4b7f0cc 100644 --- a/packages/dataviews/src/components/dataviews-filters/index.tsx +++ b/packages/dataviews/src/components/dataviews-filters/index.tsx @@ -7,6 +7,7 @@ import { useRef, useMemo, useCallback, + useEffect, } from '@wordpress/element'; import { __experimentalHStack as HStack, Button } from '@wordpress/components'; import { funnel } from '@wordpress/icons'; @@ -70,7 +71,7 @@ export function useFilters( fields: NormalizedField< any >[], view: View ) { }, [ fields, view ] ); } -export function FilterVisibilityToggle( { +export function FiltersToggle( { filters, view, onChangeView, @@ -85,6 +86,7 @@ export function FilterVisibilityToggle( { isShowingFilter: boolean; setIsShowingFilter: React.Dispatch< React.SetStateAction< boolean > >; } ) { + const buttonRef = useRef< HTMLButtonElement >( null ); const onChangeViewWithFilterVisibility = useCallback( ( _view: View ) => { onChangeView( _view ); @@ -98,48 +100,81 @@ export function FilterVisibilityToggle( { if ( filters.length === 0 ) { return null; } - if ( ! hasVisibleFilters ) { - return ( - <AddFilterMenu - filters={ filters } - view={ view } - onChangeView={ onChangeViewWithFilterVisibility } - setOpenedFilter={ setOpenedFilter } - trigger={ - <Button - className="dataviews-filters__visibility-toggle" - size="compact" - icon={ funnel } - label={ __( 'Add filter' ) } - isPressed={ false } - aria-expanded={ false } - /> - } - /> - ); - } + + const addFilterButtonProps = { + label: __( 'Add filter' ), + 'aria-expanded': false, + isPressed: false, + }; + const toggleFiltersButtonProps = { + label: _x( 'Filter', 'verb' ), + 'aria-expanded': isShowingFilter, + isPressed: isShowingFilter, + onClick: () => { + if ( ! isShowingFilter ) { + setOpenedFilter( null ); + } + setIsShowingFilter( ! isShowingFilter ); + }, + }; + const buttonComponent = ( + <Button + ref={ buttonRef } + className="dataviews-filters__visibility-toggle" + size="compact" + icon={ funnel } + { ...( hasVisibleFilters + ? toggleFiltersButtonProps + : addFilterButtonProps ) } + /> + ); return ( <div className="dataviews-filters__container-visibility-toggle"> - <Button - className="dataviews-filters__visibility-toggle" - size="compact" - icon={ funnel } - label={ _x( 'Filter', 'verb' ) } - onClick={ () => { - if ( ! isShowingFilter ) { - setOpenedFilter( null ); - } - setIsShowingFilter( ! isShowingFilter ); - } } - isPressed={ isShowingFilter } - aria-expanded={ isShowingFilter } - /> - { hasVisibleFilters && !! view.filters?.length && ( + { ! hasVisibleFilters ? ( + <AddFilterMenu + filters={ filters } + view={ view } + onChangeView={ onChangeViewWithFilterVisibility } + setOpenedFilter={ setOpenedFilter } + triggerProps={ { render: buttonComponent } } + /> + ) : ( + <FilterVisibilityToggle + buttonRef={ buttonRef } + filtersCount={ view.filters?.length } + > + { buttonComponent } + </FilterVisibilityToggle> + ) } + </div> + ); +} + +function FilterVisibilityToggle( { + buttonRef, + filtersCount, + children, +}: { + buttonRef: React.RefObject< HTMLButtonElement >; + filtersCount?: number; + children: React.ReactNode; +} ) { + // Focus the `add filter` button when unmounts. + useEffect( + () => () => { + buttonRef.current?.focus(); + }, + [ buttonRef ] + ); + return ( + <> + { children } + { !! filtersCount && ( <span className="dataviews-filters-toggle__count"> - { view.filters?.length } + { filtersCount } </span> ) } - </div> + </> ); } diff --git a/packages/dataviews/src/components/dataviews-footer/style.scss b/packages/dataviews/src/components/dataviews-footer/style.scss index cdb1359ccee393..d8f205f6c8f75c 100644 --- a/packages/dataviews/src/components/dataviews-footer/style.scss +++ b/packages/dataviews/src/components/dataviews-footer/style.scss @@ -6,20 +6,20 @@ padding: $grid-unit-15 $grid-unit-60; border-top: $border-width solid $gray-100; flex-shrink: 0; - transition: padding ease-out 0.1s; - @include reduce-motion("transition"); + + @media not (prefers-reduced-motion) { + transition: padding ease-out 0.1s; + } + z-index: z-index(".dataviews-footer"); } - -/* stylelint-disable-next-line scss/at-rule-no-unknown -- '@container' not globally permitted */ @container (max-width: 430px) { .dataviews-footer { padding: $grid-unit-15 $grid-unit-30; } } -/* stylelint-disable-next-line scss/at-rule-no-unknown -- '@container' not globally permitted */ @container (max-width: 560px) { .dataviews-footer { flex-direction: column !important; diff --git a/packages/dataviews/src/components/dataviews-item-actions/index.tsx b/packages/dataviews/src/components/dataviews-item-actions/index.tsx index 20e58a2c6bb1fb..70df04e4333e6f 100644 --- a/packages/dataviews/src/components/dataviews-item-actions/index.tsx +++ b/packages/dataviews/src/components/dataviews-item-actions/index.tsx @@ -1,7 +1,7 @@ /** * External dependencies */ -import type { MouseEventHandler, ReactElement } from 'react'; +import type { MouseEventHandler } from 'react'; /** * WordPress dependencies @@ -32,20 +32,17 @@ export interface ActionTriggerProps< Item > { items: Item[]; } -interface ActionModalProps< Item > { +export interface ActionModalProps< Item > { action: ActionModalType< Item >; items: Item[]; - closeModal?: () => void; -} - -interface ActionWithModalProps< Item > extends ActionModalProps< Item > { - ActionTrigger: ( props: ActionTriggerProps< Item > ) => ReactElement; - isBusy?: boolean; + closeModal: () => void; } interface ActionsMenuGroupProps< Item > { actions: Action< Item >[]; item: Item; + registry: ReturnType< typeof useRegistry >; + setActiveModalAction: ( action: ActionModalType< Item > | null ) => void; } interface ItemActionsProps< Item > { @@ -57,6 +54,14 @@ interface ItemActionsProps< Item > { interface CompactItemActionsProps< Item > { item: Item; actions: Action< Item >[]; + isSmall?: boolean; + registry: ReturnType< typeof useRegistry >; +} + +interface PrimaryActionsProps< Item > { + item: Item; + actions: Action< Item >[]; + registry: ReturnType< typeof useRegistry >; } function ButtonTrigger< Item >( { @@ -70,6 +75,8 @@ function ButtonTrigger< Item >( { <Button label={ label } icon={ action.icon } + disabled={ !! action.disabled } + accessibleWhenDisabled isDestructive={ action.isDestructive } size="compact" onClick={ onClick } @@ -85,10 +92,7 @@ function MenuItemTrigger< Item >( { const label = typeof action.label === 'string' ? action.label : action.label( items ); return ( - <Menu.Item - onClick={ onClick } - hideOnClick={ ! ( 'RenderModal' in action ) } - > + <Menu.Item disabled={ action.disabled } onClick={ onClick }> <Menu.ItemLabel>{ label }</Menu.ItemLabel> </Menu.Item> ); @@ -105,9 +109,9 @@ export function ActionModal< Item >( { <Modal title={ action.modalHeader || label } __experimentalHideHeader={ !! action.hideModalHeader } - onRequestClose={ closeModal ?? ( () => {} ) } + onRequestClose={ closeModal } focusOnMount="firstContentElement" - size="small" + size="medium" overlayClassName={ `dataviews-action-modal dataviews-action-modal__${ kebabCase( action.id ) }` } @@ -117,64 +121,28 @@ export function ActionModal< Item >( { ); } -export function ActionWithModal< Item >( { - action, - items, - ActionTrigger, - isBusy, -}: ActionWithModalProps< Item > ) { - const [ isModalOpen, setIsModalOpen ] = useState( false ); - const actionTriggerProps = { - action, - onClick: () => { - setIsModalOpen( true ); - }, - items, - isBusy, - }; - return ( - <> - <ActionTrigger { ...actionTriggerProps } /> - { isModalOpen && ( - <ActionModal - action={ action } - items={ items } - closeModal={ () => setIsModalOpen( false ) } - /> - ) } - </> - ); -} - export function ActionsMenuGroup< Item >( { actions, item, + registry, + setActiveModalAction, }: ActionsMenuGroupProps< Item > ) { - const registry = useRegistry(); return ( <Menu.Group> - { actions.map( ( action ) => { - if ( 'RenderModal' in action ) { - return ( - <ActionWithModal - key={ action.id } - action={ action } - items={ [ item ] } - ActionTrigger={ MenuItemTrigger } - /> - ); - } - return ( - <MenuItemTrigger - key={ action.id } - action={ action } - onClick={ () => { - action.callback( [ item ], { registry } ); - } } - items={ [ item ] } - /> - ); - } ) } + { actions.map( ( action ) => ( + <MenuItemTrigger + key={ action.id } + action={ action } + onClick={ () => { + if ( 'RenderModal' in action ) { + setActiveModalAction( action ); + return; + } + action.callback( [ item ], { registry } ); + } } + items={ [ item ] } + /> + ) ) } </Menu.Group> ); } @@ -199,9 +167,29 @@ export default function ItemActions< Item >( { eligibleActions: _eligibleActions, }; }, [ actions, item ] ); + if ( isCompact ) { - return <CompactItemActions item={ item } actions={ eligibleActions } />; + return ( + <CompactItemActions + item={ item } + actions={ eligibleActions } + isSmall + registry={ registry } + /> + ); + } + + // If all actions are primary, there is no need to render the dropdown. + if ( primaryActions.length === eligibleActions.length ) { + return ( + <PrimaryActions + item={ item } + actions={ primaryActions } + registry={ registry } + /> + ); } + return ( <HStack spacing={ 1 } @@ -212,30 +200,16 @@ export default function ItemActions< Item >( { width: 'auto', } } > - { !! primaryActions.length && - primaryActions.map( ( action ) => { - if ( 'RenderModal' in action ) { - return ( - <ActionWithModal - key={ action.id } - action={ action } - items={ [ item ] } - ActionTrigger={ ButtonTrigger } - /> - ); - } - return ( - <ButtonTrigger - key={ action.id } - action={ action } - onClick={ () => { - action.callback( [ item ], { registry } ); - } } - items={ [ item ] } - /> - ); - } ) } - <CompactItemActions item={ item } actions={ eligibleActions } /> + <PrimaryActions + item={ item } + actions={ primaryActions } + registry={ registry } + /> + <CompactItemActions + item={ item } + actions={ eligibleActions } + registry={ registry } + /> </HStack> ); } @@ -243,22 +217,79 @@ export default function ItemActions< Item >( { function CompactItemActions< Item >( { item, actions, + isSmall, + registry, }: CompactItemActionsProps< Item > ) { + const [ activeModalAction, setActiveModalAction ] = useState( + null as ActionModalType< Item > | null + ); return ( - <Menu - trigger={ - <Button - size="compact" - icon={ moreVertical } - label={ __( 'Actions' ) } - accessibleWhenDisabled - disabled={ ! actions.length } - className="dataviews-all-actions-button" + <> + <Menu placement="bottom-end"> + <Menu.TriggerButton + render={ + <Button + size={ isSmall ? 'small' : 'compact' } + icon={ moreVertical } + label={ __( 'Actions' ) } + accessibleWhenDisabled + disabled={ ! actions.length } + className="dataviews-all-actions-button" + /> + } /> - } - placement="bottom-end" - > - <ActionsMenuGroup actions={ actions } item={ item } /> - </Menu> + <Menu.Popover> + <ActionsMenuGroup + actions={ actions } + item={ item } + registry={ registry } + setActiveModalAction={ setActiveModalAction } + /> + </Menu.Popover> + </Menu> + { !! activeModalAction && ( + <ActionModal + action={ activeModalAction } + items={ [ item ] } + closeModal={ () => setActiveModalAction( null ) } + /> + ) } + </> + ); +} + +function PrimaryActions< Item >( { + item, + actions, + registry, +}: PrimaryActionsProps< Item > ) { + const [ activeModalAction, setActiveModalAction ] = useState( null as any ); + if ( ! Array.isArray( actions ) || actions.length === 0 ) { + return null; + } + return ( + <> + { actions.map( ( action ) => ( + <ButtonTrigger + key={ action.id } + action={ action } + onClick={ () => { + if ( 'RenderModal' in action ) { + setActiveModalAction( action ); + return; + } + action.callback( [ item ], { registry } ); + } } + items={ [ item ] } + /> + ) ) } + { !! activeModalAction && ( + <ActionModal + action={ activeModalAction } + items={ [ item ] } + closeModal={ () => setActiveModalAction( null ) } + /> + ) } + </> ); } diff --git a/packages/dataviews/src/components/dataviews-layout/index.tsx b/packages/dataviews/src/components/dataviews-layout/index.tsx index 4ef0125b1f64b5..d30b1d39c6524d 100644 --- a/packages/dataviews/src/components/dataviews-layout/index.tsx +++ b/packages/dataviews/src/components/dataviews-layout/index.tsx @@ -21,13 +21,13 @@ export default function DataViewsLayout() { data, fields, getItemId, + getItemLevel, isLoading, view, onChangeView, selection, onChangeSelection, setOpenedFilter, - density, onClickItem, isItemClickable, } = useContext( DataViewsContext ); @@ -41,6 +41,7 @@ export default function DataViewsLayout() { data={ data } fields={ fields } getItemId={ getItemId } + getItemLevel={ getItemLevel } isLoading={ isLoading } onChangeView={ onChangeView } onChangeSelection={ onChangeSelection } @@ -49,7 +50,6 @@ export default function DataViewsLayout() { onClickItem={ onClickItem } isItemClickable={ isItemClickable } view={ view } - density={ density } /> ); } diff --git a/packages/dataviews/src/components/dataviews-selection-checkbox/index.tsx b/packages/dataviews/src/components/dataviews-selection-checkbox/index.tsx index c71636618716ba..e069e7d74b0ef1 100644 --- a/packages/dataviews/src/components/dataviews-selection-checkbox/index.tsx +++ b/packages/dataviews/src/components/dataviews-selection-checkbox/index.tsx @@ -1,8 +1,8 @@ /** * WordPress dependencies */ -import { __, sprintf } from '@wordpress/i18n'; import { CheckboxControl } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; /** * Internal dependencies @@ -15,7 +15,7 @@ interface DataViewsSelectionCheckboxProps< Item > { onChangeSelection: SetSelection; item: Item; getItemId: ( item: Item ) => string; - primaryField?: Field< Item >; + titleField?: Field< Item >; disabled: boolean; } @@ -24,25 +24,16 @@ export default function DataViewsSelectionCheckbox< Item >( { onChangeSelection, item, getItemId, - primaryField, + titleField, disabled, }: DataViewsSelectionCheckboxProps< Item > ) { const id = getItemId( item ); const checked = ! disabled && selection.includes( id ); - let selectionLabel; - if ( primaryField?.getValue && item ) { - // eslint-disable-next-line @wordpress/valid-sprintf - selectionLabel = sprintf( - checked - ? /* translators: %s: item title. */ __( 'Deselect item: %s' ) - : /* translators: %s: item title. */ __( 'Select item: %s' ), - primaryField.getValue( { item } ) - ); - } else { - selectionLabel = checked - ? __( 'Select a new item' ) - : __( 'Deselect item' ); - } + + // Fallback label to ensure accessibility + const selectionLabel = + titleField?.getValue?.( { item } ) || __( '(no title)' ); + return ( <CheckboxControl className="dataviews-selection-checkbox" diff --git a/packages/dataviews/src/components/dataviews-view-config/index.tsx b/packages/dataviews/src/components/dataviews-view-config/index.tsx index c8b26c51275891..c80591caee255e 100644 --- a/packages/dataviews/src/components/dataviews-view-config/index.tsx +++ b/packages/dataviews/src/components/dataviews-view-config/index.tsx @@ -1,7 +1,8 @@ /** * External dependencies */ -import type { ChangeEvent } from 'react'; +import type { ChangeEvent, ReactNode } from 'react'; +import clsx from 'clsx'; /** * WordPress dependencies @@ -23,33 +24,30 @@ import { __experimentalText as Text, privateApis as componentsPrivateApis, BaseControl, + Icon, } from '@wordpress/components'; import { __, _x, sprintf } from '@wordpress/i18n'; -import { memo, useContext, useMemo } from '@wordpress/element'; -import { chevronDown, chevronUp, cog, seen, unseen } from '@wordpress/icons'; +import { memo, useContext, useMemo, useState } from '@wordpress/element'; +import { + chevronDown, + chevronUp, + cog, + seen, + unseen, + lock, + moreVertical, +} from '@wordpress/icons'; import warning from '@wordpress/warning'; import { useInstanceId } from '@wordpress/compose'; /** * Internal dependencies */ -import { - SORTING_DIRECTIONS, - LAYOUT_GRID, - LAYOUT_TABLE, - sortIcons, - sortLabels, -} from '../../constants'; -import { - VIEW_LAYOUTS, - getNotHidableFieldIds, - getVisibleFieldIds, - getHiddenFieldIds, -} from '../../dataviews-layouts'; -import type { SupportedLayouts, View, Field } from '../../types'; +import { SORTING_DIRECTIONS, sortIcons, sortLabels } from '../../constants'; +import { VIEW_LAYOUTS } from '../../dataviews-layouts'; +import type { NormalizedField, SupportedLayouts, View } from '../../types'; import DataViewsContext from '../dataviews-context'; import { unlock } from '../../lock-unlock'; -import DensityPicker from '../../dataviews-layouts/grid/density-picker'; const { Menu } = unlock( componentsPrivateApis ); @@ -57,7 +55,11 @@ interface ViewTypeMenuProps { defaultLayouts?: SupportedLayouts; } -const DATAVIEWS_CONFIG_POPOVER_PROPS = { placement: 'bottom-end', offset: 9 }; +const DATAVIEWS_CONFIG_POPOVER_PROPS = { + className: 'dataviews-config__popover', + placement: 'bottom-end', + offset: 9, +}; function ViewTypeMenu( { defaultLayouts = { list: {}, grid: {}, table: {} }, @@ -69,45 +71,57 @@ function ViewTypeMenu( { } const activeView = VIEW_LAYOUTS.find( ( v ) => view.type === v.type ); return ( - <Menu - trigger={ - <Button - size="compact" - icon={ activeView?.icon } - label={ __( 'Layout' ) } - /> - } - > - { availableLayouts.map( ( layout ) => { - const config = VIEW_LAYOUTS.find( ( v ) => v.type === layout ); - if ( ! config ) { - return null; + <Menu> + <Menu.TriggerButton + render={ + <Button + size="compact" + icon={ activeView?.icon } + label={ __( 'Layout' ) } + /> } - return ( - <Menu.RadioItem - key={ layout } - value={ layout } - name="view-actions-available-view" - checked={ layout === view.type } - hideOnClick - onChange={ ( e: ChangeEvent< HTMLInputElement > ) => { - switch ( e.target.value ) { - case 'list': - case 'grid': - case 'table': - return onChangeView( { - ...view, - type: e.target.value, - ...defaultLayouts[ e.target.value ], - } ); - } - warning( 'Invalid dataview' ); - } } - > - <Menu.ItemLabel>{ config.label }</Menu.ItemLabel> - </Menu.RadioItem> - ); - } ) } + /> + <Menu.Popover> + { availableLayouts.map( ( layout ) => { + const config = VIEW_LAYOUTS.find( + ( v ) => v.type === layout + ); + if ( ! config ) { + return null; + } + return ( + <Menu.RadioItem + key={ layout } + value={ layout } + name="view-actions-available-view" + checked={ layout === view.type } + hideOnClick + onChange={ ( + e: ChangeEvent< HTMLInputElement > + ) => { + switch ( e.target.value ) { + case 'list': + case 'grid': + case 'table': + const viewWithoutLayout = { ...view }; + if ( 'layout' in viewWithoutLayout ) { + delete viewWithoutLayout.layout; + } + // @ts-expect-error + return onChangeView( { + ...viewWithoutLayout, + type: e.target.value, + ...defaultLayouts[ e.target.value ], + } ); + } + warning( 'Invalid dataview' ); + } } + > + <Menu.ItemLabel>{ config.label }</Menu.ItemLabel> + </Menu.RadioItem> + ); + } ) } + </Menu.Popover> </Menu> ); } @@ -140,6 +154,7 @@ function SortFieldControl() { direction: view?.sort?.direction || 'desc', field: value, }, + showLevels: false, } ); } } /> @@ -182,6 +197,7 @@ function SortDirectionControl() { )?.id || '', }, + showLevels: false, } ); return; } @@ -239,236 +255,469 @@ function ItemsPerPageControl() { ); } -interface FieldItemProps { - id: any; - label: string; - index: number; - isVisible: boolean; - isHidable: boolean; +function PreviewOptions( { + previewOptions, + onChangePreviewOption, + onMenuOpenChange, + activeOption, +}: { + previewOptions?: Array< { label: string; id: string } >; + onChangePreviewOption?: ( newPreviewOption: string ) => void; + onMenuOpenChange: ( isOpen: boolean ) => void; + activeOption?: string; +} ) { + const focusPreviewOptionsField = ( id: string ) => { + // Focus the visibility button to avoid focus loss. + // Our code is safe against the component being unmounted, so we don't need to worry about cleaning the timeout. + // eslint-disable-next-line @wordpress/react-no-unsafe-timeout + setTimeout( () => { + const element = document.querySelector( + `.dataviews-field-control__field-${ id } .dataviews-field-control__field-preview-options-button` + ); + if ( element instanceof HTMLElement ) { + element.focus(); + } + }, 50 ); + }; + return ( + <Menu onOpenChange={ onMenuOpenChange }> + <Menu.TriggerButton + render={ + <Button + className="dataviews-field-control__field-preview-options-button" + size="compact" + icon={ moreVertical } + label={ __( 'Preview' ) } + /> + } + /> + <Menu.Popover> + { previewOptions?.map( ( { id, label } ) => { + return ( + <Menu.RadioItem + key={ id } + value={ id } + checked={ id === activeOption } + onChange={ () => { + onChangePreviewOption?.( id ); + focusPreviewOptionsField( id ); + } } + > + <Menu.ItemLabel>{ label }</Menu.ItemLabel> + </Menu.RadioItem> + ); + } ) } + </Menu.Popover> + </Menu> + ); } - function FieldItem( { - field: { id, label, index, isVisible, isHidable }, - fields, - view, - onChangeView, + field, + label, + description, + isVisible, + isFirst, + isLast, + canMove = true, + onToggleVisibility, + onMoveUp, + onMoveDown, + previewOptions, + onChangePreviewOption, }: { - field: FieldItemProps; - fields: Field< any >[]; - view: View; - onChangeView: ( view: View ) => void; + field: NormalizedField< any >; + label?: string; + description?: string; + isVisible: boolean; + isFirst?: boolean; + isLast?: boolean; + canMove?: boolean; + onToggleVisibility?: () => void; + onMoveUp?: () => void; + onMoveDown?: () => void; + previewOptions?: Array< { label: string; id: string } >; + onChangePreviewOption?: ( newPreviewOption: string ) => void; } ) { - const visibleFieldIds = getVisibleFieldIds( view, fields ); + const [ isChangingPreviewOption, setIsChangingPreviewOption ] = + useState< boolean >( false ); + + const focusVisibilityField = () => { + // Focus the visibility button to avoid focus loss. + // Our code is safe against the component being unmounted, so we don't need to worry about cleaning the timeout. + // eslint-disable-next-line @wordpress/react-no-unsafe-timeout + setTimeout( () => { + const element = document.querySelector( + `.dataviews-field-control__field-${ field.id } .dataviews-field-control__field-visibility-button` + ); + if ( element instanceof HTMLElement ) { + element.focus(); + } + }, 50 ); + }; return ( - <Item key={ id }> + <Item> <HStack expanded - className={ `dataviews-field-control__field dataviews-field-control__field-${ id }` } + className={ clsx( + 'dataviews-field-control__field', + `dataviews-field-control__field-${ field.id }`, + // The actions are hidden when the mouse is not hovering the item, or focus + // is outside the item. + // For actions that require a popover, a menu etc, that would mean that when the interactive element + // opens and the focus goes there the actions would be hidden. + // To avoid that we add a class to the item, that makes sure actions are visible while there is some + // interaction with the item. + { 'is-interacting': isChangingPreviewOption } + ) } + justify="flex-start" > - <span>{ label }</span> + <span className="dataviews-field-control__icon"> + { ! canMove && ! field.enableHiding && ( + <Icon icon={ lock } /> + ) } + </span> + <span className="dataviews-field-control__label-sub-label-container"> + <span className="dataviews-field-control__label"> + { label || field.label } + </span> + { description && ( + <span className="dataviews-field-control__sub-label"> + { description } + </span> + ) } + </span> <HStack justify="flex-end" expanded={ false } className="dataviews-field-control__actions" > - { view.type === LAYOUT_TABLE && isVisible && ( + { isVisible && ( <> <Button - disabled={ index < 1 } + disabled={ isFirst || ! canMove } accessibleWhenDisabled size="compact" - onClick={ () => { - onChangeView( { - ...view, - fields: [ - ...( visibleFieldIds.slice( - 0, - index - 1 - ) ?? [] ), - id, - visibleFieldIds[ index - 1 ], - ...visibleFieldIds.slice( - index + 1 - ), - ], - } ); - } } + onClick={ onMoveUp } icon={ chevronUp } - label={ sprintf( - /* translators: %s: field label */ - __( 'Move %s up' ), - label - ) } + label={ + isFirst || ! canMove + ? __( "This field can't be moved up" ) + : sprintf( + /* translators: %s: field label */ + __( 'Move %s up' ), + field.label + ) + } /> <Button - disabled={ index >= visibleFieldIds.length - 1 } + disabled={ isLast || ! canMove } accessibleWhenDisabled size="compact" - onClick={ () => { - onChangeView( { - ...view, - fields: [ - ...( visibleFieldIds.slice( - 0, - index - ) ?? [] ), - visibleFieldIds[ index + 1 ], - id, - ...visibleFieldIds.slice( - index + 2 - ), - ], - } ); - } } + onClick={ onMoveDown } icon={ chevronDown } - label={ sprintf( - /* translators: %s: field label */ - __( 'Move %s down' ), - label - ) } - />{ ' ' } + label={ + isLast || ! canMove + ? __( "This field can't be moved down" ) + : sprintf( + /* translators: %s: field label */ + __( 'Move %s down' ), + field.label + ) + } + /> </> ) } - <Button - className="dataviews-field-control__field-visibility-button" - disabled={ ! isHidable } - accessibleWhenDisabled - size="compact" - onClick={ () => { - onChangeView( { - ...view, - fields: isVisible - ? visibleFieldIds.filter( - ( fieldId ) => fieldId !== id + { onToggleVisibility && ( + <Button + className="dataviews-field-control__field-visibility-button" + disabled={ ! field.enableHiding } + accessibleWhenDisabled + size="compact" + onClick={ () => { + onToggleVisibility(); + focusVisibilityField(); + } } + icon={ isVisible ? unseen : seen } + label={ + isVisible + ? sprintf( + /* translators: %s: field label */ + _x( 'Hide %s', 'field' ), + field.label ) - : [ ...visibleFieldIds, id ], - } ); - // Focus the visibility button to avoid focus loss. - // Our code is safe against the component being unmounted, so we don't need to worry about cleaning the timeout. - // eslint-disable-next-line @wordpress/react-no-unsafe-timeout - setTimeout( () => { - const element = document.querySelector( - `.dataviews-field-control__field-${ id } .dataviews-field-control__field-visibility-button` - ); - if ( element instanceof HTMLElement ) { - element.focus(); - } - }, 50 ); - } } - icon={ isVisible ? unseen : seen } - label={ - isVisible - ? sprintf( - /* translators: %s: field label */ - _x( 'Hide %s', 'field' ), - label - ) - : sprintf( - /* translators: %s: field label */ - _x( 'Show %s', 'field' ), - label - ) - } - /> + : sprintf( + /* translators: %s: field label */ + _x( 'Show %s', 'field' ), + field.label + ) + } + /> + ) } + { previewOptions && ( + <PreviewOptions + previewOptions={ previewOptions } + onChangePreviewOption={ onChangePreviewOption } + onMenuOpenChange={ setIsChangingPreviewOption } + activeOption={ field.id } + /> + ) } </HStack> </HStack> </Item> ); } +function RegularFieldItem( { + index, + field, + view, + onChangeView, +}: { + index?: number; + field: NormalizedField< any >; + view: View; + onChangeView: ( view: View ) => void; +} ) { + const visibleFieldIds = view.fields ?? []; + const isVisible = + index !== undefined && visibleFieldIds.includes( field.id ); + + return ( + <FieldItem + field={ field } + isVisible={ isVisible } + isFirst={ index !== undefined && index < 1 } + isLast={ + index !== undefined && index === visibleFieldIds.length - 1 + } + onToggleVisibility={ () => { + onChangeView( { + ...view, + fields: isVisible + ? visibleFieldIds.filter( + ( fieldId ) => fieldId !== field.id + ) + : [ ...visibleFieldIds, field.id ], + } ); + } } + onMoveUp={ + index !== undefined + ? () => { + onChangeView( { + ...view, + fields: [ + ...( visibleFieldIds.slice( + 0, + index - 1 + ) ?? [] ), + field.id, + visibleFieldIds[ index - 1 ], + ...visibleFieldIds.slice( index + 1 ), + ], + } ); + } + : undefined + } + onMoveDown={ + index !== undefined + ? () => { + onChangeView( { + ...view, + fields: [ + ...( visibleFieldIds.slice( 0, index ) ?? + [] ), + visibleFieldIds[ index + 1 ], + field.id, + ...visibleFieldIds.slice( index + 2 ), + ], + } ); + } + : undefined + } + /> + ); +} + +function isDefined< T >( item: T | undefined ): item is T { + return !! item; +} + function FieldControl() { const { view, fields, onChangeView } = useContext( DataViewsContext ); - const visibleFieldIds = useMemo( - () => getVisibleFieldIds( view, fields ), - [ view, fields ] - ); - const hiddenFieldIds = useMemo( - () => getHiddenFieldIds( view, fields ), - [ view, fields ] - ); - const notHidableFieldIds = useMemo( - () => getNotHidableFieldIds( view ), - [ view ] + const togglableFields = [ + view?.titleField, + view?.mediaField, + view?.descriptionField, + ].filter( Boolean ); + const visibleFieldIds = view.fields ?? []; + const hiddenFields = fields.filter( + ( f ) => + ! visibleFieldIds.includes( f.id ) && + ! togglableFields.includes( f.id ) && + f.type !== 'media' ); + const visibleFields = visibleFieldIds + .map( ( fieldId ) => fields.find( ( f ) => f.id === fieldId ) ) + .filter( isDefined ); - const visibleFields = fields - .filter( ( { id } ) => visibleFieldIds.includes( id ) ) - .map( ( { id, label, enableHiding } ) => { - return { - id, - label, - index: visibleFieldIds.indexOf( id ), - isVisible: true, - isHidable: notHidableFieldIds.includes( id ) - ? false - : enableHiding, - }; - } ); - if ( view.type === LAYOUT_TABLE && view.layout?.combinedFields ) { - view.layout.combinedFields.forEach( ( { id, label } ) => { - visibleFields.push( { - id, - label, - index: visibleFieldIds.indexOf( id ), - isVisible: true, - isHidable: notHidableFieldIds.includes( id ), - } ); - } ); + if ( ! visibleFields?.length && ! hiddenFields?.length ) { + return null; } - visibleFields.sort( ( a, b ) => a.index - b.index ); + const titleField = fields.find( ( f ) => f.id === view.titleField ); + const previewField = fields.find( ( f ) => f.id === view.mediaField ); + const descriptionField = fields.find( + ( f ) => f.id === view.descriptionField + ); - const hiddenFields = fields - .filter( ( { id } ) => hiddenFieldIds.includes( id ) ) - .map( ( { id, label, enableHiding }, index ) => { - return { - id, - label, - index, - isVisible: false, - isHidable: enableHiding, - }; - } ); + const previewFields = fields.filter( ( f ) => f.type === 'media' ); - if ( ! visibleFields?.length && ! hiddenFields?.length ) { - return null; + let previewFieldUI; + if ( previewFields.length > 1 ) { + const isPreviewFieldVisible = + isDefined( previewField ) && ( view.showMedia ?? true ); + previewFieldUI = isDefined( previewField ) && ( + <FieldItem + key={ previewField.id } + field={ previewField } + label={ __( 'Preview' ) } + description={ previewField.label } + isVisible={ isPreviewFieldVisible } + onToggleVisibility={ () => { + onChangeView( { + ...view, + showMedia: ! isPreviewFieldVisible, + } ); + } } + canMove={ false } + previewOptions={ previewFields.map( ( field ) => ( { + label: field.label, + id: field.id, + } ) ) } + onChangePreviewOption={ ( newPreviewId ) => + onChangeView( { ...view, mediaField: newPreviewId } ) + } + /> + ); } + const lockedFields = [ + { + field: titleField, + isVisibleFlag: 'showTitle', + }, + { + field: previewField, + isVisibleFlag: 'showMedia', + ui: previewFieldUI, + }, + { + field: descriptionField, + isVisibleFlag: 'showDescription', + }, + ].filter( ( { field } ) => isDefined( field ) ); + const visibleLockedFields = lockedFields.filter( + ( { field, isVisibleFlag } ) => + // @ts-expect-error + isDefined( field ) && ( view[ isVisibleFlag ] ?? true ) + ) as Array< { + field: NormalizedField< any >; + isVisibleFlag: string; + ui?: ReactNode; + } >; + const hiddenLockedFields = lockedFields.filter( + ( { field, isVisibleFlag } ) => + // @ts-expect-error + isDefined( field ) && ! ( view[ isVisibleFlag ] ?? true ) + ) as Array< { + field: NormalizedField< any >; + isVisibleFlag: string; + ui?: ReactNode; + } >; return ( - <VStack spacing={ 6 } className="dataviews-field-control"> - { !! visibleFields?.length && ( - <ItemGroup isBordered isSeparated> - { visibleFields.map( ( field ) => ( - <FieldItem - key={ field.id } - field={ field } - fields={ fields } - view={ view } - onChangeView={ onChangeView } - /> - ) ) } - </ItemGroup> - ) } - { !! hiddenFields?.length && ( - <> - <VStack spacing={ 4 }> - <BaseControl.VisualLabel style={ { margin: 0 } }> - { __( 'Hidden' ) } - </BaseControl.VisualLabel> + <VStack className="dataviews-field-control" spacing={ 6 }> + <VStack className="dataviews-view-config__properties" spacing={ 0 }> + { ( visibleLockedFields.length > 0 || + !! visibleFields?.length ) && ( + <ItemGroup isBordered isSeparated> + { visibleLockedFields.map( + ( { field, isVisibleFlag, ui } ) => { + return ( + ui ?? ( + <FieldItem + key={ field.id } + field={ field } + isVisible + onToggleVisibility={ () => { + onChangeView( { + ...view, + [ isVisibleFlag ]: false, + } ); + } } + canMove={ false } + /> + ) + ); + } + ) } + + { visibleFields.map( ( field, index ) => ( + <RegularFieldItem + key={ field.id } + field={ field } + view={ view } + onChangeView={ onChangeView } + index={ index } + /> + ) ) } + </ItemGroup> + ) } + </VStack> + + { ( !! hiddenFields?.length || !! hiddenLockedFields.length ) && ( + <VStack spacing={ 4 }> + <BaseControl.VisualLabel style={ { margin: 0 } }> + { __( 'Hidden' ) } + </BaseControl.VisualLabel> + <VStack + className="dataviews-view-config__properties" + spacing={ 0 } + > <ItemGroup isBordered isSeparated> + { hiddenLockedFields.length > 0 && + hiddenLockedFields.map( + ( { field, isVisibleFlag, ui } ) => { + return ( + ui ?? ( + <FieldItem + key={ field.id } + field={ field } + isVisible={ false } + onToggleVisibility={ () => { + onChangeView( { + ...view, + [ isVisibleFlag ]: + true, + } ); + } } + canMove={ false } + /> + ) + ); + } + ) } { hiddenFields.map( ( field ) => ( - <FieldItem + <RegularFieldItem key={ field.id } field={ field } - fields={ fields } view={ view } onChangeView={ onChangeView } /> ) ) } </ItemGroup> </VStack> - </> + </VStack> ) } </VStack> ); @@ -512,21 +761,18 @@ function SettingsSection( { ); } -function DataviewsViewConfigDropdown( { - density, - setDensity, -}: { - density: number; - setDensity: React.Dispatch< React.SetStateAction< number > >; -} ) { +function DataviewsViewConfigDropdown() { const { view } = useContext( DataViewsContext ); const popoverId = useInstanceId( _DataViewsViewConfig, 'dataviews-view-config-dropdown' ); - + const activeLayout = VIEW_LAYOUTS.find( + ( layout ) => layout.type === view.type + ); return ( <Dropdown + expandOnMobile popoverProps={ { ...DATAVIEWS_CONFIG_POPOVER_PROPS, id: popoverId, @@ -544,18 +790,18 @@ function DataviewsViewConfigDropdown( { ); } } renderContent={ () => ( - <DropdownContentWrapper paddingSize="medium"> + <DropdownContentWrapper + paddingSize="medium" + className="dataviews-config__popover-content-wrapper" + > <VStack className="dataviews-view-config" spacing={ 6 }> <SettingsSection title={ __( 'Appearance' ) }> <HStack expanded className="is-divided-in-two"> <SortFieldControl /> <SortDirectionControl /> </HStack> - { view.type === LAYOUT_GRID && ( - <DensityPicker - density={ density } - setDensity={ setDensity } - /> + { !! activeLayout?.viewConfigOptions && ( + <activeLayout.viewConfigOptions /> ) } <ItemsPerPageControl /> </SettingsSection> @@ -570,21 +816,14 @@ function DataviewsViewConfigDropdown( { } function _DataViewsViewConfig( { - density, - setDensity, defaultLayouts = { list: {}, grid: {}, table: {} }, }: { - density: number; - setDensity: React.Dispatch< React.SetStateAction< number > >; defaultLayouts?: SupportedLayouts; } ) { return ( <> <ViewTypeMenu defaultLayouts={ defaultLayouts } /> - <DataviewsViewConfigDropdown - density={ density } - setDensity={ setDensity } - /> + <DataviewsViewConfigDropdown /> </> ); } diff --git a/packages/dataviews/src/components/dataviews-view-config/style.scss b/packages/dataviews/src/components/dataviews-view-config/style.scss index 7fff110337ee3a..fc38e345ec4ce2 100644 --- a/packages/dataviews/src/components/dataviews-view-config/style.scss +++ b/packages/dataviews/src/components/dataviews-view-config/style.scss @@ -6,6 +6,14 @@ line-height: $default-line-height; } +.dataviews-config__popover.is-expanded .dataviews-config__popover-content-wrapper { + overflow-y: scroll; + height: 100%; + .dataviews-view-config { + width: auto; + } +} + .dataviews-view-config__sort-direction .components-toggle-group-control-option-base { text-transform: uppercase; } @@ -35,7 +43,6 @@ display: none; } -/* stylelint-disable-next-line scss/at-rule-no-unknown -- '@container' not globally permitted */ @container (max-width: 500px) { .dataviews-settings-section.dataviews-settings-section { grid-template-columns: repeat(2, 1fr); @@ -61,9 +68,31 @@ } .dataviews-field-control__field:hover, -.dataviews-field-control__field:focus-within { +.dataviews-field-control__field:focus-within, +.dataviews-field-control__field.is-interacting { .dataviews-field-control__actions { position: unset; top: unset; } } + +.dataviews-field-control__icon { + display: flex; + width: $icon-size; +} + +.dataviews-field-control__label-sub-label-container { + flex-grow: 1; +} + +.dataviews-field-control__label { + display: block; +} + +.dataviews-field-control__sub-label { + margin-top: $grid-unit-10; + margin-bottom: 0; + font-size: 11px; + font-style: normal; + color: $gray-700; +} diff --git a/packages/dataviews/src/components/dataviews/index.tsx b/packages/dataviews/src/components/dataviews/index.tsx index 77a5cb8740f712..a0a89488136548 100644 --- a/packages/dataviews/src/components/dataviews/index.tsx +++ b/packages/dataviews/src/components/dataviews/index.tsx @@ -8,6 +8,7 @@ import type { ReactNode } from 'react'; */ import { __experimentalHStack as HStack } from '@wordpress/components'; import { useMemo, useState } from '@wordpress/element'; +import { useResizeObserver } from '@wordpress/compose'; /** * Internal dependencies @@ -16,7 +17,7 @@ import DataViewsContext from '../dataviews-context'; import { default as DataViewsFilters, useFilters, - FilterVisibilityToggle, + FiltersToggle, } from '../dataviews-filters'; import DataViewsLayout from '../dataviews-layout'; import DataViewsFooter from '../dataviews-footer'; @@ -47,13 +48,13 @@ type DataViewsProps< Item > = { onClickItem?: ( item: Item ) => void; isItemClickable?: ( item: Item ) => boolean; header?: ReactNode; + getItemLevel?: ( item: Item ) => number; } & ( Item extends ItemWithId ? { getItemId?: ( item: Item ) => string } : { getItemId: ( item: Item ) => string } ); const defaultGetItemId = ( item: ItemWithId ) => item.id; -const defaultIsItemClickable = () => false; -const defaultOnClickItem = () => {}; +const defaultIsItemClickable = () => true; const EMPTY_ARRAY: any[] = []; export default function DataViews< Item >( { @@ -65,17 +66,26 @@ export default function DataViews< Item >( { actions = EMPTY_ARRAY, data, getItemId = defaultGetItemId, + getItemLevel, isLoading = false, paginationInfo, defaultLayouts, selection: selectionProperty, onChangeSelection, - onClickItem = defaultOnClickItem, + onClickItem, isItemClickable = defaultIsItemClickable, header, }: DataViewsProps< Item > ) { + const [ containerWidth, setContainerWidth ] = useState( 0 ); + const containerRef = useResizeObserver( + ( resizeObserverEntries: any ) => { + setContainerWidth( + resizeObserverEntries[ 0 ].borderBoxSize[ 0 ].inlineSize + ); + }, + { box: 'border-box' } + ); const [ selectionState, setSelectionState ] = useState< string[] >( [] ); - const [ density, setDensity ] = useState< number >( 0 ); const isUncontrolled = selectionProperty === undefined || onChangeSelection === undefined; const selection = isUncontrolled ? selectionState : selectionProperty; @@ -117,12 +127,13 @@ export default function DataViews< Item >( { openedFilter, setOpenedFilter, getItemId, + getItemLevel, isItemClickable, onClickItem, - density, + containerWidth, } } > - <div className="dataviews-wrapper"> + <div className="dataviews-wrapper" ref={ containerRef }> <HStack alignment="top" justify="space-between" @@ -135,7 +146,7 @@ export default function DataViews< Item >( { className="dataviews__search" > { search && <DataViewsSearch label={ searchLabel } /> } - <FilterVisibilityToggle + <FiltersToggle filters={ filters } view={ view } onChangeView={ onChangeView } @@ -151,8 +162,6 @@ export default function DataViews< Item >( { > <DataViewsViewConfig defaultLayouts={ defaultLayouts } - density={ density } - setDensity={ setDensity } /> { header } </HStack> diff --git a/packages/dataviews/src/components/dataviews/stories/fixtures.tsx b/packages/dataviews/src/components/dataviews/stories/fixtures.tsx index ff098209b34684..cd4d3b97e65311 100644 --- a/packages/dataviews/src/components/dataviews/stories/fixtures.tsx +++ b/packages/dataviews/src/components/dataviews/stories/fixtures.tsx @@ -558,6 +558,7 @@ export const themeFields: Field< Theme >[] = [ }, { id: 'requires', label: 'Requires at least' }, { id: 'tested', label: 'Tested up to' }, + { id: 'icon', label: 'Icon', render: () => <Icon icon={ image } /> }, { id: 'tags', label: 'Tags', @@ -636,9 +637,6 @@ export const fields: Field< SpaceObject >[] = [ id: 'title', enableHiding: false, enableGlobalSearch: true, - render: ( { item } ) => { - return <a href="#nothing">{ item.title }</a>; - }, }, { id: 'date', diff --git a/packages/dataviews/src/components/dataviews/stories/index.story.tsx b/packages/dataviews/src/components/dataviews/stories/index.story.tsx index 878677d2eb30c6..1e96ba92a0bea9 100644 --- a/packages/dataviews/src/components/dataviews/stories/index.story.tsx +++ b/packages/dataviews/src/components/dataviews/stories/index.story.tsx @@ -7,17 +7,10 @@ import { useState, useMemo } from '@wordpress/element'; * Internal dependencies */ import DataViews from '../index'; -import { - DEFAULT_VIEW, - actions, - data, - fields, - themeData, - themeFields, -} from './fixtures'; +import { DEFAULT_VIEW, actions, data, fields } from './fixtures'; import { LAYOUT_GRID, LAYOUT_LIST, LAYOUT_TABLE } from '../../../constants'; import { filterSortAndPaginate } from '../../../filter-and-sort-data-view'; -import type { CombinedField, View } from '../../../types'; +import type { View } from '../../../types'; import './style.css'; @@ -28,44 +21,18 @@ const meta = { export default meta; const defaultLayouts = { - [ LAYOUT_TABLE ]: { - layout: { - primaryField: 'title', - styles: { - image: { - width: 50, - }, - title: { - maxWidth: 400, - }, - type: { - maxWidth: 400, - }, - description: { - maxWidth: 200, - }, - }, - }, - }, - [ LAYOUT_GRID ]: { - layout: { - mediaField: 'image', - primaryField: 'title', - }, - }, - [ LAYOUT_LIST ]: { - layout: { - mediaField: 'image', - primaryField: 'title', - }, - }, + [ LAYOUT_TABLE ]: {}, + [ LAYOUT_GRID ]: {}, + [ LAYOUT_LIST ]: {}, }; export const Default = () => { const [ view, setView ] = useState< View >( { ...DEFAULT_VIEW, - fields: [ 'title', 'description', 'categories' ], - layout: defaultLayouts[ DEFAULT_VIEW.type ].layout, + fields: [ 'categories' ], + titleField: 'title', + descriptionField: 'description', + mediaField: 'image', } ); const { data: shownData, paginationInfo } = useMemo( () => { return filterSortAndPaginate( data, view, fields ); @@ -79,6 +46,11 @@ export const Default = () => { fields={ fields } onChangeView={ setView } actions={ actions } + onClickItem={ ( item ) => { + // eslint-disable-next-line no-alert + alert( 'Clicked: ' + item.title ); + } } + isItemClickable={ () => true } defaultLayouts={ defaultLayouts } /> ); @@ -133,55 +105,3 @@ export const FieldsNoSortableNoHidable = () => { /> ); }; - -export const CombinedFields = () => { - const defaultLayoutsThemes = { - table: { - fields: [ 'theme', 'requires', 'tested' ], - layout: { - primaryField: 'name', - combinedFields: [ - { - id: 'theme', - label: 'Theme', - children: [ 'name', 'description' ], - direction: 'vertical', - }, - ] as CombinedField[], - styles: { - theme: { - maxWidth: 300, - }, - }, - }, - }, - grid: { - fields: [ 'description', 'requires', 'tested' ], - layout: { primaryField: 'name', columnFields: [ 'description' ] }, - }, - list: { - fields: [ 'requires', 'tested' ], - layout: { primaryField: 'name' }, - }, - }; - const [ view, setView ] = useState< View >( { - ...DEFAULT_VIEW, - fields: defaultLayoutsThemes.table.fields, - layout: defaultLayoutsThemes.table.layout, - } ); - const { data: shownData, paginationInfo } = useMemo( () => { - return filterSortAndPaginate( themeData, view, themeFields ); - }, [ view ] ); - - return ( - <DataViews - getItemId={ ( item ) => item.name } - paginationInfo={ paginationInfo } - data={ shownData } - view={ view } - fields={ themeFields } - onChangeView={ setView } - defaultLayouts={ defaultLayoutsThemes } - /> - ); -}; diff --git a/packages/dataviews/src/components/dataviews/style.scss b/packages/dataviews/src/components/dataviews/style.scss index bd75a1ff9e2a18..b44d5b2543f4af 100644 --- a/packages/dataviews/src/components/dataviews/style.scss +++ b/packages/dataviews/src/components/dataviews/style.scss @@ -18,16 +18,42 @@ flex-shrink: 0; position: sticky; left: 0; - transition: padding ease-out 0.1s; - @include reduce-motion( "transition" ); + + @media not (prefers-reduced-motion) { + transition: padding ease-out 0.1s; + } +} + +.dataviews-no-results, +.dataviews-loading { + padding: 0 $grid-unit-60; + flex-grow: 1; + display: flex; + align-items: center; + justify-content: center; + + @media not (prefers-reduced-motion) { + transition: padding ease-out 0.1s; + } +} + +@container (max-width: 430px) { + .dataviews__view-actions, + .dataviews-filters__container { + padding: $grid-unit-15 $grid-unit-30; + } + + .dataviews-no-results, + .dataviews-loading { + padding-left: $grid-unit-30; + padding-right: $grid-unit-30; + } } -.dataviews-view-list__primary-field, -.dataviews-view-grid__primary-field, -.dataviews-view-table__primary-field { +.dataviews-title-field { font-size: $default-font-size; font-weight: 500; - color: $gray-700; + color: $gray-800; text-overflow: ellipsis; white-space: nowrap; width: 100%; @@ -39,7 +65,7 @@ overflow: hidden; display: block; flex-grow: 0; - color: $gray-900; + color: $gray-800; &:hover { color: var(--wp-admin-theme-color); @@ -62,34 +88,11 @@ } } -.dataviews-view-list__primary-field--clickable, -.dataviews-view-grid__primary-field--clickable, -.dataviews-view-grid__media--clickable, -.dataviews-view-table__primary-field > .dataviews-view-table__cell-content--clickable { +.dataviews-title-field--clickable { cursor: pointer; -} - -.dataviews-no-results, -.dataviews-loading { - padding: 0 $grid-unit-60; - flex-grow: 1; - display: flex; - align-items: center; - justify-content: center; - transition: padding ease-out 0.1s; - @include reduce-motion( "transition" ); -} - -/* stylelint-disable-next-line scss/at-rule-no-unknown -- '@container' not globally permitted */ -@container (max-width: 430px) { - .dataviews__view-actions, - .dataviews-filters__container { - padding: $grid-unit-15 $grid-unit-30; - } - - .dataviews-no-results, - .dataviews-loading { - padding-left: $grid-unit-30; - padding-right: $grid-unit-30; + color: $gray-800; + &:hover { + color: var(--wp-admin-theme-color); } + @include link-reset(); } diff --git a/packages/dataviews/src/components/form-field-visibility/index.tsx b/packages/dataviews/src/components/form-field-visibility/index.tsx deleted file mode 100644 index 8cea59f11b7aea..00000000000000 --- a/packages/dataviews/src/components/form-field-visibility/index.tsx +++ /dev/null @@ -1,32 +0,0 @@ -/** - * WordPress dependencies - */ -import { useMemo } from '@wordpress/element'; - -/** - * Internal dependencies - */ -import type { NormalizedField } from '../../types'; - -type FormFieldVisibilityProps< Item > = React.PropsWithChildren< { - field: NormalizedField< Item >; - data: Item; -} >; - -export default function FormFieldVisibility< Item >( { - data, - field, - children, -}: FormFieldVisibilityProps< Item > ) { - const isVisible = useMemo( () => { - if ( field.isVisible ) { - return field.isVisible( data ); - } - return true; - }, [ field.isVisible, data ] ); - - if ( ! isVisible ) { - return null; - } - return children; -} diff --git a/packages/dataviews/src/dataforms-layouts/data-form-layout.tsx b/packages/dataviews/src/dataforms-layouts/data-form-layout.tsx new file mode 100644 index 00000000000000..08cc47f569eafe --- /dev/null +++ b/packages/dataviews/src/dataforms-layouts/data-form-layout.tsx @@ -0,0 +1,87 @@ +/** + * WordPress dependencies + */ +import { __experimentalVStack as VStack } from '@wordpress/components'; +import { useContext, useMemo } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import type { Form, FormField, SimpleFormField } from '../types'; +import { getFormFieldLayout } from './index'; +import DataFormContext from '../components/dataform-context'; +import { isCombinedField } from './is-combined-field'; +import normalizeFormFields from '../normalize-form-fields'; + +export function DataFormLayout< Item >( { + data, + form, + onChange, + children, +}: { + data: Item; + form: Form; + onChange: ( value: any ) => void; + children?: ( + FieldLayout: ( props: { + data: Item; + field: FormField; + onChange: ( value: any ) => void; + hideLabelFromVision?: boolean; + } ) => React.JSX.Element | null, + field: FormField + ) => React.JSX.Element; +} ) { + const { fields: fieldDefinitions } = useContext( DataFormContext ); + + function getFieldDefinition( field: SimpleFormField | string ) { + const fieldId = typeof field === 'string' ? field : field.id; + + return fieldDefinitions.find( + ( fieldDefinition ) => fieldDefinition.id === fieldId + ); + } + + const normalizedFormFields = useMemo( + () => normalizeFormFields( form ), + [ form ] + ); + + return ( + <VStack spacing={ 2 }> + { normalizedFormFields.map( ( formField ) => { + const FieldLayout = getFormFieldLayout( formField.layout ) + ?.component; + + if ( ! FieldLayout ) { + return null; + } + + const fieldDefinition = ! isCombinedField( formField ) + ? getFieldDefinition( formField ) + : undefined; + + if ( + fieldDefinition && + fieldDefinition.isVisible && + ! fieldDefinition.isVisible( data ) + ) { + return null; + } + + if ( children ) { + return children( FieldLayout, formField ); + } + + return ( + <FieldLayout + key={ formField.id } + data={ data } + field={ formField } + onChange={ onChange } + /> + ); + } ) } + </VStack> + ); +} diff --git a/packages/dataviews/src/dataforms-layouts/get-visible-fields.ts b/packages/dataviews/src/dataforms-layouts/get-visible-fields.ts deleted file mode 100644 index d95d59a88394e4..00000000000000 --- a/packages/dataviews/src/dataforms-layouts/get-visible-fields.ts +++ /dev/null @@ -1,29 +0,0 @@ -/** - * Internal dependencies - */ -import { normalizeCombinedFields } from '../normalize-fields'; -import type { - Field, - CombinedFormField, - NormalizedCombinedFormField, -} from '../types'; - -export function getVisibleFields< Item >( - fields: Field< Item >[], - formFields: string[] = [], - combinedFields?: CombinedFormField< Item >[] -): Field< Item >[] { - const visibleFields: Array< - Field< Item > | NormalizedCombinedFormField< Item > - > = [ ...fields ]; - if ( combinedFields ) { - visibleFields.push( - ...normalizeCombinedFields( combinedFields, fields ) - ); - } - return formFields - .map( ( fieldId ) => - visibleFields.find( ( { id } ) => id === fieldId ) - ) - .filter( ( field ): field is Field< Item > => !! field ); -} diff --git a/packages/dataviews/src/dataforms-layouts/index.tsx b/packages/dataviews/src/dataforms-layouts/index.tsx index 9434ea724ed4ca..5e4f3617d9c7dd 100644 --- a/packages/dataviews/src/dataforms-layouts/index.tsx +++ b/packages/dataviews/src/dataforms-layouts/index.tsx @@ -1,20 +1,20 @@ /** * Internal dependencies */ -import FormRegular from './regular'; -import FormPanel from './panel'; +import FormRegularField from './regular'; +import FormPanelField from './panel'; -const FORM_LAYOUTS = [ +const FORM_FIELD_LAYOUTS = [ { type: 'regular', - component: FormRegular, + component: FormRegularField, }, { type: 'panel', - component: FormPanel, + component: FormPanelField, }, ]; -export function getFormLayout( type: string ) { - return FORM_LAYOUTS.find( ( layout ) => layout.type === type ); +export function getFormFieldLayout( type: string ) { + return FORM_FIELD_LAYOUTS.find( ( layout ) => layout.type === type ); } diff --git a/packages/dataviews/src/dataforms-layouts/is-combined-field.ts b/packages/dataviews/src/dataforms-layouts/is-combined-field.ts new file mode 100644 index 00000000000000..3df6fdc60f906e --- /dev/null +++ b/packages/dataviews/src/dataforms-layouts/is-combined-field.ts @@ -0,0 +1,10 @@ +/** + * Internal dependencies + */ +import type { FormField, CombinedFormField } from '../types'; + +export function isCombinedField( + field: FormField +): field is CombinedFormField { + return ( field as CombinedFormField ).children !== undefined; +} diff --git a/packages/dataviews/src/dataforms-layouts/panel/index.tsx b/packages/dataviews/src/dataforms-layouts/panel/index.tsx index b74e5e4667d4b1..269b2bb418a856 100644 --- a/packages/dataviews/src/dataforms-layouts/panel/index.tsx +++ b/packages/dataviews/src/dataforms-layouts/panel/index.tsx @@ -9,29 +9,29 @@ import { Dropdown, Button, } from '@wordpress/components'; -import { useState, useMemo } from '@wordpress/element'; import { sprintf, __, _x } from '@wordpress/i18n'; +import { useState, useMemo, useContext } from '@wordpress/element'; import { closeSmall } from '@wordpress/icons'; /** * Internal dependencies */ -import { normalizeFields } from '../../normalize-fields'; -import { getVisibleFields } from '../get-visible-fields'; -import type { DataFormProps, NormalizedField } from '../../types'; -import FormFieldVisibility from '../../components/form-field-visibility'; - -interface FormFieldProps< Item > { - data: Item; - field: NormalizedField< Item >; - onChange: ( value: any ) => void; -} +import type { + Form, + FormField, + FieldLayoutProps, + NormalizedField, + SimpleFormField, +} from '../../types'; +import DataFormContext from '../../components/dataform-context'; +import { DataFormLayout } from '../data-form-layout'; +import { isCombinedField } from '../is-combined-field'; function DropdownHeader( { title, onClose, }: { - title: string; + title?: string; onClose: () => void; } ) { return ( @@ -40,9 +40,11 @@ function DropdownHeader( { spacing={ 4 } > <HStack alignment="center"> - <Heading level={ 2 } size={ 13 }> - { title } - </Heading> + { title && ( + <Heading level={ 2 } size={ 13 }> + { title } + </Heading> + ) } <Spacer /> { onClose && ( <Button @@ -57,16 +59,45 @@ function DropdownHeader( { ); } -function FormField< Item >( { +function PanelDropdown< Item >( { + fieldDefinition, + popoverAnchor, + labelPosition = 'side', data, - field, onChange, -}: FormFieldProps< Item > ) { - // Use internal state instead of a ref to make sure that the component - // re-renders when the popover's anchor updates. - const [ popoverAnchor, setPopoverAnchor ] = useState< HTMLElement | null >( - null - ); + field, +}: { + fieldDefinition: NormalizedField< Item >; + popoverAnchor: HTMLElement | null; + labelPosition: 'side' | 'top' | 'none'; + data: Item; + onChange: ( value: any ) => void; + field: FormField; +} ) { + const fieldLabel = isCombinedField( field ) + ? field.label + : fieldDefinition?.label; + const form = useMemo( () => { + if ( isCombinedField( field ) ) { + return { + type: 'regular' as const, + fields: field.children.map( ( child ) => { + if ( typeof child === 'string' ) { + return { + id: child, + }; + } + return child; + } ), + }; + } + // If not explicit children return the field id itself. + return { + type: 'regular' as const, + fields: [ { id: field.id } ], + }; + }, [ field ] ); + // Memoize popoverProps to avoid returning a new object every time. const popoverProps = useMemo( () => ( { @@ -81,95 +112,155 @@ function FormField< Item >( { ); return ( - <HStack - ref={ setPopoverAnchor } - className="dataforms-layouts-panel__field" - > - <div className="dataforms-layouts-panel__field-label"> - { field.label } - </div> - <div> - <Dropdown - contentClassName="dataforms-layouts-panel__field-dropdown" - popoverProps={ popoverProps } - focusOnMount - toggleProps={ { - size: 'compact', - variant: 'tertiary', - tooltipPosition: 'middle left', - } } - renderToggle={ ( { isOpen, onToggle } ) => ( - <Button - className="dataforms-layouts-panel__field-control" - size="compact" - variant="tertiary" - aria-expanded={ isOpen } - aria-label={ sprintf( - // translators: %s: Field name. - _x( 'Edit %s', 'field' ), - field.label - ) } - onClick={ onToggle } - > - <field.render item={ data } /> - </Button> + <Dropdown + contentClassName="dataforms-layouts-panel__field-dropdown" + popoverProps={ popoverProps } + focusOnMount + toggleProps={ { + size: 'compact', + variant: 'tertiary', + tooltipPosition: 'middle left', + } } + renderToggle={ ( { isOpen, onToggle } ) => ( + <Button + className="dataforms-layouts-panel__field-control" + size="compact" + variant={ + [ 'none', 'top' ].includes( labelPosition ) + ? 'link' + : 'tertiary' + } + aria-expanded={ isOpen } + aria-label={ sprintf( + // translators: %s: Field name. + _x( 'Edit %s', 'field' ), + fieldLabel ) } - renderContent={ ( { onClose } ) => ( - <> - <DropdownHeader - title={ field.label } - onClose={ onClose } - /> - <field.Edit - key={ field.id } + onClick={ onToggle } + > + <fieldDefinition.render item={ data } /> + </Button> + ) } + renderContent={ ( { onClose } ) => ( + <> + <DropdownHeader title={ fieldLabel } onClose={ onClose } /> + <DataFormLayout + data={ data } + form={ form as Form } + onChange={ onChange } + > + { ( FieldLayout, nestedField ) => ( + <FieldLayout + key={ nestedField.id } data={ data } - field={ field } + field={ nestedField } onChange={ onChange } - hideLabelFromVision + hideLabelFromVision={ + ( form?.fields ?? [] ).length < 2 + } /> - </> - ) } - /> - </div> - </HStack> + ) } + </DataFormLayout> + </> + ) } + /> ); } -export default function FormPanel< Item >( { +export default function FormPanelField< Item >( { data, - fields, - form, + field, onChange, -}: DataFormProps< Item > ) { - const visibleFields = useMemo( - () => - normalizeFields( - getVisibleFields< Item >( - fields, - form.fields, - form.combinedFields - ) - ), - [ fields, form.fields, form.combinedFields ] +}: FieldLayoutProps< Item > ) { + const { fields } = useContext( DataFormContext ); + const fieldDefinition = fields.find( ( fieldDef ) => { + // Default to the first child if it is a combined field. + if ( isCombinedField( field ) ) { + const children = field.children.filter( + ( child ): child is string | SimpleFormField => + typeof child === 'string' || ! isCombinedField( child ) + ); + const firstChildFieldId = + typeof children[ 0 ] === 'string' + ? children[ 0 ] + : children[ 0 ].id; + return fieldDef.id === firstChildFieldId; + } + return fieldDef.id === field.id; + } ); + const labelPosition = field.labelPosition ?? 'side'; + + // Use internal state instead of a ref to make sure that the component + // re-renders when the popover's anchor updates. + const [ popoverAnchor, setPopoverAnchor ] = useState< HTMLElement | null >( + null ); - return ( - <VStack spacing={ 2 }> - { visibleFields.map( ( field ) => { - return ( - <FormFieldVisibility - key={ field.id } - data={ data } + if ( ! fieldDefinition ) { + return null; + } + + const fieldLabel = isCombinedField( field ) + ? field.label + : fieldDefinition?.label; + + if ( labelPosition === 'top' ) { + return ( + <VStack className="dataforms-layouts-panel__field" spacing={ 0 }> + <div + className="dataforms-layouts-panel__field-label" + style={ { paddingBottom: 0 } } + > + { fieldLabel } + </div> + <div className="dataforms-layouts-panel__field-control"> + <PanelDropdown field={ field } - > - <FormField - data={ data } - field={ field } - onChange={ onChange } - /> - </FormFieldVisibility> - ); - } ) } - </VStack> + popoverAnchor={ popoverAnchor } + fieldDefinition={ fieldDefinition } + data={ data } + onChange={ onChange } + labelPosition={ labelPosition } + /> + </div> + </VStack> + ); + } + + if ( labelPosition === 'none' ) { + return ( + <div className="dataforms-layouts-panel__field"> + <PanelDropdown + field={ field } + popoverAnchor={ popoverAnchor } + fieldDefinition={ fieldDefinition } + data={ data } + onChange={ onChange } + labelPosition={ labelPosition } + /> + </div> + ); + } + + // Defaults to label position side. + return ( + <HStack + ref={ setPopoverAnchor } + className="dataforms-layouts-panel__field" + > + <div className="dataforms-layouts-panel__field-label"> + { fieldLabel } + </div> + <div className="dataforms-layouts-panel__field-control"> + <PanelDropdown + field={ field } + popoverAnchor={ popoverAnchor } + fieldDefinition={ fieldDefinition } + data={ data } + onChange={ onChange } + labelPosition={ labelPosition } + /> + </div> + </HStack> ); } diff --git a/packages/dataviews/src/dataforms-layouts/panel/style.scss b/packages/dataviews/src/dataforms-layouts/panel/style.scss index ae69c4ff45243a..c7058f6366b3b6 100644 --- a/packages/dataviews/src/dataforms-layouts/panel/style.scss +++ b/packages/dataviews/src/dataforms-layouts/panel/style.scss @@ -44,3 +44,7 @@ .dataforms-layouts-panel__dropdown-header { margin-bottom: $grid-unit-20; } + +.components-popover.components-dropdown__content.dataforms-layouts-panel__field-dropdown { + z-index: z-index(".components-popover.components-dropdown__content.dataforms-layouts-panel__field-dropdown"); +} diff --git a/packages/dataviews/src/dataforms-layouts/regular/index.tsx b/packages/dataviews/src/dataforms-layouts/regular/index.tsx index 6a340a50584df4..a3d90b807b5cd4 100644 --- a/packages/dataviews/src/dataforms-layouts/regular/index.tsx +++ b/packages/dataviews/src/dataforms-layouts/regular/index.tsx @@ -1,52 +1,116 @@ /** * WordPress dependencies */ -import { __experimentalVStack as VStack } from '@wordpress/components'; -import { useMemo } from '@wordpress/element'; +import { useContext, useMemo } from '@wordpress/element'; +import { + __experimentalHStack as HStack, + __experimentalVStack as VStack, + __experimentalHeading as Heading, + __experimentalSpacer as Spacer, +} from '@wordpress/components'; /** * Internal dependencies */ -import { normalizeFields } from '../../normalize-fields'; -import { getVisibleFields } from '../get-visible-fields'; -import type { DataFormProps } from '../../types'; -import FormFieldVisibility from '../../components/form-field-visibility'; +import type { Form, FieldLayoutProps } from '../../types'; +import DataFormContext from '../../components/dataform-context'; +import { DataFormLayout } from '../data-form-layout'; +import { isCombinedField } from '../is-combined-field'; -export default function FormRegular< Item >( { +function Header( { title }: { title: string } ) { + return ( + <VStack className="dataforms-layouts-regular__header" spacing={ 4 }> + <HStack alignment="center"> + <Heading level={ 2 } size={ 13 }> + { title } + </Heading> + <Spacer /> + </HStack> + </VStack> + ); +} + +export default function FormRegularField< Item >( { data, - fields, - form, + field, onChange, -}: DataFormProps< Item > ) { - const visibleFields = useMemo( - () => - normalizeFields( - getVisibleFields< Item >( - fields, - form.fields, - form.combinedFields - ) - ), - [ fields, form.fields, form.combinedFields ] + hideLabelFromVision, +}: FieldLayoutProps< Item > ) { + const { fields } = useContext( DataFormContext ); + + const form = useMemo( () => { + if ( isCombinedField( field ) ) { + return { + fields: field.children.map( ( child ) => { + if ( typeof child === 'string' ) { + return { + id: child, + }; + } + return child; + } ), + type: 'regular' as const, + }; + } + + return { + type: 'regular' as const, + fields: [], + }; + }, [ field ] ); + + if ( isCombinedField( field ) ) { + return ( + <> + { ! hideLabelFromVision && field.label && ( + <Header title={ field.label } /> + ) } + <DataFormLayout + data={ data } + form={ form as Form } + onChange={ onChange } + /> + </> + ); + } + + const labelPosition = field.labelPosition ?? 'top'; + const fieldDefinition = fields.find( + ( fieldDef ) => fieldDef.id === field.id ); - return ( - <VStack spacing={ 4 }> - { visibleFields.map( ( field ) => { - return ( - <FormFieldVisibility - key={ field.id } + if ( ! fieldDefinition ) { + return null; + } + if ( labelPosition === 'side' ) { + return ( + <HStack className="dataforms-layouts-regular__field"> + <div className="dataforms-layouts-regular__field-label"> + { fieldDefinition.label } + </div> + <div className="dataforms-layouts-regular__field-control"> + <fieldDefinition.Edit + key={ fieldDefinition.id } data={ data } - field={ field } - > - <field.Edit - data={ data } - field={ field } - onChange={ onChange } - /> - </FormFieldVisibility> - ); - } ) } - </VStack> + field={ fieldDefinition } + onChange={ onChange } + hideLabelFromVision + /> + </div> + </HStack> + ); + } + + return ( + <div className="dataforms-layouts-regular__field"> + <fieldDefinition.Edit + data={ data } + field={ fieldDefinition } + onChange={ onChange } + hideLabelFromVision={ + labelPosition === 'none' ? true : hideLabelFromVision + } + /> + </div> ); } diff --git a/packages/dataviews/src/dataforms-layouts/regular/style.scss b/packages/dataviews/src/dataforms-layouts/regular/style.scss new file mode 100644 index 00000000000000..d94b804fdf1fe5 --- /dev/null +++ b/packages/dataviews/src/dataforms-layouts/regular/style.scss @@ -0,0 +1,30 @@ +.dataforms-layouts-regular__field { + width: 100%; + min-height: $grid-unit-40; + justify-content: flex-start !important; + align-items: flex-start !important; +} + +.dataforms-layouts-regular__field .components-base-control__label { + font-size: inherit; + font-weight: normal; + text-transform: none; +} + +.dataforms-layouts-regular__field-label { + width: 38%; + flex-shrink: 0; + min-height: $grid-unit-40; + display: flex; + align-items: center; + padding: 6px 0; // Matches button to ensure alignment + line-height: $grid-unit-05 * 5; + hyphens: auto; +} + +.dataforms-layouts-regular__field-control { + flex-grow: 1; + min-height: $grid-unit-40; + display: flex; + align-items: center; +} diff --git a/packages/dataviews/src/dataviews-layouts/grid/density-picker.tsx b/packages/dataviews/src/dataviews-layouts/grid/density-picker.tsx deleted file mode 100644 index 34ddf6c3fe52f3..00000000000000 --- a/packages/dataviews/src/dataviews-layouts/grid/density-picker.tsx +++ /dev/null @@ -1,102 +0,0 @@ -/** - * WordPress dependencies - */ -import { RangeControl } from '@wordpress/components'; -import { __ } from '@wordpress/i18n'; -import { useViewportMatch } from '@wordpress/compose'; -import { useEffect, useMemo } from '@wordpress/element'; - -const viewportBreaks = { - xhuge: { min: 3, max: 6, default: 5 }, - huge: { min: 2, max: 4, default: 4 }, - xlarge: { min: 2, max: 3, default: 3 }, - large: { min: 1, max: 2, default: 2 }, - mobile: { min: 1, max: 2, default: 2 }, -}; - -function useViewPortBreakpoint() { - const isXHuge = useViewportMatch( 'xhuge', '>=' ); - const isHuge = useViewportMatch( 'huge', '>=' ); - const isXlarge = useViewportMatch( 'xlarge', '>=' ); - const isLarge = useViewportMatch( 'large', '>=' ); - const isMobile = useViewportMatch( 'mobile', '>=' ); - - if ( isXHuge ) { - return 'xhuge'; - } - if ( isHuge ) { - return 'huge'; - } - if ( isXlarge ) { - return 'xlarge'; - } - if ( isLarge ) { - return 'large'; - } - if ( isMobile ) { - return 'mobile'; - } - return null; -} - -export default function DensityPicker( { - density, - setDensity, -}: { - density: number; - setDensity: React.Dispatch< React.SetStateAction< number > >; -} ) { - const viewport = useViewPortBreakpoint(); - useEffect( () => { - setDensity( ( _density ) => { - if ( ! viewport || ! _density ) { - return 0; - } - const breakValues = viewportBreaks[ viewport ]; - if ( _density < breakValues.min ) { - return breakValues.min; - } - if ( _density > breakValues.max ) { - return breakValues.max; - } - return _density; - } ); - }, [ setDensity, viewport ] ); - const breakValues = viewportBreaks[ viewport || 'mobile' ]; - const densityToUse = density || breakValues.default; - - const marks = useMemo( - () => - Array.from( - { length: breakValues.max - breakValues.min + 1 }, - ( _, i ) => { - return { - value: breakValues.min + i, - }; - } - ), - [ breakValues ] - ); - - if ( ! viewport ) { - return null; - } - - return ( - <RangeControl - __nextHasNoMarginBottom - __next40pxDefaultSize - showTooltip={ false } - label={ __( 'Preview size' ) } - value={ breakValues.max + breakValues.min - densityToUse } - marks={ marks } - min={ breakValues.min } - max={ breakValues.max } - withInputField={ false } - onChange={ ( value = 0 ) => { - setDensity( breakValues.max + breakValues.min - value ); - } } - step={ 1 } - /> - ); -} diff --git a/packages/dataviews/src/dataviews-layouts/grid/index.tsx b/packages/dataviews/src/dataviews-layouts/grid/index.tsx index 91cc87ec7b35b6..e8f8a46002ebdf 100644 --- a/packages/dataviews/src/dataviews-layouts/grid/index.tsx +++ b/packages/dataviews/src/dataviews-layouts/grid/index.tsx @@ -13,35 +13,51 @@ import { Spinner, Flex, FlexItem, + privateApis as componentsPrivateApis, } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; +import { useInstanceId } from '@wordpress/compose'; /** * Internal dependencies */ +import { unlock } from '../../lock-unlock'; import ItemActions from '../../components/dataviews-item-actions'; -import SingleSelectionCheckbox from '../../components/dataviews-selection-checkbox'; -import { useHasAPossibleBulkAction } from '../../components/dataviews-bulk-actions'; -import type { Action, NormalizedField, ViewGridProps } from '../../types'; +import DataViewsSelectionCheckbox from '../../components/dataviews-selection-checkbox'; +import { + useHasAPossibleBulkAction, + useSomeItemHasAPossibleBulkAction, +} from '../../components/dataviews-bulk-actions'; +import type { + Action, + NormalizedField, + ViewGrid as ViewGridType, + ViewGridProps, +} from '../../types'; import type { SetSelection } from '../../private-types'; import getClickableItemProps from '../utils/get-clickable-item-props'; +import { useUpdatedPreviewSizeOnViewportChange } from './preview-size-picker'; +const { Badge } = unlock( componentsPrivateApis ); interface GridItemProps< Item > { + view: ViewGridType; selection: string[]; onChangeSelection: SetSelection; getItemId: ( item: Item ) => string; - onClickItem: ( item: Item ) => void; + onClickItem?: ( item: Item ) => void; isItemClickable: ( item: Item ) => boolean; item: Item; actions: Action< Item >[]; + titleField?: NormalizedField< Item >; mediaField?: NormalizedField< Item >; - primaryField?: NormalizedField< Item >; - visibleFields: NormalizedField< Item >[]; + descriptionField?: NormalizedField< Item >; + regularFields: NormalizedField< Item >[]; badgeFields: NormalizedField< Item >[]; - columnFields?: string[]; + hasBulkActions: boolean; } function GridItem< Item >( { + view, selection, onChangeSelection, onClickItem, @@ -50,34 +66,55 @@ function GridItem< Item >( { item, actions, mediaField, - primaryField, - visibleFields, + titleField, + descriptionField, + regularFields, badgeFields, - columnFields, + hasBulkActions, }: GridItemProps< Item > ) { + const { showTitle = true, showMedia = true, showDescription = true } = view; const hasBulkAction = useHasAPossibleBulkAction( actions, item ); const id = getItemId( item ); + const instanceId = useInstanceId( GridItem ); const isSelected = selection.includes( id ); const renderedMediaField = mediaField?.render ? ( <mediaField.render item={ item } /> ) : null; - const renderedPrimaryField = primaryField?.render ? ( - <primaryField.render item={ item } /> - ) : null; + const renderedTitleField = + showTitle && titleField?.render ? ( + <titleField.render item={ item } /> + ) : null; - const clickableMediaItemProps = getClickableItemProps( + const clickableMediaItemProps = getClickableItemProps( { item, isItemClickable, onClickItem, - 'dataviews-view-grid__media' - ); + className: 'dataviews-view-grid__media', + } ); - const clickablePrimaryItemProps = getClickableItemProps( + const clickableTitleItemProps = getClickableItemProps( { item, isItemClickable, onClickItem, - 'dataviews-view-grid__primary-field' - ); + className: 'dataviews-view-grid__title-field dataviews-title-field', + } ); + + let mediaA11yProps; + let titleA11yProps; + if ( isItemClickable( item ) && onClickItem ) { + if ( renderedTitleField ) { + mediaA11yProps = { + 'aria-labelledby': `dataviews-view-grid__title-field-${ instanceId }`, + }; + titleA11yProps = { + id: `dataviews-view-grid__title-field-${ instanceId }`, + }; + } else { + mediaA11yProps = { + 'aria-label': __( 'Navigate to item' ), + }; + } + } return ( <VStack @@ -101,84 +138,89 @@ function GridItem< Item >( { } } } > - <div { ...clickableMediaItemProps }>{ renderedMediaField }</div> - <SingleSelectionCheckbox - item={ item } - selection={ selection } - onChangeSelection={ onChangeSelection } - getItemId={ getItemId } - primaryField={ primaryField } - disabled={ ! hasBulkAction } - /> + { showMedia && renderedMediaField && ( + <div { ...clickableMediaItemProps } { ...mediaA11yProps }> + { renderedMediaField } + </div> + ) } + { hasBulkActions && showMedia && renderedMediaField && ( + <DataViewsSelectionCheckbox + item={ item } + selection={ selection } + onChangeSelection={ onChangeSelection } + getItemId={ getItemId } + titleField={ titleField } + disabled={ ! hasBulkAction } + /> + ) } <HStack justify="space-between" className="dataviews-view-grid__title-actions" > - <HStack> - <div { ...clickablePrimaryItemProps }> - { renderedPrimaryField } - </div> - </HStack> - <ItemActions item={ item } actions={ actions } isCompact /> + <div { ...clickableTitleItemProps } { ...titleA11yProps }> + { renderedTitleField } + </div> + { !! actions?.length && ( + <ItemActions item={ item } actions={ actions } isCompact /> + ) } </HStack> - { !! badgeFields?.length && ( - <HStack - className="dataviews-view-grid__badge-fields" - spacing={ 2 } - wrap - alignment="top" - justify="flex-start" - > - { badgeFields.map( ( field ) => { - return ( - <FlexItem - key={ field.id } - className="dataviews-view-grid__field-value" - > - <field.render item={ item } /> - </FlexItem> - ); - } ) } - </HStack> - ) } - { !! visibleFields?.length && ( - <VStack className="dataviews-view-grid__fields" spacing={ 1 }> - { visibleFields.map( ( field ) => { - return ( - <Flex - className={ clsx( - 'dataviews-view-grid__field', - columnFields?.includes( field.id ) - ? 'is-column' - : 'is-row' - ) } - key={ field.id } - gap={ 1 } - justify="flex-start" - expanded - style={ { height: 'auto' } } - direction={ - columnFields?.includes( field.id ) - ? 'column' - : 'row' - } - > - <> - <FlexItem className="dataviews-view-grid__field-name"> - { field.header } - </FlexItem> - <FlexItem - className="dataviews-view-grid__field-value" - style={ { maxHeight: 'none' } } - > - <field.render item={ item } /> - </FlexItem> - </> - </Flex> - ); - } ) } - </VStack> - ) } + <VStack spacing={ 1 }> + { showDescription && descriptionField?.render && ( + <descriptionField.render item={ item } /> + ) } + { !! badgeFields?.length && ( + <HStack + className="dataviews-view-grid__badge-fields" + spacing={ 2 } + wrap + alignment="top" + justify="flex-start" + > + { badgeFields.map( ( field ) => { + return ( + <Badge + key={ field.id } + className="dataviews-view-grid__field-value" + > + <field.render item={ item } /> + </Badge> + ); + } ) } + </HStack> + ) } + { !! regularFields?.length && ( + <VStack + className="dataviews-view-grid__fields" + spacing={ 1 } + > + { regularFields.map( ( field ) => { + return ( + <Flex + className="dataviews-view-grid__field" + key={ field.id } + gap={ 1 } + justify="flex-start" + expanded + style={ { height: 'auto' } } + direction="row" + > + <> + <FlexItem className="dataviews-view-grid__field-name"> + { field.header } + </FlexItem> + <FlexItem + className="dataviews-view-grid__field-value" + style={ { maxHeight: 'none' } } + > + <field.render item={ item } /> + </FlexItem> + </> + </Flex> + ); + } ) } + </VStack> + ) } + </VStack> </VStack> ); } @@ -194,39 +236,44 @@ export default function ViewGrid< Item >( { isItemClickable, selection, view, - density, }: ViewGridProps< Item > ) { + const titleField = fields.find( + ( field ) => field.id === view?.titleField + ); const mediaField = fields.find( - ( field ) => field.id === view.layout?.mediaField + ( field ) => field.id === view?.mediaField ); - const primaryField = fields.find( - ( field ) => field.id === view.layout?.primaryField + const descriptionField = fields.find( + ( field ) => field.id === view?.descriptionField ); - const viewFields = view.fields || fields.map( ( field ) => field.id ); - const { visibleFields, badgeFields } = fields.reduce( - ( accumulator: Record< string, NormalizedField< Item >[] >, field ) => { - if ( - ! viewFields.includes( field.id ) || - [ - view.layout?.mediaField, - view?.layout?.primaryField, - ].includes( field.id ) - ) { + const otherFields = view.fields ?? []; + const { regularFields, badgeFields } = otherFields.reduce( + ( + accumulator: Record< string, NormalizedField< Item >[] >, + fieldId + ) => { + const field = fields.find( ( f ) => f.id === fieldId ); + if ( ! field ) { return accumulator; } // If the field is a badge field, add it to the badgeFields array // otherwise add it to the rest visibleFields array. - const key = view.layout?.badgeFields?.includes( field.id ) + const key = view.layout?.badgeFields?.includes( fieldId ) ? 'badgeFields' - : 'visibleFields'; + : 'regularFields'; accumulator[ key ].push( field ); return accumulator; }, - { visibleFields: [], badgeFields: [] } + { regularFields: [], badgeFields: [] } ); const hasData = !! data?.length; - const gridStyle = density - ? { gridTemplateColumns: `repeat(${ density }, minmax(0, 1fr))` } + const updatedPreviewSize = useUpdatedPreviewSizeOnViewportChange(); + const hasBulkActions = useSomeItemHasAPossibleBulkAction( actions, data ); + const usedPreviewSize = updatedPreviewSize || view.layout?.previewSize; + const gridStyle = usedPreviewSize + ? { + gridTemplateColumns: `repeat(${ usedPreviewSize }, minmax(0, 1fr))`, + } : {}; return ( <> @@ -243,6 +290,7 @@ export default function ViewGrid< Item >( { return ( <GridItem key={ getItemId( item ) } + view={ view } selection={ selection } onChangeSelection={ onChangeSelection } onClickItem={ onClickItem } @@ -251,10 +299,11 @@ export default function ViewGrid< Item >( { item={ item } actions={ actions } mediaField={ mediaField } - primaryField={ primaryField } - visibleFields={ visibleFields } + titleField={ titleField } + descriptionField={ descriptionField } + regularFields={ regularFields } badgeFields={ badgeFields } - columnFields={ view.layout?.columnFields } + hasBulkActions={ hasBulkActions } /> ); } ) } diff --git a/packages/dataviews/src/dataviews-layouts/grid/preview-size-picker.tsx b/packages/dataviews/src/dataviews-layouts/grid/preview-size-picker.tsx new file mode 100644 index 00000000000000..027632090b31b4 --- /dev/null +++ b/packages/dataviews/src/dataviews-layouts/grid/preview-size-picker.tsx @@ -0,0 +1,110 @@ +/** + * WordPress dependencies + */ +import { RangeControl } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; +import { useMemo, useContext } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import DataViewsContext from '../../components/dataviews-context'; +import type { ViewGrid } from '../../types'; + +const viewportBreaks: { + [ key: string ]: { min: number; max: number; default: number }; +} = { + xhuge: { min: 3, max: 6, default: 5 }, + huge: { min: 2, max: 4, default: 4 }, + xlarge: { min: 2, max: 3, default: 3 }, + large: { min: 1, max: 2, default: 2 }, + mobile: { min: 1, max: 2, default: 2 }, +}; + +/** + * Breakpoints were adjusted from media queries breakpoints to account for + * the sidebar width. This was done to match the existing styles we had. + */ +const BREAKPOINTS = { + xhuge: 1520, + huge: 1140, + xlarge: 780, + large: 480, + mobile: 0, +}; + +function useViewPortBreakpoint() { + const containerWidth = useContext( DataViewsContext ).containerWidth; + for ( const [ key, value ] of Object.entries( BREAKPOINTS ) ) { + if ( containerWidth >= value ) { + return key; + } + } + return 'mobile'; +} + +export function useUpdatedPreviewSizeOnViewportChange() { + const view = useContext( DataViewsContext ).view as ViewGrid; + const viewport = useViewPortBreakpoint(); + return useMemo( () => { + const previewSize = view.layout?.previewSize; + let newPreviewSize; + if ( ! previewSize ) { + return; + } + const breakValues = viewportBreaks[ viewport ]; + if ( previewSize < breakValues.min ) { + newPreviewSize = breakValues.min; + } + if ( previewSize > breakValues.max ) { + newPreviewSize = breakValues.max; + } + return newPreviewSize; + }, [ viewport, view ] ); +} + +export default function PreviewSizePicker() { + const viewport = useViewPortBreakpoint(); + const context = useContext( DataViewsContext ); + const view = context.view as ViewGrid; + const breakValues = viewportBreaks[ viewport ]; + const previewSizeToUse = view.layout?.previewSize || breakValues.default; + const marks = useMemo( + () => + Array.from( + { length: breakValues.max - breakValues.min + 1 }, + ( _, i ) => { + return { + value: breakValues.min + i, + }; + } + ), + [ breakValues ] + ); + if ( viewport === 'mobile' ) { + return null; + } + return ( + <RangeControl + __nextHasNoMarginBottom + __next40pxDefaultSize + showTooltip={ false } + label={ __( 'Preview size' ) } + value={ breakValues.max + breakValues.min - previewSizeToUse } + marks={ marks } + min={ breakValues.min } + max={ breakValues.max } + withInputField={ false } + onChange={ ( value = 0 ) => { + context.onChangeView( { + ...view, + layout: { + ...view.layout, + previewSize: breakValues.max + breakValues.min - value, + }, + } ); + } } + step={ 1 } + /> + ); +} diff --git a/packages/dataviews/src/dataviews-layouts/grid/style.scss b/packages/dataviews/src/dataviews-layouts/grid/style.scss index 55768240a18714..a741b185572934 100644 --- a/packages/dataviews/src/dataviews-layouts/grid/style.scss +++ b/packages/dataviews/src/dataviews-layouts/grid/style.scss @@ -2,9 +2,11 @@ margin-bottom: auto; grid-template-rows: max-content; padding: 0 $grid-unit-60 $grid-unit-30; - transition: padding ease-out 0.1s; - @include reduce-motion("transition"); + container-type: inline-size; + @media not (prefers-reduced-motion) { + transition: padding ease-out 0.1s; + } .dataviews-view-grid__card { height: 100%; @@ -15,8 +17,10 @@ padding: $grid-unit-10 0 $grid-unit-05; } - .dataviews-view-grid__primary-field { - min-height: $grid-unit-40; // Preserve layout when there is no ellipsis button + .dataviews-view-grid__title-field { + min-height: $grid-unit-30; // Preserve layout when there is no ellipsis button + display: flex; + align-items: center; &--clickable { width: fit-content; @@ -28,11 +32,16 @@ .dataviews-view-grid__fields .dataviews-view-grid__field .dataviews-view-grid__field-value { color: $gray-900; } - - .dataviews-view-grid__media::after { - background-color: rgba(var(--wp-admin-theme-color--rgb), 0.08); - box-shadow: inset 0 0 0 $border-width var(--wp-admin-theme-color); - } + } + &.is-selected .dataviews-view-grid__media::after, + .dataviews-view-grid__media:focus::after { + background-color: rgba(var(--wp-admin-theme-color--rgb), 0.08); + } + &.is-selected .dataviews-view-grid__media::after { + box-shadow: inset 0 0 0 $border-width var(--wp-admin-theme-color); + } + .dataviews-view-grid__media:focus::after { + box-shadow: inset 0 0 0 var(--wp-admin-border-width-focus) var(--wp-admin-theme-color); } } @@ -79,36 +88,23 @@ } .dataviews-view-grid__field { - align-items: flex-start; min-height: $grid-unit-30; + align-items: center; - &:not(:has(.dataviews-view-grid__field-value:not(:empty))) { - display: none; - } - - &:not(.is-column) { - align-items: center; - - .dataviews-view-grid__field-name { - width: 35%; - } - - .dataviews-view-grid__field-value { - width: 65%; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - } + .dataviews-view-grid__field-name { + width: 35%; + color: $gray-700; } - &.is-column { - & + .is-row { - margin-top: $grid-unit-05; - } + .dataviews-view-grid__field-value { + width: 65%; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; } - .dataviews-view-grid__field-name { - color: $gray-700; + &:not(:has(.dataviews-view-grid__field-value:not(:empty))) { + display: none; } } } @@ -117,36 +113,29 @@ &:not(:empty) { padding-bottom: $grid-unit-15; } - - .dataviews-view-grid__field-value { - width: fit-content; - background: $gray-100; - padding: 0 $grid-unit-10; - min-height: $grid-unit-30; - border-radius: $radius-small; - display: flex; - align-items: center; - font-size: 12px; - } } } .dataviews-view-grid.dataviews-view-grid { - grid-template-columns: repeat(1, minmax(0, 1fr)); - - @include break-mobile() { + /** + * Breakpoints were adjusted from media queries breakpoints to account for + * the sidebar width. This was done to match the existing styles we had. + */ + @container (max-width: 480px) { + grid-template-columns: repeat(1, minmax(0, 1fr)); + padding-left: $grid-unit-30; + padding-right: $grid-unit-30; + } + @container (min-width: 480px) { grid-template-columns: repeat(2, minmax(0, 1fr)); } - - @include break-xlarge() { + @container (min-width: 780px) { grid-template-columns: repeat(3, minmax(0, 1fr)); } - - @include break-huge() { + @container (min-width: 1140px) { grid-template-columns: repeat(4, minmax(0, 1fr)); } - - @include break-xhuge() { + @container (min-width: 1520px) { grid-template-columns: repeat(5, minmax(0, 1fr)); } } @@ -169,10 +158,6 @@ top: $grid-unit-10; } -/* stylelint-disable-next-line scss/at-rule-no-unknown -- '@container' not globally permitted */ -@container (max-width: 430px) { - .dataviews-view-grid { - padding-left: $grid-unit-30; - padding-right: $grid-unit-30; - } +.dataviews-view-grid__media--clickable { + cursor: pointer; } diff --git a/packages/dataviews/src/dataviews-layouts/index.ts b/packages/dataviews/src/dataviews-layouts/index.ts index eece17d0f4f10c..f2455f81f1560c 100644 --- a/packages/dataviews/src/dataviews-layouts/index.ts +++ b/packages/dataviews/src/dataviews-layouts/index.ts @@ -16,7 +16,8 @@ import ViewTable from './table'; import ViewGrid from './grid'; import ViewList from './list'; import { LAYOUT_GRID, LAYOUT_LIST, LAYOUT_TABLE } from '../constants'; -import type { View, Field } from '../types'; +import PreviewSizePicker from './grid/preview-size-picker'; +import DensityPicker from './table/density-picker'; export const VIEW_LAYOUTS = [ { @@ -24,12 +25,14 @@ export const VIEW_LAYOUTS = [ label: __( 'Table' ), component: ViewTable, icon: blockTable, + viewConfigOptions: DensityPicker, }, { type: LAYOUT_GRID, label: __( 'Grid' ), component: ViewGrid, icon: category, + viewConfigOptions: PreviewSizePicker, }, { type: LAYOUT_LIST, @@ -38,90 +41,3 @@ export const VIEW_LAYOUTS = [ icon: isRTL() ? formatListBulletsRTL : formatListBullets, }, ]; - -export function getNotHidableFieldIds( view: View ): string[] { - if ( view.type === 'table' ) { - return [ view.layout?.primaryField ] - .concat( - view.layout?.combinedFields?.flatMap( - ( field ) => field.children - ) ?? [] - ) - .filter( ( item ): item is string => !! item ); - } - - if ( view.type === 'grid' ) { - return [ view.layout?.primaryField, view.layout?.mediaField ].filter( - ( item ): item is string => !! item - ); - } - - if ( view.type === 'list' ) { - return [ view.layout?.primaryField, view.layout?.mediaField ].filter( - ( item ): item is string => !! item - ); - } - - return []; -} - -function getCombinedFieldIds( view: View ): string[] { - const combinedFields: string[] = []; - if ( view.type === LAYOUT_TABLE && view.layout?.combinedFields ) { - view.layout.combinedFields.forEach( ( combination ) => { - combinedFields.push( ...combination.children ); - } ); - } - return combinedFields; -} - -export function getVisibleFieldIds( - view: View, - fields: Field< any >[] -): string[] { - const fieldsToExclude = getCombinedFieldIds( view ); - - if ( view.fields ) { - return view.fields.filter( ( id ) => ! fieldsToExclude.includes( id ) ); - } - - const visibleFields = []; - if ( view.type === LAYOUT_TABLE && view.layout?.combinedFields ) { - visibleFields.push( - ...view.layout.combinedFields.map( ( { id } ) => id ) - ); - } - visibleFields.push( - ...fields - .filter( ( { id } ) => ! fieldsToExclude.includes( id ) ) - .map( ( { id } ) => id ) - ); - - return visibleFields; -} - -export function getHiddenFieldIds( - view: View, - fields: Field< any >[] -): string[] { - const fieldsToExclude = [ - ...getCombinedFieldIds( view ), - ...getVisibleFieldIds( view, fields ), - ]; - - // The media field does not need to be in the view.fields to be displayed. - if ( view.type === LAYOUT_GRID && view.layout?.mediaField ) { - fieldsToExclude.push( view.layout?.mediaField ); - } - - if ( view.type === LAYOUT_LIST && view.layout?.mediaField ) { - fieldsToExclude.push( view.layout?.mediaField ); - } - - return fields - .filter( - ( { id, enableHiding } ) => - ! fieldsToExclude.includes( id ) && enableHiding - ) - .map( ( { id } ) => id ); -} diff --git a/packages/dataviews/src/dataviews-layouts/list/index.tsx b/packages/dataviews/src/dataviews-layouts/list/index.tsx index a4f94e482c69b3..dadc53b5d733a7 100644 --- a/packages/dataviews/src/dataviews-layouts/list/index.tsx +++ b/packages/dataviews/src/dataviews-layouts/list/index.tsx @@ -35,17 +35,25 @@ import { ActionsMenuGroup, ActionModal, } from '../../components/dataviews-item-actions'; -import type { Action, NormalizedField, ViewListProps } from '../../types'; +import type { + Action, + NormalizedField, + ViewList as ViewListType, + ViewListProps, + ActionModal as ActionModalType, +} from '../../types'; interface ListViewItemProps< Item > { + view: ViewListType; actions: Action< Item >[]; idPrefix: string; isSelected: boolean; item: Item; + titleField?: NormalizedField< Item >; mediaField?: NormalizedField< Item >; + descriptionField?: NormalizedField< Item >; onSelect: ( item: Item ) => void; - primaryField?: NormalizedField< Item >; - visibleFields: NormalizedField< Item >[]; + otherFields: NormalizedField< Item >[]; onDropdownTriggerKeyDown: React.KeyboardEventHandler< HTMLButtonElement >; } @@ -93,6 +101,8 @@ function PrimaryActionGridCell< Item >( { render={ <Button label={ label } + disabled={ !! primaryAction.disabled } + accessibleWhenDisabled icon={ primaryAction.icon } isDestructive={ primaryAction.isDestructive } size="small" @@ -116,6 +126,8 @@ function PrimaryActionGridCell< Item >( { render={ <Button label={ label } + disabled={ !! primaryAction.disabled } + accessibleWhenDisabled icon={ primaryAction.icon } isDestructive={ primaryAction.isDestructive } size="small" @@ -130,21 +142,28 @@ function PrimaryActionGridCell< Item >( { } function ListItem< Item >( { + view, actions, idPrefix, isSelected, item, + titleField, mediaField, + descriptionField, onSelect, - primaryField, - visibleFields, + otherFields, onDropdownTriggerKeyDown, }: ListViewItemProps< Item > ) { + const { showTitle = true, showMedia = true, showDescription = true } = view; const itemRef = useRef< HTMLDivElement >( null ); const labelId = `${ idPrefix }-label`; const descriptionId = `${ idPrefix }-description`; + const registry = useRegistry(); const [ isHovered, setIsHovered ] = useState( false ); + const [ activeModalAction, setActiveModalAction ] = useState( + null as ActionModalType< Item > | null + ); const handleHover: React.MouseEventHandler = ( { type } ) => { const isHover = type === 'mouseenter'; setIsHovered( isHover ); @@ -170,20 +189,24 @@ function ListItem< Item >( { ( action ) => action.isPrimary && !! action.icon ); return { - primaryAction: _primaryActions?.[ 0 ], + primaryAction: _primaryActions[ 0 ], eligibleActions: _eligibleActions, }; }, [ actions, item ] ); - const renderedMediaField = mediaField?.render ? ( - <div className="dataviews-view-list__media-wrapper"> - <mediaField.render item={ item } /> - </div> - ) : null; + const hasOnlyOnePrimaryAction = primaryAction && actions.length === 1; + + const renderedMediaField = + showMedia && mediaField?.render ? ( + <div className="dataviews-view-list__media-wrapper"> + <mediaField.render item={ item } /> + </div> + ) : null; - const renderedPrimaryField = primaryField?.render ? ( - <primaryField.render item={ item } /> - ) : null; + const renderedTitleField = + showTitle && titleField?.render ? ( + <titleField.render item={ item } /> + ) : null; const usedActions = eligibleActions?.length > 0 && ( <HStack spacing={ 3 } className="dataviews-view-list__item-actions"> @@ -194,40 +217,55 @@ function ListItem< Item >( { item={ item } /> ) } - <div role="gridcell"> - <Menu - trigger={ - <Composite.Item - id={ generateDropdownTriggerCompositeId( - idPrefix - ) } + { ! hasOnlyOnePrimaryAction && ( + <div role="gridcell"> + <Menu placement="bottom-end"> + <Menu.TriggerButton render={ - <Button - size="small" - icon={ moreVertical } - label={ __( 'Actions' ) } - accessibleWhenDisabled - disabled={ ! actions.length } - onKeyDown={ onDropdownTriggerKeyDown } + <Composite.Item + id={ generateDropdownTriggerCompositeId( + idPrefix + ) } + render={ + <Button + size="small" + icon={ moreVertical } + label={ __( 'Actions' ) } + accessibleWhenDisabled + disabled={ ! actions.length } + onKeyDown={ + onDropdownTriggerKeyDown + } + /> + } /> } /> - } - placement="bottom-end" - > - <ActionsMenuGroup - actions={ eligibleActions } - item={ item } - /> - </Menu> - </div> + <Menu.Popover> + <ActionsMenuGroup + actions={ eligibleActions } + item={ item } + registry={ registry } + setActiveModalAction={ setActiveModalAction } + /> + </Menu.Popover> + </Menu> + { !! activeModalAction && ( + <ActionModal + action={ activeModalAction } + items={ [ item ] } + closeModal={ () => setActiveModalAction( null ) } + /> + ) } + </div> + ) } </HStack> ); return ( <Composite.Row ref={ itemRef } - render={ <li /> } + render={ <div /> } role="row" className={ clsx( { 'is-selected': isSelected, @@ -255,18 +293,23 @@ function ListItem< Item >( { > <HStack spacing={ 0 }> <div - className="dataviews-view-list__primary-field" + className="dataviews-title-field" id={ labelId } > - { renderedPrimaryField } + { renderedTitleField } </div> { usedActions } </HStack> + { showDescription && descriptionField?.render && ( + <div className="dataviews-view-list__field"> + <descriptionField.render item={ item } /> + </div> + ) } <div className="dataviews-view-list__fields" id={ descriptionId } > - { visibleFields.map( ( field ) => ( + { otherFields.map( ( field ) => ( <div key={ field.id } className="dataviews-view-list__field" @@ -290,6 +333,10 @@ function ListItem< Item >( { ); } +function isDefined< T >( item: T | undefined ): item is T { + return !! item; +} + export default function ViewList< Item >( props: ViewListProps< Item > ) { const { actions, @@ -306,21 +353,14 @@ export default function ViewList< Item >( props: ViewListProps< Item > ) { const selectedItem = data?.findLast( ( item ) => selection.includes( getItemId( item ) ) ); - - const mediaField = fields.find( - ( field ) => field.id === view.layout?.mediaField - ); - const primaryField = fields.find( - ( field ) => field.id === view.layout?.primaryField - ); - const viewFields = view.fields || fields.map( ( field ) => field.id ); - const visibleFields = fields.filter( - ( field ) => - viewFields.includes( field.id ) && - ! [ view.layout?.primaryField, view.layout?.mediaField ].includes( - field.id - ) + const titleField = fields.find( ( field ) => field.id === view.titleField ); + const mediaField = fields.find( ( field ) => field.id === view.mediaField ); + const descriptionField = fields.find( + ( field ) => field.id === view.descriptionField ); + const otherFields = ( view?.fields ?? [] ) + .map( ( fieldId ) => fields.find( ( f ) => fieldId === f.id ) ) + .filter( isDefined ); const onSelect = ( item: Item ) => onChangeSelection( [ getItemId( item ) ] ); @@ -450,7 +490,7 @@ export default function ViewList< Item >( props: ViewListProps< Item > ) { return ( <Composite id={ baseId } - render={ <ul /> } + render={ <div /> } className="dataviews-view-list" role="grid" activeId={ activeCompositeId } @@ -461,14 +501,16 @@ export default function ViewList< Item >( props: ViewListProps< Item > ) { return ( <ListItem key={ id } + view={ view } idPrefix={ id } actions={ actions } item={ item } isSelected={ item === selectedItem } onSelect={ onSelect } mediaField={ mediaField } - primaryField={ primaryField } - visibleFields={ visibleFields } + titleField={ titleField } + descriptionField={ descriptionField } + otherFields={ otherFields } onDropdownTriggerKeyDown={ onDropdownTriggerKeyDown } /> ); diff --git a/packages/dataviews/src/dataviews-layouts/list/style.scss b/packages/dataviews/src/dataviews-layouts/list/style.scss index 8fe048cab77b51..e892006faecb00 100644 --- a/packages/dataviews/src/dataviews-layouts/list/style.scss +++ b/packages/dataviews/src/dataviews-layouts/list/style.scss @@ -1,17 +1,18 @@ -ul.dataviews-view-list { +div.dataviews-view-list { list-style-type: none; } .dataviews-view-list { margin: 0 0 auto; - li { + div[role="row"] { margin: 0; border-top: 1px solid $gray-100; .dataviews-view-list__item-wrapper { position: relative; padding: $grid-unit-20 $grid-unit-30; + box-sizing: border-box; } .dataviews-view-list__item-actions { @@ -44,13 +45,13 @@ ul.dataviews-view-list { &.is-selected.is-selected { border-top: 1px solid rgba(var(--wp-admin-theme-color--rgb), 0.12); - & + li { + & + div[role="row"] { border-top: 1px solid rgba(var(--wp-admin-theme-color--rgb), 0.12); } } &:not(.is-selected) { - .dataviews-view-list__primary-field { + .dataviews-view-list__title-field { color: $gray-900; } &:hover, @@ -59,7 +60,7 @@ ul.dataviews-view-list { color: var(--wp-admin-theme-color); background-color: #f8f8f8; - .dataviews-view-list__primary-field, + .dataviews-view-list__title-field, .dataviews-view-list__fields { color: var(--wp-admin-theme-color); } @@ -68,13 +69,13 @@ ul.dataviews-view-list { } - li.is-selected, - li.is-selected:focus-within { + div[role="row"].is-selected, + div[role="row"].is-selected:focus-within { .dataviews-view-list__item-wrapper { background-color: rgba(var(--wp-admin-theme-color--rgb), 0.04); color: $gray-900; - .dataviews-view-list__primary-field, + .dataviews-view-list__title-field, .dataviews-view-list__fields { color: var(--wp-admin-theme-color); } @@ -106,7 +107,7 @@ ul.dataviews-view-list { } } } - .dataviews-view-list__primary-field { + .dataviews-view-list__title-field { flex: 1; min-height: $grid-unit-30; line-height: $grid-unit-30; diff --git a/packages/dataviews/src/dataviews-layouts/table/column-header-menu.tsx b/packages/dataviews/src/dataviews-layouts/table/column-header-menu.tsx index 7071e54620f369..1d8d22193bbd07 100644 --- a/packages/dataviews/src/dataviews-layouts/table/column-header-menu.tsx +++ b/packages/dataviews/src/dataviews-layouts/table/column-header-menu.tsx @@ -27,7 +27,6 @@ import type { ViewTable as ViewTableType, Operator, } from '../../types'; -import { getVisibleFieldIds } from '../index'; const { Menu } = unlock( componentsPrivateApis ); @@ -38,6 +37,7 @@ interface HeaderMenuProps< Item > { onChangeView: ( view: ViewTableType ) => void; onHide: ( field: NormalizedField< Item > ) => void; setOpenedFilter: ( fieldId: string ) => void; + canMove?: boolean; } function WithMenuSeparators( { children }: { children: ReactNode } ) { @@ -59,194 +59,206 @@ const _HeaderMenu = forwardRef( function HeaderMenu< Item >( onChangeView, onHide, setOpenedFilter, + canMove = true, }: HeaderMenuProps< Item >, ref: Ref< HTMLButtonElement > ) { - const visibleFieldIds = getVisibleFieldIds( view, fields ); + const visibleFieldIds = view.fields ?? []; const index = visibleFieldIds?.indexOf( fieldId ) as number; const isSorted = view.sort?.field === fieldId; let isHidable = false; let isSortable = false; let canAddFilter = false; - let header; let operators: Operator[] = []; - - const combinedField = view.layout?.combinedFields?.find( - ( f ) => f.id === fieldId - ); const field = fields.find( ( f ) => f.id === fieldId ); - if ( ! combinedField ) { - if ( ! field ) { - // No combined or regular field found. - return null; - } + if ( ! field ) { + // No combined or regular field found. + return null; + } - isHidable = field.enableHiding !== false; - isSortable = field.enableSorting !== false; - header = field.header; + isHidable = field.enableHiding !== false; + isSortable = field.enableSorting !== false; + const header = field.header; - operators = sanitizeOperators( field ); - // Filter can be added: - // 1. If the field is not already part of a view's filters. - // 2. If the field meets the type and operator requirements. - // 3. If it's not primary. If it is, it should be already visible. - canAddFilter = - ! view.filters?.some( ( _filter ) => fieldId === _filter.field ) && - !! field.elements?.length && - !! operators.length && - ! field.filterBy?.isPrimary; - } else { - header = combinedField.header || combinedField.label; - } + operators = sanitizeOperators( field ); + // Filter can be added: + // 1. If the field is not already part of a view's filters. + // 2. If the field meets the type and operator requirements. + // 3. If it's not primary. If it is, it should be already visible. + canAddFilter = + ! view.filters?.some( ( _filter ) => fieldId === _filter.field ) && + !! field.elements?.length && + !! operators.length && + ! field.filterBy?.isPrimary; return ( - <Menu - align="start" - trigger={ - <Button - size="compact" - className="dataviews-view-table-header-button" - ref={ ref } - variant="tertiary" - > - { header } - { view.sort && isSorted && ( - <span aria-hidden="true"> - { sortArrows[ view.sort.direction ] } - </span> - ) } - </Button> - } - style={ { minWidth: '240px' } } - > - <WithMenuSeparators> - { isSortable && ( - <Menu.Group> - { SORTING_DIRECTIONS.map( - ( direction: SortDirection ) => { - const isChecked = - view.sort && - isSorted && - view.sort.direction === direction; + <Menu> + <Menu.TriggerButton + render={ + <Button + size="compact" + className="dataviews-view-table-header-button" + ref={ ref } + variant="tertiary" + /> + } + > + { header } + { view.sort && isSorted && ( + <span aria-hidden="true"> + { sortArrows[ view.sort.direction ] } + </span> + ) } + </Menu.TriggerButton> + <Menu.Popover style={ { minWidth: '240px' } }> + <WithMenuSeparators> + { isSortable && ( + <Menu.Group> + { SORTING_DIRECTIONS.map( + ( direction: SortDirection ) => { + const isChecked = + view.sort && + isSorted && + view.sort.direction === direction; - const value = `${ fieldId }-${ direction }`; + const value = `${ fieldId }-${ direction }`; - return ( - <Menu.RadioItem - key={ value } - // All sorting radio items share the same name, so that - // selecting a sorting option automatically deselects the - // previously selected one, even if it is displayed in - // another submenu. The field and direction are passed via - // the `value` prop. - name="view-table-sorting" - value={ value } - checked={ isChecked } - onChange={ () => { - onChangeView( { - ...view, - sort: { - field: fieldId, - direction, - }, - } ); - } } - > - <Menu.ItemLabel> - { sortLabels[ direction ] } - </Menu.ItemLabel> - </Menu.RadioItem> - ); - } - ) } - </Menu.Group> - ) } - { canAddFilter && ( - <Menu.Group> - <Menu.Item - prefix={ <Icon icon={ funnel } /> } - onClick={ () => { - setOpenedFilter( fieldId ); - onChangeView( { - ...view, - page: 1, - filters: [ - ...( view.filters || [] ), - { - field: fieldId, - value: undefined, - operator: operators[ 0 ], - }, - ], - } ); - } } - > - <Menu.ItemLabel> - { __( 'Add filter' ) } - </Menu.ItemLabel> - </Menu.Item> - </Menu.Group> - ) } - <Menu.Group> - <Menu.Item - prefix={ <Icon icon={ arrowLeft } /> } - disabled={ index < 1 } - onClick={ () => { - onChangeView( { - ...view, - fields: [ - ...( visibleFieldIds.slice( - 0, - index - 1 - ) ?? [] ), - fieldId, - visibleFieldIds[ index - 1 ], - ...visibleFieldIds.slice( index + 1 ), - ], - } ); - } } - > - <Menu.ItemLabel>{ __( 'Move left' ) }</Menu.ItemLabel> - </Menu.Item> - <Menu.Item - prefix={ <Icon icon={ arrowRight } /> } - disabled={ index >= visibleFieldIds.length - 1 } - onClick={ () => { - onChangeView( { - ...view, - fields: [ - ...( visibleFieldIds.slice( 0, index ) ?? - [] ), - visibleFieldIds[ index + 1 ], - fieldId, - ...visibleFieldIds.slice( index + 2 ), - ], - } ); - } } - > - <Menu.ItemLabel>{ __( 'Move right' ) }</Menu.ItemLabel> - </Menu.Item> - { isHidable && field && ( - <Menu.Item - prefix={ <Icon icon={ unseen } /> } - onClick={ () => { - onHide( field ); - onChangeView( { - ...view, - fields: visibleFieldIds.filter( - ( id ) => id !== fieldId - ), - } ); - } } - > - <Menu.ItemLabel> - { __( 'Hide column' ) } - </Menu.ItemLabel> - </Menu.Item> + return ( + <Menu.RadioItem + key={ value } + // All sorting radio items share the same name, so that + // selecting a sorting option automatically deselects the + // previously selected one, even if it is displayed in + // another submenu. The field and direction are passed via + // the `value` prop. + name="view-table-sorting" + value={ value } + checked={ isChecked } + onChange={ () => { + onChangeView( { + ...view, + sort: { + field: fieldId, + direction, + }, + showLevels: false, + } ); + } } + > + <Menu.ItemLabel> + { sortLabels[ direction ] } + </Menu.ItemLabel> + </Menu.RadioItem> + ); + } + ) } + </Menu.Group> + ) } + { canAddFilter && ( + <Menu.Group> + <Menu.Item + prefix={ <Icon icon={ funnel } /> } + onClick={ () => { + setOpenedFilter( fieldId ); + onChangeView( { + ...view, + page: 1, + filters: [ + ...( view.filters || [] ), + { + field: fieldId, + value: undefined, + operator: operators[ 0 ], + }, + ], + } ); + } } + > + <Menu.ItemLabel> + { __( 'Add filter' ) } + </Menu.ItemLabel> + </Menu.Item> + </Menu.Group> + ) } + { ( canMove || isHidable ) && field && ( + <Menu.Group> + { canMove && ( + <Menu.Item + prefix={ <Icon icon={ arrowLeft } /> } + disabled={ index < 1 } + onClick={ () => { + onChangeView( { + ...view, + fields: [ + ...( visibleFieldIds.slice( + 0, + index - 1 + ) ?? [] ), + fieldId, + visibleFieldIds[ index - 1 ], + ...visibleFieldIds.slice( + index + 1 + ), + ], + } ); + } } + > + <Menu.ItemLabel> + { __( 'Move left' ) } + </Menu.ItemLabel> + </Menu.Item> + ) } + { canMove && ( + <Menu.Item + prefix={ <Icon icon={ arrowRight } /> } + disabled={ + index >= visibleFieldIds.length - 1 + } + onClick={ () => { + onChangeView( { + ...view, + fields: [ + ...( visibleFieldIds.slice( + 0, + index + ) ?? [] ), + visibleFieldIds[ index + 1 ], + fieldId, + ...visibleFieldIds.slice( + index + 2 + ), + ], + } ); + } } + > + <Menu.ItemLabel> + { __( 'Move right' ) } + </Menu.ItemLabel> + </Menu.Item> + ) } + { isHidable && field && ( + <Menu.Item + prefix={ <Icon icon={ unseen } /> } + onClick={ () => { + onHide( field ); + onChangeView( { + ...view, + fields: visibleFieldIds.filter( + ( id ) => id !== fieldId + ), + } ); + } } + > + <Menu.ItemLabel> + { __( 'Hide column' ) } + </Menu.ItemLabel> + </Menu.Item> + ) } + </Menu.Group> ) } - </Menu.Group> - </WithMenuSeparators> + </WithMenuSeparators> + </Menu.Popover> </Menu> ); } ); diff --git a/packages/dataviews/src/dataviews-layouts/table/column-primary.tsx b/packages/dataviews/src/dataviews-layouts/table/column-primary.tsx new file mode 100644 index 00000000000000..6ac4057b0973ba --- /dev/null +++ b/packages/dataviews/src/dataviews-layouts/table/column-primary.tsx @@ -0,0 +1,65 @@ +/** + * WordPress dependencies + */ +import { + __experimentalHStack as HStack, + __experimentalVStack as VStack, +} from '@wordpress/components'; + +/** + * Internal dependencies + */ +import type { NormalizedField } from '../../types'; +import getClickableItemProps from '../utils/get-clickable-item-props'; + +function ColumnPrimary< Item >( { + item, + level, + titleField, + mediaField, + descriptionField, + onClickItem, + isItemClickable, +}: { + item: Item; + level?: number; + titleField?: NormalizedField< Item >; + mediaField?: NormalizedField< Item >; + descriptionField?: NormalizedField< Item >; + onClickItem?: ( item: Item ) => void; + isItemClickable: ( item: Item ) => boolean; +} ) { + const clickableProps = getClickableItemProps( { + item, + isItemClickable, + onClickItem, + className: + 'dataviews-view-table__cell-content-wrapper dataviews-title-field', + } ); + return ( + <HStack spacing={ 3 } justify="flex-start"> + { mediaField && ( + <div className="dataviews-view-table__cell-content-wrapper dataviews-column-primary__media"> + <mediaField.render item={ item } /> + </div> + ) } + <VStack spacing={ 0 }> + { titleField && ( + <div { ...clickableProps }> + { level !== undefined && ( + <span className="dataviews-view-table__level"> + { '—'.repeat( level ) }&nbsp; + </span> + ) } + <titleField.render item={ item } /> + </div> + ) } + { descriptionField && ( + <descriptionField.render item={ item } /> + ) } + </VStack> + </HStack> + ); +} + +export default ColumnPrimary; diff --git a/packages/dataviews/src/dataviews-layouts/table/density-picker.tsx b/packages/dataviews/src/dataviews-layouts/table/density-picker.tsx new file mode 100644 index 00000000000000..6d3d31aeb7345e --- /dev/null +++ b/packages/dataviews/src/dataviews-layouts/table/density-picker.tsx @@ -0,0 +1,57 @@ +/** + * WordPress dependencies + */ +import { + __experimentalToggleGroupControl as ToggleGroupControl, + __experimentalToggleGroupControlOption as ToggleGroupControlOption, +} from '@wordpress/components'; +import { __, _x } from '@wordpress/i18n'; +import { useContext } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import DataViewsContext from '../../components/dataviews-context'; +import type { ViewTable, Density } from '../../types'; + +export default function DensityPicker() { + const context = useContext( DataViewsContext ); + const view = context.view as ViewTable; + return ( + <ToggleGroupControl + __nextHasNoMarginBottom + size="__unstable-large" + label={ __( 'Density' ) } + value={ view.layout?.density || 'balanced' } + onChange={ ( value ) => { + context.onChangeView( { + ...view, + layout: { + ...view.layout, + density: value as Density, + }, + } ); + } } + isBlock + > + <ToggleGroupControlOption + key="comfortable" + value="comfortable" + label={ _x( + 'Comfortable', + 'Density option for DataView layout' + ) } + /> + <ToggleGroupControlOption + key="balanced" + value="balanced" + label={ _x( 'Balanced', 'Density option for DataView layout' ) } + /> + <ToggleGroupControlOption + key="compact" + value="compact" + label={ _x( 'Compact', 'Density option for DataView layout' ) } + /> + </ToggleGroupControl> + ); +} diff --git a/packages/dataviews/src/dataviews-layouts/table/index.tsx b/packages/dataviews/src/dataviews-layouts/table/index.tsx index 8ef41db1c38798..8e69e7353ce921 100644 --- a/packages/dataviews/src/dataviews-layouts/table/index.tsx +++ b/packages/dataviews/src/dataviews-layouts/table/index.tsx @@ -7,17 +7,13 @@ import clsx from 'clsx'; * WordPress dependencies */ import { __ } from '@wordpress/i18n'; -import { - Spinner, - __experimentalHStack as HStack, - __experimentalVStack as VStack, -} from '@wordpress/components'; +import { Spinner } from '@wordpress/components'; import { useEffect, useId, useRef, useState } from '@wordpress/element'; /** * Internal dependencies */ -import SingleSelectionCheckbox from '../../components/dataviews-selection-checkbox'; +import DataViewsSelectionCheckbox from '../../components/dataviews-selection-checkbox'; import ItemActions from '../../components/dataviews-item-actions'; import { sortValues } from '../../constants'; import { @@ -30,136 +26,64 @@ import type { NormalizedField, ViewTable as ViewTableType, ViewTableProps, - CombinedField, } from '../../types'; import type { SetSelection } from '../../private-types'; import ColumnHeaderMenu from './column-header-menu'; -import { getVisibleFieldIds } from '../index'; -import getClickableItemProps from '../utils/get-clickable-item-props'; +import ColumnPrimary from './column-primary'; interface TableColumnFieldProps< Item > { - primaryField?: NormalizedField< Item >; - field: NormalizedField< Item >; - item: Item; - isItemClickable: ( item: Item ) => boolean; - onClickItem: ( item: Item ) => void; -} - -interface TableColumnCombinedProps< Item > { - primaryField?: NormalizedField< Item >; - fields: NormalizedField< Item >[]; - field: CombinedField; - item: Item; - view: ViewTableType; - isItemClickable: ( item: Item ) => boolean; - onClickItem: ( item: Item ) => void; -} - -interface TableColumnProps< Item > { - primaryField?: NormalizedField< Item >; fields: NormalizedField< Item >[]; - item: Item; column: string; - view: ViewTableType; - isItemClickable: ( item: Item ) => boolean; - onClickItem: ( item: Item ) => void; + item: Item; } interface TableRowProps< Item > { hasBulkActions: boolean; item: Item; + level?: number; actions: Action< Item >[]; fields: NormalizedField< Item >[]; id: string; view: ViewTableType; - primaryField?: NormalizedField< Item >; + titleField?: NormalizedField< Item >; + mediaField?: NormalizedField< Item >; + descriptionField?: NormalizedField< Item >; selection: string[]; getItemId: ( item: Item ) => string; onChangeSelection: SetSelection; isItemClickable: ( item: Item ) => boolean; - onClickItem: ( item: Item ) => void; -} - -function TableColumn< Item >( { - column, - fields, - view, - ...props -}: TableColumnProps< Item > ) { - const field = fields.find( ( f ) => f.id === column ); - if ( !! field ) { - return <TableColumnField { ...props } field={ field } />; - } - const combinedField = view.layout?.combinedFields?.find( - ( f ) => f.id === column - ); - if ( !! combinedField ) { - return ( - <TableColumnCombined - { ...props } - fields={ fields } - view={ view } - field={ combinedField } - /> - ); - } - - return null; + onClickItem?: ( item: Item ) => void; } function TableColumnField< Item >( { - primaryField, item, - field, - isItemClickable, - onClickItem, + fields, + column, }: TableColumnFieldProps< Item > ) { - const isPrimaryField = primaryField?.id === field.id; - const isItemClickableField = ( i: Item ) => - isItemClickable( i ) && isPrimaryField; + const field = fields.find( ( f ) => f.id === column ); - const clickableProps = getClickableItemProps( - item, - isItemClickableField, - onClickItem, - 'dataviews-view-table__cell-content' - ); + if ( ! field ) { + return null; + } return ( - <div - className={ clsx( 'dataviews-view-table__cell-content-wrapper', { - 'dataviews-view-table__primary-field': isPrimaryField, - } ) } - > - <div { ...clickableProps }> - <field.render { ...{ item } } /> - </div> + <div className="dataviews-view-table__cell-content-wrapper"> + <field.render { ...{ item } } /> </div> ); } -function TableColumnCombined< Item >( { - field, - ...props -}: TableColumnCombinedProps< Item > ) { - const children = field.children.map( ( child ) => ( - <TableColumn key={ child } { ...props } column={ child } /> - ) ); - - if ( field.direction === 'horizontal' ) { - return <HStack spacing={ 3 }>{ children }</HStack>; - } - return <VStack spacing={ 0 }>{ children }</VStack>; -} - function TableRow< Item >( { hasBulkActions, item, + level, actions, fields, id, view, - primaryField, + titleField, + mediaField, + descriptionField, selection, getItemId, isItemClickable, @@ -169,7 +93,7 @@ function TableRow< Item >( { const hasPossibleBulkAction = useHasAPossibleBulkAction( actions, item ); const isSelected = hasPossibleBulkAction && selection.includes( id ); const [ isHovered, setIsHovered ] = useState( false ); - + const { showTitle = true, showMedia = true, showDescription = true } = view; const handleMouseEnter = () => { setIsHovered( true ); }; @@ -181,7 +105,11 @@ function TableRow< Item >( { // `onClick` and can be used to exclude touchscreen devices from certain // behaviours. const isTouchDeviceRef = useRef( false ); - const columns = getVisibleFieldIds( view, fields ); + const columns = view.fields ?? []; + const hasPrimaryColumn = + ( titleField && showTitle ) || + ( mediaField && showMedia ) || + ( descriptionField && showDescription ); return ( <tr @@ -219,32 +147,43 @@ function TableRow< Item >( { } } > <div className="dataviews-view-table__cell-content-wrapper"> - <SingleSelectionCheckbox + <DataViewsSelectionCheckbox item={ item } selection={ selection } onChangeSelection={ onChangeSelection } getItemId={ getItemId } - primaryField={ primaryField } + titleField={ titleField } disabled={ ! hasPossibleBulkAction } /> </div> </td> ) } + { hasPrimaryColumn && ( + <td> + <ColumnPrimary + item={ item } + level={ level } + titleField={ showTitle ? titleField : undefined } + mediaField={ showMedia ? mediaField : undefined } + descriptionField={ + showDescription ? descriptionField : undefined + } + isItemClickable={ isItemClickable } + onClickItem={ onClickItem } + /> + </td> + ) } { columns.map( ( column: string ) => { - // Explicits picks the supported styles. + // Explicit picks the supported styles. const { width, maxWidth, minWidth } = view.layout?.styles?.[ column ] ?? {}; return ( <td key={ column } style={ { width, maxWidth, minWidth } }> - <TableColumn - primaryField={ primaryField } - isItemClickable={ isItemClickable } - onClickItem={ onClickItem } + <TableColumnField fields={ fields } item={ item } column={ column } - view={ view } /> </td> ); @@ -274,6 +213,7 @@ function ViewTable< Item >( { data, fields, getItemId, + getItemLevel, isLoading = false, onChangeView, onChangeSelection, @@ -318,17 +258,41 @@ function ViewTable< Item >( { setNextHeaderMenuToFocus( fallback?.node ); }; - const columns = getVisibleFieldIds( view, fields ); const hasData = !! data?.length; - const primaryField = fields.find( - ( field ) => field.id === view.layout?.primaryField + const titleField = fields.find( ( field ) => field.id === view.titleField ); + const mediaField = fields.find( ( field ) => field.id === view.mediaField ); + const descriptionField = fields.find( + ( field ) => field.id === view.descriptionField ); + const { showTitle = true, showMedia = true, showDescription = true } = view; + const hasPrimaryColumn = + ( titleField && showTitle ) || + ( mediaField && showMedia ) || + ( descriptionField && showDescription ); + const columns = view.fields ?? []; + const headerMenuRef = + ( column: string, index: number ) => ( node: HTMLButtonElement ) => { + if ( node ) { + headerMenuRefs.current.set( column, { + node, + fallback: columns[ index > 0 ? index - 1 : 1 ], + } ); + } else { + headerMenuRefs.current.delete( column ); + } + }; return ( <> <table - className="dataviews-view-table" + className={ clsx( 'dataviews-view-table', { + [ `has-${ view.layout?.density }-density` ]: + view.layout?.density && + [ 'compact', 'comfortable' ].includes( + view.layout.density + ), + } ) } aria-busy={ isLoading } aria-describedby={ tableNoticeId } > @@ -351,8 +315,29 @@ function ViewTable< Item >( { /> </th> ) } + { hasPrimaryColumn && ( + <th scope="col"> + <span className="dataviews-view-table-header"> + { titleField && ( + <ColumnHeaderMenu + ref={ headerMenuRef( + titleField.id, + 0 + ) } + fieldId={ titleField.id } + view={ view } + fields={ fields } + onChangeView={ onChangeView } + onHide={ onHide } + setOpenedFilter={ setOpenedFilter } + canMove={ false } + /> + ) } + </span> + </th> + ) } { columns.map( ( column, index ) => { - // Explicits picks the supported styles. + // Explicit picks the supported styles. const { width, maxWidth, minWidth } = view.layout?.styles?.[ column ] ?? {}; return ( @@ -360,6 +345,7 @@ function ViewTable< Item >( { key={ column } style={ { width, maxWidth, minWidth } } aria-sort={ + view.sort?.direction && view.sort?.field === column ? sortValues[ view.sort.direction ] : undefined @@ -367,26 +353,7 @@ function ViewTable< Item >( { scope="col" > <ColumnHeaderMenu - ref={ ( node ) => { - if ( node ) { - headerMenuRefs.current.set( - column, - { - node, - fallback: - columns[ - index > 0 - ? index - 1 - : 1 - ], - } - ); - } else { - headerMenuRefs.current.delete( - column - ); - } - } } + ref={ headerMenuRef( column, index ) } fieldId={ column } view={ view } fields={ fields } @@ -412,12 +379,20 @@ function ViewTable< Item >( { <TableRow key={ getItemId( item ) } item={ item } + level={ + view.showLevels && + typeof getItemLevel === 'function' + ? getItemLevel( item ) + : undefined + } hasBulkActions={ hasBulkActions } actions={ actions } fields={ fields } id={ getItemId( item ) || index.toString() } view={ view } - primaryField={ primaryField } + titleField={ titleField } + mediaField={ mediaField } + descriptionField={ descriptionField } selection={ selection } getItemId={ getItemId } onChangeSelection={ onChangeSelection } diff --git a/packages/dataviews/src/dataviews-layouts/table/style.scss b/packages/dataviews/src/dataviews-layouts/table/style.scss index ea2c614e4339df..5a4ac01b566f74 100644 --- a/packages/dataviews/src/dataviews-layouts/table/style.scss +++ b/packages/dataviews/src/dataviews-layouts/table/style.scss @@ -169,9 +169,40 @@ opacity: 1; } } + + // Density style overrides. + &.has-compact-density { + thead { + th { + &:has(.dataviews-view-table-header-button):not(:first-child) { + padding-left: 0; + } + } + } + td, + th { + padding: $grid-unit-05 $grid-unit-10; + } + } + + &.has-comfortable-density { + td, + th { + padding: $grid-unit-20 $grid-unit-15; + } + } + + &.has-compact-density, + &.has-comfortable-density { + td, + th { + &.dataviews-view-table__checkbox-column { + padding-right: 0; + } + } + } } -/* stylelint-disable-next-line scss/at-rule-no-unknown -- '@container' not globally permitted */ @container (max-width: 430px) { .dataviews-view-table tr td:first-child, .dataviews-view-table tr th:first-child { @@ -190,3 +221,7 @@ --checkbox-input-size: 16px; } } + +.dataviews-column-primary__media { + max-width: 60px; +} diff --git a/packages/dataviews/src/dataviews-layouts/utils/get-clickable-item-props.ts b/packages/dataviews/src/dataviews-layouts/utils/get-clickable-item-props.ts index e2a6081a68fa3e..810f03cdfd3b76 100644 --- a/packages/dataviews/src/dataviews-layouts/utils/get-clickable-item-props.ts +++ b/packages/dataviews/src/dataviews-layouts/utils/get-clickable-item-props.ts @@ -1,20 +1,37 @@ -export default function getClickableItemProps< Item >( - item: Item, - isItemClickable: ( item: Item ) => boolean, - onClickItem: ( item: Item ) => void, - className: string -) { - if ( ! isItemClickable( item ) ) { +export default function getClickableItemProps< Item >( { + item, + isItemClickable, + onClickItem, + className, +}: { + item: Item; + isItemClickable: ( item: Item ) => boolean; + onClickItem?: ( item: Item ) => void; + className?: string; +} ) { + if ( ! isItemClickable( item ) || ! onClickItem ) { return { className }; } return { - className: `${ className } ${ className }--clickable`, + className: className + ? `${ className } ${ className }--clickable` + : undefined, role: 'button', tabIndex: 0, - onClick: () => onClickItem( item ), + onClick: ( event: React.MouseEvent ) => { + // Prevents onChangeSelection from triggering. + event.stopPropagation(); + onClickItem( item ); + }, onKeyDown: ( event: React.KeyboardEvent ) => { - if ( event.key === 'Enter' || event.key === '' ) { + if ( + event.key === 'Enter' || + event.key === '' || + event.key === ' ' + ) { + // Prevents onChangeSelection from triggering. + event.stopPropagation(); onClickItem( item ); } }, diff --git a/packages/dataviews/src/field-types/integer.tsx b/packages/dataviews/src/field-types/integer.tsx index f57c8e382db816..2b2163ef6020e4 100644 --- a/packages/dataviews/src/field-types/integer.tsx +++ b/packages/dataviews/src/field-types/integer.tsx @@ -8,7 +8,7 @@ function sort( a: any, b: any, direction: SortDirection ) { } function isValid( value: any, context?: ValidationContext ) { - // TODO: this implicitely means the value is required. + // TODO: this implicitly means the value is required. if ( value === '' ) { return false; } diff --git a/packages/dataviews/src/normalize-fields.ts b/packages/dataviews/src/normalize-fields.ts index 562f29fcce84fe..2ed87cbe112229 100644 --- a/packages/dataviews/src/normalize-fields.ts +++ b/packages/dataviews/src/normalize-fields.ts @@ -2,14 +2,8 @@ * Internal dependencies */ import getFieldTypeDefinition from './field-types'; -import type { - CombinedFormField, - Field, - NormalizedField, - NormalizedCombinedFormField, -} from './types'; +import type { Field, NormalizedField } from './types'; import { getControl } from './dataform-controls'; -import DataFormCombinedEdit from './components/dataform-combined-edit'; const getValueFromId = ( id: string ) => @@ -87,29 +81,3 @@ export function normalizeFields< Item >( }; } ); } - -/** - * Apply default values and normalize the fields config. - * - * @param combinedFields combined field list. - * @param fields Fields config. - * @return Normalized fields config. - */ -export function normalizeCombinedFields< Item >( - combinedFields: CombinedFormField< Item >[], - fields: Field< Item >[] -): NormalizedCombinedFormField< Item >[] { - return combinedFields.map( ( combinedField ) => { - return { - ...combinedField, - Edit: DataFormCombinedEdit, - fields: normalizeFields( - combinedField.children - .map( ( fieldId ) => - fields.find( ( { id } ) => id === fieldId ) - ) - .filter( ( field ): field is Field< Item > => !! field ) - ), - }; - } ); -} diff --git a/packages/dataviews/src/normalize-form-fields.ts b/packages/dataviews/src/normalize-form-fields.ts new file mode 100644 index 00000000000000..3cd5f67564d7ce --- /dev/null +++ b/packages/dataviews/src/normalize-form-fields.ts @@ -0,0 +1,42 @@ +/** + * Internal dependencies + */ +import type { Form } from './types'; + +interface NormalizedFormField { + id: string; + layout: 'regular' | 'panel'; + labelPosition: 'side' | 'top' | 'none'; +} + +export default function normalizeFormFields( + form: Form +): NormalizedFormField[] { + let layout: 'regular' | 'panel' = 'regular'; + if ( [ 'regular', 'panel' ].includes( form.type ?? '' ) ) { + layout = form.type as 'regular' | 'panel'; + } + + const labelPosition = + form.labelPosition ?? ( layout === 'regular' ? 'top' : 'side' ); + + return ( form.fields ?? [] ).map( ( field ) => { + if ( typeof field === 'string' ) { + return { + id: field, + layout, + labelPosition, + }; + } + + const fieldLayout = field.layout ?? layout; + const fieldLabelPosition = + field.labelPosition ?? + ( fieldLayout === 'regular' ? 'top' : 'side' ); + return { + ...field, + layout: fieldLayout, + labelPosition: fieldLabelPosition, + }; + } ); +} diff --git a/packages/dataviews/src/style.scss b/packages/dataviews/src/style.scss index 26c6ecea645f43..5639f3cac0da51 100644 --- a/packages/dataviews/src/style.scss +++ b/packages/dataviews/src/style.scss @@ -6,7 +6,6 @@ @import "./components/dataviews-item-actions/style.scss"; @import "./components/dataviews-selection-checkbox/style.scss"; @import "./components/dataviews-view-config/style.scss"; -@import "./components/dataform-combined-edit/style.scss"; @import "./dataviews-layouts/grid/style.scss"; @import "./dataviews-layouts/list/style.scss"; @@ -14,3 +13,4 @@ @import "./dataform-controls/style.scss"; @import "./dataforms-layouts/panel/style.scss"; +@import "./dataforms-layouts/regular/style.scss"; diff --git a/packages/dataviews/src/test/dataform.tsx b/packages/dataviews/src/test/dataform.tsx new file mode 100644 index 00000000000000..534151a0a4ab58 --- /dev/null +++ b/packages/dataviews/src/test/dataform.tsx @@ -0,0 +1,348 @@ +/** + * External dependencies + */ +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; + +/** + * Internal dependencies + */ +import Dataform from '../components/dataform/index'; + +const noop = () => {}; + +const fields = [ + { + id: 'title', + label: 'Title', + type: 'text' as const, + }, + { + id: 'order', + label: 'Order', + type: 'integer' as const, + }, + { + id: 'author', + label: 'Author', + type: 'integer' as const, + elements: [ + { value: 1, label: 'Jane' }, + { value: 2, label: 'John' }, + ], + }, +]; + +const form = { + fields: [ 'title', 'order', 'author' ], +}; + +const data = { + title: 'Hello World', + author: 1, + order: 1, +}; + +const fieldsSelector = { + title: { + view: () => + screen.getByRole( 'button', { + name: /edit title/i, + } ), + edit: () => + screen.getByRole( 'textbox', { + name: /title/i, + } ), + }, + author: { + view: () => + screen.getByRole( 'button', { + name: /edit author/i, + } ), + edit: () => + screen.queryByRole( 'combobox', { + name: /author/i, + } ), + }, + order: { + view: () => + screen.getByRole( 'button', { + name: /edit order/i, + } ), + edit: () => + screen.getByRole( 'spinbutton', { + name: /order/i, + } ), + }, +}; + +describe( 'DataForm component', () => { + describe( 'in regular mode', () => { + it( 'should display fields', () => { + render( + <Dataform + onChange={ noop } + fields={ fields } + form={ form } + data={ data } + /> + ); + + expect( fieldsSelector.title.edit() ).toBeInTheDocument(); + expect( fieldsSelector.order.edit() ).toBeInTheDocument(); + expect( fieldsSelector.author.edit() ).toBeInTheDocument(); + } ); + + it( 'should render custom Edit component', () => { + const fieldsWithCustomEditComponent = fields.map( ( field ) => { + if ( field.id === 'title' ) { + return { + ...field, + Edit: () => { + return <span>This is the Title Field</span>; + }, + }; + } + return field; + } ); + + render( + <Dataform + onChange={ noop } + fields={ fieldsWithCustomEditComponent } + form={ form } + data={ data } + /> + ); + + const titleField = screen.getByText( 'This is the Title Field' ); + expect( titleField ).toBeInTheDocument(); + } ); + + it( 'should call onChange with the correct value for each typed character', async () => { + const onChange = jest.fn(); + render( + <Dataform + onChange={ onChange } + fields={ fields } + form={ form } + data={ { ...data, title: '' } } + /> + ); + + const titleInput = fieldsSelector.title.edit(); + const user = userEvent.setup(); + await user.clear( titleInput ); + expect( titleInput ).toHaveValue( '' ); + const newValue = 'Hello folks!'; + await user.type( titleInput, newValue ); + expect( onChange ).toHaveBeenCalledTimes( newValue.length ); + for ( let i = 0; i < newValue.length; i++ ) { + expect( onChange ).toHaveBeenNthCalledWith( i + 1, { + title: newValue[ i ], + } ); + } + } ); + + it( 'should wrap fields in HStack when labelPosition is set to side', async () => { + const { container } = render( + <Dataform + onChange={ noop } + fields={ fields } + form={ { ...form, labelPosition: 'side' } } + data={ data } + /> + ); + + expect( + // It is used here to ensure that the fields are wrapped in HStack. This happens when the labelPosition is set to side. + // eslint-disable-next-line testing-library/no-container, testing-library/no-node-access + container.querySelectorAll( "[data-wp-component='HStack']" ) + ).toHaveLength( 3 ); + } ); + + it( 'should render combined fields correctly', async () => { + const formWithCombinedFields = { + fields: [ + 'order', + { + id: 'title', + children: [ 'title', 'author' ], + label: "Title and author's name", + }, + ], + }; + + render( + <Dataform + onChange={ noop } + fields={ fields } + form={ formWithCombinedFields } + data={ data } + /> + ); + + expect( + screen.getByText( "Title and author's name" ) + ).toBeInTheDocument(); + } ); + } ); + + describe( 'in panel mode', () => { + const formPanelMode = { + ...form, + type: 'panel' as const, + }; + it( 'should display fields', async () => { + render( + <Dataform + onChange={ noop } + fields={ fields } + form={ formPanelMode } + data={ data } + /> + ); + + const user = await userEvent.setup(); + + for ( const field of Object.values( fieldsSelector ) ) { + const button = field.view(); + await user.click( button ); + expect( field.edit() ).toBeInTheDocument(); + } + } ); + + it( 'should call onChange with the correct value for each typed character', async () => { + const onChange = jest.fn(); + render( + <Dataform + onChange={ onChange } + fields={ fields } + form={ formPanelMode } + data={ { ...data, title: '' } } + /> + ); + + const titleButton = fieldsSelector.title.view(); + const user = await userEvent.setup(); + await user.click( titleButton ); + const input = fieldsSelector.title.edit(); + expect( input ).toHaveValue( '' ); + const newValue = 'Hello folks!'; + await user.type( input, newValue ); + expect( onChange ).toHaveBeenCalledTimes( newValue.length ); + for ( let i = 0; i < newValue.length; i++ ) { + expect( onChange ).toHaveBeenNthCalledWith( i + 1, { + title: newValue[ i ], + } ); + } + } ); + + it( 'should wrap fields in HStack when labelPosition is set to side', async () => { + const { container } = render( + <Dataform + onChange={ noop } + fields={ fields } + form={ { ...formPanelMode, labelPosition: 'side' } } + data={ data } + /> + ); + + expect( + // eslint-disable-next-line testing-library/no-container, testing-library/no-node-access + container.querySelectorAll( "[data-wp-component='HStack']" ) + ).toHaveLength( 3 ); + } ); + + it( 'should render combined fields correctly', async () => { + const formWithCombinedFields = { + ...formPanelMode, + fields: [ + 'order', + { + id: 'title', + children: [ 'title', 'author' ], + label: "Title and author's name", + }, + ], + }; + + render( + <Dataform + onChange={ noop } + fields={ fields } + form={ formWithCombinedFields } + data={ data } + /> + ); + + const button = screen.getByRole( 'button', { + name: /edit title and author's name/i, + } ); + const user = await userEvent.setup(); + await user.click( button ); + expect( fieldsSelector.title.edit() ).toBeInTheDocument(); + expect( fieldsSelector.author.edit() ).toBeInTheDocument(); + } ); + + it( 'should render custom render component', async () => { + const fieldsWithCustomRenderFunction = fields.map( ( field ) => { + return { + ...field, + render: () => { + return <span>This is the { field.id } field</span>; + }, + }; + } ); + + render( + <Dataform + onChange={ noop } + fields={ fieldsWithCustomRenderFunction } + form={ formPanelMode } + data={ data } + /> + ); + + const titleField = screen.getByText( 'This is the title field' ); + const orderField = screen.getByText( 'This is the order field' ); + const authorField = screen.getByText( 'This is the author field' ); + expect( titleField ).toBeInTheDocument(); + expect( orderField ).toBeInTheDocument(); + expect( authorField ).toBeInTheDocument(); + } ); + + it( 'should render custom Edit component', async () => { + const fieldsWithTitleCustomEditComponent = fields.map( + ( field ) => { + if ( field.id === 'title' ) { + return { + ...field, + Edit: () => { + return <span>This is the Title Field</span>; + }, + }; + } + return field; + } + ); + + render( + <Dataform + onChange={ noop } + fields={ fieldsWithTitleCustomEditComponent } + form={ formPanelMode } + data={ data } + /> + ); + + const titleField = screen.getByText( data.title ); + const user = await userEvent.setup(); + await user.click( titleField ); + const titleEditField = screen.getByText( + 'This is the Title Field' + ); + expect( titleEditField ).toBeInTheDocument(); + } ); + } ); +} ); diff --git a/packages/dataviews/src/test/dataviews.tsx b/packages/dataviews/src/test/dataviews.tsx new file mode 100644 index 00000000000000..fb55bf8064622f --- /dev/null +++ b/packages/dataviews/src/test/dataviews.tsx @@ -0,0 +1,380 @@ +/** + * External dependencies + */ +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; + +/** + * WordPress dependencies + */ +import { useMemo, useState } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import DataViews from '../components/dataviews'; +import { LAYOUT_GRID, LAYOUT_LIST, LAYOUT_TABLE } from '../constants'; +import type { Action, View } from '../types'; +import { filterSortAndPaginate } from '../filter-and-sort-data-view'; + +type Data = { + id: number; + title: string; + author?: number; + order?: number; +}; + +const DEFAULT_VIEW = { + type: 'table' as const, + search: '', + page: 1, + perPage: 10, + layout: {}, + filters: [], +}; + +const defaultLayouts = { + [ LAYOUT_TABLE ]: {}, + [ LAYOUT_GRID ]: {}, + [ LAYOUT_LIST ]: {}, +}; + +const fields = [ + { + id: 'title', + label: 'Title', + type: 'text' as const, + }, + { + id: 'order', + label: 'Order', + type: 'integer' as const, + }, + { + id: 'author', + label: 'Author', + type: 'integer' as const, + elements: [ + { value: 1, label: 'Jane' }, + { value: 2, label: 'John' }, + ], + }, + { + label: 'Image', + id: 'image', + render: ( { item }: { item: Data } ) => { + return ( + <svg + width="400" + height="180" + data-testid={ 'image-field-' + item.id } + > + <rect + x="50" + y="20" + rx="20" + ry="20" + width="150" + height="150" + style={ { fill: 'red', opacity: 0.5 } } + /> + </svg> + ); + }, + enableSorting: false, + }, +]; + +const actions: Action< Data >[] = [ + { + id: 'delete', + label: 'Delete', + isDestructive: true, + supportsBulk: true, + RenderModal: () => <div>Modal Content</div>, + }, +]; + +const data: Data[] = [ + { + id: 1, + title: 'Hello World', + author: 1, + order: 1, + }, + { + id: 2, + title: 'Homepage', + author: 2, + order: 1, + }, + { + id: 3, + title: 'Posts', + author: 2, + order: 1, + }, +]; + +function DataViewWrapper( { + view: additionalView, + ...props +}: Partial< Parameters< typeof DataViews< Data > >[ 0 ] > ) { + const [ view, setView ] = useState< View >( { + ...DEFAULT_VIEW, + fields: [ 'title', 'order', 'author' ], + ...additionalView, + } ); + + const { data: shownData, paginationInfo } = useMemo( () => { + return filterSortAndPaginate( data, view, props.fields || fields ); + }, [ view, props.fields ] ); + + const dataViewProps = { + getItemId: ( item: Data ) => item.id.toString(), + paginationInfo, + data: shownData, + view, + fields, + onChangeView: setView, + actions: [], + defaultLayouts, + ...props, + }; + + return <DataViews { ...dataViewProps } />; +} + +// jest.useFakeTimers(); + +describe( 'DataViews component', () => { + it( 'should show "No results" if data is empty', () => { + render( <DataViewWrapper data={ [] } /> ); + expect( screen.getByText( 'No results' ) ).toBeInTheDocument(); + } ); + + it( 'should filter results by "search" text, if field has enableGlobalSearch set to true', async () => { + const fieldsWithSearch = [ + { + ...fields[ 0 ], + enableGlobalSearch: true, + }, + fields[ 1 ], + ]; + render( + <DataViewWrapper + fields={ fieldsWithSearch } + view={ { ...DEFAULT_VIEW, search: 'Hello' } } + /> + ); + // Row count includes header. + expect( screen.getAllByRole( 'row' ).length ).toEqual( 2 ); + expect( screen.getByText( 'Hello World' ) ).toBeInTheDocument(); + } ); + + it( 'should display matched element label if field contains elements list', () => { + render( + <DataViewWrapper + data={ [ { id: 1, author: 3, title: 'Hello World' } ] } + fields={ [ + { + id: 'author', + label: 'Author', + type: 'integer' as const, + elements: [ + { value: 1, label: 'Jane' }, + { value: 2, label: 'John' }, + { value: 3, label: 'Tim' }, + ], + }, + ] } + /> + ); + expect( screen.getByText( 'Tim' ) ).toBeInTheDocument(); + } ); + + it( 'should render custom render function if defined in field definition', () => { + render( + <DataViewWrapper + data={ [ { id: 1, title: 'Test Title' } ] } + fields={ [ + { + id: 'title', + label: 'Title', + type: 'text' as const, + render: ( { item }: { item: Data } ) => { + return item.title?.toUpperCase(); + }, + }, + ] } + /> + ); + expect( screen.getByText( 'TEST TITLE' ) ).toBeInTheDocument(); + } ); + + describe( 'in table view', () => { + it( 'should display columns for each field', () => { + render( <DataViewWrapper /> ); + const displayedColumnFields = fields.filter( ( field ) => + [ 'title', 'order', 'author' ].includes( field.id ) + ); + for ( const field of displayedColumnFields ) { + expect( + screen.getByRole( 'button', { name: field.label } ) + ).toBeInTheDocument(); + } + } ); + + it( 'should display the passed in data', () => { + render( <DataViewWrapper /> ); + for ( const item of data ) { + expect( + screen.getAllByText( item.title )[ 0 ] + ).toBeInTheDocument(); + } + } ); + + it( 'should display title column if defined using titleField', () => { + render( + <DataViewWrapper + view={ { + ...DEFAULT_VIEW, + fields: [ 'order', 'author' ], + titleField: 'title', + } } + /> + ); + for ( const item of data ) { + expect( + screen.getAllByText( item.title )[ 0 ] + ).toBeInTheDocument(); + } + } ); + + it( 'should render actions column if actions are supported and passed in', () => { + render( <DataViewWrapper actions={ actions } /> ); + expect( screen.getByText( 'Actions' ) ).toBeInTheDocument(); + } ); + + it( 'should trigger the onClickItem callback if isItemClickable returns true and title field is clicked', async () => { + const onClickItemCallback = jest.fn(); + + render( + <DataViewWrapper + view={ { + ...DEFAULT_VIEW, + fields: [ 'author' ], + titleField: 'title', + } } + actions={ actions } + isItemClickable={ () => true } + onClickItem={ onClickItemCallback } + /> + ); + const titleField = screen.getByText( data[ 0 ].title ); + const user = userEvent.setup(); + await user.click( titleField ); + expect( onClickItemCallback ).toHaveBeenCalledWith( data[ 0 ] ); + } ); + } ); + + describe( 'in grid view', () => { + it( 'should display the passed in data', () => { + render( + <DataViewWrapper + view={ { + type: 'grid', + } } + /> + ); + for ( const item of data ) { + expect( + screen.getAllByText( item.title )[ 0 ] + ).toBeInTheDocument(); + } + } ); + + it( 'should render mediaField if defined', () => { + render( + <DataViewWrapper + view={ { + type: 'grid', + mediaField: 'image', + } } + /> + ); + for ( const item of data ) { + expect( + screen.getByTestId( 'image-field-' + item.id ) + ).toBeInTheDocument(); + } + } ); + + it( 'should render actions dropdown if actions are supported and passed in for each grid item', () => { + render( + <DataViewWrapper + view={ { + type: 'grid', + } } + actions={ actions } + /> + ); + expect( + screen.getAllByRole( 'button', { name: 'Actions' } ).length + ).toEqual( 3 ); + } ); + + it( 'should trigger the onClickItem callback if isItemClickable returns true and a media field is clicked', async () => { + const mediaClickItemCallback = jest.fn(); + + render( + <DataViewWrapper + view={ { + type: 'grid', + mediaField: 'image', + } } + actions={ actions } + isItemClickable={ () => true } + onClickItem={ mediaClickItemCallback } + /> + ); + const imageField = screen.getByTestId( + 'image-field-' + data[ 0 ].id + ); + const user = userEvent.setup(); + await user.click( imageField ); + expect( mediaClickItemCallback ).toHaveBeenCalledWith( data[ 0 ] ); + } ); + } ); + + describe( 'in list view', () => { + it( 'should display the passed in data', () => { + render( + <DataViewWrapper + view={ { + type: 'list', + } } + /> + ); + for ( const item of data ) { + expect( + screen.getAllByText( item.title )[ 0 ] + ).toBeInTheDocument(); + } + } ); + + it( 'should render actions dropdown if actions are supported and passed in for each list item', () => { + render( + <DataViewWrapper + view={ { + type: 'list', + } } + actions={ actions } + /> + ); + expect( + screen.getAllByRole( 'button', { name: 'Actions' } ).length + ).toEqual( 3 ); + } ); + } ); +} ); diff --git a/packages/dataviews/src/types.ts b/packages/dataviews/src/types.ts index 71990f72d4eecd..8ea13ed0b459cc 100644 --- a/packages/dataviews/src/types.ts +++ b/packages/dataviews/src/types.ts @@ -42,7 +42,7 @@ export type Operator = | 'isAll' | 'isNotAll'; -export type FieldType = 'text' | 'integer' | 'datetime'; +export type FieldType = 'text' | 'integer' | 'datetime' | 'media'; export type ValidationContext = { elements?: Option[]; @@ -292,24 +292,41 @@ interface ViewBase { * The fields to render */ fields?: string[]; -} -export interface CombinedField { - id: string; + /** + * Title field + */ + titleField?: string; - label: string; + /** + * Media field + */ + mediaField?: string; - header?: string | ReactElement; + /** + * Description field + */ + descriptionField?: string; + + /** + * Whether to show the title + */ + showTitle?: boolean; + + /** + * Whether to show the media + */ + showMedia?: boolean; /** - * The fields to use as columns. + * Whether to show the description */ - children: string[]; + showDescription?: boolean; /** - * The direction of the stack. + * Whether to show the hierarchical levels. */ - direction: 'horizontal' | 'vertical'; + showLevels?: boolean; } export interface ColumnStyle { @@ -329,41 +346,26 @@ export interface ColumnStyle { minWidth?: string | number; } +export type Density = 'compact' | 'balanced' | 'comfortable'; + export interface ViewTable extends ViewBase { type: 'table'; layout?: { /** - * The field to use as the primary field. - */ - primaryField?: string; - - /** - * The fields to use as columns. + * The styles for the columns. */ - combinedFields?: CombinedField[]; + styles?: Record< string, ColumnStyle >; /** - * The styles for the columns. + * The density of the view. */ - styles?: Record< string, ColumnStyle >; + density?: Density; }; } export interface ViewList extends ViewBase { type: 'list'; - - layout?: { - /** - * The field to use as the primary field. - */ - primaryField?: string; - - /** - * The field to use as the media field. - */ - mediaField?: string; - }; } export interface ViewGrid extends ViewBase { @@ -371,24 +373,14 @@ export interface ViewGrid extends ViewBase { layout?: { /** - * The field to use as the primary field. - */ - primaryField?: string; - - /** - * The field to use as the media field. - */ - mediaField?: string; - - /** - * The fields to use as columns. + * The fields to use as badge fields. */ - columnFields?: string[]; + badgeFields?: string[]; /** - * The fields to use as badge fields. + * The preview size of the grid. */ - badgeFields?: string[]; + previewSize?: number; }; } @@ -493,15 +485,15 @@ export interface ViewBaseProps< Item > { data: Item[]; fields: NormalizedField< Item >[]; getItemId: ( item: Item ) => string; + getItemLevel?: ( item: Item ) => number; isLoading?: boolean; onChangeView: ( view: View ) => void; onChangeSelection: SetSelection; selection: string[]; setOpenedFilter: ( fieldId: string ) => void; - onClickItem: ( item: Item ) => void; + onClickItem?: ( item: Item ) => void; isItemClickable: ( item: Item ) => boolean; view: View; - density: number; } export interface ViewTableProps< Item > extends ViewBaseProps< Item > { @@ -527,37 +519,41 @@ export interface SupportedLayouts { table?: Omit< ViewTable, 'type' >; } -export interface CombinedFormField< Item > extends CombinedField { - render?: ComponentType< { item: Item } >; -} - -export interface DataFormCombinedEditProps< Item > { - field: NormalizedCombinedFormField< Item >; - data: Item; - onChange: ( value: Record< string, any > ) => void; - hideLabelFromVision?: boolean; -} +export type SimpleFormField = { + id: string; + layout?: 'regular' | 'panel'; + labelPosition?: 'side' | 'top' | 'none'; +}; -export type NormalizedCombinedFormField< Item > = CombinedFormField< Item > & { - fields: NormalizedField< Item >[]; - Edit?: ComponentType< DataFormCombinedEditProps< Item > >; +export type CombinedFormField = { + id: string; + label?: string; + layout?: 'regular' | 'panel'; + labelPosition?: 'side' | 'top' | 'none'; + children: Array< FormField | string >; }; +export type FormField = SimpleFormField | CombinedFormField; + /** * The form configuration. */ -export type Form< Item > = { +export type Form = { type?: 'regular' | 'panel'; - fields?: string[]; - /** - * The fields to combine. - */ - combinedFields?: CombinedFormField< Item >[]; + fields?: Array< FormField | string >; + labelPosition?: 'side' | 'top' | 'none'; }; export interface DataFormProps< Item > { data: Item; fields: Field< Item >[]; - form: Form< Item >; + form: Form; onChange: ( value: Record< string, any > ) => void; } + +export interface FieldLayoutProps< Item > { + data: Item; + field: FormField; + onChange: ( value: any ) => void; + hideLabelFromVision?: boolean; +} diff --git a/packages/dataviews/src/validation.ts b/packages/dataviews/src/validation.ts index 0a6542da4e8d40..bcc9a15908ff59 100644 --- a/packages/dataviews/src/validation.ts +++ b/packages/dataviews/src/validation.ts @@ -16,7 +16,7 @@ import type { Field, Form } from './types'; export function isItemValid< Item >( item: Item, fields: Field< Item >[], - form: Form< Item > + form: Form ): boolean { const _fields = normalizeFields( fields.filter( ( { id } ) => !! form.fields?.includes( id ) ) diff --git a/packages/dataviews/tsconfig.json b/packages/dataviews/tsconfig.json index 78e68b5a7c98b4..a7c8759d257cb2 100644 --- a/packages/dataviews/tsconfig.json +++ b/packages/dataviews/tsconfig.json @@ -2,9 +2,12 @@ "$schema": "https://json.schemastore.org/tsconfig.json", "extends": "../../tsconfig.base.json", "compilerOptions": { - "rootDir": "src", - "declarationDir": "build-types", - "checkJs": false + "types": [ + "gutenberg-env", + "gutenberg-test-env", + "jest", + "@testing-library/jest-dom" + ] }, "references": [ { "path": "../components" }, @@ -17,5 +20,12 @@ { "path": "../private-apis" }, { "path": "../warning" } ], - "include": [ "src" ] + "exclude": [ + "src/**/*.android.js", + "src/**/*.ios.js", + "src/**/*.native.js", + "src/**/react-native-*", + "src/**/stories/**/*.js", // only exclude js files, tsx files should be checked + "src/**/test/**/*.js" // only exclude js files, ts{x} files should be checked + ] } diff --git a/packages/date/CHANGELOG.md b/packages/date/CHANGELOG.md index 1d0508bd5325ad..810fbd5aacda66 100644 --- a/packages/date/CHANGELOG.md +++ b/packages/date/CHANGELOG.md @@ -2,6 +2,16 @@ ## Unreleased +## 5.16.0 (2025-01-15) + +## 5.15.0 (2025-01-02) + +## 5.14.0 (2024-12-11) + +## 5.13.0 (2024-11-27) + +## 5.12.0 (2024-11-16) + ## 5.11.0 (2024-10-30) ## 5.10.0 (2024-10-16) diff --git a/packages/date/README.md b/packages/date/README.md index ed2dfdd4790324..4f0a64c24aa713 100644 --- a/packages/date/README.md +++ b/packages/date/README.md @@ -75,7 +75,7 @@ Create and return a JavaScript Date Object from a date string in the WP timezone _Parameters_ -- _dateString_ `string?`: Date formatted in the WP timezone. +- _dateString_ `?string`: Date formatted in the WP timezone. _Returns_ diff --git a/packages/date/package.json b/packages/date/package.json index d67c1dc527caee..d1c0a441452223 100644 --- a/packages/date/package.json +++ b/packages/date/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/date", - "version": "5.11.0", + "version": "5.16.0", "description": "Date module for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -29,7 +29,7 @@ "types": "build-types", "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/deprecated": "*", + "@wordpress/deprecated": "file:../deprecated", "moment": "^2.29.4", "moment-timezone": "^0.5.40" }, diff --git a/packages/date/src/index.js b/packages/date/src/index.js index b632de3a7431f6..8d8f53fd8bc10f 100644 --- a/packages/date/src/index.js +++ b/packages/date/src/index.js @@ -588,7 +588,7 @@ export function isInTheFuture( dateValue ) { /** * Create and return a JavaScript Date Object from a date string in the WP timezone. * - * @param {string?} dateString Date formatted in the WP timezone. + * @param {?string} dateString Date formatted in the WP timezone. * * @return {Date} Date */ diff --git a/packages/date/src/test/index.js b/packages/date/src/test/index.js index 3c52aae1fbd0df..082679bbb87470 100644 --- a/packages/date/src/test/index.js +++ b/packages/date/src/test/index.js @@ -608,7 +608,7 @@ describe( 'Moment.js Localization', () => { }, } ); - // Get the freshly changed setings. + // Get the freshly changed settings. const newSettings = getSettings(); // Test the unchanged values. diff --git a/packages/date/tsconfig.json b/packages/date/tsconfig.json index 0c9e6d5ed02b0b..605262dd7cc952 100644 --- a/packages/date/tsconfig.json +++ b/packages/date/tsconfig.json @@ -1,10 +1,5 @@ { "$schema": "https://json.schemastore.org/tsconfig.json", "extends": "../../tsconfig.base.json", - "compilerOptions": { - "rootDir": "src", - "declarationDir": "build-types" - }, - "references": [ { "path": "../deprecated" } ], - "include": [ "src/**/*" ] + "references": [ { "path": "../deprecated" } ] } diff --git a/packages/dependency-extraction-webpack-plugin/CHANGELOG.md b/packages/dependency-extraction-webpack-plugin/CHANGELOG.md index 080234c319e477..56952827917e2b 100644 --- a/packages/dependency-extraction-webpack-plugin/CHANGELOG.md +++ b/packages/dependency-extraction-webpack-plugin/CHANGELOG.md @@ -2,6 +2,16 @@ ## Unreleased +## 6.16.0 (2025-01-15) + +## 6.15.0 (2025-01-02) + +## 6.14.0 (2024-12-11) + +## 6.13.0 (2024-11-27) + +## 6.12.0 (2024-11-16) + ## 6.11.0 (2024-10-30) ## 6.10.0 (2024-10-16) diff --git a/packages/dependency-extraction-webpack-plugin/lib/index.js b/packages/dependency-extraction-webpack-plugin/lib/index.js index cf780d7370dcfc..8bc7cb29312161 100644 --- a/packages/dependency-extraction-webpack-plugin/lib/index.js +++ b/packages/dependency-extraction-webpack-plugin/lib/index.js @@ -387,6 +387,16 @@ class DependencyExtractionWebpackPlugin { assetData.type = 'module'; } + if ( compilation.options?.optimization?.runtimeChunk !== false ) { + // Sets the script handle for the shared runtime file so WordPress registers it only once when using the asset file. + assetData.handle = + compilation.name + + '-' + + chunkJSFile + .replace( /\\/g, '/' ) + .replace( jsExtensionRegExp, '' ); + } + if ( combineAssets ) { combinedAssetsData[ chunkJSFile ] = assetData; continue; diff --git a/packages/dependency-extraction-webpack-plugin/lib/util.js b/packages/dependency-extraction-webpack-plugin/lib/util.js index cc999860244760..b5c9f9057c2052 100644 --- a/packages/dependency-extraction-webpack-plugin/lib/util.js +++ b/packages/dependency-extraction-webpack-plugin/lib/util.js @@ -5,10 +5,12 @@ const WORDPRESS_NAMESPACE = '@wordpress/'; // !! const BUNDLED_PACKAGES = [ '@wordpress/dataviews', + '@wordpress/dataviews/wp', '@wordpress/icons', '@wordpress/interface', '@wordpress/sync', '@wordpress/undo-manager', + '@wordpress/upload-media', '@wordpress/fields', ]; diff --git a/packages/dependency-extraction-webpack-plugin/package.json b/packages/dependency-extraction-webpack-plugin/package.json index cb7a4e60bf015d..ae7cbfe9eb8690 100644 --- a/packages/dependency-extraction-webpack-plugin/package.json +++ b/packages/dependency-extraction-webpack-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/dependency-extraction-webpack-plugin", - "version": "6.11.0", + "version": "6.16.0", "description": "Extract WordPress script dependencies from webpack bundles.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/dependency-extraction-webpack-plugin/test/__snapshots__/build.js.snap b/packages/dependency-extraction-webpack-plugin/test/__snapshots__/build.js.snap index 8ea40b00d7c2d1..be69ee891743da 100644 --- a/packages/dependency-extraction-webpack-plugin/test/__snapshots__/build.js.snap +++ b/packages/dependency-extraction-webpack-plugin/test/__snapshots__/build.js.snap @@ -41,7 +41,7 @@ exports[`DependencyExtractionWebpackPlugin modules Webpack \`cyclic-dependency-g `; exports[`DependencyExtractionWebpackPlugin modules Webpack \`cyclic-dynamic-dependency-graph\` should produce expected output: Asset file 'main.asset.php' should match snapshot 1`] = ` -"<?php return array('dependencies' => array(array('id' => '@wordpress/interactivity', 'import' => 'dynamic')), 'version' => '3e43f50123c85675372f', 'type' => 'module'); +"<?php return array('dependencies' => array(array('id' => '@wordpress/interactivity', 'import' => 'dynamic')), 'version' => '3ca651be783423315165', 'type' => 'module'); " `; @@ -71,7 +71,7 @@ exports[`DependencyExtractionWebpackPlugin modules Webpack \`cyclic-external-dep `; exports[`DependencyExtractionWebpackPlugin modules Webpack \`dynamic-import\` should produce expected output: Asset file 'main.asset.php' should match snapshot 1`] = ` -"<?php return array('dependencies' => array(array('id' => '@wordpress/blob', 'import' => 'dynamic')), 'version' => '1c950858231a54e241b8', 'type' => 'module'); +"<?php return array('dependencies' => array(array('id' => '@wordpress/blob', 'import' => 'dynamic')), 'version' => 'fbb215dc4902543bce35', 'type' => 'module'); " `; @@ -265,17 +265,17 @@ exports[`DependencyExtractionWebpackPlugin modules Webpack \`polyfill-magic-comm exports[`DependencyExtractionWebpackPlugin modules Webpack \`polyfill-magic-comment-minified\` should produce expected output: External modules should match snapshot 1`] = `[]`; exports[`DependencyExtractionWebpackPlugin modules Webpack \`runtime-chunk-single\` should produce expected output: Asset file 'a.asset.php' should match snapshot 1`] = ` -"<?php return array('dependencies' => array('@wordpress/blob'), 'version' => 'ee5ac21a1f0003d732e6', 'type' => 'module'); +"<?php return array('dependencies' => array('@wordpress/blob'), 'version' => 'ab7fa97b1381e41e0f70', 'type' => 'module', 'handle' => 'runtime-chunk-single-modules-a'); " `; exports[`DependencyExtractionWebpackPlugin modules Webpack \`runtime-chunk-single\` should produce expected output: Asset file 'b.asset.php' should match snapshot 1`] = ` -"<?php return array('dependencies' => array('@wordpress/blob', 'lodash'), 'version' => '5b112b32c6db548c2997', 'type' => 'module'); +"<?php return array('dependencies' => array('@wordpress/blob', 'lodash'), 'version' => '498fd24026d68fa84ba9', 'type' => 'module', 'handle' => 'runtime-chunk-single-modules-b'); " `; exports[`DependencyExtractionWebpackPlugin modules Webpack \`runtime-chunk-single\` should produce expected output: Asset file 'runtime.asset.php' should match snapshot 1`] = ` -"<?php return array('dependencies' => array(), 'version' => 'b1ca4106075e0bd94f9c', 'type' => 'module'); +"<?php return array('dependencies' => array(), 'version' => '5ac0d6987a8e7fe809a3', 'type' => 'module', 'handle' => 'runtime-chunk-single-modules-runtime'); " `; @@ -556,7 +556,7 @@ exports[`DependencyExtractionWebpackPlugin scripts Webpack \`module-renames\` sh `; exports[`DependencyExtractionWebpackPlugin scripts Webpack \`no-default\` should produce expected output: Asset file 'main.asset.php' should match snapshot 1`] = ` -"<?php return array('dependencies' => array(), 'version' => '43880e6c42e7c39fcdf1'); +"<?php return array('dependencies' => array(), 'version' => 'be8b28b738de0d6b883a'); " `; @@ -681,17 +681,17 @@ exports[`DependencyExtractionWebpackPlugin scripts Webpack \`polyfill-magic-comm exports[`DependencyExtractionWebpackPlugin scripts Webpack \`polyfill-magic-comment-minified\` should produce expected output: External modules should match snapshot 1`] = `[]`; exports[`DependencyExtractionWebpackPlugin scripts Webpack \`runtime-chunk-single\` should produce expected output: Asset file 'a.asset.php' should match snapshot 1`] = ` -"<?php return array('dependencies' => array('wp-blob'), 'version' => 'd091f1cbbf7603d6e12c'); +"<?php return array('dependencies' => array('wp-blob'), 'version' => 'd091f1cbbf7603d6e12c', 'handle' => 'runtime-chunk-single-scripts-a'); " `; exports[`DependencyExtractionWebpackPlugin scripts Webpack \`runtime-chunk-single\` should produce expected output: Asset file 'b.asset.php' should match snapshot 1`] = ` -"<?php return array('dependencies' => array('lodash', 'wp-blob'), 'version' => '845bc6d4ffbdb9419ebd'); +"<?php return array('dependencies' => array('lodash', 'wp-blob'), 'version' => '845bc6d4ffbdb9419ebd', 'handle' => 'runtime-chunk-single-scripts-b'); " `; exports[`DependencyExtractionWebpackPlugin scripts Webpack \`runtime-chunk-single\` should produce expected output: Asset file 'runtime.asset.php' should match snapshot 1`] = ` -"<?php return array('dependencies' => array(), 'version' => '717eb779e609d175a7dd'); +"<?php return array('dependencies' => array(), 'version' => '717eb779e609d175a7dd', 'handle' => 'runtime-chunk-single-scripts-runtime'); " `; diff --git a/packages/deprecated/CHANGELOG.md b/packages/deprecated/CHANGELOG.md index 5ba83861f7cd3d..bb2b26071230e5 100644 --- a/packages/deprecated/CHANGELOG.md +++ b/packages/deprecated/CHANGELOG.md @@ -2,6 +2,16 @@ ## Unreleased +## 4.16.0 (2025-01-15) + +## 4.15.0 (2025-01-02) + +## 4.14.0 (2024-12-11) + +## 4.13.0 (2024-11-27) + +## 4.12.0 (2024-11-16) + ## 4.11.0 (2024-10-30) ## 4.10.0 (2024-10-16) diff --git a/packages/deprecated/package.json b/packages/deprecated/package.json index 64ffc6cd30b251..e7dbada8db22be 100644 --- a/packages/deprecated/package.json +++ b/packages/deprecated/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/deprecated", - "version": "4.11.0", + "version": "4.16.0", "description": "Deprecation utility for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -30,7 +30,7 @@ "sideEffects": false, "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/hooks": "*" + "@wordpress/hooks": "file:../hooks" }, "publishConfig": { "access": "public" diff --git a/packages/deprecated/tsconfig.json b/packages/deprecated/tsconfig.json index f90e327f124d7e..b2186db14f4cc4 100644 --- a/packages/deprecated/tsconfig.json +++ b/packages/deprecated/tsconfig.json @@ -1,10 +1,5 @@ { "$schema": "https://json.schemastore.org/tsconfig.json", "extends": "../../tsconfig.base.json", - "compilerOptions": { - "rootDir": "src", - "declarationDir": "build-types" - }, - "references": [ { "path": "../hooks" } ], - "include": [ "src/**/*" ] + "references": [ { "path": "../hooks" } ] } diff --git a/packages/docgen/CHANGELOG.md b/packages/docgen/CHANGELOG.md index b7374d6f921cb4..f3a514d0a10f73 100644 --- a/packages/docgen/CHANGELOG.md +++ b/packages/docgen/CHANGELOG.md @@ -2,6 +2,16 @@ ## Unreleased +## 2.16.0 (2025-01-15) + +## 2.15.0 (2025-01-02) + +## 2.14.0 (2024-12-11) + +## 2.13.0 (2024-11-27) + +## 2.12.0 (2024-11-16) + ## 2.11.0 (2024-10-30) ## 2.10.0 (2024-10-16) diff --git a/packages/docgen/README.md b/packages/docgen/README.md index 4f60d0a3c7a26a..6226eea9b73293 100644 --- a/packages/docgen/README.md +++ b/packages/docgen/README.md @@ -6,7 +6,7 @@ Some characteristics: - If the export statement doesn't contain any JSDoc, it'll look up for JSDoc up to the declaration. - It can resolve relative dependencies, either files or directories. For example, `import default from './dependency'` will find `dependency.js` or `dependency/index.js` -- For TypeScript support, all types must be explicity annotated as the TypeScript Babel plugin is unable to consume inferred types (it does not run the TS compiler, after all—it merely parses TypeScript). For example, all function return types must be explicitly annotated if they are to be documented by `docgen`. +- For TypeScript support, all types must be explicitly annotated as the TypeScript Babel plugin is unable to consume inferred types (it does not run the TS compiler, after all—it merely parses TypeScript). For example, all function return types must be explicitly annotated if they are to be documented by `docgen`. ## Installation @@ -169,12 +169,12 @@ with `./count/index.js` contents being: ````js /** - * Substracts two numbers. + * Subtracts two numbers. * * @example * * ```js - * const result = substraction( 5, 2 ); + * const result = subtraction( 5, 2 ); * console.log( result ); // Will log 3 * ``` * @@ -182,7 +182,7 @@ with `./count/index.js` contents being: * @param {number} term2 Second number. * @return {number} The result of subtracting the two numbers. */ -export function substraction( term1, term2 ) { +export function subtraction( term1, term2 ) { return term1 - term2; } @@ -233,16 +233,16 @@ console.log( result ); // Will log 7 `number` The result of adding the two numbers. -## substraction +## subtraction [example-module.js#L1-L1](example-module.js#L1-L1) -Substracts two numbers. +Subtracts two numbers. **Usage** ```js -const result = substraction( 5, 2 ); +const result = subtraction( 5, 2 ); console.log( result ); // Will log 3 ``` diff --git a/packages/docgen/lib/get-type-annotation.js b/packages/docgen/lib/get-type-annotation.js index 5e72724952f29e..1dc9eee02dc62a 100644 --- a/packages/docgen/lib/get-type-annotation.js +++ b/packages/docgen/lib/get-type-annotation.js @@ -394,7 +394,7 @@ function getTypeAnnotation( typeAnnotation ) { * with their descriptions in the JSDoc comments. * * If we find more wrapper functions on selectors we should add them below following the - * example of `createSelector` and `createRegsitrySelector`. + * example of `createSelector` and `createRegistrySelector`. * * @param {ASTNode} token Contains either a function or a call to a function-wrapper. * diff --git a/packages/docgen/lib/index.js b/packages/docgen/lib/index.js index 86c230f1e901aa..4edeeb16c98879 100644 --- a/packages/docgen/lib/index.js +++ b/packages/docgen/lib/index.js @@ -133,7 +133,7 @@ module.exports = ( sourceFile, options ) => { return true; } ); - // Ouput. + // Output. if ( result === undefined ) { process.stdout.write( '\nFile was processed, but contained no ES6 module exports:' diff --git a/packages/docgen/package.json b/packages/docgen/package.json index be73d0625b63ed..2f02999eaf595f 100644 --- a/packages/docgen/package.json +++ b/packages/docgen/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/docgen", - "version": "2.11.0", + "version": "2.16.0", "description": "Autogenerate public API documentation from exports and JSDoc comments.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/docgen/tsconfig.json b/packages/docgen/tsconfig.json index df0072645c53ba..eebc743289aec2 100644 --- a/packages/docgen/tsconfig.json +++ b/packages/docgen/tsconfig.json @@ -2,8 +2,8 @@ "$schema": "https://json.schemastore.org/tsconfig.json", "extends": "../../tsconfig.base.json", "compilerOptions": { - "rootDir": "lib", - "declarationDir": "build-types" + "rootDir": "lib" }, - "include": [ "lib/get-leading-comments.js" ] + "files": [ "lib/get-leading-comments.js" ], + "include": [] } diff --git a/packages/dom-ready/CHANGELOG.md b/packages/dom-ready/CHANGELOG.md index ffdaeff6e892cf..33ae1124f64618 100644 --- a/packages/dom-ready/CHANGELOG.md +++ b/packages/dom-ready/CHANGELOG.md @@ -2,6 +2,16 @@ ## Unreleased +## 4.16.0 (2025-01-15) + +## 4.15.0 (2025-01-02) + +## 4.14.0 (2024-12-11) + +## 4.13.0 (2024-11-27) + +## 4.12.0 (2024-11-16) + ## 4.11.0 (2024-10-30) ## 4.10.0 (2024-10-16) diff --git a/packages/dom-ready/package.json b/packages/dom-ready/package.json index 6e7986c4965dc6..94663fa6893313 100644 --- a/packages/dom-ready/package.json +++ b/packages/dom-ready/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/dom-ready", - "version": "4.11.0", + "version": "4.16.0", "description": "Execute callback after the DOM is loaded.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/dom-ready/tsconfig.json b/packages/dom-ready/tsconfig.json index 6e33d8ff82d47e..7ff060ab6ce105 100644 --- a/packages/dom-ready/tsconfig.json +++ b/packages/dom-ready/tsconfig.json @@ -1,9 +1,4 @@ { "$schema": "https://json.schemastore.org/tsconfig.json", - "extends": "../../tsconfig.base.json", - "compilerOptions": { - "rootDir": "src", - "declarationDir": "build-types" - }, - "include": [ "src/**/*" ] + "extends": "../../tsconfig.base.json" } diff --git a/packages/dom/CHANGELOG.md b/packages/dom/CHANGELOG.md index b868abb474e62f..cf9fffd2c60774 100644 --- a/packages/dom/CHANGELOG.md +++ b/packages/dom/CHANGELOG.md @@ -2,6 +2,16 @@ ## Unreleased +## 4.16.0 (2025-01-15) + +## 4.15.0 (2025-01-02) + +## 4.14.0 (2024-12-11) + +## 4.13.0 (2024-11-27) + +## 4.12.0 (2024-11-16) + ## 4.11.0 (2024-10-30) ## 4.10.0 (2024-10-16) diff --git a/packages/dom/package.json b/packages/dom/package.json index 97576e9a22e0ff..0d4aacee384894 100644 --- a/packages/dom/package.json +++ b/packages/dom/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/dom", - "version": "4.11.0", + "version": "4.16.0", "description": "DOM utilities module for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -31,7 +31,7 @@ "sideEffects": false, "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/deprecated": "*" + "@wordpress/deprecated": "file:../deprecated" }, "publishConfig": { "access": "public" diff --git a/packages/dom/src/dom/clean-node-list.js b/packages/dom/src/dom/clean-node-list.js index bbdff13b470d69..f1b7e1be7264f4 100644 --- a/packages/dom/src/dom/clean-node-list.js +++ b/packages/dom/src/dom/clean-node-list.js @@ -76,7 +76,10 @@ export default function cleanNodeList( nodeList, doc, schema, inline ) { // TODO: Explore patching this in jsdom-jscore. if ( node.classList && node.classList.length ) { const mattchers = classes.map( ( item ) => { - if ( typeof item === 'string' ) { + if ( item === '*' ) { + // Keep all classes. + return () => true; + } else if ( typeof item === 'string' ) { return ( /** @type {string} */ className ) => className === item; diff --git a/packages/dom/tsconfig.json b/packages/dom/tsconfig.json index 7cdff6c141151b..e44d6b98c50856 100644 --- a/packages/dom/tsconfig.json +++ b/packages/dom/tsconfig.json @@ -2,10 +2,7 @@ "$schema": "https://json.schemastore.org/tsconfig.json", "extends": "../../tsconfig.base.json", "compilerOptions": { - "rootDir": "src", - "declarationDir": "build-types", "types": [ "gutenberg-env" ] }, - "include": [ "src/**/*" ], "references": [ { "path": "../deprecated" } ] } diff --git a/packages/e2e-test-utils-playwright/CHANGELOG.md b/packages/e2e-test-utils-playwright/CHANGELOG.md index cbe574c55790a0..3d2a0f8ce10658 100644 --- a/packages/e2e-test-utils-playwright/CHANGELOG.md +++ b/packages/e2e-test-utils-playwright/CHANGELOG.md @@ -2,6 +2,16 @@ ## Unreleased +## 1.16.0 (2025-01-15) + +## 1.15.0 (2025-01-02) + +## 1.14.0 (2024-12-11) + +## 1.13.0 (2024-11-27) + +## 1.12.0 (2024-11-16) + ## 1.11.0 (2024-10-30) ## 1.10.0 (2024-10-16) diff --git a/packages/e2e-test-utils-playwright/package.json b/packages/e2e-test-utils-playwright/package.json index 8a2703bfdec54d..ace65501cbd5d3 100644 --- a/packages/e2e-test-utils-playwright/package.json +++ b/packages/e2e-test-utils-playwright/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/e2e-test-utils-playwright", - "version": "1.11.0", + "version": "1.16.0", "description": "End-To-End (E2E) test utils for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -34,7 +34,7 @@ "change-case": "^4.1.2", "form-data": "^4.0.0", "get-port": "^5.1.1", - "lighthouse": "^10.4.0", + "lighthouse": "^12.2.2", "mime": "^3.0.0", "web-vitals": "^4.2.1" }, diff --git a/packages/e2e-test-utils-playwright/src/admin/visit-site-editor.ts b/packages/e2e-test-utils-playwright/src/admin/visit-site-editor.ts index f0a7db5ad966ff..bc6e3e23d078b5 100644 --- a/packages/e2e-test-utils-playwright/src/admin/visit-site-editor.ts +++ b/packages/e2e-test-utils-playwright/src/admin/visit-site-editor.ts @@ -39,6 +39,15 @@ export async function visitSiteEditor( await this.visitAdminPage( 'site-editor.php', query.toString() ); + if ( ! options.showWelcomeGuide ) { + await this.editor.setPreferences( 'core/edit-site', { + welcomeGuide: false, + welcomeGuideStyles: false, + welcomeGuidePage: false, + welcomeGuideTemplate: false, + } ); + } + /** * @todo This is a workaround for the fact that the editor canvas is seen as * ready and visible before the loading spinner is hidden. Ideally, the @@ -52,24 +61,22 @@ export async function visitSiteEditor( '.edit-site-canvas-loader, .edit-site-canvas-spinner' ); - // Wait for the canvas loader to appear first, so that the locator that - // waits for the hidden state doesn't resolve prematurely. - await canvasLoader.waitFor( { state: 'visible' } ); - await canvasLoader.waitFor( { - state: 'hidden', - // Bigger timeout is needed for larger entities, like the Large Post - // HTML fixture that we load for performance tests, which often - // doesn't make it under the default timeout value. - timeout: 60_000, - } ); - } - - if ( ! options.showWelcomeGuide ) { - await this.editor.setPreferences( 'core/edit-site', { - welcomeGuide: false, - welcomeGuideStyles: false, - welcomeGuidePage: false, - welcomeGuideTemplate: false, - } ); + try { + // Wait for the canvas loader to appear first, so that the locator that + // waits for the hidden state doesn't resolve prematurely. + await canvasLoader.waitFor( { state: 'visible', timeout: 60_000 } ); + await canvasLoader.waitFor( { + state: 'hidden', + // Bigger timeout is needed for larger entities, like the Large Post + // HTML fixture that we load for performance tests, which often + // doesn't make it under the default timeout value. + timeout: 60_000, + } ); + } catch ( error ) { + // If the canvas loader is already disappeared, skip the waiting. + await this.page + .getByRole( 'region', { name: 'Editor content' } ) + .waitFor(); + } } } diff --git a/packages/e2e-test-utils-playwright/src/lighthouse/index.ts b/packages/e2e-test-utils-playwright/src/lighthouse/index.ts index 0ba2c8e67ba610..699835f903d1e6 100644 --- a/packages/e2e-test-utils-playwright/src/lighthouse/index.ts +++ b/packages/e2e-test-utils-playwright/src/lighthouse/index.ts @@ -25,13 +25,13 @@ export class Lighthouse { * the summary. */ async getReport() { - // From https://github.com/GoogleChrome/lighthouse/blob/d149e9c1b628d5881ca9ca451278d99ff1b31d9a/core/config/default-config.js#L433-L503 + // From https://github.com/GoogleChrome/lighthouse/blob/36cac182a6c637b1671c57326d7c0241633d0076/core/config/default-config.js#L381-L446 const audits = { 'largest-contentful-paint': 'LCP', 'total-blocking-time': 'TBT', interactive: 'TTI', 'cumulative-layout-shift': 'CLS', - 'experimental-interaction-to-next-paint': 'INP', + 'interaction-to-next-paint': 'INP', }; const report = await lighthouse( diff --git a/packages/e2e-test-utils-playwright/tsconfig.json b/packages/e2e-test-utils-playwright/tsconfig.json index 5e52bb94f706d9..947a4a0f82fc76 100644 --- a/packages/e2e-test-utils-playwright/tsconfig.json +++ b/packages/e2e-test-utils-playwright/tsconfig.json @@ -7,16 +7,13 @@ "module": "Node16", "moduleResolution": "node16", "types": [ "node" ], - "rootDir": "src", "noEmit": false, "outDir": "build", "sourceMap": true, "declaration": true, "declarationMap": true, - "declarationDir": "build-types", "emitDeclarationOnly": false, "allowJs": true, "checkJs": false - }, - "include": [ "src/**/*" ] + } } diff --git a/packages/e2e-test-utils/CHANGELOG.md b/packages/e2e-test-utils/CHANGELOG.md index 137b1ba872dc54..00ccbb6fdc6ac4 100644 --- a/packages/e2e-test-utils/CHANGELOG.md +++ b/packages/e2e-test-utils/CHANGELOG.md @@ -2,6 +2,16 @@ ## Unreleased +## 11.16.0 (2025-01-15) + +## 11.15.0 (2025-01-02) + +## 11.14.0 (2024-12-11) + +## 11.13.0 (2024-11-27) + +## 11.12.0 (2024-11-16) + ## 11.11.0 (2024-10-30) ## 11.10.0 (2024-10-16) @@ -203,7 +213,7 @@ ### Enhancements -- `visitAdminPage` will now throw an error (emit a test failure) when there are unexpected errors on hte page. +- `visitAdminPage` will now throw an error (emit a test failure) when there are unexpected errors on the page. ### New Features diff --git a/packages/e2e-test-utils/README.md b/packages/e2e-test-utils/README.md index 196768b0e2487c..5fe454d77d8f32 100644 --- a/packages/e2e-test-utils/README.md +++ b/packages/e2e-test-utils/README.md @@ -212,7 +212,7 @@ Create a new user account. _Parameters_ - _username_ `string`: User name. -- _object_ `Object?`: Optional Settings for the new user account. +- _object_ `?Object`: Optional Settings for the new user account. - _object.firstName_ `[string]`: First name. - _object.lastName_ `[string]`: Last name. - _object.role_ `[string]`: Role. Defaults to Administrator. @@ -252,9 +252,9 @@ Deletes a theme from the site, activating another theme if necessary. _Parameters_ - _slug_ `string`: Theme slug. -- _settings_ `Object?`: Optional settings object. -- _settings.newThemeSlug_ `string?`: A theme to switch to if the theme to delete is active. Required if the theme to delete is active. -- _settings.newThemeSearchTerm_ `string?`: A search term to use if the new theme is not findable by its slug. +- _settings_ `?Object`: Optional settings object. +- _settings.newThemeSlug_ `?string`: A theme to switch to if the theme to delete is active. Required if the theme to delete is active. +- _settings.newThemeSearchTerm_ `?string`: A search term to use if the new theme is not findable by its slug. ### deleteUser @@ -414,7 +414,7 @@ _Parameters_ _Returns_ -- `Promise`: all the blocks anchor nodes matching the lable in the ListView. +- `Promise`: all the blocks anchor nodes matching the label in the ListView. ### getOption @@ -479,7 +479,7 @@ Installs a plugin from the WP.org repository. _Parameters_ - _slug_ `string`: Plugin slug. -- _searchTerm_ `string?`: If the plugin is not findable by its slug use an alternative term to search. +- _searchTerm_ `?string`: If the plugin is not findable by its slug use an alternative term to search. ### installTheme @@ -488,8 +488,8 @@ Installs a theme from the WP.org repository. _Parameters_ - _slug_ `string`: Theme slug. -- _settings_ `Object?`: Optional settings object. -- _settings.searchTerm_ `string?`: Search term to use if the theme is not findable by its slug. +- _settings_ `?Object`: Optional settings object. +- _settings.searchTerm_ `?string`: Search term to use if the theme is not findable by its slug. ### isCurrentURL @@ -921,7 +921,7 @@ _Related_ _Parameters_ - _store_ `string`: Store to query e.g: core/editor, core/blocks... -- _selector_ `string`: Selector to exectute e.g: getBlocks. +- _selector_ `string`: Selector to execute e.g: getBlocks. - _parameters_ `...Object`: Parameters to pass to the selector. _Returns_ diff --git a/packages/e2e-test-utils/package.json b/packages/e2e-test-utils/package.json index 2ff76b73ed2fbf..c58be2844ba696 100644 --- a/packages/e2e-test-utils/package.json +++ b/packages/e2e-test-utils/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/e2e-test-utils", - "version": "11.11.0", + "version": "11.16.0", "description": "End-To-End (E2E) test utils for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -31,16 +31,16 @@ "module": "build-module/index.js", "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/api-fetch": "*", - "@wordpress/keycodes": "*", - "@wordpress/url": "*", + "@wordpress/api-fetch": "file:../api-fetch", + "@wordpress/keycodes": "file:../keycodes", + "@wordpress/url": "file:../url", "change-case": "^4.1.2", "form-data": "^4.0.0", "node-fetch": "2.7.0" }, "peerDependencies": { "jest": ">=29", - "puppeteer-core": ">=11" + "puppeteer-core": ">=23" }, "publishConfig": { "access": "public" diff --git a/packages/e2e-test-utils/src/create-user.js b/packages/e2e-test-utils/src/create-user.js index 317f23c4c58d40..ac28a59482e381 100644 --- a/packages/e2e-test-utils/src/create-user.js +++ b/packages/e2e-test-utils/src/create-user.js @@ -14,7 +14,7 @@ import { visitAdminPage } from './visit-admin-page'; * Create a new user account. * * @param {string} username User name. - * @param {Object?} object Optional Settings for the new user account. + * @param {?Object} object Optional Settings for the new user account. * @param {string} [object.firstName] First name. * @param {string} [object.lastName] Last name. * @param {string} [object.role] Role. Defaults to Administrator. diff --git a/packages/e2e-test-utils/src/delete-theme.js b/packages/e2e-test-utils/src/delete-theme.js index 98b7e2b4589234..b09bc6424b99bd 100644 --- a/packages/e2e-test-utils/src/delete-theme.js +++ b/packages/e2e-test-utils/src/delete-theme.js @@ -12,9 +12,9 @@ import { isThemeInstalled } from './theme-installed'; * Deletes a theme from the site, activating another theme if necessary. * * @param {string} slug Theme slug. - * @param {Object?} settings Optional settings object. - * @param {string?} settings.newThemeSlug A theme to switch to if the theme to delete is active. Required if the theme to delete is active. - * @param {string?} settings.newThemeSearchTerm A search term to use if the new theme is not findable by its slug. + * @param {?Object} settings Optional settings object. + * @param {?string} settings.newThemeSlug A theme to switch to if the theme to delete is active. Required if the theme to delete is active. + * @param {?string} settings.newThemeSearchTerm A search term to use if the new theme is not findable by its slug. */ export async function deleteTheme( slug, diff --git a/packages/e2e-test-utils/src/get-current-user.js b/packages/e2e-test-utils/src/get-current-user.js index d59ec0da2dd5da..6777c842be01f1 100644 --- a/packages/e2e-test-utils/src/get-current-user.js +++ b/packages/e2e-test-utils/src/get-current-user.js @@ -1,7 +1,7 @@ /** * Get the username of the user that's currently logged into WordPress (if any). * - * @return {string?} username The user that's currently logged into WordPress (if any). + * @return {?string} username The user that's currently logged into WordPress (if any). */ export async function getCurrentUser() { const cookies = await page.cookies(); diff --git a/packages/e2e-test-utils/src/get-list-view-blocks.js b/packages/e2e-test-utils/src/get-list-view-blocks.js index 8b52953b58290f..aed9501296a4f5 100644 --- a/packages/e2e-test-utils/src/get-list-view-blocks.js +++ b/packages/e2e-test-utils/src/get-list-view-blocks.js @@ -2,7 +2,7 @@ * Gets all block anchor nodes in the list view that match a given block name label. * * @param {string} blockLabel the label of the block as displayed in the ListView. - * @return {Promise} all the blocks anchor nodes matching the lable in the ListView. + * @return {Promise} all the blocks anchor nodes matching the label in the ListView. */ export async function getListViewBlocks( blockLabel ) { return page.$x( diff --git a/packages/e2e-test-utils/src/install-plugin.js b/packages/e2e-test-utils/src/install-plugin.js index 5edfbb54f6642a..239909953d518f 100644 --- a/packages/e2e-test-utils/src/install-plugin.js +++ b/packages/e2e-test-utils/src/install-plugin.js @@ -9,7 +9,7 @@ import { visitAdminPage } from './visit-admin-page'; * Installs a plugin from the WP.org repository. * * @param {string} slug Plugin slug. - * @param {string?} searchTerm If the plugin is not findable by its slug use an alternative term to search. + * @param {?string} searchTerm If the plugin is not findable by its slug use an alternative term to search. */ export async function installPlugin( slug, searchTerm ) { await switchUserToAdmin(); diff --git a/packages/e2e-test-utils/src/install-theme.js b/packages/e2e-test-utils/src/install-theme.js index 7d001d395bda7f..8adf7fe58a20cf 100644 --- a/packages/e2e-test-utils/src/install-theme.js +++ b/packages/e2e-test-utils/src/install-theme.js @@ -10,8 +10,8 @@ import { isThemeInstalled } from './theme-installed'; * Installs a theme from the WP.org repository. * * @param {string} slug Theme slug. - * @param {Object?} settings Optional settings object. - * @param {string?} settings.searchTerm Search term to use if the theme is not findable by its slug. + * @param {?Object} settings Optional settings object. + * @param {?string} settings.searchTerm Search term to use if the theme is not findable by its slug. */ export async function installTheme( slug, { searchTerm } = {} ) { await switchUserToAdmin(); diff --git a/packages/e2e-test-utils/src/wp-data-select.js b/packages/e2e-test-utils/src/wp-data-select.js index 65e382730292c2..9313115c20d2e3 100644 --- a/packages/e2e-test-utils/src/wp-data-select.js +++ b/packages/e2e-test-utils/src/wp-data-select.js @@ -13,7 +13,7 @@ * @see https://github.com/WordPress/gutenberg/pull/31199 * * @param {string} store Store to query e.g: core/editor, core/blocks... - * @param {string} selector Selector to exectute e.g: getBlocks. + * @param {string} selector Selector to execute e.g: getBlocks. * @param {...Object} parameters Parameters to pass to the selector. * * @return {Promise<?Object>} Result of querying. diff --git a/packages/e2e-tests/CHANGELOG.md b/packages/e2e-tests/CHANGELOG.md index 610a2bb9697452..ee16b4bfc26547 100644 --- a/packages/e2e-tests/CHANGELOG.md +++ b/packages/e2e-tests/CHANGELOG.md @@ -2,6 +2,16 @@ ## Unreleased +## 8.16.0 (2025-01-15) + +## 8.15.0 (2025-01-02) + +## 8.14.0 (2024-12-11) + +## 8.13.0 (2024-11-27) + +## 8.12.0 (2024-11-16) + ## 8.11.0 (2024-10-30) ## 8.10.0 (2024-10-16) diff --git a/packages/e2e-tests/README.md b/packages/e2e-tests/README.md index 75283a3d9ecc82..2f0aa79434a55b 100644 --- a/packages/e2e-tests/README.md +++ b/packages/e2e-tests/README.md @@ -78,7 +78,7 @@ Debugging in a Chrome browser can be replaced with `vscode`'s debugger by adding } ``` -This will run jest, targetting the spec file currently open in the editor. `vscode`'s debugger can now be used to add breakpoints and inspect tests as you would in Chrome DevTools. +This will run jest, targeting the spec file currently open in the editor. `vscode`'s debugger can now be used to add breakpoints and inspect tests as you would in Chrome DevTools. **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. diff --git a/packages/e2e-tests/package.json b/packages/e2e-tests/package.json index cceaa6210a2dd9..bc7219713cee9c 100644 --- a/packages/e2e-tests/package.json +++ b/packages/e2e-tests/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/e2e-tests", - "version": "8.11.0", + "version": "8.16.0", "description": "End-To-End (E2E) tests for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -24,13 +24,13 @@ "npm": ">=8.19.2" }, "dependencies": { - "@wordpress/e2e-test-utils": "*", - "@wordpress/interactivity": "*", - "@wordpress/interactivity-router": "*", - "@wordpress/jest-console": "*", - "@wordpress/jest-puppeteer-axe": "*", - "@wordpress/scripts": "*", - "@wordpress/url": "*", + "@wordpress/e2e-test-utils": "file:../e2e-test-utils", + "@wordpress/interactivity": "file:../interactivity", + "@wordpress/interactivity-router": "file:../interactivity-router", + "@wordpress/jest-console": "file:../jest-console", + "@wordpress/jest-puppeteer-axe": "file:../jest-puppeteer-axe", + "@wordpress/scripts": "file:../scripts", + "@wordpress/url": "file:../url", "chalk": "^4.0.0", "expect-puppeteer": "^4.4.0", "filenamify": "^4.2.0", @@ -40,7 +40,7 @@ }, "peerDependencies": { "jest": ">=29", - "puppeteer-core": ">=11", + "puppeteer-core": ">=23", "react": "^18.0.0", "react-dom": "^18.0.0" }, diff --git a/packages/e2e-tests/plugins/block-bindings.php b/packages/e2e-tests/plugins/block-bindings.php index b86673c2c523d0..1fd6d8468c77db 100644 --- a/packages/e2e-tests/plugins/block-bindings.php +++ b/packages/e2e-tests/plugins/block-bindings.php @@ -41,7 +41,11 @@ function gutenberg_test_block_bindings_registration() { plugins_url( 'block-bindings/index.js', __FILE__ ), array( 'wp-blocks', - 'wp-private-apis', + 'wp-block-editor', + 'wp-components', + 'wp-compose', + 'wp-element', + 'wp-hooks', ), filemtime( plugin_dir_path( __FILE__ ) . 'block-bindings/index.js' ), true diff --git a/packages/e2e-tests/plugins/block-bindings/index.js b/packages/e2e-tests/plugins/block-bindings/index.js index 5c364257caed19..63c463e197fa8a 100644 --- a/packages/e2e-tests/plugins/block-bindings/index.js +++ b/packages/e2e-tests/plugins/block-bindings/index.js @@ -1,4 +1,9 @@ const { registerBlockBindingsSource } = wp.blocks; +const { InspectorControls } = wp.blockEditor; +const { PanelBody, TextControl } = wp.components; +const { createHigherOrderComponent } = wp.compose; +const { createElement: el, Fragment } = wp.element; +const { addFilter } = wp.hooks; const { fieldsList } = window.testingBindings || {}; const getValues = ( { bindings } ) => { @@ -46,3 +51,43 @@ registerBlockBindingsSource( { getValues, canUserEditValue: () => true, } ); + +const withBlockBindingsInspectorControl = createHigherOrderComponent( + ( BlockEdit ) => { + return ( props ) => { + if ( ! props.attributes?.metadata?.bindings?.content ) { + return el( BlockEdit, props ); + } + + return el( + Fragment, + {}, + el( BlockEdit, props ), + el( + InspectorControls, + {}, + el( + PanelBody, + { title: 'Bindings' }, + el( TextControl, { + __next40pxDefaultSize: true, + __nextHasNoMarginBottom: true, + label: 'Content', + value: props.attributes.content, + onChange: ( content ) => + props.setAttributes( { + content, + } ), + } ) + ) + ) + ); + }; + } +); + +addFilter( + 'editor.BlockEdit', + 'testing/bindings-inspector-control', + withBlockBindingsInspectorControl +); diff --git a/packages/e2e-tests/plugins/cpt-locking.php b/packages/e2e-tests/plugins/cpt-locking.php index 5ac97c9cfae520..310c1df91580eb 100644 --- a/packages/e2e-tests/plugins/cpt-locking.php +++ b/packages/e2e-tests/plugins/cpt-locking.php @@ -8,7 +8,7 @@ */ /** - * Registers CPT's with 3 diffferent types of locking. + * Registers CPT's with 3 different types of locking. */ function gutenberg_test_cpt_locking() { $template = array( diff --git a/packages/e2e-tests/plugins/delete-installed-fonts.php b/packages/e2e-tests/plugins/delete-installed-fonts.php index 871d19f82e635e..3ef01406f854be 100644 --- a/packages/e2e-tests/plugins/delete-installed-fonts.php +++ b/packages/e2e-tests/plugins/delete-installed-fonts.php @@ -32,7 +32,7 @@ function gutenberg_filter_e2e_font_dir( $font_dir ) { /** * Deletes all user installed fonts, associated font files, the fonts directory, and user global styles typography - * setings for the current theme so that we can test uploading/installing fonts in a clean environment. + * settings for the current theme so that we can test uploading/installing fonts in a clean environment. */ function gutenberg_delete_installed_fonts() { $font_family_ids = new WP_Query( diff --git a/packages/e2e-tests/plugins/interactive-blocks/directive-each/render.php b/packages/e2e-tests/plugins/interactive-blocks/directive-each/render.php index 47eb351d837e78..bfac62feb13595 100644 --- a/packages/e2e-tests/plugins/interactive-blocks/directive-each/render.php +++ b/packages/e2e-tests/plugins/interactive-blocks/directive-each/render.php @@ -260,3 +260,54 @@ data-wp-text="context.callbackRunCount" ></data> </div> + +<hr> + +<div + data-wp-interactive="directive-each" + data-testid="each-with-unset" +> + <template data-wp-each="state.eachUnset"><p data-wp-text="context.item"></p></template> +</div> +<div + data-wp-interactive="directive-each" + data-testid="each-with-null" +> + <template data-wp-each="state.eachNull"><p data-wp-text="context.item"></p></template> +</div> +<div + data-wp-interactive="directive-each" + data-testid="each-with-undefined" +> + <template data-wp-each="state.eachUndefined"><p data-wp-text="context.item"></p></template> +</div> +<div + data-wp-interactive="directive-each" + data-testid="each-with-array" +> + <template data-wp-each="state.eachArray"><p data-wp-text="context.item"></p></template> +</div> +<div + data-wp-interactive="directive-each" + data-testid="each-with-set" +> + <template data-wp-each="state.eachSet"><p data-wp-text="context.item"></p></template> +</div> +<div + data-wp-interactive="directive-each" + data-testid="each-with-string" +> + <template data-wp-each="state.eachString"><p data-wp-text="context.item"></p></template> +</div> +<div + data-wp-interactive="directive-each" + data-testid="each-with-generator" +> + <template data-wp-each="state.eachGenerator"><p data-wp-text="context.item"></p></template> +</div> +<div + data-wp-interactive="directive-each" + data-testid="each-with-iterator" +> + <template data-wp-each="state.eachIterator"><p data-wp-text="context.item"></p></template> +</div> diff --git a/packages/e2e-tests/plugins/interactive-blocks/directive-each/view.js b/packages/e2e-tests/plugins/interactive-blocks/directive-each/view.js index 6ceef82864d9db..7577810b6bb876 100644 --- a/packages/e2e-tests/plugins/interactive-blocks/directive-each/view.js +++ b/packages/e2e-tests/plugins/interactive-blocks/directive-each/view.js @@ -13,6 +13,28 @@ const { state } = store( 'directive-each' ); store( 'directive-each', { state: { letters: [ 'A', 'B', 'C' ], + eachUndefined: undefined, + eachNull: null, + eachArray: [ 'an', 'array' ], + eachSet: new Set( [ 'a', 'set' ] ), + eachString: 'str', + *eachGenerator() { + yield 'a'; + yield 'generator'; + }, + eachIterator: { + [ Symbol.iterator ]() { + const vals = [ 'implements', 'iterator' ]; + let i = 0; + return { + next() { + return i < vals.length + ? { value: vals[ i++ ], done: false } + : { done: true }; + }, + }; + }, + }, }, } ); diff --git a/packages/e2e-tests/plugins/interactive-blocks/router-styles-blue/assets/10x10_e2e_test_image_blue.png b/packages/e2e-tests/plugins/interactive-blocks/router-styles-blue/assets/10x10_e2e_test_image_blue.png new file mode 100644 index 00000000000000..c4f8e7c5146d36 Binary files /dev/null and b/packages/e2e-tests/plugins/interactive-blocks/router-styles-blue/assets/10x10_e2e_test_image_blue.png differ diff --git a/packages/e2e-tests/plugins/interactive-blocks/router-styles-blue/block.json b/packages/e2e-tests/plugins/interactive-blocks/router-styles-blue/block.json new file mode 100644 index 00000000000000..644ea70f74dca1 --- /dev/null +++ b/packages/e2e-tests/plugins/interactive-blocks/router-styles-blue/block.json @@ -0,0 +1,15 @@ +{ + "$schema": "https://schemas.wp.org/trunk/block.json", + "apiVersion": 2, + "name": "test/router-styles-blue", + "title": "E2E Interactivity tests - router styles - Blue", + "category": "text", + "icon": "heart", + "description": "", + "supports": { + "interactivity": true + }, + "textdomain": "e2e-interactivity", + "viewStyle": "file:./style.css", + "render": "file:./render.php" +} diff --git a/packages/e2e-tests/plugins/interactive-blocks/router-styles-blue/render.php b/packages/e2e-tests/plugins/interactive-blocks/router-styles-blue/render.php new file mode 100644 index 00000000000000..3f5da308db092a --- /dev/null +++ b/packages/e2e-tests/plugins/interactive-blocks/router-styles-blue/render.php @@ -0,0 +1,35 @@ +<?php +/** + * HTML for testing the iAPI's style assets management. + * + * @package gutenberg-test-interactive-blocks + * + * @phpcs:disable VariableAnalysis.CodeAnalysis.VariableAnalysis.UndefinedVariable + */ + +add_action( + 'wp_enqueue_scripts', + function () { + wp_enqueue_style( + 'blue-from-link', + plugin_dir_url( __FILE__ ) . 'style-from-link.css', + array() + ); + + $custom_css = ' + .blue-from-inline { + color: rgb(0, 0, 255); + } + '; + + wp_register_style( 'test-router-styles', false ); + wp_enqueue_style( 'test-router-styles' ); + wp_add_inline_style( 'test-router-styles', $custom_css ); + } +); + +$wrapper_attributes = get_block_wrapper_attributes( + array( 'data-testid' => 'blue-block' ) +); +?> +<p <?php echo $wrapper_attributes; ?>>Blue</p> diff --git a/packages/e2e-tests/plugins/interactive-blocks/router-styles-blue/style-from-link.css b/packages/e2e-tests/plugins/interactive-blocks/router-styles-blue/style-from-link.css new file mode 100644 index 00000000000000..f55f12f4d594cf --- /dev/null +++ b/packages/e2e-tests/plugins/interactive-blocks/router-styles-blue/style-from-link.css @@ -0,0 +1,7 @@ +.blue-from-link { + color: rgb(0, 0, 255); +} + +.background-from-link { + background-image: url('./assets/10x10_e2e_test_image_blue.png'); +} \ No newline at end of file diff --git a/packages/e2e-tests/plugins/interactive-blocks/router-styles-blue/style.css b/packages/e2e-tests/plugins/interactive-blocks/router-styles-blue/style.css new file mode 100644 index 00000000000000..84d891e90242a5 --- /dev/null +++ b/packages/e2e-tests/plugins/interactive-blocks/router-styles-blue/style.css @@ -0,0 +1,4 @@ +.wp-block-test-router-styles-blue, +.blue { + color: rgb(0, 0, 255); +} \ No newline at end of file diff --git a/packages/e2e-tests/plugins/interactive-blocks/router-styles-green/assets/10x10_e2e_test_image_green.png b/packages/e2e-tests/plugins/interactive-blocks/router-styles-green/assets/10x10_e2e_test_image_green.png new file mode 100644 index 00000000000000..34ec87925d8c50 Binary files /dev/null and b/packages/e2e-tests/plugins/interactive-blocks/router-styles-green/assets/10x10_e2e_test_image_green.png differ diff --git a/packages/e2e-tests/plugins/interactive-blocks/router-styles-green/block.json b/packages/e2e-tests/plugins/interactive-blocks/router-styles-green/block.json new file mode 100644 index 00000000000000..e2edda625571b9 --- /dev/null +++ b/packages/e2e-tests/plugins/interactive-blocks/router-styles-green/block.json @@ -0,0 +1,15 @@ +{ + "$schema": "https://schemas.wp.org/trunk/block.json", + "apiVersion": 2, + "name": "test/router-styles-green", + "title": "E2E Interactivity tests - router styles - Green", + "category": "text", + "icon": "heart", + "description": "", + "supports": { + "interactivity": true + }, + "textdomain": "e2e-interactivity", + "viewStyle": "file:./style.css", + "render": "file:./render.php" +} diff --git a/packages/e2e-tests/plugins/interactive-blocks/router-styles-green/render.php b/packages/e2e-tests/plugins/interactive-blocks/router-styles-green/render.php new file mode 100644 index 00000000000000..4418a2d3ab0f3d --- /dev/null +++ b/packages/e2e-tests/plugins/interactive-blocks/router-styles-green/render.php @@ -0,0 +1,35 @@ +<?php +/** + * HTML for testing the iAPI's style assets management. + * + * @package gutenberg-test-interactive-blocks + * + * @phpcs:disable VariableAnalysis.CodeAnalysis.VariableAnalysis.UndefinedVariable + */ + +add_action( + 'wp_enqueue_scripts', + function () { + wp_enqueue_style( + 'green-from-link', + plugin_dir_url( __FILE__ ) . 'style-from-link.css', + array() + ); + + $custom_css = ' + .green-from-inline { + color: rgb(0, 255, 0); + } + '; + + wp_register_style( 'test-router-styles', false ); + wp_enqueue_style( 'test-router-styles' ); + wp_add_inline_style( 'test-router-styles', $custom_css ); + } +); + +$wrapper_attributes = get_block_wrapper_attributes( + array( 'data-testid' => 'green-block' ) +); +?> +<p <?php echo $wrapper_attributes; ?>>Green</p> diff --git a/packages/e2e-tests/plugins/interactive-blocks/router-styles-green/style-from-link.css b/packages/e2e-tests/plugins/interactive-blocks/router-styles-green/style-from-link.css new file mode 100644 index 00000000000000..b3d7d7b111e52a --- /dev/null +++ b/packages/e2e-tests/plugins/interactive-blocks/router-styles-green/style-from-link.css @@ -0,0 +1,7 @@ +.green-from-link { + color: rgb(0, 255, 0); +} + +.background-from-link { + background-image: url('./assets/10x10_e2e_test_image_green.png'); +} \ No newline at end of file diff --git a/packages/e2e-tests/plugins/interactive-blocks/router-styles-green/style.css b/packages/e2e-tests/plugins/interactive-blocks/router-styles-green/style.css new file mode 100644 index 00000000000000..0c457588f625cb --- /dev/null +++ b/packages/e2e-tests/plugins/interactive-blocks/router-styles-green/style.css @@ -0,0 +1,4 @@ +.wp-block-test-router-styles-green, +.green { + color: rgb(0, 255, 0); +} \ No newline at end of file diff --git a/packages/e2e-tests/plugins/interactive-blocks/router-styles-red/assets/10x10_e2e_test_image_red.png b/packages/e2e-tests/plugins/interactive-blocks/router-styles-red/assets/10x10_e2e_test_image_red.png new file mode 100644 index 00000000000000..3264bf6427c276 Binary files /dev/null and b/packages/e2e-tests/plugins/interactive-blocks/router-styles-red/assets/10x10_e2e_test_image_red.png differ diff --git a/packages/e2e-tests/plugins/interactive-blocks/router-styles-red/block.json b/packages/e2e-tests/plugins/interactive-blocks/router-styles-red/block.json new file mode 100644 index 00000000000000..582d7019062c6e --- /dev/null +++ b/packages/e2e-tests/plugins/interactive-blocks/router-styles-red/block.json @@ -0,0 +1,15 @@ +{ + "$schema": "https://schemas.wp.org/trunk/block.json", + "apiVersion": 2, + "name": "test/router-styles-red", + "title": "E2E Interactivity tests - router styles - Red", + "category": "text", + "icon": "heart", + "description": "", + "supports": { + "interactivity": true + }, + "textdomain": "e2e-interactivity", + "viewStyle": "file:./style.css", + "render": "file:./render.php" +} diff --git a/packages/e2e-tests/plugins/interactive-blocks/router-styles-red/render.php b/packages/e2e-tests/plugins/interactive-blocks/router-styles-red/render.php new file mode 100644 index 00000000000000..e8474cf69b825a --- /dev/null +++ b/packages/e2e-tests/plugins/interactive-blocks/router-styles-red/render.php @@ -0,0 +1,35 @@ +<?php +/** + * HTML for testing the iAPI's style assets management. + * + * @package gutenberg-test-interactive-blocks + * + * @phpcs:disable VariableAnalysis.CodeAnalysis.VariableAnalysis.UndefinedVariable + */ + +add_action( + 'wp_enqueue_scripts', + function () { + wp_enqueue_style( + 'red-from-link', + plugin_dir_url( __FILE__ ) . 'style-from-link.css', + array() + ); + + $custom_css = ' + .red-from-inline { + color: rgb(255, 0, 0); + } + '; + + wp_register_style( 'test-router-styles', false ); + wp_enqueue_style( 'test-router-styles' ); + wp_add_inline_style( 'test-router-styles', $custom_css ); + } +); + +$wrapper_attributes = get_block_wrapper_attributes( + array( 'data-testid' => 'red-block' ) +); +?> +<p <?php echo $wrapper_attributes; ?>>Red</p> diff --git a/packages/e2e-tests/plugins/interactive-blocks/router-styles-red/style-from-link.css b/packages/e2e-tests/plugins/interactive-blocks/router-styles-red/style-from-link.css new file mode 100644 index 00000000000000..0f7d6228079897 --- /dev/null +++ b/packages/e2e-tests/plugins/interactive-blocks/router-styles-red/style-from-link.css @@ -0,0 +1,7 @@ +.red-from-link { + color: rgb(255, 0, 0); +} + +.background-from-link { + background-image: url('./assets/10x10_e2e_test_image_red.png'); +} \ No newline at end of file diff --git a/packages/e2e-tests/plugins/interactive-blocks/router-styles-red/style.css b/packages/e2e-tests/plugins/interactive-blocks/router-styles-red/style.css new file mode 100644 index 00000000000000..eac7e3af16e0b5 --- /dev/null +++ b/packages/e2e-tests/plugins/interactive-blocks/router-styles-red/style.css @@ -0,0 +1,4 @@ +.wp-block-test-router-styles-red, +.red { + color: rgb(255, 0, 0); +} \ No newline at end of file diff --git a/packages/e2e-tests/plugins/interactive-blocks/router-styles-wrapper/block.json b/packages/e2e-tests/plugins/interactive-blocks/router-styles-wrapper/block.json new file mode 100644 index 00000000000000..a1a95b4c81e3b6 --- /dev/null +++ b/packages/e2e-tests/plugins/interactive-blocks/router-styles-wrapper/block.json @@ -0,0 +1,16 @@ +{ + "$schema": "https://schemas.wp.org/trunk/block.json", + "apiVersion": 2, + "name": "test/router-styles-wrapper", + "title": "E2E Interactivity tests - router styles - Wrapper", + "category": "text", + "icon": "heart", + "description": "", + "supports": { + "interactivity": true + }, + "textdomain": "e2e-interactivity", + "viewScriptModule": "file:./view.js", + "viewStyle": "file:./style.css", + "render": "file:./render.php" +} diff --git a/packages/e2e-tests/plugins/interactive-blocks/router-styles-wrapper/render.php b/packages/e2e-tests/plugins/interactive-blocks/router-styles-wrapper/render.php new file mode 100644 index 00000000000000..6373e8e9bc235b --- /dev/null +++ b/packages/e2e-tests/plugins/interactive-blocks/router-styles-wrapper/render.php @@ -0,0 +1,70 @@ +<?php +/** + * HTML for testing the iAPI's style assets management. + * + * @package gutenberg-test-interactive-blocks + * + * @phpcs:disable VariableAnalysis.CodeAnalysis.VariableAnalysis.UndefinedVariable + */ + +$wrapper_attributes = get_block_wrapper_attributes(); +?> +<div <?php echo $wrapper_attributes; ?>> + <!-- These get colored when the corresponding block is present. --> + <fieldset> + <legend>Styles from block styles</legend> + <p data-testid="red" class="red">Red</p> + <p data-testid="green" class="green">Green</p> + <p data-testid="blue" class="blue">Blue</p> + <p data-testid="all" class="red green blue">All</p> + </fieldset> + + <!-- These get colored when the corresponding block enqueues a referenced stylesheet. --> + <fieldset> + <legend>Styles from referenced style sheets</legend> + <p data-testid="red-from-link" class="red-from-link">Red from link</p> + <p data-testid="green-from-link" class="green-from-link">Green from link</p> + <p data-testid="blue-from-link" class="blue-from-link">Blue from link</p> + <p data-testid="all-from-link" class="red-from-link green-from-link blue-from-link">All from link</p> + <div data-testid="background-from-link"class="background-from-link" style="width: 10px; height: 10px"></div> + </fieldset> + + <!-- These get colored when the corresponding block adds inline style. --> + <fieldset> + <legend>Styles from inline styles</legend> + <p data-testid="red-from-inline" class="red-from-inline">Red</p> + <p data-testid="green-from-inline" class="green-from-inline">Green</p> + <p data-testid="blue-from-inline" class="blue-from-inline">Blue</p> + <p data-testid="all-from-inline" class="red-from-inline green-from-inline blue-from-inline">All</p> + </fieldset> + + <!-- Links to pages with different blocks combination. --> + <nav data-wp-interactive="test/router-styles"> + <?php foreach ( $attributes['links'] as $label => $link ) : ?> + <a + data-testid="link <?php echo $label; ?>" + data-wp-on--click="actions.navigate" + href="<?php echo $link; ?>" + > + <?php echo $label; ?> + </a> + <?php endforeach; ?> + </nav> + + <!-- HTML updated on navigation. --> + <div + data-wp-interactive="test/router-styles" + data-wp-router-region="router-styles" + > + <?php echo $content; ?> + </div> + + <!-- Text to check whether a navigation was client-side. --> + <div + data-testid="client-side navigation" + data-wp-interactive="test/router-styles" + data-wp-bind--hidden="!state.clientSideNavigation" + > + Client-side navigation + </div> +</div> diff --git a/packages/e2e-tests/plugins/interactive-blocks/router-styles-wrapper/style.css b/packages/e2e-tests/plugins/interactive-blocks/router-styles-wrapper/style.css new file mode 100644 index 00000000000000..12773560c4180f --- /dev/null +++ b/packages/e2e-tests/plugins/interactive-blocks/router-styles-wrapper/style.css @@ -0,0 +1,3 @@ +.wp-block-test-router-styles-wrapper { + color: rgb(160, 12, 60); +} \ No newline at end of file diff --git a/packages/e2e-tests/plugins/interactive-blocks/router-styles-wrapper/view.asset.php b/packages/e2e-tests/plugins/interactive-blocks/router-styles-wrapper/view.asset.php new file mode 100644 index 00000000000000..bdaec8d1b67a9d --- /dev/null +++ b/packages/e2e-tests/plugins/interactive-blocks/router-styles-wrapper/view.asset.php @@ -0,0 +1,9 @@ +<?php return array( + 'dependencies' => array( + '@wordpress/interactivity', + array( + 'id' => '@wordpress/interactivity-router', + 'import' => 'dynamic', + ), + ), +); diff --git a/packages/e2e-tests/plugins/interactive-blocks/router-styles-wrapper/view.js b/packages/e2e-tests/plugins/interactive-blocks/router-styles-wrapper/view.js new file mode 100644 index 00000000000000..5b3b42f2b413e4 --- /dev/null +++ b/packages/e2e-tests/plugins/interactive-blocks/router-styles-wrapper/view.js @@ -0,0 +1,20 @@ +/** + * WordPress dependencies + */ +import { store } from '@wordpress/interactivity'; + +const { state } = store( 'test/router-styles', { + state: { + clientSideNavigation: false, + }, + actions: { + *navigate( e ) { + e.preventDefault(); + const { actions } = yield import( + '@wordpress/interactivity-router' + ); + yield actions.navigate( e.target.href ); + state.clientSideNavigation = true; + }, + }, +} ); diff --git a/packages/edit-post/CHANGELOG.md b/packages/edit-post/CHANGELOG.md index 48a101b543821e..4c5e4868fc7b10 100644 --- a/packages/edit-post/CHANGELOG.md +++ b/packages/edit-post/CHANGELOG.md @@ -2,6 +2,16 @@ ## Unreleased +## 8.16.0 (2025-01-15) + +## 8.15.0 (2025-01-02) + +## 8.14.0 (2024-12-11) + +## 8.13.0 (2024-11-27) + +## 8.12.0 (2024-11-16) + ## 8.11.0 (2024-10-30) ## 8.10.0 (2024-10-16) diff --git a/packages/edit-post/package.json b/packages/edit-post/package.json index 1b28c1d5f31aa6..84f216fa60c050 100644 --- a/packages/edit-post/package.json +++ b/packages/edit-post/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/edit-post", - "version": "8.11.0", + "version": "8.16.0", "description": "Edit Post module for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -29,35 +29,35 @@ "wpScript": true, "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/a11y": "*", - "@wordpress/api-fetch": "*", - "@wordpress/block-editor": "*", - "@wordpress/block-library": "*", - "@wordpress/blocks": "*", - "@wordpress/commands": "*", - "@wordpress/components": "*", - "@wordpress/compose": "*", - "@wordpress/core-commands": "*", - "@wordpress/core-data": "*", - "@wordpress/data": "*", - "@wordpress/deprecated": "*", - "@wordpress/dom": "*", - "@wordpress/editor": "*", - "@wordpress/element": "*", - "@wordpress/hooks": "*", - "@wordpress/html-entities": "*", - "@wordpress/i18n": "*", - "@wordpress/icons": "*", - "@wordpress/keyboard-shortcuts": "*", - "@wordpress/keycodes": "*", - "@wordpress/notices": "*", - "@wordpress/plugins": "*", - "@wordpress/preferences": "*", - "@wordpress/private-apis": "*", - "@wordpress/url": "*", - "@wordpress/viewport": "*", - "@wordpress/warning": "*", - "@wordpress/widgets": "*", + "@wordpress/a11y": "file:../a11y", + "@wordpress/api-fetch": "file:../api-fetch", + "@wordpress/block-editor": "file:../block-editor", + "@wordpress/block-library": "file:../block-library", + "@wordpress/blocks": "file:../blocks", + "@wordpress/commands": "file:../commands", + "@wordpress/components": "file:../components", + "@wordpress/compose": "file:../compose", + "@wordpress/core-commands": "file:../core-commands", + "@wordpress/core-data": "file:../core-data", + "@wordpress/data": "file:../data", + "@wordpress/deprecated": "file:../deprecated", + "@wordpress/dom": "file:../dom", + "@wordpress/editor": "file:../editor", + "@wordpress/element": "file:../element", + "@wordpress/hooks": "file:../hooks", + "@wordpress/html-entities": "file:../html-entities", + "@wordpress/i18n": "file:../i18n", + "@wordpress/icons": "file:../icons", + "@wordpress/keyboard-shortcuts": "file:../keyboard-shortcuts", + "@wordpress/keycodes": "file:../keycodes", + "@wordpress/notices": "file:../notices", + "@wordpress/plugins": "file:../plugins", + "@wordpress/preferences": "file:../preferences", + "@wordpress/private-apis": "file:../private-apis", + "@wordpress/url": "file:../url", + "@wordpress/viewport": "file:../viewport", + "@wordpress/warning": "file:../warning", + "@wordpress/widgets": "file:../widgets", "clsx": "^2.1.1", "memize": "^2.1.0" }, diff --git a/packages/edit-post/src/components/back-button/style.scss b/packages/edit-post/src/components/back-button/style.scss index aced752bfedfae..3082cb80a3e4e2 100644 --- a/packages/edit-post/src/components/back-button/style.scss +++ b/packages/edit-post/src/components/back-button/style.scss @@ -23,8 +23,9 @@ } &::before { - transition: box-shadow 0.1s ease; - @include reduce-motion("transition"); + @media not (prefers-reduced-motion) { + transition: box-shadow 0.1s ease; + } content: ""; display: block; position: absolute; diff --git a/packages/edit-post/src/components/browser-url/index.js b/packages/edit-post/src/components/browser-url/index.js index 12292cb8447217..00492afbdc2624 100644 --- a/packages/edit-post/src/components/browser-url/index.js +++ b/packages/edit-post/src/components/browser-url/index.js @@ -27,14 +27,13 @@ export class BrowserURL extends Component { } componentDidUpdate( prevProps ) { - const { postId, postStatus, hasHistory } = this.props; + const { postId, postStatus } = this.props; const { historyId } = this.state; if ( ( postId !== prevProps.postId || postId !== historyId ) && postStatus !== 'auto-draft' && - postId && - ! hasHistory + postId ) { this.setBrowserURL( postId ); } diff --git a/packages/edit-post/src/components/layout/index.js b/packages/edit-post/src/components/layout/index.js index 225788a15a8e1d..3f9f71b4f4de8a 100644 --- a/packages/edit-post/src/components/layout/index.js +++ b/packages/edit-post/src/components/layout/index.js @@ -74,6 +74,7 @@ import useEditPostCommands from '../../commands/use-commands'; import { usePaddingAppender } from './use-padding-appender'; import { useShouldIframe } from './use-should-iframe'; import useNavigateToEntityRecord from '../../hooks/use-navigate-to-entity-record'; +import { useMetaBoxInitialization } from '../meta-boxes/use-meta-box-initialization'; const { getLayoutStyles } = unlock( blockEditorPrivateApis ); const { useCommands } = unlock( coreCommandsPrivateApis ); @@ -148,11 +149,7 @@ function useEditorStyles( ...additionalStyles ) { ] ); } -/** - * @param {Object} props - * @param {boolean} props.isLegacy True when the editor canvas is not in an iframe. - */ -function MetaBoxesMain( { isLegacy } ) { +function MetaBoxesMain() { const [ isOpen, openHeight, hasAnyVisible ] = useSelect( ( select ) => { const { get } = select( preferencesStore ); const { isMetaBoxLocationVisible } = select( editPostStore ); @@ -171,7 +168,7 @@ function MetaBoxesMain( { isLegacy } ) { const [ { min, max }, setHeightConstraints ] = useState( () => ( {} ) ); // Keeps the resizable area’s size constraints updated taking into account // editor notices. The constraints are also used to derive the value for the - // aria-valuenow attribute on the seperator. + // aria-valuenow attribute on the separator. const effectSizeConstraints = useRefEffect( ( node ) => { const container = node.closest( '.interface-interface-skeleton__content' @@ -232,22 +229,15 @@ function MetaBoxesMain( { isLegacy } ) { const contents = ( <div - className={ clsx( - // The class name 'edit-post-layout__metaboxes' is retained because some plugins use it. - 'edit-post-layout__metaboxes', - ! isLegacy && 'edit-post-meta-boxes-main__liner' - ) } - hidden={ ! isLegacy && isShort && ! isOpen } + // The class name 'edit-post-layout__metaboxes' is retained because some plugins use it. + className="edit-post-layout__metaboxes edit-post-meta-boxes-main__liner" + hidden={ isShort && ! isOpen } > <MetaBoxes location="normal" /> <MetaBoxes location="advanced" /> </div> ); - if ( isLegacy ) { - return contents; - } - const isAutoHeight = openHeight === undefined; let usedMax = '50%'; // Approximation before max has a value. if ( max !== undefined ) { @@ -328,7 +318,9 @@ function MetaBoxesMain( { isLegacy } ) { // the event to end the drag is captured by the target (resize handle) // whether or not it’s under the pointer. onPointerDown: ( { pointerId, target } ) => { - target.setPointerCapture( pointerId ); + if ( separatorRef.current.parentElement.contains( target ) ) { + target.setPointerCapture( pointerId ); + } }, onResizeStart: ( event, direction, elementRef ) => { if ( isAutoHeight ) { @@ -392,17 +384,16 @@ function Layout( { showIconLabels, isDistractionFree, showMetaBoxes, - hasHistory, isWelcomeGuideVisible, templateId, enablePaddingAppender, } = useSelect( ( select ) => { const { get } = select( preferencesStore ); - const { isFeatureActive, getEditedPostTemplateId } = unlock( - select( editPostStore ) + const { isFeatureActive } = select( editPostStore ); + const { canUser, getPostType, getTemplateId } = unlock( + select( coreStore ) ); - const { canUser, getPostType } = select( coreStore ); const supportsTemplateMode = settings.supportsTemplateMode; const isViewable = @@ -414,6 +405,11 @@ function Layout( { const { isZoomOut } = unlock( select( blockEditorStore ) ); const { getEditorMode, getRenderingMode } = select( editorStore ); const isRenderingPostOnly = getRenderingMode() === 'post-only'; + const isNotDesignPostType = + ! DESIGN_POST_TYPES.includes( currentPostType ); + const isDirectlyEditingPattern = + currentPostType === 'wp_block' && + ! onNavigateToPreviousEntityRecord; return { mode: getEditorMode(), @@ -425,24 +421,30 @@ function Layout( { showIconLabels: get( 'core', 'showIconLabels' ), isDistractionFree: get( 'core', 'distractionFree' ), showMetaBoxes: - ! DESIGN_POST_TYPES.includes( currentPostType ) && - ! isZoomOut(), + ( isNotDesignPostType && ! isZoomOut() ) || + isDirectlyEditingPattern, isWelcomeGuideVisible: isFeatureActive( 'welcomeGuide' ), templateId: supportsTemplateMode && isViewable && canViewTemplate && ! isEditingTemplate - ? getEditedPostTemplateId() + ? getTemplateId( currentPostType, currentPostId ) : null, enablePaddingAppender: - ! isZoomOut() && - isRenderingPostOnly && - ! DESIGN_POST_TYPES.includes( currentPostType ), + ! isZoomOut() && isRenderingPostOnly && isNotDesignPostType, }; }, - [ currentPostType, isEditingTemplate, settings.supportsTemplateMode ] + [ + currentPostType, + currentPostId, + isEditingTemplate, + settings.supportsTemplateMode, + onNavigateToPreviousEntityRecord, + ] ); + useMetaBoxInitialization( hasActiveMetaboxes ); + const [ paddingAppenderRef, paddingStyle ] = usePaddingAppender( enablePaddingAppender ); @@ -554,7 +556,7 @@ function Layout( { return ( <SlotFillProvider> - <ErrorBoundary> + <ErrorBoundary canCopyContent> <CommandMenu /> <WelcomeGuide postType={ currentPostType } /> <div @@ -582,15 +584,13 @@ function Layout( { } extraContent={ ! isDistractionFree && - showMetaBoxes && ( - <MetaBoxesMain isLegacy={ ! shouldIframe } /> - ) + showMetaBoxes && <MetaBoxesMain /> } > <PostLockedModal /> <EditorInitialization /> <FullscreenMode isActive={ isFullscreenActive } /> - <BrowserURL hasHistory={ hasHistory } /> + <BrowserURL /> <UnsavedChangesWarning /> <AutosaveMonitor /> <LocalAutosaveMonitor /> diff --git a/packages/edit-post/src/components/layout/style.scss b/packages/edit-post/src/components/layout/style.scss index 18f12c1dbfbb92..c63f3eb2528177 100644 --- a/packages/edit-post/src/components/layout/style.scss +++ b/packages/edit-post/src/components/layout/style.scss @@ -73,8 +73,9 @@ width: inherit; height: $grid-unit-05; border-radius: $radius-small; - transition: width 0.3s ease-out; - @include reduce-motion("transition"); + @media not (prefers-reduced-motion) { + transition: width 0.3s ease-out; + } } } @@ -107,11 +108,9 @@ } .has-metaboxes .editor-visual-editor { - flex: 1; - - &.is-iframed { - isolation: isolate; - } + // Contains z-indexes of children so that the block toolbar will appear behind + // the drop shadow of the meta box pane. + isolation: isolate; } // Adjust the position of the notices diff --git a/packages/edit-post/src/components/meta-boxes/index.js b/packages/edit-post/src/components/meta-boxes/index.js index 14728c97cf6b68..21416ee43ae240 100644 --- a/packages/edit-post/src/components/meta-boxes/index.js +++ b/packages/edit-post/src/components/meta-boxes/index.js @@ -1,9 +1,7 @@ /** * WordPress dependencies */ -import { useSelect, useRegistry } from '@wordpress/data'; -import { useEffect } from '@wordpress/element'; -import { store as editorStore } from '@wordpress/editor'; +import { useSelect } from '@wordpress/data'; /** * Internal dependencies @@ -13,38 +11,11 @@ import MetaBoxVisibility from './meta-box-visibility'; import { store as editPostStore } from '../../store'; export default function MetaBoxes( { location } ) { - const registry = useRegistry(); - const { metaBoxes, areMetaBoxesInitialized, isEditorReady } = useSelect( - ( select ) => { - const { __unstableIsEditorReady } = select( editorStore ); - const { - getMetaBoxesPerLocation, - areMetaBoxesInitialized: _areMetaBoxesInitialized, - } = select( editPostStore ); - return { - metaBoxes: getMetaBoxesPerLocation( location ), - areMetaBoxesInitialized: _areMetaBoxesInitialized(), - isEditorReady: __unstableIsEditorReady(), - }; - }, + const metaBoxes = useSelect( + ( select ) => + select( editPostStore ).getMetaBoxesPerLocation( location ), [ location ] ); - - const hasMetaBoxes = !! metaBoxes?.length; - - // When editor is ready, initialize postboxes (wp core script) and metabox - // saving. This initializes all meta box locations, not just this specific - // one. - useEffect( () => { - if ( isEditorReady && hasMetaBoxes && ! areMetaBoxesInitialized ) { - registry.dispatch( editPostStore ).initializeMetaBoxes(); - } - }, [ isEditorReady, hasMetaBoxes, areMetaBoxesInitialized ] ); - - if ( ! areMetaBoxesInitialized ) { - return null; - } - return ( <> { ( metaBoxes ?? [] ).map( ( { id } ) => ( diff --git a/packages/edit-post/src/components/meta-boxes/meta-box-visibility.js b/packages/edit-post/src/components/meta-boxes/meta-box-visibility.js index 26b69d37f00210..07060afa31dea5 100644 --- a/packages/edit-post/src/components/meta-boxes/meta-box-visibility.js +++ b/packages/edit-post/src/components/meta-boxes/meta-box-visibility.js @@ -1,24 +1,21 @@ /** * WordPress dependencies */ -import { Component } from '@wordpress/element'; -import { withSelect } from '@wordpress/data'; +import { useEffect } from '@wordpress/element'; +import { useSelect } from '@wordpress/data'; import { store as editorStore } from '@wordpress/editor'; -class MetaBoxVisibility extends Component { - componentDidMount() { - this.updateDOM(); - } - - componentDidUpdate( prevProps ) { - if ( this.props.isVisible !== prevProps.isVisible ) { - this.updateDOM(); - } - } - - updateDOM() { - const { id, isVisible } = this.props; +export default function MetaBoxVisibility( { id } ) { + const isVisible = useSelect( + ( select ) => { + return select( editorStore ).isEditorPanelEnabled( + `meta-box-${ id }` + ); + }, + [ id ] + ); + useEffect( () => { const element = document.getElementById( id ); if ( ! element ) { return; @@ -29,13 +26,7 @@ class MetaBoxVisibility extends Component { } else { element.classList.add( 'is-hidden' ); } - } + }, [ id, isVisible ] ); - render() { - return null; - } + return null; } - -export default withSelect( ( select, { id } ) => ( { - isVisible: select( editorStore ).isEditorPanelEnabled( `meta-box-${ id }` ), -} ) )( MetaBoxVisibility ); diff --git a/packages/edit-post/src/components/meta-boxes/use-meta-box-initialization.js b/packages/edit-post/src/components/meta-boxes/use-meta-box-initialization.js new file mode 100644 index 00000000000000..4309d85e3c22bf --- /dev/null +++ b/packages/edit-post/src/components/meta-boxes/use-meta-box-initialization.js @@ -0,0 +1,32 @@ +/** + * WordPress dependencies + */ +import { useDispatch, useSelect } from '@wordpress/data'; +import { store as editorStore } from '@wordpress/editor'; +import { useEffect } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import { store as editPostStore } from '../../store'; + +/** + * Initializes WordPress `postboxes` script and the logic for saving meta boxes. + * + * @param { boolean } enabled + */ +export const useMetaBoxInitialization = ( enabled ) => { + const isEnabledAndEditorReady = useSelect( + ( select ) => + enabled && select( editorStore ).__unstableIsEditorReady(), + [ enabled ] + ); + const { initializeMetaBoxes } = useDispatch( editPostStore ); + // The effect has to rerun when the editor is ready because initializeMetaBoxes + // will noop until then. + useEffect( () => { + if ( isEnabledAndEditorReady ) { + initializeMetaBoxes(); + } + }, [ isEnabledAndEditorReady, initializeMetaBoxes ] ); +}; diff --git a/packages/edit-post/src/components/preferences-modal/enable-custom-fields.js b/packages/edit-post/src/components/preferences-modal/enable-custom-fields.js index b8125e96c7c2cf..f3db9d123f9d71 100644 --- a/packages/edit-post/src/components/preferences-modal/enable-custom-fields.js +++ b/packages/edit-post/src/components/preferences-modal/enable-custom-fields.js @@ -4,7 +4,7 @@ import { useState } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; import { Button } from '@wordpress/components'; -import { withSelect } from '@wordpress/data'; +import { useSelect } from '@wordpress/data'; import { store as editorStore } from '@wordpress/editor'; import { privateApis as preferencesPrivateApis } from '@wordpress/preferences'; import { getPathAndQueryString } from '@wordpress/url'; @@ -57,7 +57,10 @@ export function CustomFieldsConfirmation( { willEnable } ) { ); } -export function EnableCustomFieldsOption( { label, areCustomFieldsEnabled } ) { +export default function EnableCustomFieldsOption( { label } ) { + const areCustomFieldsEnabled = useSelect( ( select ) => { + return !! select( editorStore ).getEditorSettings().enableCustomFields; + }, [] ); const [ isChecked, setIsChecked ] = useState( areCustomFieldsEnabled ); return ( @@ -72,8 +75,3 @@ export function EnableCustomFieldsOption( { label, areCustomFieldsEnabled } ) { </PreferenceBaseOption> ); } - -export default withSelect( ( select ) => ( { - areCustomFieldsEnabled: - !! select( editorStore ).getEditorSettings().enableCustomFields, -} ) )( EnableCustomFieldsOption ); diff --git a/packages/edit-post/src/components/preferences-modal/test/enable-custom-fields.js b/packages/edit-post/src/components/preferences-modal/test/enable-custom-fields.js index 2dbeadec8350ac..adfa4a3df391da 100644 --- a/packages/edit-post/src/components/preferences-modal/test/enable-custom-fields.js +++ b/packages/edit-post/src/components/preferences-modal/test/enable-custom-fields.js @@ -4,27 +4,38 @@ import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; +/** + * WordPress dependencies + */ +import { useSelect } from '@wordpress/data'; + /** * Internal dependencies */ import { - EnableCustomFieldsOption, + default as EnableCustomFieldsOption, CustomFieldsConfirmation, } from '../enable-custom-fields'; +jest.mock( '@wordpress/data/src/components/use-select', () => jest.fn() ); + +function setupUseSelectMock( areCustomFieldsEnabled ) { + useSelect.mockImplementation( () => { + return areCustomFieldsEnabled; + } ); +} + describe( 'EnableCustomFieldsOption', () => { it( 'renders a checked checkbox when custom fields are enabled', () => { - const { container } = render( - <EnableCustomFieldsOption areCustomFieldsEnabled /> - ); + setupUseSelectMock( true ); + const { container } = render( <EnableCustomFieldsOption /> ); expect( container ).toMatchSnapshot(); } ); it( 'renders an unchecked checkbox when custom fields are disabled', () => { - const { container } = render( - <EnableCustomFieldsOption areCustomFieldsEnabled={ false } /> - ); + setupUseSelectMock( false ); + const { container } = render( <EnableCustomFieldsOption /> ); expect( container ).toMatchSnapshot(); } ); @@ -32,9 +43,8 @@ describe( 'EnableCustomFieldsOption', () => { it( 'renders an unchecked checkbox and a confirmation message when toggled off', async () => { const user = userEvent.setup(); - const { container } = render( - <EnableCustomFieldsOption areCustomFieldsEnabled /> - ); + setupUseSelectMock( true ); + const { container } = render( <EnableCustomFieldsOption /> ); await user.click( screen.getByRole( 'checkbox' ) ); @@ -44,9 +54,8 @@ describe( 'EnableCustomFieldsOption', () => { it( 'renders a checked checkbox and a confirmation message when toggled on', async () => { const user = userEvent.setup(); - const { container } = render( - <EnableCustomFieldsOption areCustomFieldsEnabled={ false } /> - ); + setupUseSelectMock( false ); + const { container } = render( <EnableCustomFieldsOption /> ); await user.click( screen.getByRole( 'checkbox' ) ); diff --git a/packages/edit-post/src/components/welcome-guide/default.js b/packages/edit-post/src/components/welcome-guide/default.js index cdaf2c7608b43e..deb4b70c3ad529 100644 --- a/packages/edit-post/src/components/welcome-guide/default.js +++ b/packages/edit-post/src/components/welcome-guide/default.js @@ -18,7 +18,7 @@ export default function WelcomeGuideDefault() { return ( <Guide className="edit-post-welcome-guide" - contentLabel={ __( 'Welcome to the block editor' ) } + contentLabel={ __( 'Welcome to the editor' ) } finishButtonText={ __( 'Get started' ) } onFinish={ () => toggleFeature( 'welcomeGuide' ) } pages={ [ @@ -32,7 +32,7 @@ export default function WelcomeGuideDefault() { content: ( <> <h1 className="edit-post-welcome-guide__heading"> - { __( 'Welcome to the block editor' ) } + { __( 'Welcome to the editor' ) } </h1> <p className="edit-post-welcome-guide__text"> { __( @@ -52,7 +52,7 @@ export default function WelcomeGuideDefault() { content: ( <> <h1 className="edit-post-welcome-guide__heading"> - { __( 'Make each block your own' ) } + { __( 'Customize each block' ) } </h1> <p className="edit-post-welcome-guide__text"> { __( @@ -72,7 +72,7 @@ export default function WelcomeGuideDefault() { content: ( <> <h1 className="edit-post-welcome-guide__heading"> - { __( 'Get to know the block library' ) } + { __( 'Explore all blocks' ) } </h1> <p className="edit-post-welcome-guide__text"> { createInterpolateElement( @@ -102,7 +102,7 @@ export default function WelcomeGuideDefault() { content: ( <> <h1 className="edit-post-welcome-guide__heading"> - { __( 'Learn how to use the block editor' ) } + { __( 'Learn more' ) } </h1> <p className="edit-post-welcome-guide__text"> { createInterpolateElement( diff --git a/packages/edit-post/src/store/index.js b/packages/edit-post/src/store/index.js index 17033b759292d7..93dae7606d0e3c 100644 --- a/packages/edit-post/src/store/index.js +++ b/packages/edit-post/src/store/index.js @@ -9,9 +9,7 @@ import { createReduxStore, register } from '@wordpress/data'; import reducer from './reducer'; import * as actions from './actions'; import * as selectors from './selectors'; -import * as privateSelectors from './private-selectors'; import { STORE_NAME } from './constants'; -import { unlock } from '../lock-unlock'; /** * Store definition for the edit post namespace. @@ -26,4 +24,3 @@ export const store = createReduxStore( STORE_NAME, { selectors, } ); register( store ); -unlock( store ).registerPrivateSelectors( privateSelectors ); diff --git a/packages/edit-post/src/store/private-selectors.js b/packages/edit-post/src/store/private-selectors.js deleted file mode 100644 index 246b2754d895ab..00000000000000 --- a/packages/edit-post/src/store/private-selectors.js +++ /dev/null @@ -1,61 +0,0 @@ -/** - * WordPress dependencies - */ -import { createRegistrySelector } from '@wordpress/data'; -import { store as coreStore } from '@wordpress/core-data'; -import { store as editorStore } from '@wordpress/editor'; - -export const getEditedPostTemplateId = createRegistrySelector( - ( select ) => () => { - const { - id: postId, - type: postType, - slug, - } = select( editorStore ).getCurrentPost(); - const { getEntityRecord, getEntityRecords, canUser } = - select( coreStore ); - const siteSettings = canUser( 'read', { - kind: 'root', - name: 'site', - } ) - ? getEntityRecord( 'root', 'site' ) - : undefined; - // First check if the current page is set as the posts page. - const isPostsPage = +postId === siteSettings?.page_for_posts; - if ( isPostsPage ) { - return select( coreStore ).getDefaultTemplateId( { slug: 'home' } ); - } - const currentTemplate = - select( editorStore ).getEditedPostAttribute( 'template' ); - if ( currentTemplate ) { - const templateWithSameSlug = getEntityRecords( - 'postType', - 'wp_template', - { per_page: -1 } - )?.find( ( template ) => template.slug === currentTemplate ); - if ( ! templateWithSameSlug ) { - return templateWithSameSlug; - } - return templateWithSameSlug.id; - } - let slugToCheck; - // In `draft` status we might not have a slug available, so we use the `single` - // post type templates slug(ex page, single-post, single-product etc..). - // Pages do not need the `single` prefix in the slug to be prioritized - // through template hierarchy. - if ( slug ) { - slugToCheck = - postType === 'page' - ? `${ postType }-${ slug }` - : `single-${ postType }-${ slug }`; - } else { - slugToCheck = postType === 'page' ? 'page' : `single-${ postType }`; - } - - if ( postType ) { - return select( coreStore ).getDefaultTemplateId( { - slug: slugToCheck, - } ); - } - } -); diff --git a/packages/edit-post/src/store/selectors.js b/packages/edit-post/src/store/selectors.js index 8d85249e8100ba..58c802f579e0d1 100644 --- a/packages/edit-post/src/store/selectors.js +++ b/packages/edit-post/src/store/selectors.js @@ -14,8 +14,6 @@ import deprecated from '@wordpress/deprecated'; * Internal dependencies */ import { unlock } from '../lock-unlock'; -import { getEditedPostTemplateId } from './private-selectors'; - const { interfaceStore } = unlock( editorPrivateApis ); const EMPTY_ARRAY = []; const EMPTY_OBJECT = {}; @@ -552,11 +550,16 @@ export function areMetaBoxesInitialized( state ) { /** * Retrieves the template of the currently edited post. * - * @return {Object?} Post Template. + * @return {?Object} Post Template. */ export const getEditedPostTemplate = createRegistrySelector( - ( select ) => ( state ) => { - const templateId = getEditedPostTemplateId( state ); + ( select ) => () => { + const { id: postId, type: postType } = + select( editorStore ).getCurrentPost(); + const templateId = unlock( select( coreStore ) ).getTemplateId( + postType, + postId + ); if ( ! templateId ) { return undefined; } diff --git a/packages/edit-site/CHANGELOG.md b/packages/edit-site/CHANGELOG.md index 7efa4f6c5b9d45..98144061eb9031 100644 --- a/packages/edit-site/CHANGELOG.md +++ b/packages/edit-site/CHANGELOG.md @@ -2,6 +2,16 @@ ## Unreleased +## 6.16.0 (2025-01-15) + +## 6.15.0 (2025-01-02) + +## 6.14.0 (2024-12-11) + +## 6.13.0 (2024-11-27) + +## 6.12.0 (2024-11-16) + ## 6.11.0 (2024-10-30) ## 6.10.0 (2024-10-16) diff --git a/packages/edit-site/package.json b/packages/edit-site/package.json index 41bf39c1e08332..e0d306d2506cf9 100644 --- a/packages/edit-site/package.json +++ b/packages/edit-site/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/edit-site", - "version": "6.11.0", + "version": "6.16.0", "description": "Edit Site Page module for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -30,45 +30,46 @@ "dependencies": { "@babel/runtime": "7.25.7", "@react-spring/web": "^9.4.5", - "@wordpress/a11y": "*", - "@wordpress/api-fetch": "*", - "@wordpress/blob": "*", - "@wordpress/block-editor": "*", - "@wordpress/block-library": "*", - "@wordpress/blocks": "*", - "@wordpress/commands": "*", - "@wordpress/components": "*", - "@wordpress/compose": "*", - "@wordpress/core-commands": "*", - "@wordpress/core-data": "*", - "@wordpress/data": "*", - "@wordpress/dataviews": "*", - "@wordpress/date": "*", - "@wordpress/deprecated": "*", - "@wordpress/dom": "*", - "@wordpress/editor": "*", - "@wordpress/element": "*", - "@wordpress/escape-html": "*", - "@wordpress/fields": "*", - "@wordpress/hooks": "*", - "@wordpress/html-entities": "*", - "@wordpress/i18n": "*", - "@wordpress/icons": "*", - "@wordpress/keyboard-shortcuts": "*", - "@wordpress/keycodes": "*", - "@wordpress/notices": "*", - "@wordpress/patterns": "*", - "@wordpress/plugins": "*", - "@wordpress/preferences": "*", - "@wordpress/primitives": "*", - "@wordpress/private-apis": "*", - "@wordpress/reusable-blocks": "*", - "@wordpress/router": "*", - "@wordpress/style-engine": "*", - "@wordpress/url": "*", - "@wordpress/viewport": "*", - "@wordpress/widgets": "*", - "@wordpress/wordcount": "*", + "@wordpress/a11y": "file:../a11y", + "@wordpress/api-fetch": "file:../api-fetch", + "@wordpress/blob": "file:../blob", + "@wordpress/block-editor": "file:../block-editor", + "@wordpress/block-library": "file:../block-library", + "@wordpress/blocks": "file:../blocks", + "@wordpress/commands": "file:../commands", + "@wordpress/components": "file:../components", + "@wordpress/compose": "file:../compose", + "@wordpress/core-commands": "file:../core-commands", + "@wordpress/core-data": "file:../core-data", + "@wordpress/data": "file:../data", + "@wordpress/dataviews": "file:../dataviews", + "@wordpress/date": "file:../date", + "@wordpress/deprecated": "file:../deprecated", + "@wordpress/dom": "file:../dom", + "@wordpress/editor": "file:../editor", + "@wordpress/element": "file:../element", + "@wordpress/escape-html": "file:../escape-html", + "@wordpress/fields": "file:../fields", + "@wordpress/hooks": "file:../hooks", + "@wordpress/html-entities": "file:../html-entities", + "@wordpress/i18n": "file:../i18n", + "@wordpress/icons": "file:../icons", + "@wordpress/keyboard-shortcuts": "file:../keyboard-shortcuts", + "@wordpress/keycodes": "file:../keycodes", + "@wordpress/media-utils": "file:../media-utils", + "@wordpress/notices": "file:../notices", + "@wordpress/patterns": "file:../patterns", + "@wordpress/plugins": "file:../plugins", + "@wordpress/preferences": "file:../preferences", + "@wordpress/primitives": "file:../primitives", + "@wordpress/private-apis": "file:../private-apis", + "@wordpress/reusable-blocks": "file:../reusable-blocks", + "@wordpress/router": "file:../router", + "@wordpress/style-engine": "file:../style-engine", + "@wordpress/url": "file:../url", + "@wordpress/viewport": "file:../viewport", + "@wordpress/widgets": "file:../widgets", + "@wordpress/wordcount": "file:../wordcount", "change-case": "^4.1.2", "clsx": "^2.1.1", "colord": "^2.9.2", diff --git a/packages/edit-site/src/components/add-new-pattern/index.js b/packages/edit-site/src/components/add-new-pattern/index.js index bb9e53da6a5660..85a8c70f9c3359 100644 --- a/packages/edit-site/src/components/add-new-pattern/index.js +++ b/packages/edit-site/src/components/add-new-pattern/index.js @@ -25,7 +25,7 @@ import { TEMPLATE_PART_POST_TYPE, } from '../../utils/constants'; -const { useHistory } = unlock( routerPrivateApis ); +const { useHistory, useLocation } = unlock( routerPrivateApis ); const { CreatePatternModal, useAddPatternCategory } = unlock( editPatternsPrivateApis ); @@ -33,6 +33,7 @@ const { CreateTemplatePartModal } = unlock( editorPrivateApis ); export default function AddNewPattern() { const history = useHistory(); + const location = useLocation(); const [ showPatternModal, setShowPatternModal ] = useState( false ); const [ showTemplatePartModal, setShowTemplatePartModal ] = useState( false ); @@ -69,23 +70,16 @@ export default function AddNewPattern() { function handleCreatePattern( { pattern } ) { setShowPatternModal( false ); - - history.push( { - postId: pattern.id, - postType: PATTERN_TYPES.user, - canvas: 'edit', - } ); + history.navigate( + `/${ PATTERN_TYPES.user }/${ pattern.id }?canvas=edit` + ); } function handleCreateTemplatePart( templatePart ) { setShowTemplatePartModal( false ); - - // Navigate to the created template part editor. - history.push( { - postId: templatePart.id, - postType: TEMPLATE_PART_POST_TYPE, - canvas: 'edit', - } ); + history.navigate( + `/${ TEMPLATE_PART_POST_TYPE }/${ templatePart.id }?canvas=edit` + ); } function handleError() { @@ -166,13 +160,12 @@ export default function AddNewPattern() { return; } try { - const { - params: { postType, categoryId }, - } = history.getLocationWithParams(); let currentCategoryId; // When we're not handling template parts, we should // add or create the proper pattern category. - if ( postType !== TEMPLATE_PART_POST_TYPE ) { + if ( + location.query.postType !== TEMPLATE_PART_POST_TYPE + ) { /* * categoryMap.values() returns an iterator. * Iterator.prototype.find() is not yet widely supported. @@ -180,7 +173,10 @@ export default function AddNewPattern() { */ const currentCategory = Array.from( categoryMap.values() - ).find( ( term ) => term.name === categoryId ); + ).find( + ( term ) => + term.name === location.query.categoryId + ); if ( currentCategory ) { currentCategoryId = currentCategory.id || @@ -201,12 +197,11 @@ export default function AddNewPattern() { // category. if ( ! currentCategoryId && - categoryId !== 'my-patterns' + location.query.categoryId !== 'my-patterns' ) { - history.push( { - postType: PATTERN_TYPES.user, - categoryId: PATTERN_DEFAULT_CATEGORY, - } ); + history.navigate( + `/pattern?categoryId=${ PATTERN_DEFAULT_CATEGORY }` + ); } createSuccessNotice( diff --git a/packages/edit-site/src/components/add-new-post/index.js b/packages/edit-site/src/components/add-new-post/index.js index 04e286e3967a44..a30f5995aab6fa 100644 --- a/packages/edit-site/src/components/add-new-post/index.js +++ b/packages/edit-site/src/components/add-new-post/index.js @@ -45,7 +45,7 @@ export default function AddNewPostModal( { postType, onSave, onClose } ) { { status: 'draft', title, - slug: title || __( 'No title' ), + slug: title ?? undefined, content: !! postTypeObject.template && postTypeObject.template.length diff --git a/packages/edit-site/src/components/add-new-template/index.js b/packages/edit-site/src/components/add-new-template/index.js index 1a2d9ea727fa85..5f06ecae6824ae 100644 --- a/packages/edit-site/src/components/add-new-template/index.js +++ b/packages/edit-site/src/components/add-new-template/index.js @@ -203,11 +203,9 @@ function NewTemplateModal( { onClose } ) { ); // Navigate to the created template editor. - history.push( { - postId: newTemplate.id, - postType: TEMPLATE_POST_TYPE, - canvas: 'edit', - } ); + history.navigate( + `/${ TEMPLATE_POST_TYPE }/${ newTemplate.id }?canvas=edit` + ); createSuccessNotice( sprintf( diff --git a/packages/edit-site/src/components/add-new-template/utils.js b/packages/edit-site/src/components/add-new-template/utils.js index e3e2faf9457926..e781036bb13d5b 100644 --- a/packages/edit-site/src/components/add-new-template/utils.js +++ b/packages/edit-site/src/components/add-new-template/utils.js @@ -3,7 +3,6 @@ */ import { useSelect } from '@wordpress/data'; import { store as coreStore } from '@wordpress/core-data'; -import { store as editorStore } from '@wordpress/editor'; import { decodeEntities } from '@wordpress/html-entities'; import { useMemo, useCallback } from '@wordpress/element'; import { __, _x, sprintf } from '@wordpress/i18n'; @@ -37,7 +36,7 @@ const getValueFromObjectPath = ( object, path ) => { * * @param {Object[]} entities The array of entities. * @param {string} path The path to map a `name` property from the entity. - * @return {IHasNameAndId[]} An array of enitities that now implement the `IHasNameAndId` interface. + * @return {IHasNameAndId[]} An array of entities that now implement the `IHasNameAndId` interface. */ export const mapToIHasNameAndId = ( entities, path ) => { return ( entities || [] ).map( ( entity ) => ( { @@ -69,7 +68,8 @@ export const useExistingTemplates = () => { export const useDefaultTemplateTypes = () => { return useSelect( ( select ) => - select( editorStore ).__experimentalGetDefaultTemplateTypes(), + select( coreStore ).getEntityRecord( 'root', '__unstableBase' ) + ?.default_template_types || [], [] ); }; diff --git a/packages/edit-site/src/components/app/index.js b/packages/edit-site/src/components/app/index.js index 3588565fcb3c17..744c682dcda5b2 100644 --- a/packages/edit-site/src/components/app/index.js +++ b/packages/edit-site/src/components/app/index.js @@ -1,63 +1,62 @@ /** * WordPress dependencies */ -import { SlotFillProvider } from '@wordpress/components'; -import { - UnsavedChangesWarning, - privateApis as editorPrivateApis, -} from '@wordpress/editor'; -import { store as noticesStore } from '@wordpress/notices'; -import { useDispatch } from '@wordpress/data'; -import { __, sprintf } from '@wordpress/i18n'; -import { PluginArea } from '@wordpress/plugins'; +import { useSelect } from '@wordpress/data'; import { privateApis as routerPrivateApis } from '@wordpress/router'; +import { useCallback } from '@wordpress/element'; /** * Internal dependencies */ import Layout from '../layout'; import { unlock } from '../../lock-unlock'; +import { store as editSiteStore } from '../../store'; import { useCommonCommands } from '../../hooks/commands/use-common-commands'; -import useActiveRoute from '../layout/router'; import useSetCommandContext from '../../hooks/commands/use-set-command-context'; import { useRegisterSiteEditorRoutes } from '../site-editor-routes'; +import { + currentlyPreviewingTheme, + isPreviewingTheme, +} from '../../utils/is-previewing-theme'; const { RouterProvider } = unlock( routerPrivateApis ); -const { GlobalStylesProvider } = unlock( editorPrivateApis ); function AppLayout() { useCommonCommands(); useSetCommandContext(); - useRegisterSiteEditorRoutes(); - const route = useActiveRoute(); - return <Layout route={ route } />; + return <Layout />; } export default function App() { - const { createErrorNotice } = useDispatch( noticesStore ); + useRegisterSiteEditorRoutes(); + const routes = useSelect( ( select ) => { + return unlock( select( editSiteStore ) ).getRoutes(); + }, [] ); + const beforeNavigate = useCallback( ( { path, query } ) => { + if ( ! isPreviewingTheme() ) { + return { path, query }; + } - function onPluginAreaError( name ) { - createErrorNotice( - sprintf( - /* translators: %s: plugin name */ - __( - 'The "%s" plugin has encountered an error and cannot be rendered.' - ), - name - ) - ); - } + return { + path, + query: { + ...query, + wp_theme_preview: + 'wp_theme_preview' in query + ? query.wp_theme_preview + : currentlyPreviewingTheme(), + }, + }; + }, [] ); return ( - <SlotFillProvider> - <GlobalStylesProvider> - <UnsavedChangesWarning /> - <RouterProvider> - <AppLayout /> - <PluginArea onError={ onPluginAreaError } /> - </RouterProvider> - </GlobalStylesProvider> - </SlotFillProvider> + <RouterProvider + routes={ routes } + pathArg="p" + beforeNavigate={ beforeNavigate } + > + <AppLayout /> + </RouterProvider> ); } diff --git a/packages/edit-site/src/components/block-editor/use-editor-iframe-props.js b/packages/edit-site/src/components/block-editor/use-editor-iframe-props.js index 7c88fee0d5b727..1c70c85aed08d3 100644 --- a/packages/edit-site/src/components/block-editor/use-editor-iframe-props.js +++ b/packages/edit-site/src/components/block-editor/use-editor-iframe-props.js @@ -12,6 +12,7 @@ import { useState, useEffect } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; import { store as editorStore } from '@wordpress/editor'; import { privateApis as routerPrivateApis } from '@wordpress/router'; +import { addQueryArgs } from '@wordpress/url'; /** * Internal dependencies @@ -21,9 +22,9 @@ import { unlock } from '../../lock-unlock'; const { useLocation, useHistory } = unlock( routerPrivateApis ); export default function useEditorIframeProps() { - const { params } = useLocation(); + const { query, path } = useLocation(); const history = useHistory(); - const { canvas = 'view' } = params; + const { canvas = 'view' } = query; const currentPostIsTrashed = useSelect( ( select ) => { return ( select( editorStore ).getCurrentPostAttribute( 'status' ) === @@ -55,13 +56,13 @@ export default function useEditorIframeProps() { ! currentPostIsTrashed ) { event.preventDefault(); - history.push( { ...params, canvas: 'edit' }, undefined, { + history.navigate( addQueryArgs( path, { canvas: 'edit' } ), { transition: 'canvas-mode-edit-transition', } ); } }, onClick: () => - history.push( { ...params, canvas: 'edit' }, undefined, { + history.navigate( addQueryArgs( path, { canvas: 'edit' } ), { transition: 'canvas-mode-edit-transition', } ), onClickCapture: ( event ) => { diff --git a/packages/edit-site/src/components/block-editor/use-navigate-to-entity-record.js b/packages/edit-site/src/components/block-editor/use-navigate-to-entity-record.js index 120b15b8551d3d..66be70fcd4e2e2 100644 --- a/packages/edit-site/src/components/block-editor/use-navigate-to-entity-record.js +++ b/packages/edit-site/src/components/block-editor/use-navigate-to-entity-record.js @@ -16,7 +16,9 @@ export default function useNavigateToEntityRecord() { const onNavigateToEntityRecord = useCallback( ( params ) => { - history.push( { ...params, focusMode: true, canvas: 'edit' } ); + history.navigate( + `/${ params.postType }/${ params.postId }?canvas=edit&focusMode=true` + ); }, [ history ] ); diff --git a/packages/edit-site/src/components/block-editor/use-site-editor-settings.js b/packages/edit-site/src/components/block-editor/use-site-editor-settings.js index 5c75de6d81e720..d37987dc3dc420 100644 --- a/packages/edit-site/src/components/block-editor/use-site-editor-settings.js +++ b/packages/edit-site/src/components/block-editor/use-site-editor-settings.js @@ -22,11 +22,11 @@ function useNavigateToPreviousEntityRecord() { const history = useHistory(); const goBack = useMemo( () => { const isFocusMode = - location.params.focusMode || - ( location.params.postId && - FOCUSABLE_ENTITIES.includes( location.params.postType ) ); + location.query.focusMode || + ( location?.params?.postId && + FOCUSABLE_ENTITIES.includes( location?.params?.postType ) ); const didComeFromEditorCanvas = - previousLocation?.params.canvas === 'edit'; + previousLocation?.query.canvas === 'edit'; const showBackButton = isFocusMode && didComeFromEditorCanvas; return showBackButton ? () => history.back() : undefined; // `previousLocation` changes when the component updates for any reason, not @@ -36,11 +36,9 @@ function useNavigateToPreviousEntityRecord() { return goBack; } -export function useSpecificEditorSettings( - shouldUseTemplateAsDefaultRenderingMode -) { - const { params } = useLocation(); - const { canvas = 'view' } = params; +export function useSpecificEditorSettings() { + const { query } = useLocation(); + const { canvas = 'view' } = query; const onNavigateToEntityRecord = useNavigateToEntityRecord(); const { settings } = useSelect( ( select ) => { const { getSettings } = select( editSiteStore ); @@ -49,11 +47,6 @@ export function useSpecificEditorSettings( }; }, [] ); - // TODO: The `shouldUseTemplateAsDefaultRenderingMode` check should be removed when the default rendering mode per post type is merged. - // @see https://github.com/WordPress/gutenberg/pull/62304/ - const defaultRenderingMode = shouldUseTemplateAsDefaultRenderingMode - ? 'template-locked' - : 'post-only'; const onNavigateToPreviousEntityRecord = useNavigateToPreviousEntityRecord(); const defaultEditorSettings = useMemo( () => { @@ -63,7 +56,6 @@ export function useSpecificEditorSettings( richEditingEnabled: true, supportsTemplateMode: true, focusMode: canvas !== 'view', - defaultRenderingMode, onNavigateToEntityRecord, onNavigateToPreviousEntityRecord, isPreviewMode: canvas === 'view', @@ -71,7 +63,6 @@ export function useSpecificEditorSettings( }, [ settings, canvas, - defaultRenderingMode, onNavigateToEntityRecord, onNavigateToPreviousEntityRecord, ] ); diff --git a/packages/edit-site/src/components/canvas-loader/style.scss b/packages/edit-site/src/components/canvas-loader/style.scss index 3d74d408aeceda..33ff6dc38c3f51 100644 --- a/packages/edit-site/src/components/canvas-loader/style.scss +++ b/packages/edit-site/src/components/canvas-loader/style.scss @@ -9,9 +9,10 @@ align-items: center; justify-content: center; - animation: 0.5s ease 0.2s edit-site-canvas-loader__fade-in-animation; - animation-fill-mode: forwards; - @include reduce-motion("animation"); + @media not (prefers-reduced-motion) { + animation: 0.5s ease 0.2s edit-site-canvas-loader__fade-in-animation; + animation-fill-mode: forwards; + } & > div { width: 160px; diff --git a/packages/edit-site/src/components/dataviews-actions/index.js b/packages/edit-site/src/components/dataviews-actions/index.js index 09b7597c6cb341..0a7b20c712c820 100644 --- a/packages/edit-site/src/components/dataviews-actions/index.js +++ b/packages/edit-site/src/components/dataviews-actions/index.js @@ -31,11 +31,7 @@ export const useEditPostAction = () => { }, callback( items ) { const post = items[ 0 ]; - history.push( { - postId: post.id, - postType: post.type, - canvas: 'edit', - } ); + history.navigate( `/${ post.type }/${ post.id }?canvas=edit` ); }, } ), [ history ] diff --git a/packages/edit-site/src/components/editor-canvas-container/index.js b/packages/edit-site/src/components/editor-canvas-container/index.js index ac1083e69abd7e..050d2e19585cc4 100644 --- a/packages/edit-site/src/components/editor-canvas-container/index.js +++ b/packages/edit-site/src/components/editor-canvas-container/index.js @@ -141,7 +141,7 @@ function EditorCanvasContainer( { } function useHasEditorCanvasContainer() { - const fills = useSlotFills( EditorContentSlotFill.privateKey ); + const fills = useSlotFills( EditorContentSlotFill.name ); return !! fills?.length; } diff --git a/packages/edit-site/src/components/editor-canvas-container/style.scss b/packages/edit-site/src/components/editor-canvas-container/style.scss index 52ac29da0696f6..c544f88f6bcd58 100644 --- a/packages/edit-site/src/components/editor-canvas-container/style.scss +++ b/packages/edit-site/src/components/editor-canvas-container/style.scss @@ -1,6 +1,10 @@ .edit-site-editor-canvas-container { height: 100%; + // This is the gray background color that's applied behind "isolation mode". + // The color normally comes from .editor-visual-editor, but that class is missing here. + background-color: $gray-300; + // Controls height of editor and editor canvas container (style book, global styles revisions previews etc.) iframe { display: block; @@ -22,7 +26,9 @@ position: absolute; right: 0; top: 0; - transition: all 0.3s; // Match .block-editor-iframe__body transition. + @media not (prefers-reduced-motion) { + transition: all 0.3s; // Match .block-editor-iframe__body transition. + } } .edit-site-editor-canvas-container__close-button { diff --git a/packages/edit-site/src/components/editor/index.js b/packages/edit-site/src/components/editor/index.js index 51d734f25c6adb..ad88ee07e2150f 100644 --- a/packages/edit-site/src/components/editor/index.js +++ b/packages/edit-site/src/components/editor/index.js @@ -20,7 +20,6 @@ import { privateApis as blockLibraryPrivateApis } from '@wordpress/block-library import { useCallback, useMemo } from '@wordpress/element'; import { store as noticesStore } from '@wordpress/notices'; import { privateApis as routerPrivateApis } from '@wordpress/router'; -import { store as preferencesStore } from '@wordpress/preferences'; import { decodeEntities } from '@wordpress/html-entities'; import { Icon, arrowUpLeft } from '@wordpress/icons'; import { store as blockEditorStore } from '@wordpress/block-editor'; @@ -54,6 +53,7 @@ import { useResolveEditedEntity, useSyncDeprecatedEntityIntoState, } from './use-resolve-edited-entity'; +import { addQueryArgs } from '@wordpress/url'; const { Editor, BackButton } = unlock( editorPrivateApis ); const { useHistory, useLocation } = unlock( routerPrivateApis ); @@ -83,10 +83,44 @@ const siteIconVariants = { }, }; +function getListPathForPostType( postType ) { + switch ( postType ) { + case 'navigation': + return '/navigation'; + case 'wp_block': + return '/pattern?postType=wp_block'; + case 'wp_template_part': + return '/pattern?postType=wp_template_part'; + case 'wp_template': + return '/template'; + case 'page': + return '/page'; + case 'post': + return '/'; + } + throw 'Unknown post type'; +} + +function getNavigationPath( location, postType ) { + const { path, name } = location; + if ( + [ + 'pattern-item', + 'template-part-item', + 'page-item', + 'template-item', + 'post-item', + ].includes( name ) + ) { + return getListPathForPostType( postType ); + } + return addQueryArgs( path, { canvas: undefined } ); +} + export default function EditSiteEditor( { isPostsList = false } ) { const disableMotion = useReducedMotion(); - const { params } = useLocation(); - const { canvas = 'view' } = params; + const location = useLocation(); + const { canvas = 'view' } = location.query; const isLoading = useIsSiteEditorLoading(); useAdaptEditorToCanvas( canvas ); const entity = useResolveEditedEntity(); @@ -95,7 +129,6 @@ export default function EditSiteEditor( { isPostsList = false } ) { const { postType, postId, context } = entity; const { supportsGlobalStyles, - showIconLabels, editorCanvasView, currentPostIsTrashed, hasSiteIcon, @@ -103,13 +136,11 @@ export default function EditSiteEditor( { isPostsList = false } ) { const { getEditorCanvasContainerView } = unlock( select( editSiteStore ) ); - const { get } = select( preferencesStore ); const { getCurrentTheme, getEntityRecord } = select( coreDataStore ); const siteData = getEntityRecord( 'root', '__unstableBase', undefined ); return { supportsGlobalStyles: getCurrentTheme()?.is_block_theme, - showIconLabels: get( 'core', 'showIconLabels' ), editorCanvasView: getEditorCanvasContainerView(), currentPostIsTrashed: select( editorStore ).getCurrentPostAttribute( 'status' ) === @@ -131,9 +162,7 @@ export default function EditSiteEditor( { isPostsList = false } ) { 'edit-site-editor__loading-progress' ); - const settings = useSpecificEditorSettings( - !! context?.postId && context?.postType !== 'post' - ); + const settings = useSpecificEditorSettings(); const styles = useMemo( () => [ ...settings.styles, @@ -159,9 +188,11 @@ export default function EditSiteEditor( { isPostsList = false } ) { case 'move-to-trash': case 'delete-post': { - history.push( { - postType: items[ 0 ].type, - } ); + history.navigate( + getListPathForPostType( + postWithTemplate ? context.postType : postType + ) + ); } break; case 'duplicate-post': @@ -184,11 +215,9 @@ export default function EditSiteEditor( { isPostsList = false } ) { { label: __( 'Edit' ), onClick: () => { - history.push( { - postId: newItem.id, - postType: newItem.type, - canvas: 'edit', - } ); + history.navigate( + `/${ newItem.type }/${ newItem.id }?canvas=edit` + ); }, }, ], @@ -198,7 +227,13 @@ export default function EditSiteEditor( { isPostsList = false } ) { break; } }, - [ history, createSuccessNotice ] + [ + postType, + context?.postType, + postWithTemplate, + history, + createSuccessNotice, + ] ); // Replace the title and icon displayed in the DocumentBar when there's an overlay visible. @@ -228,9 +263,7 @@ export default function EditSiteEditor( { isPostsList = false } ) { postId={ postWithTemplate ? context.postId : postId } templateId={ postWithTemplate ? postId : undefined } settings={ settings } - className={ clsx( 'edit-site-editor__editor-interface', { - 'show-icon-labels': showIconLabels, - } ) } + className="edit-site-editor__editor-interface" styles={ styles } customSaveButton={ _isPreviewingTheme && <SaveButton size="compact" /> @@ -270,26 +303,20 @@ export default function EditSiteEditor( { isPostsList = false } ) { // come here through `posts list` and are in focus mode editing a template, template part etc.. if ( isPostsList && - params?.focusMode + location.query?.focusMode ) { - history.push( - { - page: 'gutenberg-posts-dashboard', - postType: 'post', - }, - undefined, - { - transition: - 'canvas-mode-view-transition', - } - ); + history.navigate( '/', { + transition: + 'canvas-mode-view-transition', + } ); } else { - history.push( - { - ...params, - canvas: undefined, - }, - undefined, + history.navigate( + getNavigationPath( + location, + postWithTemplate + ? context.postType + : postType + ), { transition: 'canvas-mode-view-transition', diff --git a/packages/edit-site/src/components/editor/style.scss b/packages/edit-site/src/components/editor/style.scss index a6cc5084966947..625b2633ab7244 100644 --- a/packages/edit-site/src/components/editor/style.scss +++ b/packages/edit-site/src/components/editor/style.scss @@ -1,7 +1,9 @@ .edit-site-editor__editor-interface { opacity: 1; - transition: opacity 0.1s ease-out; - @include reduce-motion( "transition" ); + + @media not (prefers-reduced-motion) { + transition: opacity 0.1s ease-out; + } &.is-loading { opacity: 0; diff --git a/packages/edit-site/src/components/editor/use-editor-title.js b/packages/edit-site/src/components/editor/use-editor-title.js index 2ad4e94ccc3e80..6f0b36f8e3b8b6 100644 --- a/packages/edit-site/src/components/editor/use-editor-title.js +++ b/packages/edit-site/src/components/editor/use-editor-title.js @@ -4,33 +4,50 @@ import { _x, sprintf } from '@wordpress/i18n'; import { useSelect } from '@wordpress/data'; import { store as coreStore } from '@wordpress/core-data'; -import { store as editorStore } from '@wordpress/editor'; import { decodeEntities } from '@wordpress/html-entities'; +import { privateApis as editorPrivateApis } from '@wordpress/editor'; /** * Internal dependencies */ import useTitle from '../routes/use-title'; import { POST_TYPE_LABELS, TEMPLATE_POST_TYPE } from '../../utils/constants'; +import { unlock } from '../../lock-unlock'; + +const { getTemplateInfo } = unlock( editorPrivateApis ); function useEditorTitle( postType, postId ) { const { title, isLoaded } = useSelect( ( select ) => { const { getEditedEntityRecord, hasFinishedResolution } = select( coreStore ); - const { __experimentalGetTemplateInfo: getTemplateInfo } = - select( editorStore ); + + if ( ! postId ) { + return { isLoaded: false }; + } + const _record = getEditedEntityRecord( 'postType', postType, postId ); + + const { default_template_types: templateTypes = [] } = + select( coreStore ).getEntityRecord( + 'root', + '__unstableBase' + ) ?? {}; + + const templateInfo = getTemplateInfo( { + template: _record, + templateTypes, + } ); + const _isLoaded = hasFinishedResolution( 'getEditedEntityRecord', [ 'postType', postType, postId, ] ); - const templateInfo = getTemplateInfo( _record ); return { title: templateInfo.title, diff --git a/packages/edit-site/src/components/editor/use-resolve-edited-entity.js b/packages/edit-site/src/components/editor/use-resolve-edited-entity.js index eb19b3cea95375..8da076f9f00b71 100644 --- a/packages/edit-site/src/components/editor/use-resolve-edited-entity.js +++ b/packages/edit-site/src/components/editor/use-resolve-edited-entity.js @@ -30,35 +30,27 @@ const postTypesWithoutParentTemplate = [ const authorizedPostTypes = [ 'page', 'post' ]; export function useResolveEditedEntity() { - const { params = {} } = useLocation(); - const { postId, postType } = params; - const { hasLoadedAllDependencies, homepageId, postsPageId } = useSelect( - ( select ) => { - const { getEntityRecord } = select( coreDataStore ); - const siteData = getEntityRecord( 'root', 'site' ); - const _homepageId = - siteData?.show_on_front === 'page' && - [ 'number', 'string' ].includes( - typeof siteData.page_on_front - ) && - !! +siteData.page_on_front // We also need to check if it's not zero(`0`). - ? siteData.page_on_front.toString() - : null; - const _postsPageId = - siteData?.show_on_front === 'page' && - [ 'number', 'string' ].includes( - typeof siteData.page_for_posts - ) - ? siteData.page_for_posts.toString() - : null; - return { - hasLoadedAllDependencies: !! siteData, - homepageId: _homepageId, - postsPageId: _postsPageId, - }; - }, - [] - ); + const { name, params = {}, query } = useLocation(); + const { postId = query?.postId } = params; // Fallback to query param for postId for list view routes. + let postType; + if ( name === 'navigation-item' ) { + postType = NAVIGATION_POST_TYPE; + } else if ( name === 'pattern-item' ) { + postType = PATTERN_TYPES.user; + } else if ( name === 'template-part-item' ) { + postType = TEMPLATE_PART_POST_TYPE; + } else if ( name === 'template-item' || name === 'templates' ) { + postType = TEMPLATE_POST_TYPE; + } else if ( name === 'page-item' || name === 'pages' ) { + postType = 'page'; + } else if ( name === 'post-item' || name === 'posts' ) { + postType = 'post'; + } + + const homePage = useSelect( ( select ) => { + const { getHomePage } = unlock( select( coreDataStore ) ); + return getHomePage(); + }, [] ); /** * This is a hook that recreates the logic to resolve a template for a given WordPress postID postTypeId @@ -74,111 +66,15 @@ export function useResolveEditedEntity() { postTypesWithoutParentTemplate.includes( postType ) && postId ) { - return undefined; + return; } // Don't trigger resolution for multi-selected posts. if ( postId && postId.includes( ',' ) ) { - return undefined; + return; } - const { - getEditedEntityRecord, - getEntityRecords, - getDefaultTemplateId, - } = select( coreDataStore ); - - function resolveTemplateForPostTypeAndId( - postTypeToResolve, - postIdToResolve - ) { - // For the front page, we always use the front page template if existing. - if ( - postTypeToResolve === 'page' && - homepageId === postIdToResolve - ) { - // The /lookup endpoint cannot currently handle a lookup - // when a page is set as the front page, so specifically in - // that case, we want to check if there is a front page - // template, and instead of falling back to the home - // template, we want to fall back to the page template. - const templates = getEntityRecords( - 'postType', - TEMPLATE_POST_TYPE, - { - per_page: -1, - } - ); - if ( templates ) { - const id = templates?.find( - ( { slug } ) => slug === 'front-page' - )?.id; - if ( id ) { - return id; - } - - // If no front page template is found, continue with the - // logic below (fetching the page template). - } else { - // Still resolving `templates`. - return undefined; - } - } - - const editedEntity = getEditedEntityRecord( - 'postType', - postTypeToResolve, - postIdToResolve - ); - if ( ! editedEntity ) { - return undefined; - } - // Check if the current page is the posts page. - if ( - postTypeToResolve === 'page' && - postsPageId === postIdToResolve - ) { - return getDefaultTemplateId( { slug: 'home' } ); - } - // First see if the post/page has an assigned template and fetch it. - const currentTemplateSlug = editedEntity.template; - if ( currentTemplateSlug ) { - const currentTemplate = getEntityRecords( - 'postType', - TEMPLATE_POST_TYPE, - { - per_page: -1, - } - )?.find( ( { slug } ) => slug === currentTemplateSlug ); - if ( currentTemplate ) { - return currentTemplate.id; - } - } - // If no template is assigned, use the default template. - let slugToCheck; - // In `draft` status we might not have a slug available, so we use the `single` - // post type templates slug(ex page, single-post, single-product etc..). - // Pages do not need the `single` prefix in the slug to be prioritized - // through template hierarchy. - if ( editedEntity.slug ) { - slugToCheck = - postTypeToResolve === 'page' - ? `${ postTypeToResolve }-${ editedEntity.slug }` - : `single-${ postTypeToResolve }-${ editedEntity.slug }`; - } else { - slugToCheck = - postTypeToResolve === 'page' - ? 'page' - : `single-${ postTypeToResolve }`; - } - return getDefaultTemplateId( { - slug: slugToCheck, - } ); - } - - if ( ! hasLoadedAllDependencies ) { - return undefined; - } + const { getTemplateId } = unlock( select( coreDataStore ) ); // If we're rendering a specific page, we need to resolve its template. // The site editor only supports pages for now, not other CPTs. @@ -187,18 +83,19 @@ export function useResolveEditedEntity() { postId && authorizedPostTypes.includes( postType ) ) { - return resolveTemplateForPostTypeAndId( postType, postId ); + return getTemplateId( postType, postId ); } // If we're rendering the home page, and we have a static home page, resolve its template. - if ( homepageId ) { - return resolveTemplateForPostTypeAndId( 'page', homepageId ); + if ( homePage?.postType === 'page' ) { + return getTemplateId( 'page', homePage?.postId ); } - // If we're not rendering a specific page, use the front page template. - return getDefaultTemplateId( { slug: 'front-page' } ); + if ( homePage?.postType === 'wp_template' ) { + return homePage?.postId; + } }, - [ homepageId, postsPageId, hasLoadedAllDependencies, postId, postType ] + [ homePage, postId, postType ] ); const context = useMemo( () => { @@ -211,18 +108,18 @@ export function useResolveEditedEntity() { } // TODO: for post types lists we should probably not render the front page, but maybe a placeholder // with a message like "Select a page" or something similar. - if ( homepageId ) { - return { postType: 'page', postId: homepageId }; + if ( homePage?.postType === 'page' ) { + return { postType: 'page', postId: homePage?.postId }; } return {}; - }, [ homepageId, postType, postId ] ); + }, [ homePage, postType, postId ] ); if ( postTypesWithoutParentTemplate.includes( postType ) && postId ) { return { isReady: true, postType, postId, context }; } - if ( hasLoadedAllDependencies ) { + if ( !! homePage ) { return { isReady: resolvedTemplateId !== undefined, postType: TEMPLATE_POST_TYPE, diff --git a/packages/edit-site/src/components/error-boundary/index.js b/packages/edit-site/src/components/error-boundary/index.js deleted file mode 100644 index 88b04ae0b40e34..00000000000000 --- a/packages/edit-site/src/components/error-boundary/index.js +++ /dev/null @@ -1,44 +0,0 @@ -/** - * WordPress dependencies - */ -import { Component } from '@wordpress/element'; -import { __ } from '@wordpress/i18n'; -import { doAction } from '@wordpress/hooks'; - -/** - * Internal dependencies - */ -import ErrorBoundaryWarning from './warning'; - -export default class ErrorBoundary extends Component { - constructor() { - super( ...arguments ); - - this.state = { - error: null, - }; - } - - componentDidCatch( error ) { - doAction( 'editor.ErrorBoundary.errorLogged', error ); - } - - static getDerivedStateFromError( error ) { - return { error }; - } - - render() { - if ( ! this.state.error ) { - return this.props.children; - } - - return ( - <ErrorBoundaryWarning - message={ __( - 'The editor has encountered an unexpected error.' - ) } - error={ this.state.error } - /> - ); - } -} diff --git a/packages/edit-site/src/components/error-boundary/test/error-boundary.js b/packages/edit-site/src/components/error-boundary/test/error-boundary.js deleted file mode 100644 index 27ec4c80419676..00000000000000 --- a/packages/edit-site/src/components/error-boundary/test/error-boundary.js +++ /dev/null @@ -1,40 +0,0 @@ -/** - * External dependencies - */ -import { render } from '@testing-library/react'; - -/** - * WordPress dependencies - */ -import * as wpHooks from '@wordpress/hooks'; - -/** - * Internal dependencies - */ -import ErrorBoundary from '../index'; - -const theError = new Error( 'Kaboom' ); - -const ChildComponent = () => { - throw theError; -}; - -describe( 'Error Boundary', () => { - describe( 'when error is thrown from a Child component', () => { - it( 'calls the `editor.ErrorBoundary.errorLogged` hook action with the error object', () => { - const doAction = jest.spyOn( wpHooks, 'doAction' ); - - render( - <ErrorBoundary> - <ChildComponent /> - </ErrorBoundary> - ); - - expect( doAction ).toHaveBeenCalledWith( - 'editor.ErrorBoundary.errorLogged', - theError - ); - expect( console ).toHaveErrored(); - } ); - } ); -} ); diff --git a/packages/edit-site/src/components/error-boundary/warning.js b/packages/edit-site/src/components/error-boundary/warning.js deleted file mode 100644 index c4090c7e6b1190..00000000000000 --- a/packages/edit-site/src/components/error-boundary/warning.js +++ /dev/null @@ -1,30 +0,0 @@ -/** - * WordPress dependencies - */ -import { __ } from '@wordpress/i18n'; -import { Button } from '@wordpress/components'; -import { Warning } from '@wordpress/block-editor'; -import { useCopyToClipboard } from '@wordpress/compose'; - -function CopyButton( { text, children } ) { - const ref = useCopyToClipboard( text ); - return ( - <Button __next40pxDefaultSize variant="secondary" ref={ ref }> - { children } - </Button> - ); -} - -export default function ErrorBoundaryWarning( { message, error } ) { - const actions = [ - <CopyButton key="copy-error" text={ error.stack }> - { __( 'Copy Error' ) } - </CopyButton>, - ]; - - return ( - <Warning className="editor-error-boundary" actions={ actions }> - { message } - </Warning> - ); -} diff --git a/packages/edit-site/src/components/global-styles-sidebar/index.js b/packages/edit-site/src/components/global-styles-sidebar/index.js index b1cf4fec92c86e..02a29dac5c0b7d 100644 --- a/packages/edit-site/src/components/global-styles-sidebar/index.js +++ b/packages/edit-site/src/components/global-styles-sidebar/index.js @@ -28,8 +28,8 @@ const { interfaceStore } = unlock( editorPrivateApis ); const { useLocation } = unlock( routerPrivateApis ); export default function GlobalStylesSidebar() { - const { params } = useLocation(); - const { canvas = 'view', path } = params; + const { query } = useLocation(); + const { canvas = 'view', name } = query; const { shouldClearCanvasContainerView, isStyleBookOpened, @@ -133,14 +133,14 @@ export default function GlobalStylesSidebar() { const previousActiveAreaRef = useRef( null ); useEffect( () => { - if ( path === '/wp_global_styles' && canvas === 'edit' ) { + if ( name === 'styles' && canvas === 'edit' ) { previousActiveAreaRef.current = getActiveComplementaryArea( 'core' ); enableComplementaryArea( 'core', 'edit-site/global-styles' ); } else if ( previousActiveAreaRef.current ) { enableComplementaryArea( 'core', previousActiveAreaRef.current ); } - }, [ path, enableComplementaryArea, canvas, getActiveComplementaryArea ] ); + }, [ name, enableComplementaryArea, canvas, getActiveComplementaryArea ] ); return ( <DefaultSidebar diff --git a/packages/edit-site/src/components/global-styles/block-preview-panel.js b/packages/edit-site/src/components/global-styles/block-preview-panel.js index 6b1b980377c9cb..b796ec6e0cb964 100644 --- a/packages/edit-site/src/components/global-styles/block-preview-panel.js +++ b/packages/edit-site/src/components/global-styles/block-preview-panel.js @@ -18,16 +18,16 @@ const BlockPreviewPanel = ( { name, variation = '' } ) => { return null; } - let example = blockExample; - if ( variation ) { - example = { - ...example, - attributes: { - ...example.attributes, - className: getVariationClassName( variation ), - }, - }; - } + const example = { + ...blockExample, + attributes: { + ...blockExample.attributes, + style: undefined, + className: variation + ? getVariationClassName( variation ) + : blockExample.attributes?.className, + }, + }; return getBlockFromExample( name, example ); }, [ name, blockExample, variation ] ); diff --git a/packages/edit-site/src/components/global-styles/confirm-reset-shadow-dialog.js b/packages/edit-site/src/components/global-styles/confirm-reset-shadow-dialog.js new file mode 100644 index 00000000000000..b8f5b77010ff6d --- /dev/null +++ b/packages/edit-site/src/components/global-styles/confirm-reset-shadow-dialog.js @@ -0,0 +1,37 @@ +/** + * WordPress dependencies + */ +import { __experimentalConfirmDialog as ConfirmDialog } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; + +function ConfirmResetShadowDialog( { + text, + confirmButtonText, + isOpen, + toggleOpen, + onConfirm, +} ) { + const handleConfirm = async () => { + toggleOpen(); + onConfirm(); + }; + + const handleCancel = () => { + toggleOpen(); + }; + + return ( + <ConfirmDialog + isOpen={ isOpen } + cancelButtonText={ __( 'Cancel' ) } + confirmButtonText={ confirmButtonText } + onCancel={ handleCancel } + onConfirm={ handleConfirm } + size="medium" + > + { text } + </ConfirmDialog> + ); +} + +export default ConfirmResetShadowDialog; diff --git a/packages/edit-site/src/components/global-styles/font-library-modal/font-collection.js b/packages/edit-site/src/components/global-styles/font-library-modal/font-collection.js index caf339091de752..3aef0171ec358f 100644 --- a/packages/edit-site/src/components/global-styles/font-library-modal/font-collection.js +++ b/packages/edit-site/src/components/global-styles/font-library-modal/font-collection.js @@ -27,7 +27,13 @@ import { } from '@wordpress/components'; import { debounce } from '@wordpress/compose'; import { sprintf, __, _x, isRTL } from '@wordpress/i18n'; -import { moreVertical, chevronLeft, chevronRight } from '@wordpress/icons'; +import { + moreVertical, + next, + previous, + chevronLeft, + chevronRight, +} from '@wordpress/icons'; /** * Internal dependencies @@ -486,37 +492,30 @@ function FontCollection( { slug } ) { { ! selectedFont && ( <HStack - spacing={ 4 } - justify="center" + expanded={ false } className="font-library-modal__footer" + justify="end" + spacing={ 6 } > - <Button - label={ __( 'Previous page' ) } - size="compact" - onClick={ () => setPage( page - 1 ) } - disabled={ page === 1 } - showTooltip - accessibleWhenDisabled - icon={ isRTL() ? chevronRight : chevronLeft } - tooltipPosition="top" - /> <HStack justify="flex-start" expanded={ false } - spacing={ 2 } + spacing={ 1 } className="font-library-modal__page-selection" > { createInterpolateElement( sprintf( - // translators: %s: Total number of pages. + // translators: 1: Current page number, 2: Total number of pages. _x( - 'Page <CurrentPageControl /> of %s', + '<div>Page</div>%1$s<div>of %2$s</div>', 'paging' ), + '<CurrentPage />', totalPages ), { - CurrentPageControl: ( + div: <div aria-hidden />, + CurrentPage: ( <SelectControl aria-label={ __( 'Current page' @@ -535,22 +534,36 @@ function FontCollection( { slug } ) { parseInt( newPage ) ) } - size="compact" + size="small" __nextHasNoMarginBottom + variant="minimal" /> ), } ) } </HStack> - <Button - label={ __( 'Next page' ) } - size="compact" - onClick={ () => setPage( page + 1 ) } - disabled={ page === totalPages } - accessibleWhenDisabled - icon={ isRTL() ? chevronLeft : chevronRight } - tooltipPosition="top" - /> + <HStack expanded={ false } spacing={ 1 }> + <Button + onClick={ () => setPage( page - 1 ) } + disabled={ page === 1 } + accessibleWhenDisabled + label={ __( 'Previous page' ) } + icon={ isRTL() ? next : previous } + showTooltip + size="compact" + tooltipPosition="top" + /> + <Button + onClick={ () => setPage( page + 1 ) } + disabled={ page === totalPages } + accessibleWhenDisabled + label={ __( 'Next page' ) } + icon={ isRTL() ? previous : next } + showTooltip + size="compact" + tooltipPosition="top" + /> + </HStack> </HStack> ) } </> diff --git a/packages/edit-site/src/components/global-styles/font-library-modal/index.js b/packages/edit-site/src/components/global-styles/font-library-modal/index.js index 27093e0ef1cbba..5661a002f71ecb 100644 --- a/packages/edit-site/src/components/global-styles/font-library-modal/index.js +++ b/packages/edit-site/src/components/global-styles/font-library-modal/index.js @@ -28,7 +28,7 @@ const DEFAULT_TAB = { const UPLOAD_TAB = { id: 'upload-fonts', - title: __( 'Upload' ), + title: _x( 'Upload', 'noun' ), }; const tabsFromCollections = ( collections ) => diff --git a/packages/edit-site/src/components/global-styles/font-library-modal/style.scss b/packages/edit-site/src/components/global-styles/font-library-modal/style.scss index 7d94376ac8d942..11a1c6d6689370 100644 --- a/packages/edit-site/src/components/global-styles/font-library-modal/style.scss +++ b/packages/edit-site/src/components/global-styles/font-library-modal/style.scss @@ -64,9 +64,15 @@ $footer-height: 70px; .font-library-modal__page-selection { font-size: 11px; - text-transform: uppercase; font-weight: 500; - color: $gray-900; + text-transform: uppercase; + + @include break-small() { + .components-select-control__input { + font-size: 11px !important; + font-weight: 500; + } + } } // TODO: See if this can be removed after https://github.com/WordPress/gutenberg/issues/38730 @@ -123,8 +129,10 @@ $footer-height: 70px; .font-library-modal__font-variant_demo-text { white-space: nowrap; flex-shrink: 0; - transition: opacity 0.3s ease-in-out; - @include reduce-motion("transition"); + + @media not (prefers-reduced-motion) { + transition: opacity 0.3s ease-in-out; + } } } @@ -147,7 +155,6 @@ $footer-height: 70px; } } - .font-library-modal__upload-area { align-items: center; display: flex; @@ -200,7 +207,9 @@ button.font-library-modal__upload-area { } .font-library-modal__select-all { - padding: $grid-unit-20 $grid-unit-20 $grid-unit-20 $grid-unit-20 + $border-width; + padding: + $grid-unit-20 $grid-unit-20 $grid-unit-20 $grid-unit-20 + + $border-width; .components-checkbox-control__label { padding-left: $grid-unit-20; diff --git a/packages/edit-site/src/components/global-styles/font-library-modal/utils/test/preview-styles.spec.js b/packages/edit-site/src/components/global-styles/font-library-modal/utils/test/preview-styles.spec.js index 7e98b77964f7f2..a2dc7e63d14a92 100644 --- a/packages/edit-site/src/components/global-styles/font-library-modal/utils/test/preview-styles.spec.js +++ b/packages/edit-site/src/components/global-styles/font-library-modal/utils/test/preview-styles.spec.js @@ -201,7 +201,7 @@ describe( 'formatFontFaceName', () => { ); } ); - it( 'should ouput the font face name with quotes on Firefox', () => { + it( 'should output the font face name with quotes on Firefox', () => { const mockUserAgent = 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:122.0) Gecko/20100101 Firefox/122.0'; diff --git a/packages/edit-site/src/components/global-styles/font-sizes/font-size.js b/packages/edit-site/src/components/global-styles/font-sizes/font-size.js index 25dcc69185cae6..cca4a26e1b7368 100644 --- a/packages/edit-site/src/components/global-styles/font-sizes/font-size.js +++ b/packages/edit-site/src/components/global-styles/font-sizes/font-size.js @@ -166,25 +166,34 @@ function FontSize() { marginBottom={ 0 } paddingX={ 4 } > - <Menu - trigger={ - <Button - size="small" - icon={ moreVertical } - label={ __( 'Font size options' ) } - /> - } - > - <Menu.Item onClick={ toggleRenameDialog }> - <Menu.ItemLabel> - { __( 'Rename' ) } - </Menu.ItemLabel> - </Menu.Item> - <Menu.Item onClick={ toggleDeleteConfirm }> - <Menu.ItemLabel> - { __( 'Delete' ) } - </Menu.ItemLabel> - </Menu.Item> + <Menu> + <Menu.TriggerButton + render={ + <Button + size="small" + icon={ moreVertical } + label={ __( + 'Font size options' + ) } + /> + } + /> + <Menu.Popover> + <Menu.Item + onClick={ toggleRenameDialog } + > + <Menu.ItemLabel> + { __( 'Rename' ) } + </Menu.ItemLabel> + </Menu.Item> + <Menu.Item + onClick={ toggleDeleteConfirm } + > + <Menu.ItemLabel> + { __( 'Delete' ) } + </Menu.ItemLabel> + </Menu.Item> + </Menu.Popover> </Menu> </Spacer> </FlexItem> diff --git a/packages/edit-site/src/components/global-styles/font-sizes/font-sizes.js b/packages/edit-site/src/components/global-styles/font-sizes/font-sizes.js index 4bda7a7b3266b5..5b759d1e0468d8 100644 --- a/packages/edit-site/src/components/global-styles/font-sizes/font-sizes.js +++ b/packages/edit-site/src/components/global-styles/font-sizes/font-sizes.js @@ -11,7 +11,6 @@ import { __experimentalVStack as VStack, __experimentalHStack as HStack, FlexItem, - FlexBlock, Button, } from '@wordpress/components'; import { @@ -27,14 +26,15 @@ import { useState } from '@wordpress/element'; * Internal dependencies */ import { unlock } from '../../../lock-unlock'; -const { Menu } = unlock( componentsPrivateApis ); -const { useGlobalSetting } = unlock( blockEditorPrivateApis ); import Subtitle from '../subtitle'; import { NavigationButtonAsItem } from '../navigation-button'; import { getNewIndexFromPresets } from '../utils'; import ScreenHeader from '../header'; import ConfirmResetFontSizesDialog from './confirm-reset-font-sizes-dialog'; +const { Menu } = unlock( componentsPrivateApis ); +const { useGlobalSetting } = unlock( blockEditorPrivateApis ); + function FontSizeGroup( { label, origin, @@ -81,24 +81,31 @@ function FontSizeGroup( { /> ) } { !! handleResetFontSizes && ( - <Menu - trigger={ - <Button - size="small" - icon={ moreVertical } - label={ __( - 'Font size presets options' - ) } - /> - } - > - <Menu.Item onClick={ toggleResetDialog }> - <Menu.ItemLabel> - { origin === 'custom' - ? __( 'Remove font size presets' ) - : __( 'Reset font size presets' ) } - </Menu.ItemLabel> - </Menu.Item> + <Menu> + <Menu.TriggerButton + render={ + <Button + size="small" + icon={ moreVertical } + label={ __( + 'Font size presets options' + ) } + /> + } + /> + <Menu.Popover> + <Menu.Item onClick={ toggleResetDialog }> + <Menu.ItemLabel> + { origin === 'custom' + ? __( + 'Remove font size presets' + ) + : __( + 'Reset font size presets' + ) } + </Menu.ItemLabel> + </Menu.Item> + </Menu.Popover> </Menu> ) } </FlexItem> @@ -111,23 +118,18 @@ function FontSizeGroup( { key={ size.slug } path={ `/typography/font-sizes/${ origin }/${ size.slug }` } > - <HStack direction="row"> + <HStack> <FlexItem className="edit-site-font-size__item"> { size.name } </FlexItem> - <FlexItem> - <HStack justify="flex-end"> - <FlexBlock className="edit-site-font-size__item edit-site-font-size__item-value"> - { size.size } - </FlexBlock> - <Icon - icon={ - isRTL() - ? chevronLeft - : chevronRight - } - /> - </HStack> + <FlexItem display="flex"> + <Icon + icon={ + isRTL() + ? chevronLeft + : chevronRight + } + /> </FlexItem> </HStack> </NavigationButtonAsItem> diff --git a/packages/edit-site/src/components/global-styles/preview-colors.js b/packages/edit-site/src/components/global-styles/preview-colors.js index 8c1008330ec068..a7e6563748ca03 100644 --- a/packages/edit-site/src/components/global-styles/preview-colors.js +++ b/packages/edit-site/src/components/global-styles/preview-colors.js @@ -10,7 +10,7 @@ import { * Internal dependencies */ import PresetColors from './preset-colors'; -import PreviewIframe from './preview-iframe'; +import PreviewWrapper from './preview-wrapper'; const firstFrameVariants = { start: { @@ -25,7 +25,7 @@ const firstFrameVariants = { const StylesPreviewColors = ( { label, isFocused, withHoverView } ) => { return ( - <PreviewIframe + <PreviewWrapper label={ label } isFocused={ isFocused } withHoverView={ withHoverView } @@ -51,7 +51,7 @@ const StylesPreviewColors = ( { label, isFocused, withHoverView } ) => { </HStack> </motion.div> ) } - </PreviewIframe> + </PreviewWrapper> ); }; diff --git a/packages/edit-site/src/components/global-styles/preview-styles.js b/packages/edit-site/src/components/global-styles/preview-styles.js index dff1ef5a6f42ba..f392e99ae951e6 100644 --- a/packages/edit-site/src/components/global-styles/preview-styles.js +++ b/packages/edit-site/src/components/global-styles/preview-styles.js @@ -15,7 +15,7 @@ import { unlock } from '../../lock-unlock'; import { useStylesPreviewColors } from './hooks'; import TypographyExample from './typography-example'; import HighlightedColors from './highlighted-colors'; -import PreviewIframe from './preview-iframe'; +import PreviewWrapper from './preview-wrapper'; const { useGlobalStyle } = unlock( blockEditorPrivateApis ); @@ -67,7 +67,7 @@ const PreviewStyles = ( { label, isFocused, withHoverView, variation } ) => { const { paletteColors } = useStylesPreviewColors(); return ( - <PreviewIframe + <PreviewWrapper label={ label } isFocused={ isFocused } withHoverView={ withHoverView } @@ -178,7 +178,7 @@ const PreviewStyles = ( { label, isFocused, withHoverView, variation } ) => { </VStack> </motion.div> ) } - </PreviewIframe> + </PreviewWrapper> ); }; diff --git a/packages/edit-site/src/components/global-styles/preview-typography.js b/packages/edit-site/src/components/global-styles/preview-typography.js index 26ae13eaa09083..686ebd1e6f0656 100644 --- a/packages/edit-site/src/components/global-styles/preview-typography.js +++ b/packages/edit-site/src/components/global-styles/preview-typography.js @@ -7,11 +7,11 @@ import { __experimentalHStack as HStack } from '@wordpress/components'; * Internal dependencies */ import TypographyExample from './typography-example'; -import PreviewIframe from './preview-iframe'; +import PreviewWrapper from './preview-wrapper'; const StylesPreviewTypography = ( { variation, isFocused, withHoverView } ) => { return ( - <PreviewIframe + <PreviewWrapper label={ variation.title } isFocused={ isFocused } withHoverView={ withHoverView } @@ -32,7 +32,7 @@ const StylesPreviewTypography = ( { variation, isFocused, withHoverView } ) => { /> </HStack> ) } - </PreviewIframe> + </PreviewWrapper> ); }; diff --git a/packages/edit-site/src/components/global-styles/preview-iframe.js b/packages/edit-site/src/components/global-styles/preview-wrapper.js similarity index 73% rename from packages/edit-site/src/components/global-styles/preview-iframe.js rename to packages/edit-site/src/components/global-styles/preview-wrapper.js index e830accf6d939b..b3c83bad69d844 100644 --- a/packages/edit-site/src/components/global-styles/preview-iframe.js +++ b/packages/edit-site/src/components/global-styles/preview-wrapper.js @@ -1,27 +1,21 @@ /** * WordPress dependencies */ -import { - __unstableIframe as Iframe, - __unstableEditorStyles as EditorStyles, - privateApis as blockEditorPrivateApis, -} from '@wordpress/block-editor'; +import { privateApis as blockEditorPrivateApis } from '@wordpress/block-editor'; import { __unstableMotion as motion } from '@wordpress/components'; import { useThrottle, useReducedMotion, useResizeObserver, } from '@wordpress/compose'; -import { useLayoutEffect, useState, useMemo } from '@wordpress/element'; +import { useLayoutEffect, useState } from '@wordpress/element'; /** * Internal dependencies */ import { unlock } from '../../lock-unlock'; -const { useGlobalStyle, useGlobalStylesOutput } = unlock( - blockEditorPrivateApis -); +const { useGlobalStyle } = unlock( blockEditorPrivateApis ); const normalizedWidth = 248; const normalizedHeight = 152; @@ -33,7 +27,7 @@ const THROTTLE_OPTIONS = { trailing: true, }; -export default function PreviewIframe( { +export default function PreviewWrapper( { children, label, isFocused, @@ -41,7 +35,6 @@ export default function PreviewIframe( { } ) { const [ backgroundColor = 'white' ] = useGlobalStyle( 'color.background' ); const [ gradientValue ] = useGlobalStyle( 'color.gradient' ); - const [ styles ] = useGlobalStylesOutput(); const disableMotion = useReducedMotion(); const [ isHovered, setIsHovered ] = useState( false ); const [ containerResizeListener, { width } ] = useResizeObserver(); @@ -54,7 +47,7 @@ export default function PreviewIframe( { THROTTLE_OPTIONS ); - // Must use useLayoutEffect to avoid a flash of the iframe at the wrong + // Must use useLayoutEffect to avoid a flash of the container at the wrong // size before the width is set. useLayoutEffect( () => { if ( width ) { @@ -62,7 +55,7 @@ export default function PreviewIframe( { } }, [ width, setThrottledWidth ] ); - // Must use useLayoutEffect to avoid a flash of the iframe at the wrong + // Must use useLayoutEffect to avoid a flash of the container at the wrong // size before the width is set. useLayoutEffect( () => { const newRatio = throttledWidth ? throttledWidth / normalizedWidth : 1; @@ -89,24 +82,6 @@ export default function PreviewIframe( { */ const ratio = ratioState ? ratioState : fallbackRatio; - /* - * Reset leaked styles from WP common.css and remove main content layout padding and border. - * Add pointer cursor to the body to indicate the iframe is interactive, - * similar to Typography variation previews. - */ - const editorStyles = useMemo( () => { - if ( styles ) { - return [ - ...styles, - { - css: 'html{overflow:hidden}body{min-width: 0;padding: 0;border: none;cursor: pointer;}', - isGlobalStyles: true, - }, - ]; - } - - return styles; - }, [ styles ] ); const isReady = !! width; return ( @@ -115,8 +90,8 @@ export default function PreviewIframe( { { containerResizeListener } </div> { isReady && ( - <Iframe - className="edit-site-global-styles-preview__iframe" + <div + className="edit-site-global-styles-preview__wrapper" style={ { height: normalizedHeight * ratio, } } @@ -124,7 +99,6 @@ export default function PreviewIframe( { onMouseLeave={ () => setIsHovered( false ) } tabIndex={ -1 } > - <EditorStyles styles={ editorStyles } /> <motion.div style={ { height: normalizedHeight * ratio, @@ -145,7 +119,7 @@ export default function PreviewIframe( { .concat( children ) // This makes sure children is always an array. .map( ( child, key ) => child( { ratio, key } ) ) } </motion.div> - </Iframe> + </div> ) } </> ); diff --git a/packages/edit-site/src/components/global-styles/screen-block.js b/packages/edit-site/src/components/global-styles/screen-block.js index b1489167f2dc75..64f49574b6b03b 100644 --- a/packages/edit-site/src/components/global-styles/screen-block.js +++ b/packages/edit-site/src/components/global-styles/screen-block.js @@ -102,12 +102,16 @@ function ScreenBlock( { name, variation } ) { } ); const [ userSettings ] = useGlobalSetting( '', name, 'user' ); const [ rawSettings, setSettings ] = useGlobalSetting( '', name ); - const settings = useSettingsForBlockElement( rawSettings, name ); + const settingsForBlockElement = useSettingsForBlockElement( + rawSettings, + name + ); const blockType = getBlockType( name ); // Only allow `blockGap` support if serialization has not been skipped, to be sure global spacing can be applied. + let disableBlockGap = false; if ( - settings?.spacing?.blockGap && + settingsForBlockElement?.spacing?.blockGap && blockType?.supports?.spacing?.blockGap && ( blockType?.supports?.spacing?.__experimentalSkipSerialization === true || @@ -115,7 +119,7 @@ function ScreenBlock( { name, variation } ) { ( spacingType ) => spacingType === 'blockGap' ) ) ) { - settings.spacing.blockGap = false; + disableBlockGap = true; } // Only allow `aspectRatio` support if the block is not the grouping block. @@ -124,10 +128,25 @@ function ScreenBlock( { name, variation } ) { // for all three at once. Until there is the ability to set a different aspect // ratio for each variation, we disable the aspect ratio controls for the // grouping block in global styles. - if ( settings?.dimensions?.aspectRatio && name === 'core/group' ) { - settings.dimensions.aspectRatio = false; + let disableAspectRatio = false; + if ( + settingsForBlockElement?.dimensions?.aspectRatio && + name === 'core/group' + ) { + disableAspectRatio = true; } + const settings = useMemo( () => { + const updatedSettings = structuredClone( settingsForBlockElement ); + if ( disableBlockGap ) { + updatedSettings.spacing.blockGap = false; + } + if ( disableAspectRatio ) { + updatedSettings.dimensions.aspectRatio = false; + } + return updatedSettings; + }, [ settingsForBlockElement, disableBlockGap, disableAspectRatio ] ); + const blockVariations = useBlockVariations( name ); const hasBackgroundPanel = useHasBackgroundPanel( settings ); const hasTypographyPanel = useHasTypographyPanel( settings ); diff --git a/packages/edit-site/src/components/global-styles/screen-root.js b/packages/edit-site/src/components/global-styles/screen-root.js index ffa85b046ead71..ce9e4d08daf0a0 100644 --- a/packages/edit-site/src/components/global-styles/screen-root.js +++ b/packages/edit-site/src/components/global-styles/screen-root.js @@ -93,7 +93,7 @@ function ScreenRoot() { paddingTop={ 2 } /* * 13px matches the text inset of the NavigationButton (12px padding, plus the width of the button's border). - * This is an ad hoc override for this instance and the Addtional CSS option below. Other options for matching the + * This is an ad hoc override for this instance and the Additional CSS option below. Other options for matching the * the nav button inset should be looked at before reusing further. */ paddingX="13px" diff --git a/packages/edit-site/src/components/global-styles/screen-style-variations.js b/packages/edit-site/src/components/global-styles/screen-style-variations.js index 12690966ba3c4f..f6036aaace4358 100644 --- a/packages/edit-site/src/components/global-styles/screen-style-variations.js +++ b/packages/edit-site/src/components/global-styles/screen-style-variations.js @@ -1,11 +1,15 @@ /** * WordPress dependencies */ +import { + privateApis as blockEditorPrivateApis, + store as blockEditorStore, +} from '@wordpress/block-editor'; import { Card, CardBody } from '@wordpress/components'; +import { useSelect, useDispatch } from '@wordpress/data'; import { __ } from '@wordpress/i18n'; -import { privateApis as blockEditorPrivateApis } from '@wordpress/block-editor'; -import { useDispatch } from '@wordpress/data'; import { store as editorStore } from '@wordpress/editor'; +import { useEffect } from '@wordpress/element'; /** * Internal dependencies @@ -18,11 +22,16 @@ const { useZoomOut } = unlock( blockEditorPrivateApis ); function ScreenStyleVariations() { // Style Variations should only be previewed in with - // - a "zoomed out" editor + // - a "zoomed out" editor (but not when in preview mode) // - "Desktop" device preview + const isPreviewMode = useSelect( ( select ) => { + return select( blockEditorStore ).getSettings().isPreviewMode; + }, [] ); const { setDeviceType } = useDispatch( editorStore ); - useZoomOut(); - setDeviceType( 'desktop' ); + useZoomOut( ! isPreviewMode ); + useEffect( () => { + setDeviceType( 'desktop' ); + }, [ setDeviceType ] ); return ( <> diff --git a/packages/edit-site/src/components/global-styles/shadows-edit-panel.js b/packages/edit-site/src/components/global-styles/shadows-edit-panel.js index f26a8a5ed17436..93c6fe5751327e 100644 --- a/packages/edit-site/src/components/global-styles/shadows-edit-panel.js +++ b/packages/edit-site/src/components/global-styles/shadows-edit-panel.js @@ -35,7 +35,7 @@ import { reset, moreVertical, } from '@wordpress/icons'; -import { useState, useMemo, useEffect } from '@wordpress/element'; +import { useState, useMemo, useEffect, useRef } from '@wordpress/element'; /** * Internal dependencies @@ -163,33 +163,38 @@ export default function ShadowsEditPanel() { <ScreenHeader title={ selectedShadow.name } /> <FlexItem> <Spacer marginTop={ 2 } marginBottom={ 0 } paddingX={ 4 }> - <Menu - trigger={ - <Button - size="small" - icon={ moreVertical } - label={ __( 'Menu' ) } - /> - } - > - { ( category === 'custom' - ? customShadowMenuItems - : presetShadowMenuItems - ).map( ( item ) => ( - <Menu.Item - key={ item.action } - onClick={ () => onMenuClick( item.action ) } - disabled={ - item.action === 'reset' && - selectedShadow.shadow === - baseSelectedShadow.shadow - } - > - <Menu.ItemLabel> - { item.label } - </Menu.ItemLabel> - </Menu.Item> - ) ) } + <Menu> + <Menu.TriggerButton + render={ + <Button + size="small" + icon={ moreVertical } + label={ __( 'Menu' ) } + /> + } + /> + <Menu.Popover> + { ( category === 'custom' + ? customShadowMenuItems + : presetShadowMenuItems + ).map( ( item ) => ( + <Menu.Item + key={ item.action } + onClick={ () => + onMenuClick( item.action ) + } + disabled={ + item.action === 'reset' && + selectedShadow.shadow === + baseSelectedShadow.shadow + } + > + <Menu.ItemLabel> + { item.label } + </Menu.ItemLabel> + </Menu.Item> + ) ) } + </Menu.Popover> </Menu> </Spacer> </FlexItem> @@ -300,6 +305,7 @@ function ShadowsPreview( { shadow } ) { } function ShadowEditor( { shadow, onChange } ) { + const addShadowButtonRef = useRef(); const shadowParts = useMemo( () => getShadowParts( shadow ), [ shadow ] ); const onChangeShadowPart = ( index, part ) => { @@ -314,6 +320,7 @@ function ShadowEditor( { shadow, onChange } ) { const onRemoveShadowPart = ( index ) => { onChange( shadowParts.filter( ( p, i ) => i !== index ).join( ', ' ) ); + addShadowButtonRef.current.focus(); }; return ( @@ -334,6 +341,7 @@ function ShadowEditor( { shadow, onChange } ) { onClick={ () => { onAddShadowPart(); } } + ref={ addShadowButtonRef } /> </FlexItem> </HStack> @@ -384,7 +392,12 @@ function ShadowItem( { shadow, onChange, canRemove, onRemove } ) { 'aria-expanded': isOpen, }; const removeButtonProps = { - onClick: onRemove, + onClick: () => { + if ( isOpen ) { + onToggle(); + } + onRemove(); + }, className: clsx( 'edit-site-global-styles__shadow-editor__remove-button', { 'is-open': isOpen } @@ -393,28 +406,24 @@ function ShadowItem( { shadow, onChange, canRemove, onRemove } ) { }; return ( - <HStack align="center" justify="flex-start" spacing={ 0 }> - <FlexItem style={ { flexGrow: 1 } }> - <Button - __next40pxDefaultSize - icon={ shadowIcon } - { ...toggleProps } - > - { shadowObj.inset - ? __( 'Inner shadow' ) - : __( 'Drop shadow' ) } - </Button> - </FlexItem> + <> + <Button + __next40pxDefaultSize + icon={ shadowIcon } + { ...toggleProps } + > + { shadowObj.inset + ? __( 'Inner shadow' ) + : __( 'Drop shadow' ) } + </Button> { canRemove && ( - <FlexItem> - <Button - __next40pxDefaultSize - icon={ reset } - { ...removeButtonProps } - /> - </FlexItem> + <Button + size="small" + icon={ reset } + { ...removeButtonProps } + /> ) } - </HStack> + </> ); } } renderContent={ () => ( diff --git a/packages/edit-site/src/components/global-styles/shadows-panel.js b/packages/edit-site/src/components/global-styles/shadows-panel.js index 43e0c063f492b8..8e93ab2b15fb0a 100644 --- a/packages/edit-site/src/components/global-styles/shadows-panel.js +++ b/packages/edit-site/src/components/global-styles/shadows-panel.js @@ -8,10 +8,17 @@ import { Button, Flex, FlexItem, + privateApis as componentsPrivateApis, } from '@wordpress/components'; -import { __, sprintf } from '@wordpress/i18n'; +import { __, sprintf, isRTL } from '@wordpress/i18n'; import { privateApis as blockEditorPrivateApis } from '@wordpress/block-editor'; -import { plus, shadow as shadowIcon } from '@wordpress/icons'; +import { + plus, + Icon, + chevronLeft, + chevronRight, + moreVertical, +} from '@wordpress/icons'; /** * Internal dependencies @@ -21,8 +28,11 @@ import Subtitle from './subtitle'; import { NavigationButtonAsItem } from './navigation-button'; import ScreenHeader from './header'; import { getNewIndexFromPresets } from './utils'; +import { useState } from '@wordpress/element'; +import ConfirmResetShadowDialog from './confirm-reset-shadow-dialog'; const { useGlobalSetting } = unlock( blockEditorPrivateApis ); +const { Menu } = unlock( componentsPrivateApis ); export const defaultShadow = '6px 6px 9px rgba(0, 0, 0, 0.2)'; @@ -40,8 +50,27 @@ export default function ShadowsPanel() { setCustomShadows( [ ...( customShadows || [] ), shadow ] ); }; + const handleResetShadows = () => { + setCustomShadows( [] ); + }; + + const [ isResetDialogOpen, setIsResetDialogOpen ] = useState( false ); + + const toggleResetDialog = () => setIsResetDialogOpen( ! isResetDialogOpen ); + return ( <> + { isResetDialogOpen && ( + <ConfirmResetShadowDialog + text={ __( + 'Are you sure you want to remove all custom shadows?' + ) } + confirmButtonText={ __( 'Remove' ) } + isOpen={ isResetDialogOpen } + toggleOpen={ toggleResetDialog } + onConfirm={ handleResetShadows } + /> + ) } <ScreenHeader title={ __( 'Shadows' ) } description={ __( @@ -73,6 +102,7 @@ export default function ShadowsPanel() { category="custom" canCreate onCreate={ onCreateShadow } + onReset={ toggleResetDialog } /> </VStack> </div> @@ -80,7 +110,14 @@ export default function ShadowsPanel() { ); } -function ShadowList( { label, shadows, category, canCreate, onCreate } ) { +function ShadowList( { + label, + shadows, + category, + canCreate, + onCreate, + onReset, +} ) { const handleAddShadow = () => { const newIndex = getNewIndexFromPresets( shadows, 'shadow-' ); onCreate( { @@ -115,6 +152,26 @@ function ShadowList( { label, shadows, category, canCreate, onCreate } ) { /> </FlexItem> ) } + { !! shadows?.length && category === 'custom' && ( + <Menu> + <Menu.TriggerButton + render={ + <Button + size="small" + icon={ moreVertical } + label={ __( 'Shadow options' ) } + /> + } + /> + <Menu.Popover> + <Menu.Item onClick={ onReset }> + <Menu.ItemLabel> + { __( 'Remove all custom shadows' ) } + </Menu.ItemLabel> + </Menu.Item> + </Menu.Popover> + </Menu> + ) } </HStack> { shadows.length > 0 && ( <ItemGroup isBordered isSeparated> @@ -135,9 +192,11 @@ function ShadowItem( { shadow, category } ) { return ( <NavigationButtonAsItem path={ `/shadows/edit/${ category }/${ shadow.slug }` } - icon={ shadowIcon } > - { shadow.name } + <HStack> + <FlexItem>{ shadow.name }</FlexItem> + <Icon icon={ isRTL() ? chevronLeft : chevronRight } /> + </HStack> </NavigationButtonAsItem> ); } diff --git a/packages/edit-site/src/components/global-styles/style.scss b/packages/edit-site/src/components/global-styles/style.scss index 5574fe52d81f58..99b1c8c92bbd02 100644 --- a/packages/edit-site/src/components/global-styles/style.scss +++ b/packages/edit-site/src/components/global-styles/style.scss @@ -6,7 +6,7 @@ cursor: pointer; } -.edit-site-global-styles-preview__iframe { +.edit-site-global-styles-preview__wrapper { max-width: 100%; display: block; width: 100%; @@ -146,20 +146,42 @@ .edit-site-global-styles__shadow-editor__dropdown { width: 100%; + position: relative; +} - .edit-site-global-styles__shadow-editor__dropdown-toggle, - .edit-site-global-styles__shadow-editor__remove-button { - width: 100%; - height: auto; - padding-top: $grid-unit; - padding-bottom: $grid-unit; - text-align: left; - border-radius: inherit; - - &.is-open { - background: $gray-100; - color: var(--wp-admin-theme-color); - } +.edit-site-global-styles__shadow-editor__dropdown-toggle { + width: 100%; + height: auto; + padding-top: $grid-unit; + padding-bottom: $grid-unit; + text-align: left; + border-radius: inherit; + + &.is-open { + background: $gray-100; + color: var(--wp-admin-theme-color); + } +} + +.edit-site-global-styles__shadow-editor__remove-button { + position: absolute; + right: $grid-unit; + top: $grid-unit; + opacity: 0; + + &.edit-site-global-styles__shadow-editor__remove-button { + border: none; + } + + .edit-site-global-styles__shadow-editor__dropdown-toggle:hover + &, + &:focus, + &:hover { + opacity: 1; + } + + @media (hover: none) { + // Show reset button on devices that do not support hover. + opacity: 1; } } @@ -211,6 +233,10 @@ flex-direction: column; } +.edit-site-global-styles-sidebar__navigator-screen .single-column { + grid-column: span 1; +} + .edit-site-global-styles-screen-root.edit-site-global-styles-screen-root, .edit-site-global-styles-screen-style-variations.edit-site-global-styles-screen-style-variations { background: unset; diff --git a/packages/edit-site/src/components/global-styles/typography-example.js b/packages/edit-site/src/components/global-styles/typography-example.js index a491ca57bf5be4..9c0a4e0e1cb13a 100644 --- a/packages/edit-site/src/components/global-styles/typography-example.js +++ b/packages/edit-site/src/components/global-styles/typography-example.js @@ -14,7 +14,9 @@ import { unlock } from '../../lock-unlock'; import { getFamilyPreviewStyle } from './font-library-modal/utils/preview-styles'; import { getFontFamilies } from './utils'; -const { GlobalStylesContext } = unlock( blockEditorPrivateApis ); +const { useGlobalStyle, GlobalStylesContext } = unlock( + blockEditorPrivateApis +); const { mergeBaseAndUserConfigs } = unlock( editorPrivateApis ); export default function PreviewTypography( { fontSize, variation } ) { @@ -23,6 +25,9 @@ export default function PreviewTypography( { fontSize, variation } ) { if ( variation ) { config = mergeBaseAndUserConfigs( base, variation ); } + + const [ textColor ] = useGlobalStyle( 'color.text' ); + const [ bodyFontFamilies, headingFontFamilies ] = getFontFamilies( config ); const bodyPreviewStyle = bodyFontFamilies ? getFamilyPreviewStyle( bodyFontFamilies ) @@ -31,6 +36,11 @@ export default function PreviewTypography( { fontSize, variation } ) { ? getFamilyPreviewStyle( headingFontFamilies ) : {}; + if ( textColor ) { + bodyPreviewStyle.color = textColor; + headingPreviewStyle.color = textColor; + } + if ( fontSize ) { bodyPreviewStyle.fontSize = fontSize; headingPreviewStyle.fontSize = fontSize; @@ -52,6 +62,7 @@ export default function PreviewTypography( { fontSize, variation } ) { } } style={ { textAlign: 'center', + lineHeight: 1, } } > <span style={ headingPreviewStyle }> diff --git a/packages/edit-site/src/components/global-styles/ui.js b/packages/edit-site/src/components/global-styles/ui.js index 0cab4c13aa3ee7..22ebad383884e9 100644 --- a/packages/edit-site/src/components/global-styles/ui.js +++ b/packages/edit-site/src/components/global-styles/ui.js @@ -19,7 +19,7 @@ import { __ } from '@wordpress/i18n'; import { store as preferencesStore } from '@wordpress/preferences'; import { moreVertical } from '@wordpress/icons'; import { store as coreStore } from '@wordpress/core-data'; -import { useEffect, Fragment } from '@wordpress/element'; +import { useEffect } from '@wordpress/element'; import { usePrevious } from '@wordpress/compose'; /** diff --git a/packages/edit-site/src/components/global-styles/variations/style.scss b/packages/edit-site/src/components/global-styles/variations/style.scss index 5f57c72f180b12..b092e09e487508 100644 --- a/packages/edit-site/src/components/global-styles/variations/style.scss +++ b/packages/edit-site/src/components/global-styles/variations/style.scss @@ -9,9 +9,10 @@ outline-offset: -$border-width; overflow: hidden; position: relative; - // Add the same transition that block style variations and other buttons have. - transition: outline 0.1s linear; - @include reduce-motion("transition"); + @media not (prefers-reduced-motion) { + // Add the same transition that block style variations and other buttons have. + transition: outline 0.1s linear; + } &.is-pill { height: $button-size-compact; diff --git a/packages/edit-site/src/components/layout/index.js b/packages/edit-site/src/components/layout/index.js index cbc0a4661bf3e7..a5e14f0be82816 100644 --- a/packages/edit-site/src/components/layout/index.js +++ b/packages/edit-site/src/components/layout/index.js @@ -10,6 +10,7 @@ import { __unstableMotion as motion, __unstableAnimatePresence as AnimatePresence, __unstableUseNavigateRegions as useNavigateRegions, + SlotFillProvider, } from '@wordpress/components'; import { useReducedMotion, @@ -17,21 +18,26 @@ import { useResizeObserver, usePrevious, } from '@wordpress/compose'; -import { __ } from '@wordpress/i18n'; +import { __, sprintf } from '@wordpress/i18n'; import { useState, useRef, useEffect } from '@wordpress/element'; import { CommandMenu } from '@wordpress/commands'; import { privateApis as blockEditorPrivateApis } from '@wordpress/block-editor'; import { EditorSnackbars, + UnsavedChangesWarning, + ErrorBoundary, privateApis as editorPrivateApis, } from '@wordpress/editor'; import { privateApis as coreCommandsPrivateApis } from '@wordpress/core-commands'; import { privateApis as routerPrivateApis } from '@wordpress/router'; +import { PluginArea } from '@wordpress/plugins'; +import { store as noticesStore } from '@wordpress/notices'; +import { useDispatch, useSelect } from '@wordpress/data'; +import { store as preferencesStore } from '@wordpress/preferences'; /** * Internal dependencies */ -import ErrorBoundary from '../error-boundary'; import { default as SiteHub, SiteHubMobile } from '../site-hub'; import ResizableFrame from '../resizable-frame'; import { unlock } from '../../lock-unlock'; @@ -44,14 +50,14 @@ import SavePanel from '../save-panel'; const { useCommands } = unlock( coreCommandsPrivateApis ); const { useGlobalStyle } = unlock( blockEditorPrivateApis ); -const { NavigableRegion } = unlock( editorPrivateApis ); +const { NavigableRegion, GlobalStylesProvider } = unlock( editorPrivateApis ); const { useLocation } = unlock( routerPrivateApis ); const ANIMATION_DURATION = 0.3; -export default function Layout( { route } ) { - const { params } = useLocation(); - const { canvas = 'view' } = params; +function Layout() { + const { query, name: routeKey, areas, widths } = useLocation(); + const { canvas = 'view' } = query; useCommands(); const isMobileViewport = useViewportMatch( 'medium', '<' ); const toggleRef = useRef(); @@ -61,11 +67,19 @@ export default function Layout( { route } ) { const isEditorLoading = useIsSiteEditorLoading(); const [ isResizableFrameOversized, setIsResizableFrameOversized ] = useState( false ); - const { name: routeKey, areas, widths } = route; const animationRef = useMovingAnimation( { triggerAnimationOnChange: routeKey + '-' + canvas, } ); + const { showIconLabels } = useSelect( ( select ) => { + return { + showIconLabels: select( preferencesStore ).get( + 'core', + 'showIconLabels' + ), + }; + } ); + const [ backgroundColor ] = useGlobalStyle( 'color.background' ); const [ gradientValue ] = useGlobalStyle( 'color.gradient' ); const previousCanvaMode = usePrevious( canvas ); @@ -78,6 +92,7 @@ export default function Layout( { route } ) { return ( <> + <UnsavedChangesWarning /> <CommandMenu /> { canvas === 'view' && <SaveKeyboardShortcut /> } <div @@ -88,6 +103,7 @@ export default function Layout( { route } ) { navigateRegionsProps.className, { 'is-full-canvas': canvas === 'edit', + 'show-icon-labels': showIconLabels, } ) } > @@ -127,11 +143,13 @@ export default function Layout( { route } ) { /> <SidebarContent shouldAnimate={ - routeKey !== 'styles-view' + routeKey !== 'styles' } routeKey={ routeKey } > - { areas.sidebar } + <ErrorBoundary> + { areas.sidebar } + </ErrorBoundary> </SidebarContent> <SaveHub /> <SavePanel /> @@ -155,7 +173,7 @@ export default function Layout( { route } ) { /> </SidebarContent> ) } - { areas.mobile } + <ErrorBoundary>{ areas.mobile }</ErrorBoundary> </div> ) } @@ -168,7 +186,7 @@ export default function Layout( { route } ) { maxWidth: widths?.content, } } > - { areas.content } + <ErrorBoundary>{ areas.content }</ErrorBoundary> </div> ) } @@ -179,7 +197,7 @@ export default function Layout( { route } ) { maxWidth: widths?.edit, } } > - { areas.edit } + <ErrorBoundary>{ areas.edit }</ErrorBoundary> </div> ) } @@ -231,3 +249,28 @@ export default function Layout( { route } ) { </> ); } + +export default function LayoutWithGlobalStylesProvider( props ) { + const { createErrorNotice } = useDispatch( noticesStore ); + function onPluginAreaError( name ) { + createErrorNotice( + sprintf( + /* translators: %s: plugin name */ + __( + 'The "%s" plugin has encountered an error and cannot be rendered.' + ), + name + ) + ); + } + + return ( + <SlotFillProvider> + <GlobalStylesProvider> + { /** This needs to be within the SlotFillProvider */ } + <PluginArea onError={ onPluginAreaError } /> + <Layout { ...props } /> + </GlobalStylesProvider> + </SlotFillProvider> + ); +} diff --git a/packages/edit-site/src/components/layout/router.js b/packages/edit-site/src/components/layout/router.js deleted file mode 100644 index 912a837555e0d1..00000000000000 --- a/packages/edit-site/src/components/layout/router.js +++ /dev/null @@ -1,86 +0,0 @@ -/** - * WordPress dependencies - */ -import { privateApis as routerPrivateApis } from '@wordpress/router'; -import { useEffect, useMemo } from '@wordpress/element'; -import { useSelect } from '@wordpress/data'; -/** - * Internal dependencies - */ -import { unlock } from '../../lock-unlock'; -import { - NAVIGATION_POST_TYPE, - PATTERN_TYPES, - TEMPLATE_PART_POST_TYPE, - TEMPLATE_POST_TYPE, -} from '../../utils/constants'; -import { store as editSiteStore } from '../../store'; - -const { useLocation, useHistory } = unlock( routerPrivateApis ); - -function useRedirectOldPaths() { - const history = useHistory(); - const { params } = useLocation(); - useEffect( () => { - const { postType, path, categoryType, ...rest } = params; - - if ( path === '/wp_template_part/all' ) { - history.replace( { postType: TEMPLATE_PART_POST_TYPE } ); - } - - if ( path === '/page' ) { - history.replace( { - postType: 'page', - ...rest, - } ); - } - - if ( path === '/wp_template' ) { - history.replace( { - postType: TEMPLATE_POST_TYPE, - ...rest, - } ); - } - - if ( path === '/patterns' ) { - history.replace( { - postType: - categoryType === TEMPLATE_PART_POST_TYPE - ? TEMPLATE_PART_POST_TYPE - : PATTERN_TYPES.user, - ...rest, - } ); - } - - if ( path === '/navigation' ) { - history.replace( { - postType: NAVIGATION_POST_TYPE, - ...rest, - } ); - } - }, [ history, params ] ); -} - -export default function useActiveRoute() { - const { params } = useLocation(); - useRedirectOldPaths(); - const routes = useSelect( ( select ) => { - return unlock( select( editSiteStore ) ).getRoutes(); - }, [] ); - return useMemo( () => { - const matchedRoute = routes.find( ( route ) => route.match( params ) ); - if ( ! matchedRoute ) { - return { - key: 404, - areas: {}, - widths: {}, - }; - } - - return { - name: matchedRoute.name, - areas: matchedRoute.areas, - widths: matchedRoute.widths, - }; - }, [ routes, params ] ); -} diff --git a/packages/edit-site/src/components/layout/style.scss b/packages/edit-site/src/components/layout/style.scss index 2c7e6ce1b10c8b..caf7dd78da4b34 100644 --- a/packages/edit-site/src/components/layout/style.scss +++ b/packages/edit-site/src/components/layout/style.scss @@ -115,10 +115,13 @@ .edit-site-resizable-frame__inner-content { box-shadow: $elevation-x-small; - transition: border-radius, box-shadow 0.4s; // This ensure the radius work properly. overflow: hidden; + @media not (prefers-reduced-motion) { + transition: border-radius, box-shadow 0.4s; + } + .edit-site-layout:not(.is-full-canvas) & { border-radius: $radius-large; } @@ -195,8 +198,6 @@ html.canvas-mode-edit-transition::view-transition-group(toggle) { } &::before { - transition: box-shadow 0.1s ease; - @include reduce-motion("transition"); content: ""; display: block; position: absolute; @@ -206,6 +207,10 @@ html.canvas-mode-edit-transition::view-transition-group(toggle) { left: 9px; border-radius: $radius-medium; box-shadow: none; + + @media not (prefers-reduced-motion) { + transition: box-shadow 0.1s ease; + } } .edit-site-layout__view-mode-toggle-icon { diff --git a/packages/edit-site/src/components/maybe-editor/index.js b/packages/edit-site/src/components/maybe-editor/index.js new file mode 100644 index 00000000000000..3ffa5a35b82ef7 --- /dev/null +++ b/packages/edit-site/src/components/maybe-editor/index.js @@ -0,0 +1,61 @@ +/** + * WordPress dependencies + */ + +import { store as coreStore } from '@wordpress/core-data'; +import { useSelect } from '@wordpress/data'; +import { __ } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ + +import Editor from '../editor'; + +export function MaybeEditor( { showEditor = true } ) { + const { isBlockBasedTheme, siteUrl } = useSelect( ( select ) => { + const { getEntityRecord, getCurrentTheme } = select( coreStore ); + const siteData = getEntityRecord( 'root', '__unstableBase' ); + + return { + isBlockBasedTheme: getCurrentTheme()?.is_block_theme, + siteUrl: siteData?.home, + }; + }, [] ); + + // If theme is block based, return the Editor, otherwise return the site preview. + return isBlockBasedTheme || showEditor ? ( + <Editor /> + ) : ( + <iframe + src={ siteUrl } + title={ __( 'Site Preview' ) } + style={ { + display: 'block', + width: '100%', + height: '100%', + backgroundColor: '#fff', + } } + onLoad={ ( event ) => { + // Hide the admin bar in the front-end preview. + const document = event.target.contentDocument; + document.getElementById( 'wpadminbar' ).remove(); + document + .getElementsByTagName( 'html' )[ 0 ] + .setAttribute( 'style', 'margin-top: 0 !important;' ); + document + .getElementsByTagName( 'body' )[ 0 ] + .classList.remove( 'admin-bar' ); + // Make interactive elements unclickable. + const interactiveElements = document.querySelectorAll( + 'a, button, input, details, audio' + ); + interactiveElements.forEach( ( element ) => { + element.style.pointerEvents = 'none'; + element.tabIndex = -1; + element.setAttribute( 'aria-hidden', 'true' ); + } ); + } } + /> + ); +} diff --git a/packages/edit-site/src/components/page-patterns/delete-category-menu-item.js b/packages/edit-site/src/components/page-patterns/delete-category-menu-item.js index d87737c55326c6..ca7bbf2fa73220 100644 --- a/packages/edit-site/src/components/page-patterns/delete-category-menu-item.js +++ b/packages/edit-site/src/components/page-patterns/delete-category-menu-item.js @@ -59,10 +59,9 @@ export default function DeleteCategoryMenuItem( { category, onClose } ) { ); onClose?.(); - history.push( { - postType: PATTERN_TYPES.user, - categoryId: PATTERN_DEFAULT_CATEGORY, - } ); + history.navigate( + `/pattern?categoryId=${ PATTERN_DEFAULT_CATEGORY }` + ); } catch ( error ) { const errorMessage = error.message && error.code !== 'unknown_error' diff --git a/packages/edit-site/src/components/page-patterns/fields.js b/packages/edit-site/src/components/page-patterns/fields.js index e016dca6cd8557..d884508e575068 100644 --- a/packages/edit-site/src/components/page-patterns/fields.js +++ b/packages/edit-site/src/components/page-patterns/fields.js @@ -6,64 +6,34 @@ import clsx from 'clsx'; /** * WordPress dependencies */ -import { - __experimentalHStack as HStack, - Button, - Tooltip, - FlexBlock, -} from '@wordpress/components'; +import { __experimentalHStack as HStack } from '@wordpress/components'; import { __, _x } from '@wordpress/i18n'; import { useState, useMemo, useId } from '@wordpress/element'; import { BlockPreview, privateApis as blockEditorPrivateApis, } from '@wordpress/block-editor'; -import { Icon, lockSmall } from '@wordpress/icons'; +import { Icon } from '@wordpress/icons'; import { parse } from '@wordpress/blocks'; -import { decodeEntities } from '@wordpress/html-entities'; /** * Internal dependencies */ import { - PATTERN_TYPES, TEMPLATE_PART_POST_TYPE, PATTERN_SYNC_TYPES, OPERATOR_IS, } from '../../utils/constants'; import { unlock } from '../../lock-unlock'; -import { useLink } from '../routes/link'; import { useAddedBy } from '../page-templates/hooks'; -import { defaultGetTitle } from './search-items'; const { useGlobalStyle } = unlock( blockEditorPrivateApis ); -function PreviewWrapper( { item, onClick, ariaDescribedBy, children } ) { - return ( - <button - className="page-patterns-preview-field__button" - type="button" - onClick={ item.type !== PATTERN_TYPES.theme ? onClick : undefined } - aria-label={ defaultGetTitle( item ) } - aria-describedby={ ariaDescribedBy } - aria-disabled={ item.type === PATTERN_TYPES.theme } - > - { children } - </button> - ); -} - function PreviewField( { item } ) { const descriptionId = useId(); const description = item.description || item?.excerpt?.raw; - const isUserPattern = item.type === PATTERN_TYPES.user; const isTemplatePart = item.type === TEMPLATE_PART_POST_TYPE; const [ backgroundColor ] = useGlobalStyle( 'color.background' ); - const { onClick } = useLink( { - postType: item.type, - postId: isUserPattern || isTemplatePart ? item.id : item.name, - canvas: 'edit', - } ); const blocks = useMemo( () => { return ( item.blocks ?? @@ -78,23 +48,18 @@ function PreviewField( { item } ) { <div className="page-patterns-preview-field" style={ { backgroundColor } } + aria-describedby={ !! description ? descriptionId : undefined } > - <PreviewWrapper - item={ item } - onClick={ onClick } - ariaDescribedBy={ !! description ? descriptionId : undefined } - > - { isEmpty && isTemplatePart && __( 'Empty template part' ) } - { isEmpty && ! isTemplatePart && __( 'Empty pattern' ) } - { ! isEmpty && ( - <BlockPreview.Async> - <BlockPreview - blocks={ blocks } - viewportWidth={ item.viewportWidth } - /> - </BlockPreview.Async> - ) } - </PreviewWrapper> + { isEmpty && isTemplatePart && __( 'Empty template part' ) } + { isEmpty && ! isTemplatePart && __( 'Empty pattern' ) } + { ! isEmpty && ( + <BlockPreview.Async> + <BlockPreview + blocks={ blocks } + viewportWidth={ item.viewportWidth } + /> + </BlockPreview.Async> + ) } { !! description && ( <div hidden id={ descriptionId }> { description } @@ -111,57 +76,6 @@ export const previewField = { enableSorting: false, }; -function TitleField( { item } ) { - const isUserPattern = item.type === PATTERN_TYPES.user; - const isTemplatePart = item.type === TEMPLATE_PART_POST_TYPE; - const { onClick } = useLink( { - postType: item.type, - postId: isUserPattern || isTemplatePart ? item.id : item.name, - canvas: 'edit', - } ); - const title = decodeEntities( defaultGetTitle( item ) ); - return ( - <HStack alignment="center" justify="flex-start" spacing={ 2 }> - <FlexBlock className="edit-site-patterns__pattern-title"> - { item.type === PATTERN_TYPES.theme ? ( - title - ) : ( - <Button - __next40pxDefaultSize - variant="link" - onClick={ onClick } - // Required for the grid's roving tab index system. - // See https://github.com/WordPress/gutenberg/pull/51898#discussion_r1243399243. - tabIndex="-1" - > - { title } - </Button> - ) } - </FlexBlock> - { item.type === PATTERN_TYPES.theme && ( - <Tooltip - placement="top" - text={ __( 'This pattern cannot be edited.' ) } - > - <Icon - className="edit-site-patterns__pattern-lock-icon" - icon={ lockSmall } - size={ 24 } - /> - </Tooltip> - ) } - </HStack> - ); -} - -export const titleField = { - label: __( 'Title' ), - id: 'title', - getValue: ( { item } ) => item.title?.raw || item.title, - render: TitleField, - enableHiding: false, -}; - const SYNC_FILTERS = [ { value: PATTERN_SYNC_TYPES.full, diff --git a/packages/edit-site/src/components/page-patterns/header.js b/packages/edit-site/src/components/page-patterns/header.js index 641dc577cb7fe0..0d3763aec62c1a 100644 --- a/packages/edit-site/src/components/page-patterns/header.js +++ b/packages/edit-site/src/components/page-patterns/header.js @@ -9,7 +9,7 @@ import { __experimentalText as Text, __experimentalVStack as VStack, } from '@wordpress/components'; -import { store as editorStore } from '@wordpress/editor'; +import { store as coreStore } from '@wordpress/core-data'; import { useSelect } from '@wordpress/data'; import { __, sprintf } from '@wordpress/i18n'; import { moreVertical } from '@wordpress/icons'; @@ -32,7 +32,8 @@ export default function PatternsHeader( { const { patternCategories } = usePatternCategories(); const templatePartAreas = useSelect( ( select ) => - select( editorStore ).__experimentalGetDefaultTemplatePartAreas(), + select( coreStore ).getEntityRecord( 'root', '__unstableBase' ) + ?.default_template_part_areas || [], [] ); diff --git a/packages/edit-site/src/components/page-patterns/index.js b/packages/edit-site/src/components/page-patterns/index.js index 69ebf66093806a..b23213fa1a6db3 100644 --- a/packages/edit-site/src/components/page-patterns/index.js +++ b/packages/edit-site/src/components/page-patterns/index.js @@ -9,6 +9,7 @@ import { usePrevious } from '@wordpress/compose'; import { useEntityRecords } from '@wordpress/core-data'; import { privateApis as editorPrivateApis } from '@wordpress/editor'; import { privateApis as routerPrivateApis } from '@wordpress/router'; +import { patternTitleField } from '@wordpress/fields'; /** * Internal dependencies @@ -29,23 +30,18 @@ import { useEditPostAction } from '../dataviews-actions'; import { patternStatusField, previewField, - titleField, templatePartAuthorField, } from './fields'; const { ExperimentalBlockEditorProvider } = unlock( blockEditorPrivateApis ); const { usePostActions } = unlock( editorPrivateApis ); -const { useLocation } = unlock( routerPrivateApis ); +const { useLocation, useHistory } = unlock( routerPrivateApis ); const EMPTY_ARRAY = []; const defaultLayouts = { [ LAYOUT_TABLE ]: { layout: { - primaryField: 'title', styles: { - preview: { - width: '1%', - }, author: { width: '1%', }, @@ -54,8 +50,6 @@ const defaultLayouts = { }, [ LAYOUT_GRID ]: { layout: { - mediaField: 'preview', - primaryField: 'title', badgeFields: [ 'sync-status' ], }, }, @@ -65,24 +59,26 @@ const DEFAULT_VIEW = { search: '', page: 1, perPage: 20, - layout: defaultLayouts[ LAYOUT_GRID ].layout, - fields: [ 'title', 'sync-status' ], + titleField: 'title', + mediaField: 'preview', + fields: [ 'sync-status' ], filters: [], + ...defaultLayouts[ LAYOUT_GRID ], }; export default function DataviewsPatterns() { const { - params: { postType, categoryId: categoryIdFromURL }, + query: { postType = 'wp_block', categoryId: categoryIdFromURL }, } = useLocation(); - const type = postType || PATTERN_TYPES.user; + const history = useHistory(); const categoryId = categoryIdFromURL || PATTERN_DEFAULT_CATEGORY; const [ view, setView ] = useState( DEFAULT_VIEW ); const previousCategoryId = usePrevious( categoryId ); - const previousPostType = usePrevious( type ); + const previousPostType = usePrevious( postType ); const viewSyncStatus = view.filters?.find( ( { field } ) => field === 'sync-status' )?.value; - const { patterns, isResolving } = usePatterns( type, categoryId, { + const { patterns, isResolving } = usePatterns( postType, categoryId, { search: view.search, syncStatus: viewSyncStatus, } ); @@ -106,11 +102,11 @@ export default function DataviewsPatterns() { }, [ records ] ); const fields = useMemo( () => { - const _fields = [ previewField, titleField ]; + const _fields = [ previewField, patternTitleField ]; - if ( type === PATTERN_TYPES.user ) { + if ( postType === PATTERN_TYPES.user ) { _fields.push( patternStatusField ); - } else if ( type === TEMPLATE_PART_POST_TYPE ) { + } else if ( postType === TEMPLATE_PART_POST_TYPE ) { _fields.push( { ...templatePartAuthorField, elements: authors, @@ -118,24 +114,27 @@ export default function DataviewsPatterns() { } return _fields; - }, [ type, authors ] ); + }, [ postType, authors ] ); // Reset the page number when the category changes. useEffect( () => { - if ( previousCategoryId !== categoryId || previousPostType !== type ) { + if ( + previousCategoryId !== categoryId || + previousPostType !== postType + ) { setView( ( prevView ) => ( { ...prevView, page: 1 } ) ); } - }, [ categoryId, previousCategoryId, previousPostType, type ] ); + }, [ categoryId, previousCategoryId, previousPostType, postType ] ); const { data, paginationInfo } = useMemo( () => { // Search is managed server-side as well as filters for patterns. // However, the author filter in template parts is done client-side. const viewWithoutFilters = { ...view }; delete viewWithoutFilters.search; - if ( type !== TEMPLATE_PART_POST_TYPE ) { + if ( postType !== TEMPLATE_PART_POST_TYPE ) { viewWithoutFilters.filters = []; } return filterSortAndPaginate( patterns, viewWithoutFilters, fields ); - }, [ patterns, view, fields, type ] ); + }, [ patterns, view, fields, postType ] ); const dataWithPermissions = useAugmentPatternsWithPermissions( data ); @@ -150,11 +149,11 @@ export default function DataviewsPatterns() { const editAction = useEditPostAction(); const actions = useMemo( () => { - if ( type === TEMPLATE_PART_POST_TYPE ) { + if ( postType === TEMPLATE_PART_POST_TYPE ) { return [ editAction, ...templatePartActions ].filter( Boolean ); } return [ editAction, ...patternActions ].filter( Boolean ); - }, [ editAction, type, templatePartActions, patternActions ] ); + }, [ editAction, postType, templatePartActions, patternActions ] ); const id = useId(); const settings = usePatternSettings(); // Wrap everything in a block editor provider. @@ -169,7 +168,7 @@ export default function DataviewsPatterns() { > <PatternsHeader categoryId={ categoryId } - type={ type } + type={ postType } titleId={ `${ id }-title` } descriptionId={ `${ id }-description` } /> @@ -181,6 +180,21 @@ export default function DataviewsPatterns() { data={ dataWithPermissions || EMPTY_ARRAY } getItemId={ ( item ) => item.name ?? item.id } isLoading={ isResolving } + isItemClickable={ ( item ) => + item.type !== PATTERN_TYPES.theme + } + onClickItem={ ( item ) => { + history.navigate( + `/${ item.type }/${ + [ + PATTERN_TYPES.user, + TEMPLATE_PART_POST_TYPE, + ].includes( item.type ) + ? item.id + : item.name + }?canvas=edit` + ); + } } view={ view } onChangeView={ setView } defaultLayouts={ defaultLayouts } diff --git a/packages/edit-site/src/components/page-patterns/style.scss b/packages/edit-site/src/components/page-patterns/style.scss index d59cce420202ee..2cacc8fab607c2 100644 --- a/packages/edit-site/src/components/page-patterns/style.scss +++ b/packages/edit-site/src/components/page-patterns/style.scss @@ -12,28 +12,6 @@ width: 96px; flex-grow: 0; } - - .page-patterns-preview-field__button { - box-shadow: none; - border: none; - padding: 0; - background-color: unset; - box-sizing: border-box; - cursor: pointer; - overflow: hidden; - height: 100%; - border-radius: $grid-unit-05; - - &:focus-visible { - box-shadow: 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; - } - - &[aria-disabled="true"] { - cursor: default; - } - } } .edit-site-patterns__pattern-icon { @@ -48,10 +26,12 @@ top: 0; z-index: 2; flex-shrink: 0; - transition: padding ease-out 0.1s; - @include reduce-motion("transition"); min-height: $grid-unit-50; + @media not (prefers-reduced-motion) { + transition: padding ease-out 0.1s; + } + .edit-site-patterns__title { min-height: $grid-unit-50; @@ -71,34 +51,6 @@ } } -.edit-site-patterns__pattern-title { - display: block; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - color: inherit; - - .is-link { - text-decoration: none; - color: $gray-200; - - &:hover, - &:focus { - color: $white; - } - } - - .edit-site-patterns__pattern-icon { - border-radius: $grid-unit-05; - background: var(--wp-block-synced-color); - fill: $white; - } - - .edit-site-patterns__pattern-lock-icon { - fill: currentcolor; - } -} - .edit-site-page-patterns-dataviews { .dataviews-view-grid__badge-fields { .dataviews-view-grid__field-value:has(.edit-site-patterns__field-sync-status-fully) { @@ -142,7 +94,6 @@ } } -/* stylelint-disable-next-line scss/at-rule-no-unknown -- '@container' not globally permitted */ @container (max-width: 430px) { .edit-site-page-patterns-dataviews .edit-site-patterns__section-header { padding-left: $grid-unit-30; diff --git a/packages/edit-site/src/components/page-patterns/use-patterns.js b/packages/edit-site/src/components/page-patterns/use-patterns.js index f32e278e6354f5..3b3e33d5650e63 100644 --- a/packages/edit-site/src/components/page-patterns/use-patterns.js +++ b/packages/edit-site/src/components/page-patterns/use-patterns.js @@ -4,7 +4,6 @@ import { parse } from '@wordpress/blocks'; import { useSelect, createSelector } from '@wordpress/data'; import { store as coreStore } from '@wordpress/core-data'; -import { store as editorStore } from '@wordpress/editor'; import { useMemo } from '@wordpress/element'; /** @@ -28,8 +27,7 @@ const selectTemplateParts = createSelector( ( select, categoryId, search = '' ) => { const { getEntityRecords, isResolving: isResolvingSelector } = select( coreStore ); - const { __experimentalGetDefaultTemplatePartAreas } = - select( editorStore ); + const query = { per_page: -1 }; const templateParts = getEntityRecords( 'postType', TEMPLATE_PART_POST_TYPE, query ) ?? @@ -38,7 +36,10 @@ const selectTemplateParts = createSelector( // In the case where a custom template part area has been removed we need // the current list of areas to cross check against so orphaned template // parts can be treated as uncategorized. - const knownAreas = __experimentalGetDefaultTemplatePartAreas() || []; + const knownAreas = + select( coreStore ).getEntityRecord( 'root', '__unstableBase' ) + ?.default_template_part_areas || []; + const templatePartAreas = knownAreas.map( ( area ) => area.area ); const templatePartHasCategory = ( item, category ) => { @@ -78,7 +79,8 @@ const selectTemplateParts = createSelector( TEMPLATE_PART_POST_TYPE, { per_page: -1 }, ] ), - select( editorStore ).__experimentalGetDefaultTemplatePartAreas(), + select( coreStore ).getEntityRecord( 'root', '__unstableBase' ) + ?.default_template_part_areas, ] ); @@ -156,7 +158,7 @@ const selectPatterns = createSelector( categoryId, hasCategory: ( item, currentCategory ) => { if ( item.type === PATTERN_TYPES.user ) { - return item.wp_pattern_category.some( + return item.wp_pattern_category?.some( ( catId ) => userPatternCategories.find( ( cat ) => cat.id === catId @@ -173,7 +175,7 @@ const selectPatterns = createSelector( return ( userPatternCategories?.length && ( ! item.wp_pattern_category?.length || - ! item.wp_pattern_category.some( ( catId ) => + ! item.wp_pattern_category?.some( ( catId ) => userPatternCategories.find( ( cat ) => cat.id === catId ) diff --git a/packages/edit-site/src/components/page-templates/fields.js b/packages/edit-site/src/components/page-templates/fields.js index 69e0596bf49d47..97b427690901a6 100644 --- a/packages/edit-site/src/components/page-templates/fields.js +++ b/packages/edit-site/src/components/page-templates/fields.js @@ -20,9 +20,7 @@ import { EditorProvider } from '@wordpress/editor'; /** * Internal dependencies */ -import { default as Link, useLink } from '../routes/link'; import { useAddedBy } from './hooks'; - import usePatternSettings from '../page-patterns/use-pattern-settings'; import { unlock } from '../../lock-unlock'; @@ -34,11 +32,6 @@ function PreviewField( { item } ) { const blocks = useMemo( () => { return parse( item.content.raw ); }, [ item.content.raw ] ); - const { onClick } = useLink( { - postId: item.id, - postType: item.type, - canvas: 'edit', - } ); const isEmpty = ! blocks?.length; // Wrap everything in a block editor provider to ensure 'styles' that are needed @@ -54,19 +47,12 @@ function PreviewField( { item } ) { className="page-templates-preview-field" style={ { backgroundColor } } > - <button - className="page-templates-preview-field__button" - type="button" - onClick={ onClick } - aria-label={ item.title?.rendered || item.title } - > - { isEmpty && __( 'Empty template' ) } - { ! isEmpty && ( - <BlockPreview.Async> - <BlockPreview blocks={ blocks } /> - </BlockPreview.Async> - ) } - </button> + { isEmpty && __( 'Empty template' ) } + { ! isEmpty && ( + <BlockPreview.Async> + <BlockPreview blocks={ blocks } /> + </BlockPreview.Async> + ) } </div> </EditorProvider> ); @@ -79,30 +65,6 @@ export const previewField = { enableSorting: false, }; -function TitleField( { item } ) { - const linkProps = { - params: { - postId: item.id, - postType: item.type, - canvas: 'edit', - }, - }; - return ( - <Link { ...linkProps }> - { decodeEntities( item.title?.rendered ) || __( '(no title)' ) } - </Link> - ); -} - -export const titleField = { - label: __( 'Template' ), - id: 'title', - getValue: ( { item } ) => item.title?.rendered, - render: TitleField, - enableHiding: false, - enableGlobalSearch: true, -}; - export const descriptionField = { label: __( 'Description' ), id: 'description', diff --git a/packages/edit-site/src/components/page-templates/index.js b/packages/edit-site/src/components/page-templates/index.js index ea026ca53566e8..e0e04e0f5da924 100644 --- a/packages/edit-site/src/components/page-templates/index.js +++ b/packages/edit-site/src/components/page-templates/index.js @@ -7,6 +7,8 @@ import { privateApis as corePrivateApis } from '@wordpress/core-data'; import { DataViews, filterSortAndPaginate } from '@wordpress/dataviews'; import { privateApis as routerPrivateApis } from '@wordpress/router'; import { privateApis as editorPrivateApis } from '@wordpress/editor'; +import { templateTitleField } from '@wordpress/fields'; +import { addQueryArgs } from '@wordpress/url'; /** * Internal dependencies @@ -22,12 +24,8 @@ import { } from '../../utils/constants'; import { unlock } from '../../lock-unlock'; import { useEditPostAction } from '../dataviews-actions'; -import { - authorField, - descriptionField, - previewField, - titleField, -} from './fields'; +import { authorField, descriptionField, previewField } from './fields'; +import { useEvent } from '@wordpress/compose'; const { usePostActions } = unlock( editorPrivateApis ); const { useHistory, useLocation } = unlock( routerPrivateApis ); @@ -37,25 +35,9 @@ const EMPTY_ARRAY = []; const defaultLayouts = { [ LAYOUT_TABLE ]: { - fields: [ 'template', 'author' ], + showMedia: false, layout: { - primaryField: 'title', - combinedFields: [ - { - id: 'template', - label: __( 'Template' ), - children: [ 'title', 'description' ], - direction: 'vertical', - }, - ], styles: { - template: { - maxWidth: 400, - minWidth: 320, - }, - preview: { - width: '1%', - }, author: { width: '1%', }, @@ -63,18 +45,10 @@ const defaultLayouts = { }, }, [ LAYOUT_GRID ]: { - fields: [ 'title', 'description', 'author' ], - layout: { - mediaField: 'preview', - primaryField: 'title', - columnFields: [ 'description' ], - }, + showMedia: true, }, [ LAYOUT_LIST ]: { - fields: [ 'title', 'description', 'author' ], - layout: { - primaryField: 'title', - }, + showMedia: false, }, }; @@ -87,14 +61,17 @@ const DEFAULT_VIEW = { field: 'title', direction: 'asc', }, - fields: defaultLayouts[ LAYOUT_GRID ].fields, - layout: defaultLayouts[ LAYOUT_GRID ].layout, + titleField: 'title', + descriptionField: 'description', + mediaField: 'preview', + fields: [ 'author' ], filters: [], + ...defaultLayouts[ LAYOUT_GRID ], }; export default function PageTemplates() { - const { params } = useLocation(); - const { activeView = 'all', layout, postId } = params; + const { path, query } = useLocation(); + const { activeView = 'all', layout, postId } = query; const [ selection, setSelection ] = useState( [ postId ] ); const defaultView = useMemo( () => { @@ -102,8 +79,6 @@ export default function PageTemplates() { return { ...DEFAULT_VIEW, type: usedType, - layout: defaultLayouts[ usedType ].layout, - fields: defaultLayouts[ usedType ].fields, filters: activeView !== 'all' ? [ @@ -114,9 +89,20 @@ export default function PageTemplates() { }, ] : [], + ...defaultLayouts[ usedType ], }; }, [ layout, activeView ] ); const [ view, setView ] = useState( defaultView ); + + // Sync the layout from the URL to the view state. + useEffect( () => { + setView( ( currentView ) => ( { + ...currentView, + type: layout ?? DEFAULT_VIEW.type, + } ) ); + }, [ setView, layout ] ); + + // Sync the active view from the URL to the view state. useEffect( () => { setView( ( currentView ) => ( { ...currentView, @@ -131,7 +117,7 @@ export default function PageTemplates() { ] : [], } ) ); - }, [ activeView ] ); + }, [ setView, activeView ] ); const { records, isResolving: isLoadingData } = useEntityRecordsWithPermissions( 'postType', TEMPLATE_POST_TYPE, { @@ -142,13 +128,14 @@ export default function PageTemplates() { ( items ) => { setSelection( items ); if ( view?.type === LAYOUT_LIST ) { - history.push( { - ...params, - postId: items.length === 1 ? items[ 0 ] : undefined, - } ); + history.navigate( + addQueryArgs( path, { + postId: items.length === 1 ? items[ 0 ] : undefined, + } ) + ); } }, - [ history, params, view?.type ] + [ history, path, view?.type ] ); const authors = useMemo( () => { @@ -168,7 +155,7 @@ export default function PageTemplates() { const fields = useMemo( () => [ previewField, - titleField, + templateTitleField, descriptionField, { ...authorField, @@ -192,19 +179,16 @@ export default function PageTemplates() { [ postTypeActions, editAction ] ); - const onChangeView = useCallback( - ( newView ) => { - if ( newView.type !== view.type ) { - history.push( { - ...params, + const onChangeView = useEvent( ( newView ) => { + setView( newView ); + if ( newView.type !== layout ) { + history.navigate( + addQueryArgs( path, { layout: newView.type, - } ); - } - - setView( newView ); - }, - [ view.type, setView, history, params ] - ); + } ) + ); + } + } ); return ( <Page @@ -222,6 +206,10 @@ export default function PageTemplates() { view={ view } onChangeView={ onChangeView } onChangeSelection={ onChangeSelection } + isItemClickable={ () => true } + onClickItem={ ( { id } ) => { + history.navigate( `/wp_template/${ id }?canvas=edit` ); + } } selection={ selection } defaultLayouts={ defaultLayouts } /> diff --git a/packages/edit-site/src/components/page-templates/style.scss b/packages/edit-site/src/components/page-templates/style.scss index 6a753921f6f40a..bb9069e2c5038a 100644 --- a/packages/edit-site/src/components/page-templates/style.scss +++ b/packages/edit-site/src/components/page-templates/style.scss @@ -5,24 +5,6 @@ width: 100%; border-radius: $radius-medium; - .page-templates-preview-field__button { - box-shadow: none; - border: none; - padding: 0; - background-color: unset; - box-sizing: border-box; - cursor: pointer; - overflow: hidden; - height: 100%; - border-radius: $radius-medium; - - &:focus-visible { - box-shadow: 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; - } - } - .dataviews-view-list & { .block-editor-block-preview__container { height: 120px; @@ -60,6 +42,7 @@ .dataviews-view-table & { margin-bottom: $grid-unit-10; + display: block; } } @@ -84,9 +67,11 @@ height: $grid-unit-20; object-fit: cover; opacity: 0; - transition: opacity 0.1s linear; - @include reduce-motion("transition"); border-radius: 100%; + + @media not (prefers-reduced-motion) { + transition: opacity 0.1s linear; + } } &.is-loaded { diff --git a/packages/edit-site/src/components/page/style.scss b/packages/edit-site/src/components/page/style.scss index 03e062a576b6e6..23e79420a7fbb2 100644 --- a/packages/edit-site/src/components/page/style.scss +++ b/packages/edit-site/src/components/page/style.scss @@ -4,8 +4,10 @@ height: calc(100% - #{$header-height}); /* stylelint-disable-next-line property-no-unknown -- '@container' not globally permitted */ container: edit-site-page / inline-size; - transition: width ease-out 0.2s; - @include reduce-motion("transition"); + + @media not (prefers-reduced-motion) { + transition: width ease-out 0.2s; + } @include break-medium() { height: 100%; @@ -19,8 +21,10 @@ position: sticky; top: 0; z-index: z-index(".edit-site-page-header"); - transition: padding ease-out 0.1s; - @include reduce-motion("transition"); + + @media not (prefers-reduced-motion) { + transition: padding ease-out 0.1s; + } .components-heading { color: $gray-900; @@ -41,7 +45,6 @@ } } -/* stylelint-disable-next-line scss/at-rule-no-unknown -- '@container' not globally permitted */ @container (max-width: 430px) { .edit-site-page-header { padding: $grid-unit-20 $grid-unit-30; diff --git a/packages/edit-site/src/components/post-edit/index.js b/packages/edit-site/src/components/post-edit/index.js index fbff29ed67afa1..97484c89ed670b 100644 --- a/packages/edit-site/src/components/post-edit/index.js +++ b/packages/edit-site/src/components/post-edit/index.js @@ -18,10 +18,11 @@ import { privateApis as editorPrivateApis } from '@wordpress/editor'; * Internal dependencies */ import Page from '../page'; -import usePostFields from '../post-fields'; import { unlock } from '../../lock-unlock'; +import usePatternSettings from '../page-patterns/use-pattern-settings'; +import { privateApis as blockEditorPrivateApis } from '@wordpress/block-editor'; -const { PostCardPanel } = unlock( editorPrivateApis ); +const { usePostFields, PostCardPanel } = unlock( editorPrivateApis ); const fieldsWithBulkEditSupport = [ 'title', @@ -33,24 +34,29 @@ const fieldsWithBulkEditSupport = [ function PostEditForm( { postType, postId } ) { const ids = useMemo( () => postId.split( ',' ), [ postId ] ); - const { record } = useSelect( + const { record, hasFinishedResolution } = useSelect( ( select ) => { + const args = [ 'postType', postType, ids[ 0 ] ]; + + const { + getEditedEntityRecord, + hasFinishedResolution: hasFinished, + } = select( coreDataStore ); + return { record: - ids.length === 1 - ? select( coreDataStore ).getEditedEntityRecord( - 'postType', - postType, - ids[ 0 ] - ) - : null, + ids.length === 1 ? getEditedEntityRecord( ...args ) : null, + hasFinishedResolution: hasFinished( + 'getEditedEntityRecord', + args + ), }; }, [ postType, ids ] ); const [ multiEdits, setMultiEdits ] = useState( {} ); const { editEntityRecord } = useDispatch( coreDataStore ); - const { fields: _fields } = usePostFields(); + const { fields: _fields } = usePostFields( { postType } ); const fields = useMemo( () => _fields?.map( ( field ) => { @@ -71,28 +77,31 @@ function PostEditForm( { postType, postId } ) { () => ( { type: 'panel', fields: [ - 'featured_media', - 'title', - 'status_and_visibility', + { + id: 'featured_media', + layout: 'regular', + }, + { + id: 'status', + label: __( 'Status & Visibility' ), + children: [ 'status', 'password' ], + }, 'author', 'date', 'slug', 'parent', 'comment_status', + { + label: __( 'Template' ), + labelPosition: 'side', + id: 'template', + layout: 'regular', + }, ].filter( ( field ) => ids.length === 1 || fieldsWithBulkEditSupport.includes( field ) ), - combinedFields: [ - { - id: 'status_and_visibility', - label: __( 'Status & Visibility' ), - children: [ 'status', 'password' ], - direction: 'vertical', - render: ( { item } ) => item.status, - }, - ], } ), [ ids ] ); @@ -126,17 +135,43 @@ function PostEditForm( { postType, postId } ) { setMultiEdits( {} ); }, [ ids ] ); + const { ExperimentalBlockEditorProvider } = unlock( + blockEditorPrivateApis + ); + const settings = usePatternSettings(); + + /** + * The template field depends on the block editor settings. + * This is a workaround to ensure that the block editor settings are available. + * For more information, see: https://github.com/WordPress/gutenberg/issues/67521 + */ + const fieldsWithDependency = useMemo( () => { + return fields.map( ( field ) => { + if ( field.id === 'template' ) { + return { + ...field, + Edit: ( data ) => ( + <ExperimentalBlockEditorProvider settings={ settings }> + <field.Edit { ...data } /> + </ExperimentalBlockEditorProvider> + ), + }; + } + return field; + } ); + }, [ fields, settings ] ); + return ( <VStack spacing={ 4 }> - { ids.length === 1 && ( - <PostCardPanel postType={ postType } postId={ ids[ 0 ] } /> + <PostCardPanel postType={ postType } postId={ ids } /> + { hasFinishedResolution && ( + <DataForm + data={ ids.length === 1 ? record : multiEdits } + fields={ fieldsWithDependency } + form={ form } + onChange={ onChange } + /> ) } - <DataForm - data={ ids.length === 1 ? record : multiEdits } - fields={ fields } - form={ form } - onChange={ onChange } - /> </VStack> ); } diff --git a/packages/edit-site/src/components/post-fields/index.js b/packages/edit-site/src/components/post-fields/index.js deleted file mode 100644 index 6ba9453709f0d3..00000000000000 --- a/packages/edit-site/src/components/post-fields/index.js +++ /dev/null @@ -1,50 +0,0 @@ -/** - * WordPress dependencies - */ -import { - featuredImageField, - slugField, - parentField, - passwordField, - statusField, - commentStatusField, - titleField, - dateField, - authorField, -} from '@wordpress/fields'; -import { useMemo } from '@wordpress/element'; -import { useEntityRecords } from '@wordpress/core-data'; - -function usePostFields() { - const { records: authors, isResolving: isLoadingAuthors } = - useEntityRecords( 'root', 'user', { per_page: -1 } ); - - const fields = useMemo( - () => [ - featuredImageField, - titleField, - { - ...authorField, - elements: - authors?.map( ( { id, name } ) => ( { - value: id, - label: name, - } ) ) || [], - }, - statusField, - dateField, - slugField, - parentField, - commentStatusField, - passwordField, - ], - [ authors ] - ); - - return { - isLoading: isLoadingAuthors, - fields, - }; -} - -export default usePostFields; diff --git a/packages/edit-site/src/components/post-fields/style.scss b/packages/edit-site/src/components/post-fields/style.scss deleted file mode 100644 index adeaf9a2678253..00000000000000 --- a/packages/edit-site/src/components/post-fields/style.scss +++ /dev/null @@ -1,3 +0,0 @@ -.components-popover.components-dropdown__content.dataforms-layouts-panel__field-dropdown { - z-index: z-index(".components-popover.components-dropdown__content.dataforms-layouts-panel__field-dropdown"); -} diff --git a/packages/edit-site/src/components/post-list/index.js b/packages/edit-site/src/components/post-list/index.js index 4639cb3c950b76..6ab3a47efb4653 100644 --- a/packages/edit-site/src/components/post-list/index.js +++ b/packages/edit-site/src/components/post-list/index.js @@ -13,6 +13,8 @@ import { DataViews, filterSortAndPaginate } from '@wordpress/dataviews'; import { privateApis as editorPrivateApis } from '@wordpress/editor'; import { __ } from '@wordpress/i18n'; import { drawerRight } from '@wordpress/icons'; +import { useEvent, usePrevious } from '@wordpress/compose'; +import { addQueryArgs } from '@wordpress/url'; /** * Internal dependencies @@ -31,10 +33,8 @@ import { import AddNewPostModal from '../add-new-post'; import { unlock } from '../../lock-unlock'; import { useEditPostAction } from '../dataviews-actions'; -import { usePrevious } from '@wordpress/compose'; -import usePostFields from '../post-fields'; -const { usePostActions } = unlock( editorPrivateApis ); +const { usePostActions, usePostFields } = unlock( editorPrivateApis ); const { useLocation, useHistory } = unlock( routerPrivateApis ); const { useEntityRecordsWithPermissions } = unlock( coreDataPrivateApis ); const EMPTY_ARRAY = []; @@ -55,7 +55,7 @@ const getCustomView = ( editedEntityRecord ) => { return { ...content, - layout: defaultLayouts[ content.type ]?.layout, + ...defaultLayouts[ content.type ], }; }; @@ -71,7 +71,8 @@ const getCustomView = ( editedEntityRecord ) => { */ function useView( postType ) { const { - params: { activeView = 'all', isCustom = 'false', layout }, + path, + query: { activeView = 'all', isCustom = 'false', layout }, } = useLocation(); const history = useHistory(); @@ -108,50 +109,56 @@ function useView( postType ) { return { ...initialView, type, + ...defaultLayouts[ type ], }; } ); - const setViewWithUrlUpdate = useCallback( - ( newView ) => { - const { params } = history.getLocationWithParams(); + const setViewWithUrlUpdate = useEvent( ( newView ) => { + setView( newView ); - if ( newView.type === LAYOUT_LIST && ! params?.layout ) { - // Skip updating the layout URL param if - // it is not present and the newView.type is LAYOUT_LIST. - } else if ( newView.type !== params?.layout ) { - history.push( { - ...params, - layout: newView.type, - } ); - } - - setView( newView ); + if ( isCustom === 'true' && editedEntityRecord?.id ) { + editEntityRecord( + 'postType', + 'wp_dataviews', + editedEntityRecord?.id, + { + content: JSON.stringify( newView ), + } + ); + } - if ( isCustom === 'true' && editedEntityRecord?.id ) { - editEntityRecord( - 'postType', - 'wp_dataviews', - editedEntityRecord?.id, - { - content: JSON.stringify( newView ), - } - ); - } - }, - [ history, isCustom, editEntityRecord, editedEntityRecord?.id ] - ); + const currentUrlLayout = layout ?? LAYOUT_LIST; + if ( newView.type !== currentUrlLayout ) { + history.navigate( + addQueryArgs( path, { + layout: newView.type, + } ) + ); + } + } ); // When layout URL param changes, update the view type // without affecting any other config. + const onUrlLayoutChange = useEvent( () => { + setView( ( prevView ) => { + const newType = layout ?? LAYOUT_LIST; + if ( newType === prevView.type ) { + return prevView; + } + + return { + ...prevView, + type: newType, + ...defaultLayouts[ newType ], + }; + } ); + } ); useEffect( () => { - setView( ( prevView ) => ( { - ...prevView, - type: layout ?? LAYOUT_LIST, - } ) ); - }, [ layout ] ); + onUrlLayoutChange(); + }, [ onUrlLayoutChange, layout ] ); // When activeView or isCustom URL parameters change, reset the view. - useEffect( () => { + const onUrlActiveViewChange = useEvent( () => { let newView; if ( isCustom === 'true' ) { newView = getCustomView( editedEntityRecord ); @@ -164,11 +171,21 @@ function useView( postType ) { setView( { ...newView, type, + ...defaultLayouts[ type ], } ); } - }, [ activeView, isCustom, layout, defaultViews, editedEntityRecord ] ); + } ); + useEffect( () => { + onUrlActiveViewChange(); + }, [ + onUrlActiveViewChange, + activeView, + isCustom, + defaultViews, + editedEntityRecord, + ] ); - return [ view, setViewWithUrlUpdate, setViewWithUrlUpdate ]; + return [ view, setViewWithUrlUpdate ]; } const DEFAULT_STATUSES = 'draft,future,pending,private,publish'; // All but 'trash'. @@ -177,6 +194,10 @@ function getItemId( item ) { return item.id.toString(); } +function getItemLevel( item ) { + return item.level; +} + export default function PostList( { postType } ) { const [ view, setView ] = useView( postType ); const defaultViews = useDefaultViews( { postType } ); @@ -187,28 +208,29 @@ export default function PostList( { postType } ) { quickEdit = false, isCustom, activeView = 'all', - } = location.params; + } = location.query; const [ selection, setSelection ] = useState( postId?.split( ',' ) ?? [] ); const onChangeSelection = useCallback( ( items ) => { setSelection( items ); - const { params } = history.getLocationWithParams(); - if ( ( params.isCustom ?? 'false' ) === 'false' ) { - history.push( { - ...params, - postId: items.join( ',' ), - } ); + if ( ( location.query.isCustom ?? 'false' ) === 'false' ) { + history.navigate( + addQueryArgs( location.path, { + postId: items.join( ',' ), + } ) + ); } }, - [ history ] + [ location.path, location.query.isCustom, history ] ); - const getActiveViewFilters = ( views, match ) => { const found = views.find( ( { slug } ) => slug === match ); return found?.filters ?? []; }; - const { isLoading: isLoadingFields, fields: _fields } = usePostFields(); + const { isLoading: isLoadingFields, fields: _fields } = usePostFields( { + postType, + } ); const fields = useMemo( () => { const activeViewFilters = getActiveViewFilters( defaultViews, @@ -281,6 +303,7 @@ export default function PostList( { postType } ) { _embed: 'author', order: view.sort?.direction, orderby: view.sort?.field, + orderby_hierarchy: !! view.showLevels, search: view.search, ...filters, }; @@ -312,12 +335,13 @@ export default function PostList( { postType } ) { useEffect( () => { if ( postIdWasDeleted ) { - history.push( { - ...history.getLocationWithParams().params, - postId: undefined, - } ); + history.navigate( + addQueryArgs( location.path, { + postId: undefined, + } ) + ); } - }, [ postIdWasDeleted, history ] ); + }, [ history, postIdWasDeleted, location.path ] ); const paginationInfo = useMemo( () => ( { @@ -356,11 +380,7 @@ export default function PostList( { postType } ) { const openModal = () => setShowAddPostModal( true ); const closeModal = () => setShowAddPostModal( false ); const handleNewPage = ( { type, id } ) => { - history.push( { - postId: id, - postType: type, - canvas: 'edit', - } ); + history.navigate( `/${ type }/${ id }?canvas=edit` ); closeModal(); }; @@ -402,13 +422,10 @@ export default function PostList( { postType } ) { onChangeSelection={ onChangeSelection } isItemClickable={ ( item ) => item.status !== 'trash' } onClickItem={ ( { id } ) => { - history.push( { - postId: id, - postType, - canvas: 'edit', - } ); + history.navigate( `/${ postType }/${ id }?canvas=edit` ); } } getItemId={ getItemId } + getItemLevel={ getItemLevel } defaultLayouts={ defaultLayouts } header={ window.__experimentalQuickEditDataViews && @@ -420,10 +437,11 @@ export default function PostList( { postType } ) { icon={ drawerRight } label={ __( 'Details' ) } onClick={ () => { - history.push( { - ...location.params, - quickEdit: quickEdit ? undefined : true, - } ); + history.navigate( + addQueryArgs( location.path, { + quickEdit: quickEdit ? undefined : true, + } ) + ); } } /> ) diff --git a/packages/edit-site/src/components/post-list/style.scss b/packages/edit-site/src/components/post-list/style.scss index 14bb11b41d4450..8ff7e31c79dd6f 100644 --- a/packages/edit-site/src/components/post-list/style.scss +++ b/packages/edit-site/src/components/post-list/style.scss @@ -65,42 +65,6 @@ } } -.edit-site-post-list__title span { - text-overflow: ellipsis; - overflow: hidden; -} - -.dataviews-view-grid__primary-field.dataviews-view-grid__primary-field--clickable -.edit-site-post-list__title -span, -.dataviews-view-table__primary-field > .dataviews-view-table__cell-content--clickable -.edit-site-post-list__title -span { - text-decoration: none; - text-overflow: ellipsis; - white-space: nowrap; - overflow: hidden; - display: block; - flex-grow: 0; - color: $gray-900; - - &:hover { - color: var(--wp-admin-theme-color); - } - @include link-reset(); -} - -.edit-site-post-list__title-badge { - background: $gray-100; - color: $gray-800; - padding: 0 $grid-unit-05; - border-radius: $radius-small; - font-size: 12px; - font-weight: 400; - flex-shrink: 0; - line-height: $grid-unit-05 * 5; -} - .edit-site-post-list__status-icon { height: $grid-unit-30; width: $grid-unit-30; diff --git a/packages/edit-site/src/components/posts-app-routes/index.js b/packages/edit-site/src/components/posts-app-routes/index.js new file mode 100644 index 00000000000000..3919ea3930d073 --- /dev/null +++ b/packages/edit-site/src/components/posts-app-routes/index.js @@ -0,0 +1,25 @@ +/** + * WordPress dependencies + */ +import { useRegistry, useDispatch } from '@wordpress/data'; +import { useEffect } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import { unlock } from '../../lock-unlock'; +import { store as siteEditorStore } from '../../store'; +import { postsRoute } from './posts'; +import { postItemRoute } from './post-item'; + +const routes = [ postItemRoute, postsRoute ]; + +export function useRegisterPostsAppRoutes() { + const registry = useRegistry(); + const { registerRoute } = unlock( useDispatch( siteEditorStore ) ); + useEffect( () => { + registry.batch( () => { + routes.forEach( registerRoute ); + } ); + }, [ registry, registerRoute ] ); +} diff --git a/packages/edit-site/src/components/posts-app-routes/post-item.js b/packages/edit-site/src/components/posts-app-routes/post-item.js new file mode 100644 index 00000000000000..54131814f1ae22 --- /dev/null +++ b/packages/edit-site/src/components/posts-app-routes/post-item.js @@ -0,0 +1,27 @@ +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import Editor from '../editor'; +import DataViewsSidebarContent from '../sidebar-dataviews'; +import SidebarNavigationScreen from '../sidebar-navigation-screen'; + +export const postItemRoute = { + name: 'post-item', + path: '/post/:postId', + areas: { + sidebar: ( + <SidebarNavigationScreen + title={ __( 'Posts' ) } + isRoot + content={ <DataViewsSidebarContent postType="post" /> } + /> + ), + mobile: <Editor isPostsList />, + preview: <Editor isPostsList />, + }, +}; diff --git a/packages/edit-site/src/components/posts-app-routes/posts.js b/packages/edit-site/src/components/posts-app-routes/posts.js new file mode 100644 index 00000000000000..80af8a75fbc800 --- /dev/null +++ b/packages/edit-site/src/components/posts-app-routes/posts.js @@ -0,0 +1,66 @@ +/** + * WordPress dependencies + */ +import { privateApis as routerPrivateApis } from '@wordpress/router'; +import { __ } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import Editor from '../editor'; +import SidebarNavigationScreen from '../sidebar-navigation-screen'; +import DataViewsSidebarContent from '../sidebar-dataviews'; +import PostList from '../post-list'; +import { unlock } from '../../lock-unlock'; +import { PostEdit } from '../post-edit'; + +const { useLocation } = unlock( routerPrivateApis ); + +function MobilePostsView() { + const { query = {} } = useLocation(); + const { canvas = 'view' } = query; + + return canvas === 'edit' ? <Editor /> : <PostList postType="post" />; +} + +export const postsRoute = { + name: 'posts', + path: '/', + areas: { + sidebar: ( + <SidebarNavigationScreen + title={ __( 'Posts' ) } + isRoot + content={ <DataViewsSidebarContent postType="post" /> } + /> + ), + content: <PostList postType="post" />, + preview( { query } ) { + const isListView = + ( query.layout === 'list' || ! query.layout ) && + query.isCustom !== 'true'; + return isListView ? <Editor isPostsList /> : undefined; + }, + mobile: <MobilePostsView />, + edit( { query } ) { + const hasQuickEdit = + ( query.layout ?? 'list' ) === 'list' && !! query.quickEdit; + return hasQuickEdit ? ( + <PostEdit postType="post" postId={ query.postId } /> + ) : undefined; + }, + }, + widths: { + content( { query } ) { + const isListView = + ( query.layout === 'list' || ! query.layout ) && + query.isCustom !== 'true'; + return isListView ? 380 : undefined; + }, + edit( { query } ) { + const hasQuickEdit = + ( query.layout ?? 'list' ) === 'list' && !! query.quickEdit; + return hasQuickEdit ? 380 : undefined; + }, + }, +}; diff --git a/packages/edit-site/src/components/posts-app/index.js b/packages/edit-site/src/components/posts-app/index.js index 80d2c1c7eba86f..ab8cfab99f7628 100644 --- a/packages/edit-site/src/components/posts-app/index.js +++ b/packages/edit-site/src/components/posts-app/index.js @@ -1,34 +1,27 @@ /** * WordPress dependencies */ -import { - UnsavedChangesWarning, - privateApis as editorPrivateApis, -} from '@wordpress/editor'; import { privateApis as routerPrivateApis } from '@wordpress/router'; +import { useSelect } from '@wordpress/data'; /** * Internal dependencies */ import Layout from '../layout'; -import useActiveRoute from './router'; +import { useRegisterPostsAppRoutes } from '../posts-app-routes'; import { unlock } from '../../lock-unlock'; +import { store as editSiteStore } from '../../store'; const { RouterProvider } = unlock( routerPrivateApis ); -const { GlobalStylesProvider } = unlock( editorPrivateApis ); - -function PostsLayout() { - const route = useActiveRoute(); - return <Layout route={ route } />; -} export default function PostsApp() { + useRegisterPostsAppRoutes(); + const routes = useSelect( ( select ) => { + return unlock( select( editSiteStore ) ).getRoutes(); + }, [] ); return ( - <GlobalStylesProvider> - <UnsavedChangesWarning /> - <RouterProvider> - <PostsLayout /> - </RouterProvider> - </GlobalStylesProvider> + <RouterProvider routes={ routes } pathArg="p"> + <Layout /> + </RouterProvider> ); } diff --git a/packages/edit-site/src/components/posts-app/router.js b/packages/edit-site/src/components/posts-app/router.js deleted file mode 100644 index de89567b262094..00000000000000 --- a/packages/edit-site/src/components/posts-app/router.js +++ /dev/null @@ -1,69 +0,0 @@ -/** - * WordPress dependencies - */ -import { privateApis as routerPrivateApis } from '@wordpress/router'; -import { useSelect } from '@wordpress/data'; -import { store as coreStore } from '@wordpress/core-data'; - -/** - * Internal dependencies - */ -import { unlock } from '../../lock-unlock'; -import Editor from '../editor'; -import SidebarNavigationScreen from '../sidebar-navigation-screen'; -import SidebarNavigationScreenMain from '../sidebar-navigation-screen-main'; -import DataViewsSidebarContent from '../sidebar-dataviews'; -import PostList from '../post-list'; - -const { useLocation } = unlock( routerPrivateApis ); - -export default function useActiveRoute() { - const { params = {} } = useLocation(); - const { postType, layout, canvas } = params; - const labels = useSelect( - ( select ) => { - return select( coreStore ).getPostType( postType )?.labels; - }, - [ postType ] - ); - - // Posts list. - if ( [ 'post' ].includes( postType ) ) { - const isListLayout = layout === 'list' || ! layout; - return { - name: 'posts-list', - areas: { - sidebar: ( - <SidebarNavigationScreen - title={ labels?.name } - isRoot - content={ <DataViewsSidebarContent /> } - /> - ), - content: <PostList postType={ postType } />, - preview: ( isListLayout || canvas === 'edit' ) && ( - <Editor isPostsList /> - ), - mobile: - canvas === 'edit' ? ( - <Editor isPostsList /> - ) : ( - <PostList postType={ postType } /> - ), - }, - widths: { - content: isListLayout ? 380 : undefined, - }, - }; - } - - // Fallback shows the home page preview - return { - name: 'default', - areas: { - sidebar: <SidebarNavigationScreenMain />, - preview: <Editor isPostsList />, - mobile: canvas === 'edit' && <Editor isPostsList />, - }, - }; -} diff --git a/packages/edit-site/src/components/resizable-frame/index.js b/packages/edit-site/src/components/resizable-frame/index.js index 95ccfe4fdd966f..ecb7204f9f05ba 100644 --- a/packages/edit-site/src/components/resizable-frame/index.js +++ b/packages/edit-site/src/components/resizable-frame/index.js @@ -15,11 +15,14 @@ import { import { useInstanceId, useReducedMotion } from '@wordpress/compose'; import { __, isRTL } from '@wordpress/i18n'; import { privateApis as routerPrivateApis } from '@wordpress/router'; +import { useSelect } from '@wordpress/data'; +import { store as coreStore } from '@wordpress/core-data'; /** * Internal dependencies */ import { unlock } from '../../lock-unlock'; +import { addQueryArgs } from '@wordpress/url'; const { useLocation, useHistory } = unlock( routerPrivateApis ); @@ -83,13 +86,13 @@ function ResizableFrame( { setIsOversized, isReady, children, - /** The default (unresized) width/height of the frame, based on the space availalbe in the viewport. */ + /** The default (unresized) width/height of the frame, based on the space available in the viewport. */ defaultSize, innerContentStyle, } ) { const history = useHistory(); - const { params } = useLocation(); - const { canvas = 'view' } = params; + const { path, query } = useLocation(); + const { canvas = 'view' } = query; const disableMotion = useReducedMotion(); const [ frameSize, setFrameSize ] = useState( INITIAL_FRAME_SIZE ); // The width of the resizable frame when a new resize gesture starts. @@ -105,6 +108,10 @@ function ResizableFrame( { 'edit-site-resizable-frame-handle-help' ); const defaultAspectRatio = defaultSize.width / defaultSize.height; + const isBlockTheme = useSelect( ( select ) => { + const { getCurrentTheme } = select( coreStore ); + return getCurrentTheme()?.is_block_theme; + }, [] ); const handleResizeStart = ( _event, _direction, ref ) => { // Remember the starting width so we don't have to get `ref.offsetWidth` on @@ -152,18 +159,19 @@ function ResizableFrame( { const remainingWidth = ref.ownerDocument.documentElement.offsetWidth - ref.offsetWidth; - if ( remainingWidth > SNAP_TO_EDIT_CANVAS_MODE_THRESHOLD ) { + if ( + remainingWidth > SNAP_TO_EDIT_CANVAS_MODE_THRESHOLD || + ! isBlockTheme + ) { // Reset the initial aspect ratio if the frame is resized slightly // above the sidebar but not far enough to trigger full screen. setFrameSize( INITIAL_FRAME_SIZE ); } else { // Trigger full screen if the frame is resized far enough to the left. - history.push( - { - ...params, + history.navigate( + addQueryArgs( path, { canvas: 'edit', - }, - undefined, + } ), { transition: 'canvas-mode-edit-transition', } diff --git a/packages/edit-site/src/components/routes/link.js b/packages/edit-site/src/components/routes/link.js deleted file mode 100644 index a34b37943a0799..00000000000000 --- a/packages/edit-site/src/components/routes/link.js +++ /dev/null @@ -1,68 +0,0 @@ -/** - * WordPress dependencies - */ -import { addQueryArgs, getQueryArgs, removeQueryArgs } from '@wordpress/url'; -import { privateApis as routerPrivateApis } from '@wordpress/router'; - -/** - * Internal dependencies - */ -import { unlock } from '../../lock-unlock'; -import { - isPreviewingTheme, - currentlyPreviewingTheme, -} from '../../utils/is-previewing-theme'; - -const { useHistory } = unlock( routerPrivateApis ); - -export function useLink( params, state, shouldReplace = false ) { - const history = useHistory(); - function onClick( event ) { - event?.preventDefault(); - - if ( shouldReplace ) { - history.replace( params, state ); - } else { - history.push( params, state ); - } - } - - const currentArgs = getQueryArgs( window.location.href ); - const currentUrlWithoutArgs = removeQueryArgs( - window.location.href, - ...Object.keys( currentArgs ) - ); - - let extraParams = {}; - if ( isPreviewingTheme() ) { - extraParams = { - wp_theme_preview: currentlyPreviewingTheme(), - }; - } - - const newUrl = addQueryArgs( currentUrlWithoutArgs, { - ...params, - ...extraParams, - } ); - - return { - href: newUrl, - onClick, - }; -} - -export default function Link( { - params = {}, - state, - replace: shouldReplace = false, - children, - ...props -} ) { - const { href, onClick } = useLink( params, state, shouldReplace ); - - return ( - <a href={ href } onClick={ onClick } { ...props }> - { children } - </a> - ); -} diff --git a/packages/edit-site/src/components/save-panel/index.js b/packages/edit-site/src/components/save-panel/index.js index b77e5a9a1a10ba..95ec9b9ffc8c46 100644 --- a/packages/edit-site/src/components/save-panel/index.js +++ b/packages/edit-site/src/components/save-panel/index.js @@ -31,7 +31,7 @@ const { EntitiesSavedStatesExtensible, NavigableRegion } = unlock( privateApis ); const { useLocation } = unlock( routerPrivateApis ); -const EntitiesSavedStatesForPreview = ( { onClose } ) => { +const EntitiesSavedStatesForPreview = ( { onClose, renderDialog } ) => { const isDirtyProps = useEntitiesSavedStatesIsDirty(); let activateSaveLabel; if ( isDirtyProps.isDirty ) { @@ -75,14 +75,20 @@ const EntitiesSavedStatesForPreview = ( { onClose } ) => { onSave, saveEnabled: true, saveLabel: activateSaveLabel, + renderDialog, } } /> ); }; -const _EntitiesSavedStates = ( { onClose, renderDialog = undefined } ) => { +const _EntitiesSavedStates = ( { onClose, renderDialog } ) => { if ( isPreviewingTheme() ) { - return <EntitiesSavedStatesForPreview onClose={ onClose } />; + return ( + <EntitiesSavedStatesForPreview + onClose={ onClose } + renderDialog={ renderDialog } + /> + ); } return ( <EntitiesSavedStates close={ onClose } renderDialog={ renderDialog } /> @@ -90,8 +96,8 @@ const _EntitiesSavedStates = ( { onClose, renderDialog = undefined } ) => { }; export default function SavePanel() { - const { params } = useLocation(); - const { canvas = 'view' } = params; + const { query } = useLocation(); + const { canvas = 'view' } = query; const { isSaveViewOpen, isDirty, isSaving } = useSelect( ( select ) => { const { __experimentalGetDirtyEntityRecords, diff --git a/packages/edit-site/src/components/sidebar-dataviews/add-new-view.js b/packages/edit-site/src/components/sidebar-dataviews/add-new-view.js index 62956ccd18960d..815de181a9dde0 100644 --- a/packages/edit-site/src/components/sidebar-dataviews/add-new-view.js +++ b/packages/edit-site/src/components/sidebar-dataviews/add-new-view.js @@ -14,6 +14,7 @@ import { store as coreStore } from '@wordpress/core-data'; import { useState } from '@wordpress/element'; import { plus } from '@wordpress/icons'; import { privateApis as routerPrivateApis } from '@wordpress/router'; +import { addQueryArgs } from '@wordpress/url'; /** * Internal dependencies @@ -22,10 +23,11 @@ import SidebarNavigationItem from '../sidebar-navigation-item'; import { useDefaultViews } from './default-views'; import { unlock } from '../../lock-unlock'; -const { useHistory } = unlock( routerPrivateApis ); +const { useLocation, useHistory } = unlock( routerPrivateApis ); function AddNewItemModalContent( { type, setIsAdding } ) { const history = useHistory(); + const { path } = useLocation(); const { saveEntityRecord } = useDispatch( coreStore ); const [ title, setTitle ] = useState( '' ); const [ isSaving, setIsSaving ] = useState( false ); @@ -64,14 +66,12 @@ function AddNewItemModalContent( { type, setIsAdding } ) { content: JSON.stringify( defaultViews[ 0 ].view ), } ); - const { - params: { postType }, - } = history.getLocationWithParams(); - history.push( { - postType, - activeView: savedRecord.id, - isCustom: 'true', - } ); + history.navigate( + addQueryArgs( path, { + activeView: savedRecord.id, + isCustom: 'true', + } ) + ); setIsSaving( false ); setIsAdding( false ); } } diff --git a/packages/edit-site/src/components/sidebar-dataviews/custom-dataviews-list.js b/packages/edit-site/src/components/sidebar-dataviews/custom-dataviews-list.js index 847029e8d6dcfe..463ce0003fba26 100644 --- a/packages/edit-site/src/components/sidebar-dataviews/custom-dataviews-list.js +++ b/packages/edit-site/src/components/sidebar-dataviews/custom-dataviews-list.js @@ -27,7 +27,7 @@ import DataViewItem from './dataview-item'; import AddNewItem from './add-new-view'; import { unlock } from '../../lock-unlock'; -const { useHistory } = unlock( routerPrivateApis ); +const { useHistory, useLocation } = unlock( routerPrivateApis ); const EMPTY_ARRAY = []; @@ -85,6 +85,7 @@ function RenameItemModalContent( { dataviewId, currentTitle, setIsRenaming } ) { function CustomDataViewItem( { dataviewId, isActive } ) { const history = useHistory(); + const location = useLocation(); const { dataview } = useSelect( ( select ) => { const { getEditedEntityRecord } = select( coreStore ); @@ -145,10 +146,10 @@ function CustomDataViewItem( { dataviewId, isActive } ) { } ); if ( isActive ) { - const { - params: { postType }, - } = history.getLocationWithParams(); - history.replace( { postType } ); + history.replace( { + postType: + location.query.postType, + } ); } onClose(); } } @@ -212,7 +213,7 @@ export default function CustomDataViewsList( { type, activeView, isCustom } ) { <div className="edit-site-sidebar-navigation-screen-dataviews__group-header"> <Heading level={ 2 }>{ __( 'Custom Views' ) }</Heading> </div> - <ItemGroup> + <ItemGroup className="edit-site-sidebar-navigation-screen-dataviews__custom-items"> { customDataViews.map( ( customViewRecord ) => { return ( <CustomDataViewItem diff --git a/packages/edit-site/src/components/sidebar-dataviews/dataview-item.js b/packages/edit-site/src/components/sidebar-dataviews/dataview-item.js index 1e12d6706d81b5..b98f8b80938d68 100644 --- a/packages/edit-site/src/components/sidebar-dataviews/dataview-item.js +++ b/packages/edit-site/src/components/sidebar-dataviews/dataview-item.js @@ -9,11 +9,11 @@ import clsx from 'clsx'; import { privateApis as routerPrivateApis } from '@wordpress/router'; import { __experimentalHStack as HStack } from '@wordpress/components'; import { VIEW_LAYOUTS } from '@wordpress/dataviews'; +import { addQueryArgs } from '@wordpress/url'; /** * Internal dependencies */ -import { useLink } from '../routes/link'; import SidebarNavigationItem from '../sidebar-navigation-item'; import { unlock } from '../../lock-unlock'; const { useLocation } = unlock( routerPrivateApis ); @@ -28,9 +28,7 @@ export default function DataViewItem( { isCustom, suffix, } ) { - const { - params: { postType }, - } = useLocation(); + const { path } = useLocation(); const iconToUse = icon || VIEW_LAYOUTS.find( ( v ) => v.type === type ).icon; @@ -39,12 +37,11 @@ export default function DataViewItem( { if ( activeView === 'all' ) { activeView = undefined; } - const linkInfo = useLink( { - postType, + const query = { layout: type, activeView, isCustom: isCustom ? 'true' : undefined, - } ); + }; return ( <HStack justify="flex-start" @@ -54,7 +51,7 @@ export default function DataViewItem( { > <SidebarNavigationItem icon={ iconToUse } - { ...linkInfo } + to={ addQueryArgs( path, query ) } aria-current={ isActive ? 'true' : undefined } > { title } diff --git a/packages/edit-site/src/components/sidebar-dataviews/default-views.js b/packages/edit-site/src/components/sidebar-dataviews/default-views.js index 72f4c94fe6bcdd..c6edf7d2dd1203 100644 --- a/packages/edit-site/src/components/sidebar-dataviews/default-views.js +++ b/packages/edit-site/src/components/sidebar-dataviews/default-views.js @@ -27,28 +27,9 @@ import { } from '../../utils/constants'; export const defaultLayouts = { - [ LAYOUT_TABLE ]: { - layout: { - primaryField: 'title', - styles: { - title: { - maxWidth: 300, - }, - }, - }, - }, - [ LAYOUT_GRID ]: { - layout: { - mediaField: 'featured_media', - primaryField: 'title', - }, - }, - [ LAYOUT_LIST ]: { - layout: { - primaryField: 'title', - mediaField: 'featured_media', - }, - }, + [ LAYOUT_TABLE ]: {}, + [ LAYOUT_GRID ]: {}, + [ LAYOUT_LIST ]: {}, }; const DEFAULT_POST_BASE = { @@ -58,11 +39,14 @@ const DEFAULT_POST_BASE = { page: 1, perPage: 20, sort: { - field: 'date', - direction: 'desc', + field: 'title', + direction: 'asc', }, - fields: [ 'title', 'author', 'status' ], - layout: defaultLayouts[ LAYOUT_LIST ].layout, + showLevels: true, + titleField: 'title', + mediaField: 'featured_media', + fields: [ 'author', 'status' ], + ...defaultLayouts[ LAYOUT_LIST ], }; export function useDefaultViews( { postType } ) { diff --git a/packages/edit-site/src/components/sidebar-dataviews/index.js b/packages/edit-site/src/components/sidebar-dataviews/index.js index 86420c4eec1d1f..312bf43d6df657 100644 --- a/packages/edit-site/src/components/sidebar-dataviews/index.js +++ b/packages/edit-site/src/components/sidebar-dataviews/index.js @@ -14,9 +14,9 @@ import CustomDataViewsList from './custom-dataviews-list'; const { useLocation } = unlock( routerPrivateApis ); -export default function DataViewsSidebarContent() { +export default function DataViewsSidebarContent( { postType } ) { const { - params: { postType, activeView = 'all', isCustom = 'false' }, + query: { activeView = 'all', isCustom = 'false' }, } = useLocation(); const defaultViews = useDefaultViews( { postType } ); if ( ! postType ) { @@ -26,7 +26,7 @@ export default function DataViewsSidebarContent() { return ( <> - <ItemGroup> + <ItemGroup className="edit-site-sidebar-dataviews"> { defaultViews.map( ( dataview ) => { return ( <DataViewItem diff --git a/packages/edit-site/src/components/sidebar-dataviews/style.scss b/packages/edit-site/src/components/sidebar-dataviews/style.scss index 14e6bf1d03fca8..36eabd0b4c079b 100644 --- a/packages/edit-site/src/components/sidebar-dataviews/style.scss +++ b/packages/edit-site/src/components/sidebar-dataviews/style.scss @@ -7,9 +7,17 @@ } } +.edit-site-sidebar-dataviews { + margin-left: -$grid-unit-20; + margin-right: -$grid-unit-20; +} + +.edit-site-sidebar-navigation-screen-dataviews__custom-items .edit-site-sidebar-dataviews-dataview-item { + padding-right: $grid-unit-10; +} + .edit-site-sidebar-dataviews-dataview-item { border-radius: $radius-small; - padding-right: $grid-unit-10; .edit-site-sidebar-dataviews-dataview-item__dropdown-menu { min-width: initial; @@ -19,11 +27,11 @@ &:focus, &[aria-current] { color: $gray-200; - background: $gray-800; } &.is-selected { - background: var(--wp-admin-theme-color); + background: $gray-800; + font-weight: $font-weight-medium; color: $white; } } diff --git a/packages/edit-site/src/components/sidebar-global-styles-wrapper/index.js b/packages/edit-site/src/components/sidebar-global-styles-wrapper/index.js index 342fb1b5db52d2..de12bbe466bf3b 100644 --- a/packages/edit-site/src/components/sidebar-global-styles-wrapper/index.js +++ b/packages/edit-site/src/components/sidebar-global-styles-wrapper/index.js @@ -5,10 +5,9 @@ import { __ } from '@wordpress/i18n'; import { useMemo, useState } from '@wordpress/element'; import { privateApis as routerPrivateApis } from '@wordpress/router'; import { useViewportMatch } from '@wordpress/compose'; -import { - Button, - privateApis as componentsPrivateApis, -} from '@wordpress/components'; +import { Button } from '@wordpress/components'; +import { addQueryArgs, removeQueryArgs } from '@wordpress/url'; +import { seen } from '@wordpress/icons'; /** * Internal dependencies @@ -16,75 +15,63 @@ import { import GlobalStylesUI from '../global-styles/ui'; import Page from '../page'; import { unlock } from '../../lock-unlock'; -import StyleBook from '../style-book'; -import { STYLE_BOOK_COLOR_GROUPS } from '../style-book/constants'; const { useLocation, useHistory } = unlock( routerPrivateApis ); -const { Menu } = unlock( componentsPrivateApis ); -const GLOBAL_STYLES_PATH_PREFIX = '/wp_global_styles'; const GlobalStylesPageActions = ( { isStyleBookOpened, setIsStyleBookOpened, + path, } ) => { + const history = useHistory(); return ( - <Menu - trigger={ - <Button __next40pxDefaultSize variant="tertiary" size="compact"> - { __( 'Preview' ) } - </Button> - } - > - <Menu.RadioItem - value - checked={ isStyleBookOpened } - name="styles-preview-actions" - onChange={ () => setIsStyleBookOpened( true ) } - defaultChecked - > - <Menu.ItemLabel>{ __( 'Style book' ) }</Menu.ItemLabel> - <Menu.ItemHelpText> - { __( 'Preview blocks and styles.' ) } - </Menu.ItemHelpText> - </Menu.RadioItem> - <Menu.RadioItem - value={ false } - checked={ ! isStyleBookOpened } - name="styles-preview-actions" - onChange={ () => setIsStyleBookOpened( false ) } - > - <Menu.ItemLabel>{ __( 'Site' ) }</Menu.ItemLabel> - <Menu.ItemHelpText> - { __( 'Preview your site.' ) } - </Menu.ItemHelpText> - </Menu.RadioItem> - </Menu> + <Button + isPressed={ isStyleBookOpened } + icon={ seen } + label={ __( 'Style Book' ) } + onClick={ () => { + setIsStyleBookOpened( ! isStyleBookOpened ); + const updatedPath = ! isStyleBookOpened + ? addQueryArgs( path, { preview: 'stylebook' } ) + : removeQueryArgs( path, 'preview' ); + // Navigate to the updated path. + history.navigate( updatedPath ); + } } + size="compact" + /> ); }; -export default function GlobalStylesUIWrapper() { - const { params } = useLocation(); +/** + * Hook to deal with navigation and location state. + * + * @return {Array} The current section and a function to update it. + */ +export const useSection = () => { + const { path, query } = useLocation(); const history = useHistory(); - const { canvas = 'view' } = params; - const [ isStyleBookOpened, setIsStyleBookOpened ] = useState( false ); - const isMobileViewport = useViewportMatch( 'medium', '<' ); - const pathWithPrefix = params.path; - const [ path, onPathChange ] = useMemo( () => { - const processedPath = pathWithPrefix.substring( - GLOBAL_STYLES_PATH_PREFIX.length - ); + return useMemo( () => { return [ - processedPath ? processedPath : '/', - ( newPath ) => { - history.push( { - path: - ! newPath || newPath === '/' - ? GLOBAL_STYLES_PATH_PREFIX - : `${ GLOBAL_STYLES_PATH_PREFIX }${ newPath }`, - } ); + query.section ?? '/', + ( updatedSection ) => { + history.navigate( + addQueryArgs( path, { + section: updatedSection, + } ) + ); }, ]; - }, [ pathWithPrefix, history ] ); + }, [ path, query.section, history ] ); +}; + +export default function GlobalStylesUIWrapper() { + const { path } = useLocation(); + + const [ isStyleBookOpened, setIsStyleBookOpened ] = useState( + path.includes( 'preview=stylebook' ) + ); + const isMobileViewport = useViewportMatch( 'medium', '<' ); + const [ section, onChangeSection ] = useSection(); return ( <> @@ -94,57 +81,18 @@ export default function GlobalStylesUIWrapper() { <GlobalStylesPageActions isStyleBookOpened={ isStyleBookOpened } setIsStyleBookOpened={ setIsStyleBookOpened } + path={ path } /> ) : null } className="edit-site-styles" title={ __( 'Styles' ) } > - <GlobalStylesUI path={ path } onPathChange={ onPathChange } /> - </Page> - { canvas === 'view' && isStyleBookOpened && ( - <StyleBook - enableResizing={ false } - showCloseButton={ false } - showTabs={ false } - isSelected={ ( blockName ) => - // Match '/blocks/core%2Fbutton' and - // '/blocks/core%2Fbutton/typography', but not - // '/blocks/core%2Fbuttons'. - path === - `/wp_global_styles/blocks/${ encodeURIComponent( - blockName - ) }` || - path.startsWith( - `/wp_global_styles/blocks/${ encodeURIComponent( - blockName - ) }/` - ) - } - path={ path } - onSelect={ ( blockName ) => { - if ( - STYLE_BOOK_COLOR_GROUPS.find( - ( group ) => group.slug === blockName - ) - ) { - // Go to color palettes Global Styles. - onPathChange( '/colors/palette' ); - return; - } - if ( blockName === 'typography' ) { - // Go to typography Global Styles. - onPathChange( '/typography' ); - return; - } - - // Now go to the selected block. - onPathChange( - `/blocks/${ encodeURIComponent( blockName ) }` - ); - } } + <GlobalStylesUI + path={ section } + onPathChange={ onChangeSection } /> - ) } + </Page> </> ); } diff --git a/packages/edit-site/src/components/sidebar-global-styles-wrapper/style.scss b/packages/edit-site/src/components/sidebar-global-styles-wrapper/style.scss index 88aa9ddf0c1618..0fa4e158fe7f10 100644 --- a/packages/edit-site/src/components/sidebar-global-styles-wrapper/style.scss +++ b/packages/edit-site/src/components/sidebar-global-styles-wrapper/style.scss @@ -33,3 +33,25 @@ color: $gray-900; } } + +.show-icon-labels { + .edit-site-styles .edit-site-page-content { + .edit-site-page-header__actions { + .components-button.has-icon { + width: auto; + padding: 0 $grid-unit-10; + + // Hide the button icons when labels are set to display... + svg { + display: none; + } + // ... and display labels. + &::after { + content: attr(aria-label); + font-size: $helptext-font-size; + } + } + + } + } +} diff --git a/packages/edit-site/src/components/sidebar-navigation-item/index.js b/packages/edit-site/src/components/sidebar-navigation-item/index.js index 80f06d7e93133b..4bde94dcbbeb4d 100644 --- a/packages/edit-site/src/components/sidebar-navigation-item/index.js +++ b/packages/edit-site/src/components/sidebar-navigation-item/index.js @@ -22,7 +22,7 @@ import { useContext } from '@wordpress/element'; import { unlock } from '../../lock-unlock'; import { SidebarNavigationContext } from '../sidebar'; -const { useHistory } = unlock( routerPrivateApis ); +const { useHistory, useLink } = unlock( routerPrivateApis ); export default function SidebarNavigationItem( { className, @@ -30,7 +30,7 @@ export default function SidebarNavigationItem( { withChevron = false, suffix, uid, - params, + to, onClick, children, ...props @@ -42,12 +42,13 @@ export default function SidebarNavigationItem( { if ( onClick ) { onClick( e ); navigate( 'forward' ); - } else if ( params ) { + } else if ( to ) { e.preventDefault(); - history.push( params ); + history.navigate( to ); navigate( 'forward', `[id="${ uid }"]` ); } } + const linkProps = useLink( to ); return ( <Item @@ -56,8 +57,9 @@ export default function SidebarNavigationItem( { { 'with-suffix': ! withChevron && suffix }, className ) } - onClick={ handleClick } id={ uid } + onClick={ handleClick } + href={ to ? linkProps.href : undefined } { ...props } > <HStack justify="flex-start"> diff --git a/packages/edit-site/src/components/sidebar-navigation-item/style.scss b/packages/edit-site/src/components/sidebar-navigation-item/style.scss index 202de5300076c1..57b7e84bd57a8b 100644 --- a/packages/edit-site/src/components/sidebar-navigation-item/style.scss +++ b/packages/edit-site/src/components/sidebar-navigation-item/style.scss @@ -9,7 +9,6 @@ &:focus, &[aria-current="true"] { color: $gray-200; - background: $gray-800; .edit-site-sidebar-navigation-item__drilldown-indicator { fill: $gray-200; @@ -17,8 +16,14 @@ } &[aria-current="true"] { - background: var(--wp-admin-theme-color); + background: $gray-800; color: $white; + font-weight: $font-weight-medium; + } + + // Make sure the focus style is drawn on top of the current item background. + &:focus-visible { + transform: translateZ(0); } .edit-site-sidebar-navigation-item__drilldown-indicator { diff --git a/packages/edit-site/src/components/sidebar-navigation-screen-details-footer/index.js b/packages/edit-site/src/components/sidebar-navigation-screen-details-footer/index.js index 3dec3f0a7b2eb5..f7e5f8bbec34de 100644 --- a/packages/edit-site/src/components/sidebar-navigation-screen-details-footer/index.js +++ b/packages/edit-site/src/components/sidebar-navigation-screen-details-footer/index.js @@ -1,28 +1,19 @@ /** * WordPress dependencies */ -import { __, sprintf } from '@wordpress/i18n'; -import { humanTimeDiff } from '@wordpress/date'; -import { createInterpolateElement } from '@wordpress/element'; +import { _n, sprintf } from '@wordpress/i18n'; import { addQueryArgs } from '@wordpress/url'; -import { - Icon, - __experimentalItemGroup as ItemGroup, -} from '@wordpress/components'; +import { __experimentalItemGroup as ItemGroup } from '@wordpress/components'; import { backup } from '@wordpress/icons'; /** * Internal dependencies */ -import { - SidebarNavigationScreenDetailsPanelRow, - SidebarNavigationScreenDetailsPanelLabel, - SidebarNavigationScreenDetailsPanelValue, -} from '../sidebar-navigation-screen-details-panel'; import SidebarNavigationItem from '../sidebar-navigation-item'; export default function SidebarNavigationScreenDetailsFooter( { record, + revisionsCount, ...otherProps } ) { /* @@ -34,9 +25,20 @@ export default function SidebarNavigationScreenDetailsFooter( { const hrefProps = {}; const lastRevisionId = record?._links?.[ 'predecessor-version' ]?.[ 0 ]?.id ?? null; - const revisionsCount = - record?._links?.[ 'version-history' ]?.[ 0 ]?.count ?? 0; - // Enable the revisions link if there is a last revision and there are more than one revisions. + + // Use incoming prop first, then the record's version history, if available. + revisionsCount = + revisionsCount || + record?._links?.[ 'version-history' ]?.[ 0 ]?.count || + 0; + + /* + * Enable the revisions link if there is a last revision and there is more than one revision. + * This link is used for theme assets, e.g., templates, which have no database record until they're edited. + * For these files there's only a "revision" after they're edited twice, + * which means the revision.php page won't display a proper diff. + * See: https://github.com/WordPress/gutenberg/issues/49164. + */ if ( lastRevisionId && revisionsCount > 1 ) { hrefProps.href = addQueryArgs( 'revision.php', { revision: record?._links[ 'predecessor-version' ][ 0 ].id, @@ -44,33 +46,20 @@ export default function SidebarNavigationScreenDetailsFooter( { hrefProps.as = 'a'; } return ( - <ItemGroup className="edit-site-sidebar-navigation-screen-details-footer"> + <ItemGroup + size="large" + className="edit-site-sidebar-navigation-screen-details-footer" + > <SidebarNavigationItem - aria-label={ __( 'Revisions' ) } + icon={ backup } { ...hrefProps } { ...otherProps } > - <SidebarNavigationScreenDetailsPanelRow justify="space-between"> - <SidebarNavigationScreenDetailsPanelLabel> - { __( 'Last modified' ) } - </SidebarNavigationScreenDetailsPanelLabel> - <SidebarNavigationScreenDetailsPanelValue> - { createInterpolateElement( - sprintf( - /* translators: %s: is the relative time when the post was last modified. */ - __( '<time>%s</time>' ), - humanTimeDiff( record.modified ) - ), - { - time: <time dateTime={ record.modified } />, - } - ) } - </SidebarNavigationScreenDetailsPanelValue> - <Icon - className="edit-site-sidebar-navigation-screen-details-footer__icon" - icon={ backup } - /> - </SidebarNavigationScreenDetailsPanelRow> + { sprintf( + /* translators: %d: Number of Styles revisions. */ + _n( '%d Revision', '%d Revisions', revisionsCount ), + revisionsCount + ) } </SidebarNavigationItem> </ItemGroup> ); diff --git a/packages/edit-site/src/components/sidebar-navigation-screen-details-footer/style.scss b/packages/edit-site/src/components/sidebar-navigation-screen-details-footer/style.scss index 866a36c02174d3..0b48d451129662 100644 --- a/packages/edit-site/src/components/sidebar-navigation-screen-details-footer/style.scss +++ b/packages/edit-site/src/components/sidebar-navigation-screen-details-footer/style.scss @@ -5,8 +5,4 @@ div.edit-site-sidebar-navigation-item.components-item[aria-current] { background: none; } - .edit-site-sidebar-navigation-screen-details-footer__icon { - margin-left: auto; - fill: $gray-600; - } } diff --git a/packages/edit-site/src/components/sidebar-navigation-screen-details-panel/index.js b/packages/edit-site/src/components/sidebar-navigation-screen-details-panel/index.js deleted file mode 100644 index 7d7a3932c99473..00000000000000 --- a/packages/edit-site/src/components/sidebar-navigation-screen-details-panel/index.js +++ /dev/null @@ -1,40 +0,0 @@ -/** - * WordPress dependencies - */ -import { - __experimentalVStack as VStack, - __experimentalHeading as Heading, -} from '@wordpress/components'; - -/** - * Internal dependencies - */ -import SidebarNavigationScreenDetailsPanelLabel from './sidebar-navigation-screen-details-panel-label'; -import SidebarNavigationScreenDetailsPanelRow from './sidebar-navigation-screen-details-panel-row'; -import SidebarNavigationScreenDetailsPanelValue from './sidebar-navigation-screen-details-panel-value'; - -function SidebarNavigationScreenDetailsPanel( { title, children, spacing } ) { - return ( - <VStack - className="edit-site-sidebar-navigation-details-screen-panel" - spacing={ spacing } - > - { title && ( - <Heading - className="edit-site-sidebar-navigation-details-screen-panel__heading" - level={ 2 } - > - { title } - </Heading> - ) } - { children } - </VStack> - ); -} - -export { - SidebarNavigationScreenDetailsPanel, - SidebarNavigationScreenDetailsPanelRow, - SidebarNavigationScreenDetailsPanelLabel, - SidebarNavigationScreenDetailsPanelValue, -}; diff --git a/packages/edit-site/src/components/sidebar-navigation-screen-details-panel/sidebar-navigation-screen-details-panel-label.js b/packages/edit-site/src/components/sidebar-navigation-screen-details-panel/sidebar-navigation-screen-details-panel-label.js deleted file mode 100644 index 157eecd557519c..00000000000000 --- a/packages/edit-site/src/components/sidebar-navigation-screen-details-panel/sidebar-navigation-screen-details-panel-label.js +++ /dev/null @@ -1,14 +0,0 @@ -/** - * WordPress dependencies - */ -import { __experimentalText as Text } from '@wordpress/components'; - -export default function SidebarNavigationScreenDetailsPanelLabel( { - children, -} ) { - return ( - <Text className="edit-site-sidebar-navigation-details-screen-panel__label"> - { children } - </Text> - ); -} diff --git a/packages/edit-site/src/components/sidebar-navigation-screen-details-panel/sidebar-navigation-screen-details-panel-row.js b/packages/edit-site/src/components/sidebar-navigation-screen-details-panel/sidebar-navigation-screen-details-panel-row.js deleted file mode 100644 index 1c7f9abafdf503..00000000000000 --- a/packages/edit-site/src/components/sidebar-navigation-screen-details-panel/sidebar-navigation-screen-details-panel-row.js +++ /dev/null @@ -1,31 +0,0 @@ -/** - * External dependencies - */ -import clsx from 'clsx'; - -/** - * WordPress dependencies - */ -import { __experimentalHStack as HStack } from '@wordpress/components'; - -export default function SidebarNavigationScreenDetailsPanelRow( { - label, - children, - className, - ...extraProps -} ) { - return ( - <HStack - key={ label } - spacing={ 5 } - alignment="left" - className={ clsx( - 'edit-site-sidebar-navigation-details-screen-panel__row', - className - ) } - { ...extraProps } - > - { children } - </HStack> - ); -} diff --git a/packages/edit-site/src/components/sidebar-navigation-screen-details-panel/sidebar-navigation-screen-details-panel-value.js b/packages/edit-site/src/components/sidebar-navigation-screen-details-panel/sidebar-navigation-screen-details-panel-value.js deleted file mode 100644 index 80e8ba8cf1d538..00000000000000 --- a/packages/edit-site/src/components/sidebar-navigation-screen-details-panel/sidebar-navigation-screen-details-panel-value.js +++ /dev/null @@ -1,14 +0,0 @@ -/** - * WordPress dependencies - */ -import { __experimentalText as Text } from '@wordpress/components'; - -export default function SidebarNavigationScreenDetailsPanelValue( { - children, -} ) { - return ( - <Text className="edit-site-sidebar-navigation-details-screen-panel__value"> - { children } - </Text> - ); -} diff --git a/packages/edit-site/src/components/sidebar-navigation-screen-details-panel/style.scss b/packages/edit-site/src/components/sidebar-navigation-screen-details-panel/style.scss deleted file mode 100644 index 2757ce5a620c58..00000000000000 --- a/packages/edit-site/src/components/sidebar-navigation-screen-details-panel/style.scss +++ /dev/null @@ -1,26 +0,0 @@ -.edit-site-sidebar-navigation-details-screen-panel { - margin: $grid-unit-30 0; - - &:last-of-type { - margin-bottom: 0; - } - - .edit-site-sidebar-navigation-details-screen-panel__heading { - color: $gray-400; - text-transform: uppercase; - font-weight: 500; - font-size: 11px; - padding: 0; - margin-bottom: 0; - } -} - -.edit-site-sidebar-navigation-details-screen-panel__label.edit-site-sidebar-navigation-details-screen-panel__label { - color: $gray-600; - width: 100px; - flex-shrink: 0; -} - -.edit-site-sidebar-navigation-details-screen-panel__value.edit-site-sidebar-navigation-details-screen-panel__value { - color: $gray-200; -} diff --git a/packages/edit-site/src/components/sidebar-navigation-screen-global-styles/content.js b/packages/edit-site/src/components/sidebar-navigation-screen-global-styles/content.js index 986238697f7346..ce8cd32aa009c5 100644 --- a/packages/edit-site/src/components/sidebar-navigation-screen-global-styles/content.js +++ b/packages/edit-site/src/components/sidebar-navigation-screen-global-styles/content.js @@ -2,54 +2,26 @@ * WordPress dependencies */ import { __ } from '@wordpress/i18n'; -import { useSelect } from '@wordpress/data'; import { __experimentalVStack as VStack } from '@wordpress/components'; -import { BlockEditorProvider } from '@wordpress/block-editor'; /** * Internal dependencies */ import StyleVariationsContainer from '../global-styles/style-variations-container'; -import { unlock } from '../../lock-unlock'; -import { store as editSiteStore } from '../../store'; import ColorVariations from '../global-styles/variations/variations-color'; import TypographyVariations from '../global-styles/variations/variations-typography'; -const noop = () => {}; - export default function SidebarNavigationScreenGlobalStylesContent() { - const { storedSettings } = useSelect( ( select ) => { - const { getSettings } = unlock( select( editSiteStore ) ); - - return { - storedSettings: getSettings(), - }; - }, [] ); - const gap = 3; - // Wrap in a BlockEditorProvider to ensure that the Iframe's dependencies are - // loaded. This is necessary because the Iframe component waits until - // the block editor store's `__internalIsInitialized` is true before - // rendering the iframe. Without this, the iframe previews will not render - // in mobile viewport sizes, where the editor canvas is hidden. return ( - <BlockEditorProvider - settings={ storedSettings } - onChange={ noop } - onInput={ noop } + <VStack + spacing={ 10 } + className="edit-site-global-styles-variation-container" > - <VStack - spacing={ 10 } - className="edit-site-global-styles-variation-container" - > - <StyleVariationsContainer gap={ gap } /> - <ColorVariations title={ __( 'Palettes' ) } gap={ gap } /> - <TypographyVariations - title={ __( 'Typography' ) } - gap={ gap } - /> - </VStack> - </BlockEditorProvider> + <StyleVariationsContainer gap={ gap } /> + <ColorVariations title={ __( 'Palettes' ) } gap={ gap } /> + <TypographyVariations title={ __( 'Typography' ) } gap={ gap } /> + </VStack> ); } diff --git a/packages/edit-site/src/components/sidebar-navigation-screen-global-styles/index.js b/packages/edit-site/src/components/sidebar-navigation-screen-global-styles/index.js index 3dc93ff4d4df63..a5902dc37362c5 100644 --- a/packages/edit-site/src/components/sidebar-navigation-screen-global-styles/index.js +++ b/packages/edit-site/src/components/sidebar-navigation-screen-global-styles/index.js @@ -2,11 +2,11 @@ * WordPress dependencies */ import { __ } from '@wordpress/i18n'; -import { useSelect, useDispatch } from '@wordpress/data'; -import { store as coreStore } from '@wordpress/core-data'; +import { useDispatch } from '@wordpress/data'; import { useCallback } from '@wordpress/element'; import { store as preferencesStore } from '@wordpress/preferences'; import { privateApis as routerPrivateApis } from '@wordpress/router'; +import { addQueryArgs } from '@wordpress/url'; /** * Internal dependencies @@ -22,68 +22,38 @@ import { MainSidebarNavigationContent } from '../sidebar-navigation-screen-main' const { useLocation, useHistory } = unlock( routerPrivateApis ); export function SidebarNavigationItemGlobalStyles( props ) { - const { params } = useLocation(); - const hasGlobalStyleVariations = useSelect( - ( select ) => - !! select( - coreStore - ).__experimentalGetCurrentThemeGlobalStylesVariations()?.length, - [] + const { name } = useLocation(); + return ( + <SidebarNavigationItem + { ...props } + aria-current={ name === 'styles' } + /> ); - if ( hasGlobalStyleVariations ) { - return ( - <SidebarNavigationItem - { ...props } - params={ { path: '/wp_global_styles' } } - uid="global-styles-navigation-item" - aria-current={ - params.path && params.path.startsWith( '/wp_global_styles' ) - } - /> - ); - } - return <SidebarNavigationItem { ...props } />; } export default function SidebarNavigationScreenGlobalStyles() { const history = useHistory(); - const { params } = useLocation(); - const { revisions, isLoading: isLoadingRevisions } = - useGlobalStylesRevisions(); + const { path } = useLocation(); + const { + revisions, + isLoading: isLoadingRevisions, + revisionsCount, + } = useGlobalStylesRevisions(); const { openGeneralSidebar } = useDispatch( editSiteStore ); const { setEditorCanvasContainerView } = unlock( useDispatch( editSiteStore ) ); - const { revisionsCount } = useSelect( ( select ) => { - const { getEntityRecord, __experimentalGetCurrentGlobalStylesId } = - select( coreStore ); - const globalStylesId = __experimentalGetCurrentGlobalStylesId(); - const globalStyles = globalStylesId - ? getEntityRecord( 'root', 'globalStyles', globalStylesId ) - : undefined; - return { - revisionsCount: - globalStyles?._links?.[ 'version-history' ]?.[ 0 ]?.count ?? 0, - }; - }, [] ); const { set: setPreference } = useDispatch( preferencesStore ); const openGlobalStyles = useCallback( async () => { - history.push( - { - ...params, - canvas: 'edit', - }, - undefined, - { - transition: 'canvas-mode-edit-transition', - } - ); + history.navigate( addQueryArgs( path, { canvas: 'edit' } ), { + transition: 'canvas-mode-edit-transition', + } ); return Promise.all( [ setPreference( 'core', 'distractionFree', false ), openGeneralSidebar( 'edit-site/global-styles' ), ] ); - }, [ history, params, openGeneralSidebar, setPreference ] ); + }, [ path, history, openGeneralSidebar, setPreference ] ); const openRevisions = useCallback( async () => { await openGlobalStyles(); @@ -95,10 +65,9 @@ export default function SidebarNavigationScreenGlobalStyles() { }, [ openGlobalStyles, setEditorCanvasContainerView ] ); // If there are no revisions, do not render a footer. - const hasRevisions = revisionsCount > 0; - const modifiedDateTime = revisions?.[ 0 ]?.modified; const shouldShowGlobalStylesFooter = - hasRevisions && ! isLoadingRevisions && modifiedDateTime; + !! revisionsCount && ! isLoadingRevisions; + return ( <> <SidebarNavigationScreen @@ -114,6 +83,7 @@ export default function SidebarNavigationScreenGlobalStyles() { shouldShowGlobalStylesFooter && ( <SidebarNavigationScreenDetailsFooter record={ revisions?.[ 0 ] } + revisionsCount={ revisionsCount } onClick={ openRevisions } /> ) diff --git a/packages/edit-site/src/components/sidebar-navigation-screen-main/index.js b/packages/edit-site/src/components/sidebar-navigation-screen-main/index.js index 49e60d44047326..abcc7183f6604e 100644 --- a/packages/edit-site/src/components/sidebar-navigation-screen-main/index.js +++ b/packages/edit-site/src/components/sidebar-navigation-screen-main/index.js @@ -4,8 +4,9 @@ import { __experimentalItemGroup as ItemGroup } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import { layout, symbol, navigation, styles, page } from '@wordpress/icons'; -import { useDispatch } from '@wordpress/data'; +import { useDispatch, useSelect } from '@wordpress/data'; import { useEffect } from '@wordpress/element'; +import { store as coreStore } from '@wordpress/core-data'; /** * Internal dependencies @@ -15,48 +16,58 @@ import SidebarNavigationItem from '../sidebar-navigation-item'; import { SidebarNavigationItemGlobalStyles } from '../sidebar-navigation-screen-global-styles'; import { unlock } from '../../lock-unlock'; import { store as editSiteStore } from '../../store'; -import { - NAVIGATION_POST_TYPE, - TEMPLATE_POST_TYPE, - PATTERN_TYPES, -} from '../../utils/constants'; -export function MainSidebarNavigationContent() { +export function MainSidebarNavigationContent( { isBlockBasedTheme = true } ) { return ( - <ItemGroup> - <SidebarNavigationItem - uid="navigation-navigation-item" - params={ { postType: NAVIGATION_POST_TYPE } } - withChevron - icon={ navigation } - > - { __( 'Navigation' ) } - </SidebarNavigationItem> - <SidebarNavigationItemGlobalStyles - uid="styles-navigation-item" - icon={ styles } - > - { __( 'Styles' ) } - </SidebarNavigationItemGlobalStyles> - <SidebarNavigationItem - uid="page-navigation-item" - params={ { postType: 'page' } } - withChevron - icon={ page } - > - { __( 'Pages' ) } - </SidebarNavigationItem> - <SidebarNavigationItem - uid="template-navigation-item" - params={ { postType: TEMPLATE_POST_TYPE } } - withChevron - icon={ layout } - > - { __( 'Templates' ) } - </SidebarNavigationItem> + <ItemGroup className="edit-site-sidebar-navigation-screen-main"> + { isBlockBasedTheme && ( + <> + <SidebarNavigationItem + uid="navigation-navigation-item" + to="/navigation" + withChevron + icon={ navigation } + > + { __( 'Navigation' ) } + </SidebarNavigationItem> + <SidebarNavigationItemGlobalStyles + to="/styles" + uid="global-styles-navigation-item" + icon={ styles } + > + { __( 'Styles' ) } + </SidebarNavigationItemGlobalStyles> + <SidebarNavigationItem + uid="page-navigation-item" + to="/page" + withChevron + icon={ page } + > + { __( 'Pages' ) } + </SidebarNavigationItem> + <SidebarNavigationItem + uid="template-navigation-item" + to="/template" + withChevron + icon={ layout } + > + { __( 'Templates' ) } + </SidebarNavigationItem> + </> + ) } + { ! isBlockBasedTheme && ( + <SidebarNavigationItem + uid="stylebook-navigation-item" + to="/stylebook" + withChevron + icon={ styles } + > + { __( 'Styles' ) } + </SidebarNavigationItem> + ) } <SidebarNavigationItem uid="patterns-navigation-item" - params={ { postType: PATTERN_TYPES.user } } + to="/pattern" withChevron icon={ symbol } > @@ -67,6 +78,10 @@ export function MainSidebarNavigationContent() { } export default function SidebarNavigationScreenMain() { + const isBlockBasedTheme = useSelect( + ( select ) => select( coreStore ).getCurrentTheme()?.is_block_theme, + [] + ); const { setEditorCanvasContainerView } = unlock( useDispatch( editSiteStore ) ); @@ -80,10 +95,20 @@ export default function SidebarNavigationScreenMain() { <SidebarNavigationScreen isRoot title={ __( 'Design' ) } - description={ __( - 'Customize the appearance of your website using the block editor.' - ) } - content={ <MainSidebarNavigationContent /> } + description={ + isBlockBasedTheme + ? __( + 'Customize the appearance of your website using the block editor.' + ) + : __( + 'Explore block styles and patterns to refine your site' + ) + } + content={ + <MainSidebarNavigationContent + isBlockBasedTheme={ isBlockBasedTheme } + /> + } /> ); } diff --git a/packages/edit-site/src/components/sidebar-navigation-screen-main/style.scss b/packages/edit-site/src/components/sidebar-navigation-screen-main/style.scss new file mode 100644 index 00000000000000..f8190ec097c485 --- /dev/null +++ b/packages/edit-site/src/components/sidebar-navigation-screen-main/style.scss @@ -0,0 +1,4 @@ +.edit-site-sidebar-navigation-screen-main { + margin-left: -$grid-unit-20; + margin-right: -$grid-unit-20; +} diff --git a/packages/edit-site/src/components/sidebar-navigation-screen-navigation-menu/more-menu.js b/packages/edit-site/src/components/sidebar-navigation-screen-navigation-menu/more-menu.js index 6b85e088817edf..a07167413ae119 100644 --- a/packages/edit-site/src/components/sidebar-navigation-screen-navigation-menu/more-menu.js +++ b/packages/edit-site/src/components/sidebar-navigation-screen-navigation-menu/more-menu.js @@ -58,11 +58,9 @@ export default function ScreenNavigationMoreMenu( props ) { </MenuItem> <MenuItem onClick={ () => { - history.push( { - postId: menuId, - postType: 'wp_navigation', - canvas: 'edit', - } ); + history.navigate( + `/wp_navigation/${ menuId }?canvas=edit` + ); } } > { __( 'Edit' ) } diff --git a/packages/edit-site/src/components/sidebar-navigation-screen-navigation-menu/use-navigation-menu-handlers.js b/packages/edit-site/src/components/sidebar-navigation-screen-navigation-menu/use-navigation-menu-handlers.js index 4a7e1deddc6d93..4138280c2ba28e 100644 --- a/packages/edit-site/src/components/sidebar-navigation-screen-navigation-menu/use-navigation-menu-handlers.js +++ b/packages/edit-site/src/components/sidebar-navigation-screen-navigation-menu/use-navigation-menu-handlers.js @@ -42,7 +42,7 @@ function useDeleteNavigationMenu() { type: 'snackbar', } ); - history.push( { postType: 'wp_navigation' } ); + history.navigate( '/navigation' ); } catch ( error ) { createErrorNotice( sprintf( @@ -165,7 +165,7 @@ function useDuplicateNavigationMenu() { createSuccessNotice( __( 'Duplicated Navigation Menu' ), { type: 'snackbar', } ); - history.push( { postType, postId: savedRecord.id } ); + history.navigate( `/wp_navigation/${ savedRecord.id }` ); } } catch ( error ) { createErrorNotice( diff --git a/packages/edit-site/src/components/sidebar-navigation-screen-navigation-menus/index.js b/packages/edit-site/src/components/sidebar-navigation-screen-navigation-menus/index.js index ece549f57378b2..1fc7d61318c57a 100644 --- a/packages/edit-site/src/components/sidebar-navigation-screen-navigation-menus/index.js +++ b/packages/edit-site/src/components/sidebar-navigation-screen-navigation-menus/index.js @@ -18,7 +18,6 @@ import { navigation } from '@wordpress/icons'; import SidebarNavigationScreen from '../sidebar-navigation-screen'; import SidebarNavigationItem from '../sidebar-navigation-item'; import { PRELOADED_NAVIGATION_MENUS_QUERY } from './constants'; -import { useLink } from '../routes/link'; import SingleNavigationMenu from '../sidebar-navigation-screen-navigation-menu/single-navigation-menu'; import useNavigationMenuHandlers from '../sidebar-navigation-screen-navigation-menu/use-navigation-menu-handlers'; import { unlock } from '../../lock-unlock'; @@ -117,7 +116,7 @@ export default function SidebarNavigationScreenNavigationMenus( { backPath } ) { return ( <SidebarNavigationScreenWrapper backPath={ backPath }> - <ItemGroup> + <ItemGroup className="edit-site-sidebar-navigation-screen-navigation-menus"> { navigationMenus?.map( ( { id, title, status }, index ) => ( <NavMenuItem postId={ id } @@ -152,9 +151,10 @@ export function SidebarNavigationScreenWrapper( { } const NavMenuItem = ( { postId, ...props } ) => { - const linkInfo = useLink( { - postId, - postType: 'wp_navigation', - } ); - return <SidebarNavigationItem { ...linkInfo } { ...props } />; + return ( + <SidebarNavigationItem + to={ `/wp_navigation/${ postId }` } + { ...props } + /> + ); }; diff --git a/packages/edit-site/src/components/sidebar-navigation-screen-navigation-menus/leaf-more-menu.js b/packages/edit-site/src/components/sidebar-navigation-screen-navigation-menus/leaf-more-menu.js index 568ec291f9ed11..ba01faab0291ce 100644 --- a/packages/edit-site/src/components/sidebar-navigation-screen-navigation-menus/leaf-more-menu.js +++ b/packages/edit-site/src/components/sidebar-navigation-screen-navigation-menus/leaf-more-menu.js @@ -20,10 +20,11 @@ const POPOVER_PROPS = { */ import { unlock } from '../../lock-unlock'; -const { useHistory } = unlock( routerPrivateApis ); +const { useHistory, useLocation } = unlock( routerPrivateApis ); export default function LeafMoreMenu( props ) { const history = useHistory(); + const { path } = useLocation(); const { block } = props; const { clientId } = block; const { moveBlocksDown, moveBlocksUp, removeBlocks } = @@ -59,33 +60,20 @@ export default function LeafMoreMenu( props ) { attributes.type && history ) { - const { params } = history.getLocationWithParams(); - history.push( + history.navigate( + `/${ attributes.type }/${ attributes.id }?canvas=edit`, { - postType: attributes.type, - postId: attributes.id, - canvas: 'edit', - }, - { - backPath: params, + state: { backPath: path }, } ); } if ( name === 'core/page-list-item' && attributes.id && history ) { - const { params } = history.getLocationWithParams(); - history.push( - { - postType: 'page', - postId: attributes.id, - canvas: 'edit', - }, - { - backPath: params, - } - ); + history.navigate( `/page/${ attributes.id }?canvas=edit`, { + state: { backPath: path }, + } ); } }, - [ history ] + [ path, history ] ); return ( diff --git a/packages/edit-site/src/components/sidebar-navigation-screen-navigation-menus/style.scss b/packages/edit-site/src/components/sidebar-navigation-screen-navigation-menus/style.scss index 334e90e93c42ce..959ed07938e5df 100644 --- a/packages/edit-site/src/components/sidebar-navigation-screen-navigation-menus/style.scss +++ b/packages/edit-site/src/components/sidebar-navigation-screen-navigation-menus/style.scss @@ -2,6 +2,11 @@ margin: 0 0 $grid-unit-40 0; } +.edit-site-sidebar-navigation-screen-navigation-menus { + margin-left: -$grid-unit-20; + margin-right: -$grid-unit-20; +} + .edit-site-sidebar-navigation-screen-navigation-menus__content { .block-editor-list-view-leaf .block-editor-list-view-block__contents-cell { width: 100%; @@ -27,6 +32,14 @@ color: $white; } } + + .block-editor-list-view-block__menu { + color: $gray-600; + &:hover, + &:focus { + color: $white; + } + } } .edit-site-sidebar-navigation-screen-navigation-menus__loading.components-spinner { diff --git a/packages/edit-site/src/components/sidebar-navigation-screen-patterns/category-item.js b/packages/edit-site/src/components/sidebar-navigation-screen-patterns/category-item.js index 9c193304b99fc8..4e92af1d84f50e 100644 --- a/packages/edit-site/src/components/sidebar-navigation-screen-patterns/category-item.js +++ b/packages/edit-site/src/components/sidebar-navigation-screen-patterns/category-item.js @@ -2,13 +2,6 @@ * Internal dependencies */ import SidebarNavigationItem from '../sidebar-navigation-item'; -import { useLink } from '../routes/link'; -import { - TEMPLATE_PART_POST_TYPE, - TEMPLATE_PART_ALL_AREAS_CATEGORY, - PATTERN_DEFAULT_CATEGORY, - PATTERN_TYPES, -} from '../../utils/constants'; export default function CategoryItem( { count, @@ -18,28 +11,20 @@ export default function CategoryItem( { label, type, } ) { - const linkInfo = useLink( { - categoryId: - id !== TEMPLATE_PART_ALL_AREAS_CATEGORY && - id !== PATTERN_DEFAULT_CATEGORY - ? id - : undefined, - postType: - type === TEMPLATE_PART_POST_TYPE - ? TEMPLATE_PART_POST_TYPE - : PATTERN_TYPES.user, - } ); - if ( ! count ) { return; } + const queryArgs = [ `postType=${ type }` ]; + if ( id ) { + queryArgs.push( `categoryId=${ id }` ); + } return ( <SidebarNavigationItem - { ...linkInfo } icon={ icon } suffix={ <span>{ count }</span> } aria-current={ isActive ? 'true' : undefined } + to={ `/pattern?${ queryArgs.join( '&' ) }` } > { label } </SidebarNavigationItem> diff --git a/packages/edit-site/src/components/sidebar-navigation-screen-patterns/index.js b/packages/edit-site/src/components/sidebar-navigation-screen-patterns/index.js index eeec513cb99afb..d978bf892d6ccf 100644 --- a/packages/edit-site/src/components/sidebar-navigation-screen-patterns/index.js +++ b/packages/edit-site/src/components/sidebar-navigation-screen-patterns/index.js @@ -7,8 +7,6 @@ import { } from '@wordpress/components'; import { getTemplatePartIcon } from '@wordpress/editor'; import { __ } from '@wordpress/i18n'; -import { store as coreStore } from '@wordpress/core-data'; -import { useSelect } from '@wordpress/data'; import { file } from '@wordpress/icons'; import { privateApis as routerPrivateApis } from '@wordpress/router'; @@ -104,26 +102,20 @@ function CategoriesGroup( { export default function SidebarNavigationScreenPatterns( { backPath } ) { const { - params: { postType, categoryId }, + query: { postType = 'wp_block', categoryId }, } = useLocation(); - const currentType = postType || PATTERN_TYPES.user; const currentCategory = categoryId || - ( currentType === PATTERN_TYPES.user + ( postType === PATTERN_TYPES.user ? PATTERN_DEFAULT_CATEGORY : TEMPLATE_PART_ALL_AREAS_CATEGORY ); const { templatePartAreas, hasTemplateParts, isLoading } = useTemplatePartAreas(); const { patternCategories, hasPatterns } = usePatternCategories(); - const isBlockBasedTheme = useSelect( - ( select ) => select( coreStore ).getCurrentTheme()?.is_block_theme, - [] - ); return ( <SidebarNavigationScreen - isRoot={ ! isBlockBasedTheme } title={ __( 'Patterns' ) } description={ __( 'Manage what patterns are available when editing the site.' @@ -143,7 +135,7 @@ export default function SidebarNavigationScreenPatterns( { backPath } ) { templatePartAreas={ templatePartAreas } patternCategories={ patternCategories } currentCategory={ currentCategory } - currentType={ currentType } + currentType={ postType } /> </> ) } diff --git a/packages/edit-site/src/components/sidebar-navigation-screen-patterns/style.scss b/packages/edit-site/src/components/sidebar-navigation-screen-patterns/style.scss index 19885eb3baf2cb..fd50d757f276b1 100644 --- a/packages/edit-site/src/components/sidebar-navigation-screen-patterns/style.scss +++ b/packages/edit-site/src/components/sidebar-navigation-screen-patterns/style.scss @@ -1,5 +1,7 @@ .edit-site-sidebar-navigation-screen-patterns__group { margin-bottom: $grid-unit-30; + margin-left: -$grid-unit-20; + margin-right: -$grid-unit-20; &:last-of-type { border-bottom: 0; diff --git a/packages/edit-site/src/components/sidebar-navigation-screen-patterns/use-pattern-categories.js b/packages/edit-site/src/components/sidebar-navigation-screen-patterns/use-pattern-categories.js index a41e0ea7660616..70aaf12dc68c77 100644 --- a/packages/edit-site/src/components/sidebar-navigation-screen-patterns/use-pattern-categories.js +++ b/packages/edit-site/src/components/sidebar-navigation-screen-patterns/use-pattern-categories.js @@ -69,7 +69,7 @@ export default function usePatternCategories() { // If the pattern has no categories, add it to uncategorized. if ( ! pattern.wp_pattern_category?.length || - ! pattern.wp_pattern_category.some( ( catId ) => + ! pattern.wp_pattern_category?.some( ( catId ) => userPatternCategories.find( ( cat ) => cat.id === catId ) ) ) { diff --git a/packages/edit-site/src/components/sidebar-navigation-screen-patterns/use-template-part-areas.js b/packages/edit-site/src/components/sidebar-navigation-screen-patterns/use-template-part-areas.js index 77cbf87b3d439e..6a67a8f8a30fb9 100644 --- a/packages/edit-site/src/components/sidebar-navigation-screen-patterns/use-template-part-areas.js +++ b/packages/edit-site/src/components/sidebar-navigation-screen-patterns/use-template-part-areas.js @@ -1,9 +1,8 @@ /** * WordPress dependencies */ -import { useEntityRecords } from '@wordpress/core-data'; +import { useEntityRecords, store as coreStore } from '@wordpress/core-data'; import { useSelect } from '@wordpress/data'; -import { store as editorStore } from '@wordpress/editor'; /** * Internal dependencies @@ -18,7 +17,8 @@ const useTemplatePartsGroupedByArea = ( items ) => { const templatePartAreas = useSelect( ( select ) => - select( editorStore ).__experimentalGetDefaultTemplatePartAreas(), + select( coreStore ).getEntityRecord( 'root', '__unstableBase' ) + ?.default_template_part_areas || [], [] ); @@ -43,7 +43,7 @@ const useTemplatePartsGroupedByArea = ( items ) => { const key = accumulator[ item.area ] ? item.area : TEMPLATE_PART_AREA_DEFAULT_CATEGORY; - accumulator[ key ].templateParts.push( item ); + accumulator[ key ]?.templateParts?.push( item ); return accumulator; }, knownAreas ); diff --git a/packages/edit-site/src/components/sidebar-navigation-screen-templates-browse/content.js b/packages/edit-site/src/components/sidebar-navigation-screen-templates-browse/content.js index 5a07adf62d9b31..7920d49a43c8cd 100644 --- a/packages/edit-site/src/components/sidebar-navigation-screen-templates-browse/content.js +++ b/packages/edit-site/src/components/sidebar-navigation-screen-templates-browse/content.js @@ -4,35 +4,41 @@ import { useEntityRecords } from '@wordpress/core-data'; import { useMemo } from '@wordpress/element'; import { __experimentalItemGroup as ItemGroup } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; +import { privateApis as routerPrivateApis } from '@wordpress/router'; +import { addQueryArgs } from '@wordpress/url'; /** * Internal dependencies */ -import DataViewItem from '../sidebar-dataviews/dataview-item'; +import SidebarNavigationItem from '../sidebar-navigation-item'; import { useAddedBy } from '../page-templates/hooks'; import { layout } from '@wordpress/icons'; import { TEMPLATE_POST_TYPE } from '../../utils/constants'; +import { unlock } from '../../lock-unlock'; + +const { useLocation } = unlock( routerPrivateApis ); const EMPTY_ARRAY = []; function TemplateDataviewItem( { template, isActive } ) { const { text, icon } = useAddedBy( template.type, template.id ); + return ( - <DataViewItem - key={ text } - slug={ text } - title={ text } + <SidebarNavigationItem + to={ addQueryArgs( '/template', { activeView: text } ) } icon={ icon } - isActive={ isActive } - isCustom={ false } - /> + aria-current={ isActive } + > + { text } + </SidebarNavigationItem> ); } -export default function DataviewsTemplatesSidebarContent( { - activeView, - title, -} ) { +export default function DataviewsTemplatesSidebarContent() { + const { + query: { activeView = 'all' }, + } = useLocation(); const { records } = useEntityRecords( 'postType', TEMPLATE_POST_TYPE, { per_page: -1, } ); @@ -51,14 +57,14 @@ export default function DataviewsTemplatesSidebarContent( { }, [ records ] ); return ( - <ItemGroup> - <DataViewItem - slug="all" - title={ title } + <ItemGroup className="edit-site-sidebar-navigation-screen-templates-browse"> + <SidebarNavigationItem + to="/template" icon={ layout } - isActive={ activeView === 'all' } - isCustom={ false } - /> + aria-current={ activeView === 'all' } + > + { __( 'All templates' ) } + </SidebarNavigationItem> { firstItemPerAuthorText.map( ( template ) => { return ( <TemplateDataviewItem diff --git a/packages/edit-site/src/components/sidebar-navigation-screen-templates-browse/index.js b/packages/edit-site/src/components/sidebar-navigation-screen-templates-browse/index.js index 8e7946390a3640..d2215a9abd9c61 100644 --- a/packages/edit-site/src/components/sidebar-navigation-screen-templates-browse/index.js +++ b/packages/edit-site/src/components/sidebar-navigation-screen-templates-browse/index.js @@ -2,22 +2,14 @@ * WordPress dependencies */ import { __ } from '@wordpress/i18n'; -import { privateApis as routerPrivateApis } from '@wordpress/router'; /** * Internal dependencies */ import SidebarNavigationScreen from '../sidebar-navigation-screen'; -import { unlock } from '../../lock-unlock'; import DataviewsTemplatesSidebarContent from './content'; -const { useLocation } = unlock( routerPrivateApis ); - export default function SidebarNavigationScreenTemplatesBrowse( { backPath } ) { - const { - params: { activeView = 'all' }, - } = useLocation(); - return ( <SidebarNavigationScreen title={ __( 'Templates' ) } @@ -25,12 +17,7 @@ export default function SidebarNavigationScreenTemplatesBrowse( { backPath } ) { 'Create new templates, or reset any customizations made to the templates supplied by your theme.' ) } backPath={ backPath } - content={ - <DataviewsTemplatesSidebarContent - activeView={ activeView } - title={ __( 'All templates' ) } - /> - } + content={ <DataviewsTemplatesSidebarContent /> } /> ); } diff --git a/packages/edit-site/src/components/sidebar-navigation-screen-templates-browse/style.scss b/packages/edit-site/src/components/sidebar-navigation-screen-templates-browse/style.scss new file mode 100644 index 00000000000000..8846d5a4b47b4d --- /dev/null +++ b/packages/edit-site/src/components/sidebar-navigation-screen-templates-browse/style.scss @@ -0,0 +1,4 @@ +.edit-site-sidebar-navigation-screen-templates-browse { + margin-left: -$grid-unit-20; + margin-right: -$grid-unit-20; +} diff --git a/packages/edit-site/src/components/sidebar-navigation-screen/index.js b/packages/edit-site/src/components/sidebar-navigation-screen/index.js index 0080964310525b..c6b3742a3fd8bc 100644 --- a/packages/edit-site/src/components/sidebar-navigation-screen/index.js +++ b/packages/edit-site/src/components/sidebar-navigation-screen/index.js @@ -83,7 +83,7 @@ export default function SidebarNavigationScreen( { { ! isRoot && ( <SidebarButton onClick={ () => { - history.push( backPath ); + history.navigate( backPath ); navigate( 'back' ); } } icon={ icon } @@ -97,7 +97,7 @@ export default function SidebarNavigationScreen( { label={ dashboardLinkText || __( 'Go to the Dashboard' ) } - href={ dashboardLink || 'index.php' } + href={ dashboardLink } /> ) } <Heading diff --git a/packages/edit-site/src/components/sidebar-navigation-screen/style.scss b/packages/edit-site/src/components/sidebar-navigation-screen/style.scss index 1d57472f7e9f94..71704653b4cd89 100644 --- a/packages/edit-site/src/components/sidebar-navigation-screen/style.scss +++ b/packages/edit-site/src/components/sidebar-navigation-screen/style.scss @@ -18,11 +18,6 @@ .edit-site-sidebar-navigation-screen__content { padding: 0 $grid-unit-20; - .components-item-group { - margin-left: -$grid-unit-20; - margin-right: -$grid-unit-20; - } - .components-text { color: $gray-400; } @@ -127,9 +122,14 @@ bottom: 0; background-color: $gray-900; gap: 0; - padding: $grid-unit-20 0; + padding: $grid-unit-10 $grid-unit-20; margin: $grid-unit-20 0 0; border-top: 1px solid $gray-800; + + .edit-site-sidebar-navigation-screen-details-footer { + margin-left: -$grid-unit-20; + margin-right: -$grid-unit-20; + } } /* In general style overrides are discouraged. diff --git a/packages/edit-site/src/components/site-editor-routes/README.md b/packages/edit-site/src/components/site-editor-routes/README.md index 64121c8bd9a6b5..06e2cfdd331f1b 100644 --- a/packages/edit-site/src/components/site-editor-routes/README.md +++ b/packages/edit-site/src/components/site-editor-routes/README.md @@ -2,7 +2,7 @@ ## Areas -When `canvasMode` is not `edit`, the areas avaliable to use are: +When `canvasMode` is not `edit`, the areas available to use are: | Area | Non-mobile viewport | Mobile viewport | | --- | --- | --- | @@ -12,7 +12,7 @@ When `canvasMode` is not `edit`, the areas avaliable to use are: | `edit` | Rendered if provided. | Not rendered. | | `mobile` | Not rendered | Rendered as full-screen, if provided. | -When `canvasMode` is `edit`, the areas avaliable to use are: +When `canvasMode` is `edit`, the areas available to use are: | Area | Non-mobile viewport | Mobile viewport | | --- | --- | --- | diff --git a/packages/edit-site/src/components/site-editor-routes/home-edit.js b/packages/edit-site/src/components/site-editor-routes/home-edit.js deleted file mode 100644 index f6e6499254082f..00000000000000 --- a/packages/edit-site/src/components/site-editor-routes/home-edit.js +++ /dev/null @@ -1,17 +0,0 @@ -/** - * Internal dependencies - */ -import Editor from '../editor'; -import SidebarNavigationScreenMain from '../sidebar-navigation-screen-main'; - -export const homeEditRoute = { - name: 'home-edit', - match: ( params ) => { - return params.canvas === 'edit'; - }, - areas: { - sidebar: <SidebarNavigationScreenMain />, - preview: <Editor />, - mobile: <Editor />, - }, -}; diff --git a/packages/edit-site/src/components/site-editor-routes/home-view.js b/packages/edit-site/src/components/site-editor-routes/home-view.js deleted file mode 100644 index 63d3d021e82083..00000000000000 --- a/packages/edit-site/src/components/site-editor-routes/home-view.js +++ /dev/null @@ -1,16 +0,0 @@ -/** - * Internal dependencies - */ -import Editor from '../editor'; -import SidebarNavigationScreenMain from '../sidebar-navigation-screen-main'; - -export const homeViewRoute = { - name: 'home-view', - match: ( params ) => { - return params.canvas !== 'edit'; - }, - areas: { - sidebar: <SidebarNavigationScreenMain />, - preview: <Editor />, - }, -}; diff --git a/packages/edit-site/src/components/site-editor-routes/home.js b/packages/edit-site/src/components/site-editor-routes/home.js new file mode 100644 index 00000000000000..1c32000acb177a --- /dev/null +++ b/packages/edit-site/src/components/site-editor-routes/home.js @@ -0,0 +1,15 @@ +/** + * Internal dependencies + */ +import SidebarNavigationScreenMain from '../sidebar-navigation-screen-main'; +import { MaybeEditor } from '../maybe-editor'; + +export const homeRoute = { + name: 'home', + path: '/', + areas: { + sidebar: <SidebarNavigationScreenMain />, + preview: <MaybeEditor showEditor={ false } />, + mobile: <SidebarNavigationScreenMain />, + }, +}; diff --git a/packages/edit-site/src/components/site-editor-routes/index.js b/packages/edit-site/src/components/site-editor-routes/index.js index ddc37b353885c1..232cb640cff264 100644 --- a/packages/edit-site/src/components/site-editor-routes/index.js +++ b/packages/edit-site/src/components/site-editor-routes/index.js @@ -9,44 +9,32 @@ import { useEffect } from '@wordpress/element'; */ import { unlock } from '../../lock-unlock'; import { store as siteEditorStore } from '../../store'; -import { homeViewRoute } from './home-view'; -import { homeEditRoute } from './home-edit'; -import { navigationViewRoute } from './navigation-view'; -import { navigationEditRoute } from './navigation-edit'; -import { navigationItemEditRoute } from './navigation-item-edit'; -import { navigationItemViewRoute } from './navigation-item-view'; -import { stylesEditRoute } from './styles-edit'; -import { stylesViewRoute } from './styles-view'; -import { patternsEditRoute } from './patterns-edit'; -import { patternsViewRoute } from './patterns-view'; -import { templatesEditRoute } from './templates-edit'; -import { templatesListViewRoute } from './templates-list-view'; -import { templatesViewRoute } from './templates-view'; -import { pagesViewRoute } from './pages-view'; -import { pagesEditRoute } from './pages-edit'; -import { pagesListViewRoute } from './pages-list-view'; -import { pagesListViewQuickEditRoute } from './pages-list-view-quick-edit'; -import { pagesViewQuickEditRoute } from './pages-view-quick-edit'; +import { homeRoute } from './home'; +import { stylesRoute } from './styles'; +import { navigationRoute } from './navigation'; +import { navigationItemRoute } from './navigation-item'; +import { patternsRoute } from './patterns'; +import { patternItemRoute } from './pattern-item'; +import { templatePartItemRoute } from './template-part-item'; +import { templatesRoute } from './templates'; +import { templateItemRoute } from './template-item'; +import { pagesRoute } from './pages'; +import { pageItemRoute } from './page-item'; +import { stylebookRoute } from './stylebook'; const routes = [ - pagesListViewQuickEditRoute, - pagesListViewRoute, - pagesViewQuickEditRoute, - pagesViewRoute, - pagesEditRoute, - templatesEditRoute, - templatesListViewRoute, - templatesViewRoute, - patternsViewRoute, - patternsEditRoute, - stylesViewRoute, - stylesEditRoute, - navigationItemViewRoute, - navigationItemEditRoute, - navigationViewRoute, - navigationEditRoute, - homeViewRoute, - homeEditRoute, + pageItemRoute, + pagesRoute, + templateItemRoute, + templatesRoute, + templatePartItemRoute, + patternItemRoute, + patternsRoute, + navigationItemRoute, + navigationRoute, + stylesRoute, + homeRoute, + stylebookRoute, ]; export function useRegisterSiteEditorRoutes() { diff --git a/packages/edit-site/src/components/site-editor-routes/navigation-edit.js b/packages/edit-site/src/components/site-editor-routes/navigation-edit.js deleted file mode 100644 index fdba963c41d0cb..00000000000000 --- a/packages/edit-site/src/components/site-editor-routes/navigation-edit.js +++ /dev/null @@ -1,22 +0,0 @@ -/** - * Internal dependencies - */ -import { NAVIGATION_POST_TYPE } from '../../utils/constants'; -import Editor from '../editor'; -import SidebarNavigationScreenNavigationMenus from '../sidebar-navigation-screen-navigation-menus'; - -export const navigationEditRoute = { - name: 'navigation-edit', - match: ( params ) => { - return ( - params.postType === NAVIGATION_POST_TYPE && - ! params.postId && - params.canvas === 'edit' - ); - }, - areas: { - sidebar: <SidebarNavigationScreenNavigationMenus backPath={ {} } />, - preview: <Editor />, - mobile: <Editor />, - }, -}; diff --git a/packages/edit-site/src/components/site-editor-routes/navigation-item-edit.js b/packages/edit-site/src/components/site-editor-routes/navigation-item-edit.js deleted file mode 100644 index b03cdbd995ac7c..00000000000000 --- a/packages/edit-site/src/components/site-editor-routes/navigation-item-edit.js +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Internal dependencies - */ -import { NAVIGATION_POST_TYPE } from '../../utils/constants'; -import Editor from '../editor'; -import SidebarNavigationScreenNavigationMenu from '../sidebar-navigation-screen-navigation-menu'; - -export const navigationItemEditRoute = { - name: 'navigation-item-edit', - match: ( params ) => { - return ( - params.postType === NAVIGATION_POST_TYPE && - !! params.postId && - params.canvas === 'edit' - ); - }, - areas: { - sidebar: ( - <SidebarNavigationScreenNavigationMenu - backPath={ { postType: NAVIGATION_POST_TYPE } } - /> - ), - preview: <Editor />, - mobile: <Editor />, - }, -}; diff --git a/packages/edit-site/src/components/site-editor-routes/navigation-item-view.js b/packages/edit-site/src/components/site-editor-routes/navigation-item-view.js deleted file mode 100644 index d04a03a8f9df38..00000000000000 --- a/packages/edit-site/src/components/site-editor-routes/navigation-item-view.js +++ /dev/null @@ -1,25 +0,0 @@ -/** - * Internal dependencies - */ -import { NAVIGATION_POST_TYPE } from '../../utils/constants'; -import Editor from '../editor'; -import SidebarNavigationScreenNavigationMenu from '../sidebar-navigation-screen-navigation-menu'; - -export const navigationItemViewRoute = { - name: 'navigation-item-view', - match: ( params ) => { - return ( - params.postType === NAVIGATION_POST_TYPE && - !! params.postId && - params.canvas !== 'edit' - ); - }, - areas: { - sidebar: ( - <SidebarNavigationScreenNavigationMenu - backPath={ { postType: NAVIGATION_POST_TYPE } } - /> - ), - preview: <Editor />, - }, -}; diff --git a/packages/edit-site/src/components/site-editor-routes/navigation-item.js b/packages/edit-site/src/components/site-editor-routes/navigation-item.js new file mode 100644 index 00000000000000..76983d8ff8daa4 --- /dev/null +++ b/packages/edit-site/src/components/site-editor-routes/navigation-item.js @@ -0,0 +1,39 @@ +/** + * WordPress dependencies + */ +import { privateApis as routerPrivateApis } from '@wordpress/router'; + +/** + * Internal dependencies + */ +import { NAVIGATION_POST_TYPE } from '../../utils/constants'; +import Editor from '../editor'; +import SidebarNavigationScreenNavigationMenu from '../sidebar-navigation-screen-navigation-menu'; +import { unlock } from '../../lock-unlock'; + +const { useLocation } = unlock( routerPrivateApis ); + +function MobileNavigationItemView() { + const { query = {} } = useLocation(); + const { canvas = 'view' } = query; + + return canvas === 'edit' ? ( + <Editor /> + ) : ( + <SidebarNavigationScreenNavigationMenu + backPath={ { postType: NAVIGATION_POST_TYPE } } + /> + ); +} + +export const navigationItemRoute = { + name: 'navigation-item', + path: '/wp_navigation/:postId', + areas: { + sidebar: ( + <SidebarNavigationScreenNavigationMenu backPath="/navigation" /> + ), + preview: <Editor />, + mobile: <MobileNavigationItemView />, + }, +}; diff --git a/packages/edit-site/src/components/site-editor-routes/navigation-view.js b/packages/edit-site/src/components/site-editor-routes/navigation-view.js deleted file mode 100644 index 59c38a2f1d099a..00000000000000 --- a/packages/edit-site/src/components/site-editor-routes/navigation-view.js +++ /dev/null @@ -1,21 +0,0 @@ -/** - * Internal dependencies - */ -import { NAVIGATION_POST_TYPE } from '../../utils/constants'; -import Editor from '../editor'; -import SidebarNavigationScreenNavigationMenus from '../sidebar-navigation-screen-navigation-menus'; - -export const navigationViewRoute = { - name: 'navigation-view', - match: ( params ) => { - return ( - params.postType === NAVIGATION_POST_TYPE && - ! params.postId && - params.canvas !== 'edit' - ); - }, - areas: { - sidebar: <SidebarNavigationScreenNavigationMenus backPath={ {} } />, - preview: <Editor />, - }, -}; diff --git a/packages/edit-site/src/components/site-editor-routes/navigation.js b/packages/edit-site/src/components/site-editor-routes/navigation.js new file mode 100644 index 00000000000000..4c435e78a495f2 --- /dev/null +++ b/packages/edit-site/src/components/site-editor-routes/navigation.js @@ -0,0 +1,34 @@ +/** + * WordPress dependencies + */ +import { privateApis as routerPrivateApis } from '@wordpress/router'; + +/** + * Internal dependencies + */ +import Editor from '../editor'; +import SidebarNavigationScreenNavigationMenus from '../sidebar-navigation-screen-navigation-menus'; +import { unlock } from '../../lock-unlock'; + +const { useLocation } = unlock( routerPrivateApis ); + +function MobileNavigationView() { + const { query = {} } = useLocation(); + const { canvas = 'view' } = query; + + return canvas === 'edit' ? ( + <Editor /> + ) : ( + <SidebarNavigationScreenNavigationMenus backPath="/" /> + ); +} + +export const navigationRoute = { + name: 'navigation', + path: '/navigation', + areas: { + sidebar: <SidebarNavigationScreenNavigationMenus backPath="/" />, + preview: <Editor />, + mobile: <MobileNavigationView />, + }, +}; diff --git a/packages/edit-site/src/components/site-editor-routes/pages-edit.js b/packages/edit-site/src/components/site-editor-routes/page-item.js similarity index 54% rename from packages/edit-site/src/components/site-editor-routes/pages-edit.js rename to packages/edit-site/src/components/site-editor-routes/page-item.js index ef4c7efbfb09c2..c20720316b10e0 100644 --- a/packages/edit-site/src/components/site-editor-routes/pages-edit.js +++ b/packages/edit-site/src/components/site-editor-routes/page-item.js @@ -6,29 +6,21 @@ import { __ } from '@wordpress/i18n'; /** * Internal dependencies */ -import PostList from '../post-list'; +import Editor from '../editor'; import DataViewsSidebarContent from '../sidebar-dataviews'; import SidebarNavigationScreen from '../sidebar-navigation-screen'; -import Editor from '../editor'; -function PageList() { - return <PostList postType="page" />; -} - -export const pagesEditRoute = { - name: 'pages-edit', - match: ( params ) => { - return params.postType === 'page' && params.canvas === 'edit'; - }, +export const pageItemRoute = { + name: 'page-item', + path: '/page/:postId', areas: { sidebar: ( <SidebarNavigationScreen title={ __( 'Pages' ) } - backPath={ {} } - content={ <DataViewsSidebarContent /> } + backPath="/" + content={ <DataViewsSidebarContent postType="page" /> } /> ), - content: <PageList />, mobile: <Editor />, preview: <Editor />, }, diff --git a/packages/edit-site/src/components/site-editor-routes/pages-list-view-quick-edit.js b/packages/edit-site/src/components/site-editor-routes/pages-list-view-quick-edit.js deleted file mode 100644 index 9eb33e05a99bb0..00000000000000 --- a/packages/edit-site/src/components/site-editor-routes/pages-list-view-quick-edit.js +++ /dev/null @@ -1,56 +0,0 @@ -/** - * WordPress dependencies - */ -import { __ } from '@wordpress/i18n'; -import { privateApis as routerPrivateApis } from '@wordpress/router'; - -/** - * Internal dependencies - */ -import PostList from '../post-list'; -import DataViewsSidebarContent from '../sidebar-dataviews'; -import SidebarNavigationScreen from '../sidebar-navigation-screen'; -import { unlock } from '../../lock-unlock'; -import { PostEdit } from '../post-edit'; -import Editor from '../editor'; - -const { useLocation } = unlock( routerPrivateApis ); - -function PageList() { - return <PostList postType="page" />; -} - -function PageQuickEdit() { - const { params } = useLocation(); - return <PostEdit postType="page" postId={ params.postId } />; -} - -export const pagesListViewQuickEditRoute = { - name: 'pages-list-view-quick-edit', - match: ( params ) => { - return ( - params.isCustom !== 'true' && - ( params.layout ?? 'list' ) === 'list' && - !! params.quickEdit && - params.postType === 'page' && - params.canvas !== 'edit' - ); - }, - areas: { - sidebar: ( - <SidebarNavigationScreen - title={ __( 'Pages' ) } - backPath={ {} } - content={ <DataViewsSidebarContent /> } - /> - ), - content: <PageList />, - mobile: <PageList />, - preview: <Editor />, - edit: <PageQuickEdit />, - }, - widths: { - content: 380, - edit: 380, - }, -}; diff --git a/packages/edit-site/src/components/site-editor-routes/pages-list-view.js b/packages/edit-site/src/components/site-editor-routes/pages-list-view.js deleted file mode 100644 index 74b39848e83f2b..00000000000000 --- a/packages/edit-site/src/components/site-editor-routes/pages-list-view.js +++ /dev/null @@ -1,44 +0,0 @@ -/** - * WordPress dependencies - */ -import { __ } from '@wordpress/i18n'; - -/** - * Internal dependencies - */ -import PostList from '../post-list'; -import DataViewsSidebarContent from '../sidebar-dataviews'; -import SidebarNavigationScreen from '../sidebar-navigation-screen'; -import Editor from '../editor'; - -function PageList() { - return <PostList postType="page" />; -} - -export const pagesListViewRoute = { - name: 'pages-list-view', - match: ( params ) => { - return ( - params.isCustom !== 'true' && - ( params.layout ?? 'list' ) === 'list' && - ! params.quickEdit && - params.postType === 'page' && - params.canvas !== 'edit' - ); - }, - areas: { - sidebar: ( - <SidebarNavigationScreen - title={ __( 'Pages' ) } - backPath={ {} } - content={ <DataViewsSidebarContent /> } - /> - ), - content: <PageList />, - preview: <Editor />, - mobile: <PageList />, - }, - widths: { - content: 380, - }, -}; diff --git a/packages/edit-site/src/components/site-editor-routes/pages-view-quick-edit.js b/packages/edit-site/src/components/site-editor-routes/pages-view-quick-edit.js deleted file mode 100644 index 907054364c8a93..00000000000000 --- a/packages/edit-site/src/components/site-editor-routes/pages-view-quick-edit.js +++ /dev/null @@ -1,53 +0,0 @@ -/** - * WordPress dependencies - */ -import { __ } from '@wordpress/i18n'; -import { privateApis as routerPrivateApis } from '@wordpress/router'; - -/** - * Internal dependencies - */ -import PostList from '../post-list'; -import DataViewsSidebarContent from '../sidebar-dataviews'; -import SidebarNavigationScreen from '../sidebar-navigation-screen'; -import { unlock } from '../../lock-unlock'; -import { PostEdit } from '../post-edit'; - -const { useLocation } = unlock( routerPrivateApis ); - -function PageList() { - return <PostList postType="page" />; -} - -function PageQuickEdit() { - const { params } = useLocation(); - return <PostEdit postType="page" postId={ params.postId } />; -} - -export const pagesViewQuickEditRoute = { - name: 'pages-view-quick-edit', - match: ( params ) => { - return ( - ( params.isCustom === 'true' || - ( params.layout ?? 'list' ) !== 'list' ) && - !! params.quickEdit && - params.postType === 'page' && - params.canvas !== 'edit' - ); - }, - areas: { - sidebar: ( - <SidebarNavigationScreen - title={ __( 'Pages' ) } - backPath={ {} } - content={ <DataViewsSidebarContent /> } - /> - ), - content: <PageList />, - mobile: <PageList />, - edit: <PageQuickEdit />, - }, - widths: { - edit: 380, - }, -}; diff --git a/packages/edit-site/src/components/site-editor-routes/pages-view.js b/packages/edit-site/src/components/site-editor-routes/pages-view.js deleted file mode 100644 index df7e211022cacf..00000000000000 --- a/packages/edit-site/src/components/site-editor-routes/pages-view.js +++ /dev/null @@ -1,39 +0,0 @@ -/** - * WordPress dependencies - */ -import { __ } from '@wordpress/i18n'; - -/** - * Internal dependencies - */ -import PostList from '../post-list'; -import DataViewsSidebarContent from '../sidebar-dataviews'; -import SidebarNavigationScreen from '../sidebar-navigation-screen'; - -function PageList() { - return <PostList postType="page" />; -} - -export const pagesViewRoute = { - name: 'pages-view', - match: ( params ) => { - return ( - ( params.isCustom === 'true' || - ( params.layout ?? 'list' ) !== 'list' ) && - ! params.quickEdit && - params.postType === 'page' && - params.canvas !== 'edit' - ); - }, - areas: { - sidebar: ( - <SidebarNavigationScreen - title={ __( 'Pages' ) } - backPath={ {} } - content={ <DataViewsSidebarContent /> } - /> - ), - content: <PageList />, - mobile: <PageList />, - }, -}; diff --git a/packages/edit-site/src/components/site-editor-routes/pages.js b/packages/edit-site/src/components/site-editor-routes/pages.js new file mode 100644 index 00000000000000..5f2a4e341e0dc7 --- /dev/null +++ b/packages/edit-site/src/components/site-editor-routes/pages.js @@ -0,0 +1,66 @@ +/** + * WordPress dependencies + */ +import { privateApis as routerPrivateApis } from '@wordpress/router'; +import { __ } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import Editor from '../editor'; +import SidebarNavigationScreen from '../sidebar-navigation-screen'; +import DataViewsSidebarContent from '../sidebar-dataviews'; +import PostList from '../post-list'; +import { unlock } from '../../lock-unlock'; +import { PostEdit } from '../post-edit'; + +const { useLocation } = unlock( routerPrivateApis ); + +function MobilePagesView() { + const { query = {} } = useLocation(); + const { canvas = 'view' } = query; + + return canvas === 'edit' ? <Editor /> : <PostList postType="page" />; +} + +export const pagesRoute = { + name: 'pages', + path: '/page', + areas: { + sidebar: ( + <SidebarNavigationScreen + title={ __( 'Pages' ) } + backPath="/" + content={ <DataViewsSidebarContent postType="page" /> } + /> + ), + content: <PostList postType="page" />, + preview( { query } ) { + const isListView = + ( query.layout === 'list' || ! query.layout ) && + query.isCustom !== 'true'; + return isListView ? <Editor /> : undefined; + }, + mobile: <MobilePagesView />, + edit( { query } ) { + const hasQuickEdit = + ( query.layout ?? 'list' ) !== 'list' && !! query.quickEdit; + return hasQuickEdit ? ( + <PostEdit postType="page" postId={ query.postId } /> + ) : undefined; + }, + }, + widths: { + content( { query } ) { + const isListView = + ( query.layout === 'list' || ! query.layout ) && + query.isCustom !== 'true'; + return isListView ? 380 : undefined; + }, + edit( { query } ) { + const hasQuickEdit = + ( query.layout ?? 'list' ) !== 'list' && !! query.quickEdit; + return hasQuickEdit ? 380 : undefined; + }, + }, +}; diff --git a/packages/edit-site/src/components/site-editor-routes/pattern-item.js b/packages/edit-site/src/components/site-editor-routes/pattern-item.js new file mode 100644 index 00000000000000..c4cbcf871f3686 --- /dev/null +++ b/packages/edit-site/src/components/site-editor-routes/pattern-item.js @@ -0,0 +1,15 @@ +/** + * Internal dependencies + */ +import Editor from '../editor'; +import SidebarNavigationScreenPatterns from '../sidebar-navigation-screen-patterns'; + +export const patternItemRoute = { + name: 'pattern-item', + path: '/wp_block/:postId', + areas: { + sidebar: <SidebarNavigationScreenPatterns backPath="/" />, + mobile: <Editor />, + preview: <Editor />, + }, +}; diff --git a/packages/edit-site/src/components/site-editor-routes/patterns-edit.js b/packages/edit-site/src/components/site-editor-routes/patterns-edit.js deleted file mode 100644 index eaf1fd68020181..00000000000000 --- a/packages/edit-site/src/components/site-editor-routes/patterns-edit.js +++ /dev/null @@ -1,24 +0,0 @@ -/** - * Internal dependencies - */ -import Editor from '../editor'; -import SidebarNavigationScreenPatterns from '../sidebar-navigation-screen-patterns'; -import PagePatterns from '../page-patterns'; -import { PATTERN_TYPES, TEMPLATE_PART_POST_TYPE } from '../../utils/constants'; - -export const patternsEditRoute = { - name: 'patterns-edit', - match: ( params ) => { - return ( - [ TEMPLATE_PART_POST_TYPE, PATTERN_TYPES.user ].includes( - params.postType - ) && params.canvas === 'edit' - ); - }, - areas: { - sidebar: <SidebarNavigationScreenPatterns backPath={ {} } />, - content: <PagePatterns />, - mobile: <Editor />, - preview: <Editor />, - }, -}; diff --git a/packages/edit-site/src/components/site-editor-routes/patterns-view.js b/packages/edit-site/src/components/site-editor-routes/patterns-view.js deleted file mode 100644 index 468f7f14abc139..00000000000000 --- a/packages/edit-site/src/components/site-editor-routes/patterns-view.js +++ /dev/null @@ -1,22 +0,0 @@ -/** - * Internal dependencies - */ -import SidebarNavigationScreenPatterns from '../sidebar-navigation-screen-patterns'; -import PagePatterns from '../page-patterns'; -import { PATTERN_TYPES, TEMPLATE_PART_POST_TYPE } from '../../utils/constants'; - -export const patternsViewRoute = { - name: 'patterns-view', - match: ( params ) => { - return ( - [ TEMPLATE_PART_POST_TYPE, PATTERN_TYPES.user ].includes( - params.postType - ) && params.canvas !== 'edit' - ); - }, - areas: { - sidebar: <SidebarNavigationScreenPatterns backPath={ {} } />, - content: <PagePatterns />, - mobile: <PagePatterns />, - }, -}; diff --git a/packages/edit-site/src/components/site-editor-routes/patterns.js b/packages/edit-site/src/components/site-editor-routes/patterns.js new file mode 100644 index 00000000000000..db97c4b5c080fd --- /dev/null +++ b/packages/edit-site/src/components/site-editor-routes/patterns.js @@ -0,0 +1,15 @@ +/** + * Internal dependencies + */ +import SidebarNavigationScreenPatterns from '../sidebar-navigation-screen-patterns'; +import PagePatterns from '../page-patterns'; + +export const patternsRoute = { + name: 'patterns', + path: '/pattern', + areas: { + sidebar: <SidebarNavigationScreenPatterns backPath="/" />, + content: <PagePatterns />, + mobile: <PagePatterns />, + }, +}; diff --git a/packages/edit-site/src/components/site-editor-routes/stylebook.js b/packages/edit-site/src/components/site-editor-routes/stylebook.js new file mode 100644 index 00000000000000..cb1e414098ab3f --- /dev/null +++ b/packages/edit-site/src/components/site-editor-routes/stylebook.js @@ -0,0 +1,28 @@ +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import SidebarNavigationScreen from '../sidebar-navigation-screen'; +import { StyleBookPreview } from '../style-book'; + +export const stylebookRoute = { + name: 'stylebook', + path: '/stylebook', + areas: { + sidebar: ( + <SidebarNavigationScreen + title={ __( 'Styles' ) } + backPath="/" + description={ __( + `Preview your website's visual identity: colors, typography, and blocks.` + ) } + /> + ), + preview: <StyleBookPreview isStatic />, + mobile: <StyleBookPreview isStatic />, + }, +}; diff --git a/packages/edit-site/src/components/site-editor-routes/styles-edit.js b/packages/edit-site/src/components/site-editor-routes/styles-edit.js deleted file mode 100644 index e8225a8f526ebd..00000000000000 --- a/packages/edit-site/src/components/site-editor-routes/styles-edit.js +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Internal dependencies - */ -import Editor from '../editor'; -import SidebarNavigationScreenGlobalStyles from '../sidebar-navigation-screen-global-styles'; -import GlobalStylesUIWrapper from '../sidebar-global-styles-wrapper'; - -export const stylesEditRoute = { - name: 'styles-edit', - match: ( params ) => { - return ( - params.path && - params.path.startsWith( '/wp_global_styles' ) && - params.canvas !== 'edit' - ); - }, - areas: { - content: <GlobalStylesUIWrapper />, - sidebar: <SidebarNavigationScreenGlobalStyles backPath={ {} } />, - preview: <Editor />, - mobile: <Editor />, - }, - widths: { - content: 380, - }, -}; diff --git a/packages/edit-site/src/components/site-editor-routes/styles-view.js b/packages/edit-site/src/components/site-editor-routes/styles-view.js deleted file mode 100644 index cc9411eb8144c0..00000000000000 --- a/packages/edit-site/src/components/site-editor-routes/styles-view.js +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Internal dependencies - */ -import Editor from '../editor'; -import SidebarNavigationScreenGlobalStyles from '../sidebar-navigation-screen-global-styles'; -import GlobalStylesUIWrapper from '../sidebar-global-styles-wrapper'; - -export const stylesViewRoute = { - name: 'styles-view', - match: ( params ) => { - return ( - params.path && - params.path.startsWith( '/wp_global_styles' ) && - params.canvas !== 'edit' - ); - }, - areas: { - content: <GlobalStylesUIWrapper />, - sidebar: <SidebarNavigationScreenGlobalStyles backPath={ {} } />, - preview: <Editor />, - mobile: <GlobalStylesUIWrapper />, - }, - widths: { - content: 380, - }, -}; diff --git a/packages/edit-site/src/components/site-editor-routes/styles.js b/packages/edit-site/src/components/site-editor-routes/styles.js new file mode 100644 index 00000000000000..a1827bee763390 --- /dev/null +++ b/packages/edit-site/src/components/site-editor-routes/styles.js @@ -0,0 +1,43 @@ +/** + * WordPress dependencies + */ +import { privateApis as routerPrivateApis } from '@wordpress/router'; + +/** + * Internal dependencies + */ +import Editor from '../editor'; +import { unlock } from '../../lock-unlock'; +import SidebarNavigationScreenGlobalStyles from '../sidebar-navigation-screen-global-styles'; +import GlobalStylesUIWrapper from '../sidebar-global-styles-wrapper'; +import { StyleBookPreview } from '../style-book'; + +const { useLocation } = unlock( routerPrivateApis ); + +function MobileGlobalStylesUI() { + const { query = {} } = useLocation(); + const { canvas } = query; + + if ( canvas === 'edit' ) { + return <Editor />; + } + + return <GlobalStylesUIWrapper />; +} + +export const stylesRoute = { + name: 'styles', + path: '/styles', + areas: { + content: <GlobalStylesUIWrapper />, + sidebar: <SidebarNavigationScreenGlobalStyles backPath="/" />, + preview( { query } ) { + const isStylebook = query.preview === 'stylebook'; + return isStylebook ? <StyleBookPreview /> : <Editor />; + }, + mobile: <MobileGlobalStylesUI />, + }, + widths: { + content: 380, + }, +}; diff --git a/packages/edit-site/src/components/site-editor-routes/template-item.js b/packages/edit-site/src/components/site-editor-routes/template-item.js new file mode 100644 index 00000000000000..22726f6a5ac430 --- /dev/null +++ b/packages/edit-site/src/components/site-editor-routes/template-item.js @@ -0,0 +1,15 @@ +/** + * Internal dependencies + */ +import { MaybeEditor } from '../maybe-editor'; +import SidebarNavigationScreenTemplatesBrowse from '../sidebar-navigation-screen-templates-browse'; + +export const templateItemRoute = { + name: 'template-item', + path: '/wp_template/*postId', + areas: { + sidebar: <SidebarNavigationScreenTemplatesBrowse backPath="/" />, + mobile: <MaybeEditor />, + preview: <MaybeEditor />, + }, +}; diff --git a/packages/edit-site/src/components/site-editor-routes/template-part-item.js b/packages/edit-site/src/components/site-editor-routes/template-part-item.js new file mode 100644 index 00000000000000..e98af337b1c669 --- /dev/null +++ b/packages/edit-site/src/components/site-editor-routes/template-part-item.js @@ -0,0 +1,15 @@ +/** + * Internal dependencies + */ +import { MaybeEditor } from '../maybe-editor'; +import SidebarNavigationScreenPatterns from '../sidebar-navigation-screen-patterns'; + +export const templatePartItemRoute = { + name: 'template-part-item', + path: '/wp_template_part/*postId', + areas: { + sidebar: <SidebarNavigationScreenPatterns backPath="/" />, + mobile: <MaybeEditor />, + preview: <MaybeEditor />, + }, +}; diff --git a/packages/edit-site/src/components/site-editor-routes/templates-edit.js b/packages/edit-site/src/components/site-editor-routes/templates-edit.js deleted file mode 100644 index 488e9decc1888c..00000000000000 --- a/packages/edit-site/src/components/site-editor-routes/templates-edit.js +++ /dev/null @@ -1,22 +0,0 @@ -/** - * Internal dependencies - */ -import { TEMPLATE_POST_TYPE } from '../../utils/constants'; -import PageTemplates from '../page-templates'; -import Editor from '../editor'; -import SidebarNavigationScreenTemplatesBrowse from '../sidebar-navigation-screen-templates-browse'; - -export const templatesEditRoute = { - name: 'templates-edit', - match: ( params ) => { - return ( - params.postType === TEMPLATE_POST_TYPE && params.canvas === 'edit' - ); - }, - areas: { - sidebar: <SidebarNavigationScreenTemplatesBrowse backPath={ {} } />, - content: <PageTemplates />, - mobile: <Editor />, - preview: <Editor />, - }, -}; diff --git a/packages/edit-site/src/components/site-editor-routes/templates-list-view.js b/packages/edit-site/src/components/site-editor-routes/templates-list-view.js deleted file mode 100644 index 7cdda1b13c0b47..00000000000000 --- a/packages/edit-site/src/components/site-editor-routes/templates-list-view.js +++ /dev/null @@ -1,28 +0,0 @@ -/** - * Internal dependencies - */ -import { TEMPLATE_POST_TYPE } from '../../utils/constants'; -import PageTemplates from '../page-templates'; -import Editor from '../editor'; -import SidebarNavigationScreenTemplatesBrowse from '../sidebar-navigation-screen-templates-browse'; - -export const templatesListViewRoute = { - name: 'templates-list-view', - match: ( params ) => { - return ( - params.isCustom !== 'true' && - params.layout === 'list' && - params.postType === TEMPLATE_POST_TYPE && - params.canvas !== 'edit' - ); - }, - areas: { - sidebar: <SidebarNavigationScreenTemplatesBrowse backPath={ {} } />, - content: <PageTemplates />, - mobile: <PageTemplates />, - preview: <Editor />, - }, - widths: { - content: 380, - }, -}; diff --git a/packages/edit-site/src/components/site-editor-routes/templates-view.js b/packages/edit-site/src/components/site-editor-routes/templates-view.js deleted file mode 100644 index 40fd88c0e60a61..00000000000000 --- a/packages/edit-site/src/components/site-editor-routes/templates-view.js +++ /dev/null @@ -1,22 +0,0 @@ -/** - * Internal dependencies - */ -import { TEMPLATE_POST_TYPE } from '../../utils/constants'; -import PageTemplates from '../page-templates'; -import SidebarNavigationScreenTemplatesBrowse from '../sidebar-navigation-screen-templates-browse'; - -export const templatesViewRoute = { - name: 'templates-view', - match: ( params ) => { - return ( - ( params.isCustom === 'true' || params.layout !== 'list' ) && - params.postType === TEMPLATE_POST_TYPE && - params.canvas !== 'edit' - ); - }, - areas: { - sidebar: <SidebarNavigationScreenTemplatesBrowse backPath={ {} } />, - content: <PageTemplates />, - mobile: <PageTemplates />, - }, -}; diff --git a/packages/edit-site/src/components/site-editor-routes/templates.js b/packages/edit-site/src/components/site-editor-routes/templates.js new file mode 100644 index 00000000000000..cb9f650bbf9267 --- /dev/null +++ b/packages/edit-site/src/components/site-editor-routes/templates.js @@ -0,0 +1,26 @@ +/** + * Internal dependencies + */ +import Editor from '../editor'; +import SidebarNavigationScreenTemplatesBrowse from '../sidebar-navigation-screen-templates-browse'; +import PageTemplates from '../page-templates'; + +export const templatesRoute = { + name: 'templates', + path: '/template', + areas: { + sidebar: <SidebarNavigationScreenTemplatesBrowse backPath="/" />, + content: <PageTemplates />, + preview( { query } ) { + const isListView = query.layout === 'list'; + return isListView ? <Editor /> : undefined; + }, + mobile: <PageTemplates />, + }, + widths: { + content( { query } ) { + const isListView = query.layout === 'list'; + return isListView ? 380 : undefined; + }, + }, +}; diff --git a/packages/edit-site/src/components/site-hub/index.js b/packages/edit-site/src/components/site-hub/index.js index 9e57034bfe73aa..91324356d01975 100644 --- a/packages/edit-site/src/components/site-hub/index.js +++ b/packages/edit-site/src/components/site-hub/index.js @@ -39,8 +39,7 @@ const SiteHub = memo( const { getEntityRecord } = select( coreStore ); const _site = getEntityRecord( 'root', 'site' ); return { - dashboardLink: - getSettings().__experimentalDashboardLink || 'index.php', + dashboardLink: getSettings().__experimentalDashboardLink, homeUrl: getEntityRecord( 'root', '__unstableBase' )?.home, siteTitle: ! _site?.title && !! _site?.url @@ -129,9 +128,7 @@ export const SiteHubMobile = memo( select( coreStore ); const _site = getEntityRecord( 'root', 'site' ); return { - dashboardLink: - getSettings().__experimentalDashboardLink || - 'index.php', + dashboardLink: getSettings().__experimentalDashboardLink, isBlockTheme: getCurrentTheme()?.is_block_theme, homeUrl: getEntityRecord( 'root', '__unstableBase' )?.home, siteTitle: @@ -170,7 +167,7 @@ export const SiteHubMobile = memo( } : { onClick: () => { - history.push( {} ); + history.navigate( '/' ); navigate( 'back' ); }, label: __( 'Go to Site Editor' ), diff --git a/packages/edit-site/src/components/site-hub/style.scss b/packages/edit-site/src/components/site-hub/style.scss index b1c1a3a41cc532..4099f7064ff05f 100644 --- a/packages/edit-site/src/components/site-hub/style.scss +++ b/packages/edit-site/src/components/site-hub/style.scss @@ -4,7 +4,7 @@ justify-content: space-between; gap: $grid-unit-10; margin-right: $grid-unit-15; - height: $grid-unit-70; + height: $header-height; } .edit-site-site-hub__actions { @@ -65,8 +65,10 @@ opacity: 0; position: absolute; right: 0; - transition: opacity 0.1s linear; - @include reduce-motion("transition"); + + @media not (prefers-reduced-motion) { + transition: opacity 0.1s linear; + } } &:hover::after, diff --git a/packages/edit-site/src/components/style-book/categories.ts b/packages/edit-site/src/components/style-book/categories.ts index 2c1b627c6d0c60..b36c211eaa546c 100644 --- a/packages/edit-site/src/components/style-book/categories.ts +++ b/packages/edit-site/src/components/style-book/categories.ts @@ -1,6 +1,8 @@ /** * WordPress dependencies */ +// @wordpress/blocks imports are not typed. +// @ts-expect-error import { getCategories } from '@wordpress/blocks'; /** @@ -29,15 +31,19 @@ export function getExamplesByCategory( if ( ! categoryDefinition?.slug || ! examples?.length ) { return; } - - if ( categoryDefinition?.subcategories?.length ) { - return categoryDefinition.subcategories.reduce( + const categories: CategoryExamples[] = + categoryDefinition?.subcategories ?? []; + if ( categories.length ) { + return categories.reduce( ( acc, subcategoryDefinition ) => { const subcategoryExamples = getExamplesByCategory( subcategoryDefinition, examples ); if ( subcategoryExamples ) { + if ( ! acc.subcategories ) { + acc.subcategories = []; + } acc.subcategories = [ ...acc.subcategories, subcategoryExamples, @@ -48,7 +54,6 @@ export function getExamplesByCategory( { title: categoryDefinition.title, slug: categoryDefinition.slug, - subcategories: [], } ); } @@ -84,8 +89,9 @@ export function getTopLevelStyleBookCategories(): StyleBookCategory[] { ...STYLE_BOOK_THEME_SUBCATEGORIES, ...STYLE_BOOK_CATEGORIES, ].map( ( { slug } ) => slug ); - const extraCategories = getCategories().filter( + const extraCategories: StyleBookCategory[] = getCategories(); + const extraCategoriesFiltered = extraCategories.filter( ( { slug } ) => ! reservedCategories.includes( slug ) ); - return [ ...STYLE_BOOK_CATEGORIES, ...extraCategories ]; + return [ ...STYLE_BOOK_CATEGORIES, ...extraCategoriesFiltered ]; } diff --git a/packages/edit-site/src/components/style-book/color-examples.tsx b/packages/edit-site/src/components/style-book/color-examples.tsx index 97bdeecee32d3d..032a3d92faa2bf 100644 --- a/packages/edit-site/src/components/style-book/color-examples.tsx +++ b/packages/edit-site/src/components/style-book/color-examples.tsx @@ -11,20 +11,27 @@ import { View } from '@wordpress/primitives'; import { getColorClassName, __experimentalGetGradientClass, + // @wordpress/block-editor imports are not typed. + // @ts-expect-error } from '@wordpress/block-editor'; /** * Internal dependencies */ -import type { Color, Gradient } from './types'; +import type { Color, Gradient, ColorExampleProps } from './types'; -const ColorExamples = ( { colors, type } ): JSX.Element | null => { +const ColorExamples = ( { + colors, + type, + templateColumns = '1fr 1fr', + itemHeight = '52px', +}: ColorExampleProps ): JSX.Element | null => { if ( ! colors ) { return null; } return ( - <Grid columns={ 2 } rowGap={ 8 } columnGap={ 16 }> + <Grid templateColumns={ templateColumns } rowGap={ 8 } columnGap={ 16 }> { colors.map( ( color: Color | Gradient ) => { const className = type === 'gradients' @@ -35,7 +42,13 @@ const ColorExamples = ( { colors, type } ): JSX.Element | null => { className ); - return <View key={ color.slug } className={ classes } />; + return ( + <View + key={ color.slug } + className={ classes } + style={ { height: itemHeight } } + /> + ); } ) } </Grid> ); diff --git a/packages/edit-site/src/components/style-book/constants.ts b/packages/edit-site/src/components/style-book/constants.ts index 7b13e3d4ef7f60..284816a55da907 100644 --- a/packages/edit-site/src/components/style-book/constants.ts +++ b/packages/edit-site/src/components/style-book/constants.ts @@ -148,6 +148,55 @@ export const STYLE_BOOK_CATEGORIES: StyleBookCategory[] = [ }, ]; +// Style book preview subcategories for all blocks section. +export const STYLE_BOOK_ALL_BLOCKS_SUBCATEGORIES: StyleBookCategory[] = [ + ...STYLE_BOOK_THEME_SUBCATEGORIES, + { + slug: 'media', + title: __( 'Media' ), + blocks: [ 'core/post-featured-image' ], + }, + { + slug: 'widgets', + title: __( 'Widgets' ), + blocks: [], + }, + { + slug: 'embed', + title: __( 'Embeds' ), + include: [], + }, +]; + +// Style book preview categories are organized slightly differently to the editor ones. +export const STYLE_BOOK_PREVIEW_CATEGORIES: StyleBookCategory[] = [ + { + slug: 'overview', + title: __( 'Overview' ), + blocks: [], + }, + { + slug: 'text', + title: __( 'Text' ), + blocks: [ + 'core/post-content', + 'core/home-link', + 'core/navigation-link', + ], + }, + { + slug: 'colors', + title: __( 'Colors' ), + blocks: [], + }, + { + slug: 'blocks', + title: __( 'All Blocks' ), + blocks: [], + subcategories: STYLE_BOOK_ALL_BLOCKS_SUBCATEGORIES, + }, +]; + // Forming a "block formatting context" to prevent margin collapsing. // @see https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Block_formatting_context const ROOT_CONTAINER = ` @@ -216,48 +265,35 @@ export const STYLE_BOOK_IFRAME_STYLES = ` } .edit-site-style-book__duotone-example > div:not(:first-child) { height: 20px; - border: 1px solid #ddd; + border: 1px solid color-mix( in srgb, currentColor 10%, transparent ); } .edit-site-style-book__color-example { - height: 52px; - border: 1px solid #ddd; - } - - .edit-site-style-book__examples.is-wide .edit-site-style-book__example { - flex-direction: row; + border: 1px solid color-mix( in srgb, currentColor 10%, transparent ); } .edit-site-style-book__subcategory-title, .edit-site-style-book__example-title { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; - font-size: 11px; - font-weight: 500; + font-size: 13px; + font-weight: normal; line-height: normal; margin: 0; text-align: left; - text-transform: uppercase; + padding-top: 8px; + border-top: 1px solid color-mix( in srgb, currentColor 10%, transparent ); + color: color-mix( in srgb, currentColor 60%, transparent ); } .edit-site-style-book__subcategory-title { font-size: 16px; margin-bottom: 40px; - border-bottom: 1px solid #ddd; padding-bottom: 8px; } - .edit-site-style-book__examples.is-wide .edit-site-style-book__example-title { - text-align: right; - width: 120px; - } - .edit-site-style-book__example-preview { width: 100%; } - - .is-wide .edit-site-style-book__example-preview { - width: calc(100% - 120px); - } .edit-site-style-book__example-preview .block-editor-block-list__insertion-point, .edit-site-style-book__example-preview .block-list-appender { diff --git a/packages/edit-site/src/components/style-book/duotone-examples.tsx b/packages/edit-site/src/components/style-book/duotone-examples.tsx index 7ee90e61f1c6aa..babba4328bcc21 100644 --- a/packages/edit-site/src/components/style-book/duotone-examples.tsx +++ b/packages/edit-site/src/components/style-book/duotone-examples.tsx @@ -9,7 +9,11 @@ import { View } from '@wordpress/primitives'; */ import type { Duotone } from './types'; -const DuotoneExamples = ( { duotones } ): JSX.Element | null => { +const DuotoneExamples = ( { + duotones, +}: { + duotones: Duotone[]; +} ): JSX.Element | null => { if ( ! duotones ) { return null; } diff --git a/packages/edit-site/src/components/style-book/examples.tsx b/packages/edit-site/src/components/style-book/examples.tsx index 2fefe227ca52b7..046f08524851eb 100644 --- a/packages/edit-site/src/components/style-book/examples.tsx +++ b/packages/edit-site/src/components/style-book/examples.tsx @@ -7,12 +7,19 @@ import { getBlockTypes, getBlockFromExample, createBlock, + // @wordpress/blocks imports are not typed. + // @ts-expect-error } from '@wordpress/blocks'; /** * Internal dependencies */ -import type { BlockExample, ColorOrigin, MultiOriginPalettes } from './types'; +import type { + BlockExample, + ColorOrigin, + MultiOriginPalettes, + BlockType, +} from './types'; import ColorExamples from './color-examples'; import DuotoneExamples from './duotone-examples'; import { STYLE_BOOK_COLOR_GROUPS } from './constants'; @@ -32,11 +39,14 @@ function getColorExamples( colors: MultiOriginPalettes ): BlockExample[] { const examples: BlockExample[] = []; STYLE_BOOK_COLOR_GROUPS.forEach( ( group ) => { - const palette = colors[ group.type ].find( - ( origin: ColorOrigin ) => origin.slug === group.origin - ); + const palette = colors[ group.type as keyof MultiOriginPalettes ]; + const paletteFiltered = Array.isArray( palette ) + ? palette.find( + ( origin: ColorOrigin ) => origin.slug === group.origin + ) + : undefined; - if ( palette?.[ group.type ] ) { + if ( paletteFiltered?.[ group.type ] ) { const example: BlockExample = { name: group.slug, title: group.title, @@ -44,13 +54,15 @@ function getColorExamples( colors: MultiOriginPalettes ): BlockExample[] { }; if ( group.type === 'duotones' ) { example.content = ( - <DuotoneExamples duotones={ palette[ group.type ] } /> + <DuotoneExamples + duotones={ paletteFiltered[ group.type ] } + /> ); examples.push( example ); } else { example.content = ( <ColorExamples - colors={ palette[ group.type ] } + colors={ paletteFiltered[ group.type ] } type={ group.type } /> ); @@ -73,10 +85,12 @@ function getOverviewBlockExamples( ): BlockExample[] { const examples: BlockExample[] = []; - // Get theme palette from colors. - const themePalette = colors.colors.find( - ( origin: ColorOrigin ) => origin.slug === 'theme' - ); + // Get theme palette from colors if they exist. + const themePalette = Array.isArray( colors?.colors ) + ? colors.colors.find( + ( origin: ColorOrigin ) => origin.slug === 'theme' + ) + : undefined; if ( themePalette ) { const themeColorexample: BlockExample = { @@ -84,37 +98,45 @@ function getOverviewBlockExamples( title: __( 'Colors' ), category: 'overview', content: ( - <ColorExamples colors={ themePalette.colors } type={ colors } /> + <ColorExamples + colors={ themePalette.colors } + type="colors" + templateColumns="repeat(auto-fill, minmax( 200px, 1fr ))" + itemHeight="32px" + /> ), }; examples.push( themeColorexample ); } - const headingBlock = createBlock( 'core/heading', { - content: __( - `AaBbCcDdEeFfGgHhiiJjKkLIMmNnOoPpQakRrssTtUuVVWwXxxYyZzOl23356789X{(…)},2!*&:/A@HELFO™` - ), - level: 1, - } ); - const firstParagraphBlock = createBlock( 'core/paragraph', { - content: __( - `A paragraph in a website refers to a distinct block of text that is used to present and organize information. It is a fundamental unit of content in web design and is typically composed of a group of related sentences or thoughts focused on a particular topic or idea. Paragraphs play a crucial role in improving the readability and user experience of a website. They break down the text into smaller, manageable chunks, allowing readers to scan the content more easily.` - ), - } ); - const secondParagraphBlock = createBlock( 'core/paragraph', { - content: __( - `Additionally, paragraphs help structure the flow of information and provide logical breaks between different concepts or pieces of information. In terms of formatting, paragraphs in websites are commonly denoted by a vertical gap or indentation between each block of text. This visual separation helps visually distinguish one paragraph from another, creating a clear and organized layout that guides the reader through the content smoothly.` - ), - } ); + // Get examples for typography blocks. + const typographyBlockExamples: BlockType[] = []; + + if ( getBlockType( 'core/heading' ) ) { + const headingBlock = createBlock( 'core/heading', { + content: __( + `AaBbCcDdEeFfGgHhiiJjKkLIMmNnOoPpQakRrssTtUuVVWwXxxYyZzOl23356789X{(…)},2!*&:/A@HELFO™` + ), + level: 1, + } ); + typographyBlockExamples.push( headingBlock ); + } + + if ( getBlockType( 'core/paragraph' ) ) { + const firstParagraphBlock = createBlock( 'core/paragraph', { + content: __( + `A paragraph in a website refers to a distinct block of text that is used to present and organize information. It is a fundamental unit of content in web design and is typically composed of a group of related sentences or thoughts focused on a particular topic or idea. Paragraphs play a crucial role in improving the readability and user experience of a website. They break down the text into smaller, manageable chunks, allowing readers to scan the content more easily.` + ), + } ); + const secondParagraphBlock = createBlock( 'core/paragraph', { + content: __( + `Additionally, paragraphs help structure the flow of information and provide logical breaks between different concepts or pieces of information. In terms of formatting, paragraphs in websites are commonly denoted by a vertical gap or indentation between each block of text. This visual separation helps visually distinguish one paragraph from another, creating a clear and organized layout that guides the reader through the content smoothly.` + ), + } ); - const textExample = { - name: 'typography', - title: __( 'Typography' ), - category: 'overview', - blocks: [ - headingBlock, - createBlock( + if ( getBlockType( 'core/group' ) ) { + const groupBlock = createBlock( 'core/group', { layout: { @@ -129,10 +151,21 @@ function getOverviewBlockExamples( }, }, [ firstParagraphBlock, secondParagraphBlock ] - ), - ], - }; - examples.push( textExample ); + ); + typographyBlockExamples.push( groupBlock ); + } else { + typographyBlockExamples.push( firstParagraphBlock ); + } + } + + if ( !! typographyBlockExamples.length ) { + examples.push( { + name: 'typography', + title: __( 'Typography' ), + category: 'overview', + blocks: typographyBlockExamples, + } ); + } const otherBlockExamples = [ 'core/image', @@ -150,7 +183,18 @@ function getOverviewBlockExamples( name: blockName, title: blockType.title, category: 'overview', - blocks: getBlockFromExample( blockName, blockType.example ), + /* + * CSS generated from style attributes will take precedence over global styles CSS, + * so remove the style attribute from the example to ensure the example + * demonstrates changes to global styles. + */ + blocks: getBlockFromExample( blockName, { + ...blockType.example, + attributes: { + ...blockType.example.attributes, + style: undefined, + }, + } ), }; examples.push( blockExample ); } @@ -167,7 +211,7 @@ function getOverviewBlockExamples( */ export function getExamples( colors: MultiOriginPalettes ): BlockExample[] { const nonHeadingBlockExamples = getBlockTypes() - .filter( ( blockType ) => { + .filter( ( blockType: BlockType ) => { const { name, example, supports } = blockType; return ( name !== 'core/heading' && @@ -175,11 +219,22 @@ export function getExamples( colors: MultiOriginPalettes ): BlockExample[] { supports?.inserter !== false ); } ) - .map( ( blockType ) => ( { + .map( ( blockType: BlockType ) => ( { name: blockType.name, title: blockType.title, category: blockType.category, - blocks: getBlockFromExample( blockType.name, blockType.example ), + /* + * CSS generated from style attributes will take precedence over global styles CSS, + * so remove the style attribute from the example to ensure the example + * demonstrates changes to global styles. + */ + blocks: getBlockFromExample( blockType.name, { + ...blockType.example, + attributes: { + ...blockType.example.attributes, + style: undefined, + }, + } ), } ) ); const isHeadingBlockRegistered = !! getBlockType( 'core/heading' ); diff --git a/packages/edit-site/src/components/style-book/index.js b/packages/edit-site/src/components/style-book/index.js index de4c38bd40c05d..723953777e2b28 100644 --- a/packages/edit-site/src/components/style-book/index.js +++ b/packages/edit-site/src/components/style-book/index.js @@ -17,12 +17,13 @@ import { privateApis as blockEditorPrivateApis, store as blockEditorStore, useSettings, + BlockEditorProvider, __unstableEditorStyles as EditorStyles, __unstableIframe as Iframe, __experimentalUseMultipleOriginColorsAndGradients as useMultipleOriginColorsAndGradients, } from '@wordpress/block-editor'; import { privateApis as editorPrivateApis } from '@wordpress/editor'; -import { useSelect } from '@wordpress/data'; +import { useSelect, dispatch } from '@wordpress/data'; import { useResizeObserver } from '@wordpress/compose'; import { useMemo, @@ -31,8 +32,11 @@ import { useContext, useRef, useLayoutEffect, + useEffect, } from '@wordpress/element'; import { ENTER, SPACE } from '@wordpress/keycodes'; +import { uploadMedia } from '@wordpress/media-utils'; +import { store as coreStore } from '@wordpress/core-data'; /** * Internal dependencies @@ -45,6 +49,13 @@ import { getTopLevelStyleBookCategories, } from './categories'; import { getExamples } from './examples'; +import { store as siteEditorStore } from '../../store'; +import { useSection } from '../sidebar-global-styles-wrapper'; +import { GlobalStylesRenderer } from '../global-styles-renderer'; +import { + STYLE_BOOK_COLOR_GROUPS, + STYLE_BOOK_PREVIEW_CATEGORIES, +} from '../style-book/constants'; const { ExperimentalBlockEditorProvider, @@ -67,11 +78,14 @@ function isObjectEmpty( object ) { * @param {HTMLIFrameElement} iframe The target iframe. */ const scrollToSection = ( anchorId, iframe ) => { - if ( ! iframe || ! iframe?.contentDocument ) { + if ( ! anchorId || ! iframe || ! iframe?.contentDocument ) { return; } - const element = iframe.contentDocument.getElementById( anchorId ); + const element = + anchorId === 'top' + ? iframe.contentDocument.body + : iframe.contentDocument.getElementById( anchorId ); if ( element ) { element.scrollIntoView( { behavior: 'smooth', @@ -80,24 +94,24 @@ const scrollToSection = ( anchorId, iframe ) => { }; /** - * Parses a Block Editor navigation path to extract the block name and - * build a style book navigation path. The object can be extended to include a category, - * representing a style book tab/section. + * Parses a Block Editor navigation path to build a style book navigation path. + * The object can be extended to include a category, representing a style book tab/section. * * @param {string} path An internal Block Editor navigation path. * @return {null|{block: string}} An object containing the example to navigate to. */ const getStyleBookNavigationFromPath = ( path ) => { if ( path && typeof path === 'string' ) { - let block = path.includes( '/blocks/' ) - ? decodeURIComponent( path.split( '/blocks/' )[ 1 ] ) - : null; - // Default to theme-colors if the path ends with /colors. - block = path.endsWith( '/colors' ) ? 'theme-colors' : block; - - return { - block, - }; + if ( + path === '/' || + path.startsWith( '/typography' ) || + path.startsWith( '/colors' ) || + path.startsWith( '/blocks' ) + ) { + return { + top: true, + }; + } } return null; }; @@ -177,6 +191,31 @@ function useMultiOriginPalettes() { return palettes; } +/** + * Get deduped examples for single page stylebook. + * @param {Array} examples Array of examples. + * @return {Array} Deduped examples. + */ +export function getExamplesForSinglePageUse( examples ) { + const examplesForSinglePageUse = []; + const overviewCategoryExamples = getExamplesByCategory( + { slug: 'overview' }, + examples + ); + examplesForSinglePageUse.push( ...overviewCategoryExamples.examples ); + const otherExamples = examples.filter( ( example ) => { + return ( + example.category !== 'overview' && + ! overviewCategoryExamples.examples.find( + ( overviewExample ) => overviewExample.name === example.name + ) + ); + } ); + examplesForSinglePageUse.push( ...otherExamples ); + + return examplesForSinglePageUse; +} + function StyleBook( { enableResizing = true, isSelected, @@ -203,21 +242,7 @@ function StyleBook( { [ examples ] ); - const examplesForSinglePageUse = []; - const overviewCategoryExamples = getExamplesByCategory( - { slug: 'overview' }, - examples - ); - examplesForSinglePageUse.push( ...overviewCategoryExamples.examples ); - const otherExamples = examples.filter( ( example ) => { - return ( - example.category !== 'overview' && - ! overviewCategoryExamples.examples.find( - ( overviewExample ) => overviewExample.name === example.name - ) - ); - } ); - examplesForSinglePageUse.push( ...otherExamples ); + const examplesForSinglePageUse = getExamplesForSinglePageUse( examples ); const { base: baseConfig } = useContext( GlobalStylesContext ); const goTo = getStyleBookNavigationFromPath( path ); @@ -280,29 +305,43 @@ function StyleBook( { ) ) } </Tabs.TabList> </div> - { tabs.map( ( tab ) => ( - <Tabs.TabPanel - key={ tab.slug } - tabId={ tab.slug } - focusable={ false } - className="edit-site-style-book__tabpanel" - > - <StyleBookBody - category={ tab.slug } - examples={ examples } - isSelected={ isSelected } - onSelect={ onSelect } - settings={ settings } - sizes={ sizes } - title={ tab.title } - goTo={ goTo } - /> - </Tabs.TabPanel> - ) ) } + { tabs.map( ( tab ) => { + const categoryDefinition = tab.slug + ? getTopLevelStyleBookCategories().find( + ( _category ) => + _category.slug === tab.slug + ) + : null; + const filteredExamples = categoryDefinition + ? getExamplesByCategory( + categoryDefinition, + examples + ) + : { examples }; + return ( + <Tabs.TabPanel + key={ tab.slug } + tabId={ tab.slug } + focusable={ false } + className="edit-site-style-book__tabpanel" + > + <StyleBookBody + category={ tab.slug } + examples={ filteredExamples } + isSelected={ isSelected } + onSelect={ onSelect } + settings={ settings } + sizes={ sizes } + title={ tab.title } + goTo={ goTo } + /> + </Tabs.TabPanel> + ); + } ) } </Tabs> ) : ( <StyleBookBody - examples={ examplesForSinglePageUse } + examples={ { examples: examplesForSinglePageUse } } isSelected={ isSelected } onClick={ onClick } onSelect={ onSelect } @@ -316,8 +355,157 @@ function StyleBook( { ); } -const StyleBookBody = ( { - category, +/** + * Style Book Preview component renders the stylebook without the Editor dependency. + * + * @param {Object} props Component props. + * @param {Object} props.userConfig User configuration. + * @param {boolean} props.isStatic Whether the stylebook is static or clickable. + * @return {Object} Style Book Preview component. + */ +export const StyleBookPreview = ( { userConfig = {}, isStatic = false } ) => { + const siteEditorSettings = useSelect( + ( select ) => select( siteEditorStore ).getSettings(), + [] + ); + + const canUserUploadMedia = useSelect( + ( select ) => + select( coreStore ).canUser( 'create', { + kind: 'root', + name: 'media', + } ), + [] + ); + + // Update block editor settings because useMultipleOriginColorsAndGradients fetch colours from there. + useEffect( () => { + dispatch( blockEditorStore ).updateSettings( { + ...siteEditorSettings, + mediaUpload: canUserUploadMedia ? uploadMedia : undefined, + } ); + }, [ siteEditorSettings, canUserUploadMedia ] ); + + const [ section, onChangeSection ] = useSection(); + + const isSelected = ( blockName ) => { + // Match '/blocks/core%2Fbutton' and + // '/blocks/core%2Fbutton/typography', but not + // '/blocks/core%2Fbuttons'. + return ( + section === `/blocks/${ encodeURIComponent( blockName ) }` || + section.startsWith( + `/blocks/${ encodeURIComponent( blockName ) }/` + ) + ); + }; + + const onSelect = ( blockName ) => { + if ( + STYLE_BOOK_COLOR_GROUPS.find( + ( group ) => group.slug === blockName + ) + ) { + // Go to color palettes Global Styles. + onChangeSection( '/colors/palette' ); + return; + } + if ( blockName === 'typography' ) { + // Go to typography Global Styles. + onChangeSection( '/typography' ); + return; + } + + // Now go to the selected block. + onChangeSection( `/blocks/${ encodeURIComponent( blockName ) }` ); + }; + + const [ resizeObserver, sizes ] = useResizeObserver(); + const colors = useMultiOriginPalettes(); + const examples = getExamples( colors ); + const examplesForSinglePageUse = getExamplesForSinglePageUse( examples ); + + let previewCategory = null; + if ( section.includes( '/colors' ) ) { + previewCategory = 'colors'; + } else if ( section.includes( '/typography' ) ) { + previewCategory = 'text'; + } else if ( section.includes( '/blocks' ) ) { + previewCategory = 'blocks'; + const blockName = + decodeURIComponent( section ).split( '/blocks/' )[ 1 ]; + if ( + blockName && + examples.find( ( example ) => example.name === blockName ) + ) { + previewCategory = blockName; + } + } else if ( ! isStatic ) { + previewCategory = 'overview'; + } + const categoryDefinition = STYLE_BOOK_PREVIEW_CATEGORIES.find( + ( category ) => category.slug === previewCategory + ); + + // If there's no category definition there may be a single block. + const filteredExamples = categoryDefinition + ? getExamplesByCategory( categoryDefinition, examples ) + : { + examples: [ + examples.find( + ( example ) => example.name === previewCategory + ), + ], + }; + + // If there's no preview category, show all examples. + const displayedExamples = previewCategory + ? filteredExamples + : { examples: examplesForSinglePageUse }; + + const { base: baseConfig } = useContext( GlobalStylesContext ); + const goTo = getStyleBookNavigationFromPath( section ); + + const mergedConfig = useMemo( () => { + if ( ! isObjectEmpty( userConfig ) && ! isObjectEmpty( baseConfig ) ) { + return mergeBaseAndUserConfigs( baseConfig, userConfig ); + } + return {}; + }, [ baseConfig, userConfig ] ); + + const [ globalStyles ] = useGlobalStylesOutputWithConfig( mergedConfig ); + + const settings = useMemo( + () => ( { + ...siteEditorSettings, + styles: + ! isObjectEmpty( globalStyles ) && ! isObjectEmpty( userConfig ) + ? globalStyles + : siteEditorSettings.styles, + isPreviewMode: true, + } ), + [ globalStyles, siteEditorSettings, userConfig ] + ); + + return ( + <div className="edit-site-style-book"> + { resizeObserver } + <BlockEditorProvider settings={ settings }> + <GlobalStylesRenderer disableRootPadding /> + <StyleBookBody + examples={ displayedExamples } + settings={ settings } + goTo={ goTo } + sizes={ sizes } + isSelected={ ! isStatic ? isSelected : null } + onSelect={ ! isStatic ? onSelect : null } + /> + </BlockEditorProvider> + </div> + ); +}; + +export const StyleBookBody = ( { examples, isSelected, onClick, @@ -360,10 +548,12 @@ const StyleBookBody = ( { const handleLoad = () => setHasIframeLoaded( true ); useLayoutEffect( () => { - if ( goTo?.block && hasIframeLoaded && iframeRef?.current ) { - scrollToSection( `example-${ goTo?.block }`, iframeRef?.current ); + if ( hasIframeLoaded && iframeRef?.current ) { + if ( goTo?.top ) { + scrollToSection( 'top', iframeRef?.current ); + } } - }, [ iframeRef?.current, goTo?.block, scrollToSection, hasIframeLoaded ] ); + }, [ iframeRef?.current, goTo, scrollToSection, hasIframeLoaded ] ); return ( <Iframe @@ -387,8 +577,7 @@ const StyleBookBody = ( { className={ clsx( 'edit-site-style-book__examples', { 'is-wide': sizes.width > 600, } ) } - examples={ examples } - category={ category } + filteredExamples={ examples } label={ title ? sprintf( @@ -400,24 +589,14 @@ const StyleBookBody = ( { } isSelected={ isSelected } onSelect={ onSelect } - key={ category } + key={ title } /> </Iframe> ); }; const Examples = memo( - ( { className, examples, category, label, isSelected, onSelect } ) => { - const categoryDefinition = category - ? getTopLevelStyleBookCategories().find( - ( _category ) => _category.slug === category - ) - : null; - - const filteredExamples = categoryDefinition - ? getExamplesByCategory( categoryDefinition, examples ) - : { examples }; - + ( { className, filteredExamples, label, isSelected, onSelect } ) => { return ( <Composite orientation="vertical" @@ -434,7 +613,11 @@ const Examples = memo( content={ example.content } blocks={ example.blocks } isSelected={ isSelected?.( example.name ) } - onClick={ () => onSelect?.( example.name ) } + onClick={ + !! onSelect + ? () => onSelect( example.name ) + : null + } /> ) ) } { !! filteredExamples?.subcategories?.length && @@ -471,9 +654,7 @@ const Subcategory = ( { examples, isSelected, onSelect } ) => { content={ example.content } blocks={ example.blocks } isSelected={ isSelected?.( example.name ) } - onClick={ () => { - onSelect?.( example.name ); - } } + onClick={ !! onSelect ? () => onSelect( example.name ) : null } /> ) ) ); @@ -501,12 +682,13 @@ const Example = ( { id, title, blocks, isSelected, onClick, content } ) => { [ blocks ] ); - const disabledProps = disabledExamples.includes( id ) - ? { - disabled: true, - accessibleWhenDisabled: true, - } - : {}; + const disabledProps = + disabledExamples.includes( id ) || ! onClick + ? { + disabled: true, + accessibleWhenDisabled: !! onClick, + } + : {}; return ( <div role="row"> @@ -517,13 +699,17 @@ const Example = ( { id, title, blocks, isSelected, onClick, content } ) => { 'is-disabled-example': !! disabledProps?.disabled, } ) } id={ id } - aria-label={ sprintf( - // translators: %s: Title of a block, e.g. Heading. - __( 'Open %s styles in Styles panel' ), - title - ) } + aria-label={ + !! onClick + ? sprintf( + // translators: %s: Title of a block, e.g. Heading. + __( 'Open %s styles in Styles panel' ), + title + ) + : undefined + } render={ <div /> } - role="button" + role={ !! onClick ? 'button' : null } onClick={ onClick } { ...disabledProps } > diff --git a/packages/edit-site/src/components/style-book/style.scss b/packages/edit-site/src/components/style-book/style.scss index 773b4f10f581bb..42ffca2af1ed1d 100644 --- a/packages/edit-site/src/components/style-book/style.scss +++ b/packages/edit-site/src/components/style-book/style.scss @@ -12,6 +12,10 @@ } .edit-site-style-book__iframe { + display: block; + height: 100%; + width: 100%; + &.is-button { border-radius: $radius-large; } diff --git a/packages/edit-site/src/components/style-book/types.ts b/packages/edit-site/src/components/style-book/types.ts index e7be17b17dd4df..9a97c3aad7f79d 100644 --- a/packages/edit-site/src/components/style-book/types.ts +++ b/packages/edit-site/src/components/style-book/types.ts @@ -1,4 +1,4 @@ -type Block = { +export type Block = { name: string; attributes: Record< string, unknown >; innerBlocks?: Block[]; @@ -32,7 +32,7 @@ export type StyleBookColorGroup = { origin: string; slug: string; title: string; - type: string; + type: 'colors' | 'gradients' | 'duotones'; }; export type Color = { slug: string }; @@ -42,6 +42,13 @@ export type Duotone = { slug: string; }; +export type ColorExampleProps = { + colors: Color[] | Gradient[]; + type: StyleBookColorGroup[ 'type' ]; + templateColumns?: string | number; + itemHeight?: string; +}; + export type ColorOrigin = { name: string; slug: string; @@ -58,3 +65,16 @@ export type MultiOriginPalettes = { duotones: Omit< ColorOrigin, 'colors' | 'gradients' >; gradients: Omit< ColorOrigin, 'colors' | 'duotones' >; }; + +/* + * Typing the items from getBlockTypes from '@wordpress/blocks' + * to appease the TS linter. + */ +export type BlockType = { + name: string; + title: string; + category: string; + example: BlockType; + attributes: Record< string, unknown >; + supports: Record< string, unknown >; +}; diff --git a/packages/edit-site/src/hooks/commands/use-common-commands.js b/packages/edit-site/src/hooks/commands/use-common-commands.js index 3e87f8721e116a..34ddae3e1af7a4 100644 --- a/packages/edit-site/src/hooks/commands/use-common-commands.js +++ b/packages/edit-site/src/hooks/commands/use-common-commands.js @@ -49,27 +49,17 @@ const getGlobalStylesOpenStylesCommands = () => label: __( 'Open styles' ), callback: ( { close } ) => { close(); - if ( ! params.postId ) { - history.push( { - path: '/wp_global_styles', - canvas: 'edit', + if ( canvas !== 'edit' ) { + history.navigate( '/styles?canvas=edit', { + transition: 'canvas-mode-edit-transition', } ); } - if ( params.postId && canvas !== 'edit' ) { - history.push( - { ...params, canvas: 'edit' }, - undefined, - { - transition: 'canvas-mode-edit-transition', - } - ); - } openGeneralSidebar( 'edit-site/global-styles' ); }, icon: styles, }, ]; - }, [ history, openGeneralSidebar, params, canvas, isBlockBasedTheme ] ); + }, [ history, openGeneralSidebar, canvas, isBlockBasedTheme ] ); return { isLoading: false, @@ -100,24 +90,11 @@ const getGlobalStylesToggleWelcomeGuideCommands = () => label: __( 'Learn about styles' ), callback: ( { close } ) => { close(); - if ( ! params.postId ) { - history.push( { - path: '/wp_global_styles', - canvas: 'edit', + if ( canvas !== 'edit' ) { + history.navigate( '/styles?canvas=edit', { + transition: 'canvas-mode-edit-transition', } ); } - if ( params.postId && canvas !== 'edit' ) { - history.push( - { - ...params, - canvas: 'edit', - }, - undefined, - { - transition: 'canvas-mode-edit-transition', - } - ); - } openGeneralSidebar( 'edit-site/global-styles' ); set( 'core/edit-site', 'welcomeGuideStyles', true ); // sometimes there's a focus loss that happens after some time @@ -129,14 +106,7 @@ const getGlobalStylesToggleWelcomeGuideCommands = () => icon: help, }, ]; - }, [ - history, - openGeneralSidebar, - canvas, - isBlockBasedTheme, - set, - params, - ] ); + }, [ history, openGeneralSidebar, canvas, isBlockBasedTheme, set ] ); return { isLoading: false, @@ -205,24 +175,11 @@ const getGlobalStylesOpenCssCommands = () => icon: brush, callback: ( { close } ) => { close(); - if ( ! params.postId ) { - history.push( { - path: '/wp_global_styles', - canvas: 'edit', + if ( canvas !== 'edit' ) { + history.navigate( '/styles?canvas=edit', { + transition: 'canvas-mode-edit-transition', } ); } - if ( params.postId && canvas !== 'edit' ) { - history.push( - { - ...params, - canvas: 'edit', - }, - undefined, - { - transition: 'canvas-mode-edit-transition', - } - ); - } openGeneralSidebar( 'edit-site/global-styles' ); setEditorCanvasContainerView( 'global-styles-css' ); }, @@ -234,7 +191,6 @@ const getGlobalStylesOpenCssCommands = () => setEditorCanvasContainerView, canEditCSS, canvas, - params, ] ); return { isLoading: false, @@ -272,24 +228,11 @@ const getGlobalStylesOpenRevisionsCommands = () => icon: backup, callback: ( { close } ) => { close(); - if ( ! params.postId ) { - history.push( { - path: '/wp_global_styles', - canvas: 'edit', + if ( canvas !== 'edit' ) { + history.navigate( '/styles?canvas=edit', { + transition: 'canvas-mode-edit-transition', } ); } - if ( params.postId && canvas !== 'edit' ) { - history.push( - { - ...params, - canvas: 'edit', - }, - undefined, - { - transition: 'canvas-mode-edit-transition', - } - ); - } openGeneralSidebar( 'edit-site/global-styles' ); setEditorCanvasContainerView( 'global-styles-revisions' @@ -303,7 +246,6 @@ const getGlobalStylesOpenRevisionsCommands = () => openGeneralSidebar, setEditorCanvasContainerView, canvas, - params, ] ); return { diff --git a/packages/edit-site/src/hooks/commands/use-set-command-context.js b/packages/edit-site/src/hooks/commands/use-set-command-context.js index e27c4ca91582fd..6ecdf04989609b 100644 --- a/packages/edit-site/src/hooks/commands/use-set-command-context.js +++ b/packages/edit-site/src/hooks/commands/use-set-command-context.js @@ -19,8 +19,8 @@ const { useLocation } = unlock( routerPrivateApis ); * React hook used to set the correct command context based on the current state. */ export default function useSetCommandContext() { - const { params } = useLocation(); - const { canvas = 'view' } = params; + const { query = {} } = useLocation(); + const { canvas = 'view' } = query; const hasBlockSelected = useSelect( ( select ) => { return select( blockEditorStore ).getBlockSelectionStart(); }, [] ); diff --git a/packages/edit-site/src/index.js b/packages/edit-site/src/index.js index 7f124e6b5f7ac6..b96ce5cb67f5e1 100644 --- a/packages/edit-site/src/index.js +++ b/packages/edit-site/src/index.js @@ -10,10 +10,7 @@ import { import { dispatch } from '@wordpress/data'; import deprecated from '@wordpress/deprecated'; import { createRoot, StrictMode } from '@wordpress/element'; -import { - store as editorStore, - privateApis as editorPrivateApis, -} from '@wordpress/editor'; +import { privateApis as editorPrivateApis } from '@wordpress/editor'; import { store as preferencesStore } from '@wordpress/preferences'; import { registerLegacyWidgetBlock, @@ -88,15 +85,6 @@ export function initializeEditor( id, settings ) { dispatch( editSiteStore ).updateSettings( settings ); - // Keep the defaultTemplateTypes in the core/editor settings too, - // so that they can be selected with core/editor selectors in any editor. - // This is needed because edit-site doesn't initialize with EditorProvider, - // which internally uses updateEditorSettings as well. - dispatch( editorStore ).updateEditorSettings( { - defaultTemplateTypes: settings.defaultTemplateTypes, - defaultTemplatePartAreas: settings.defaultTemplatePartAreas, - } ); - // Prevent the default browser action for files dropped outside of dropzones. window.addEventListener( 'dragover', ( e ) => e.preventDefault(), false ); window.addEventListener( 'drop', ( e ) => e.preventDefault(), false ); diff --git a/packages/edit-site/src/store/private-actions.js b/packages/edit-site/src/store/private-actions.js index 1db3873acedda2..9b16748049cd0e 100644 --- a/packages/edit-site/src/store/private-actions.js +++ b/packages/edit-site/src/store/private-actions.js @@ -18,3 +18,10 @@ export function registerRoute( route ) { route, }; } + +export function unregisterRoute( name ) { + return { + type: 'UNREGISTER_ROUTE', + name, + }; +} diff --git a/packages/edit-site/src/store/reducer.js b/packages/edit-site/src/store/reducer.js index 3ce067c25c1954..7ffb276a35da10 100644 --- a/packages/edit-site/src/store/reducer.js +++ b/packages/edit-site/src/store/reducer.js @@ -85,6 +85,8 @@ function routes( state = [], action ) { switch ( action.type ) { case 'REGISTER_ROUTE': return [ ...state, action.route ]; + case 'UNREGISTER_ROUTE': + return state.filter( ( route ) => route.name !== action.name ); } return state; diff --git a/packages/edit-site/src/style.scss b/packages/edit-site/src/style.scss index 0e5744fe362e35..9f98c2c172e091 100644 --- a/packages/edit-site/src/style.scss +++ b/packages/edit-site/src/style.scss @@ -20,17 +20,17 @@ @import "./components/sidebar-navigation-screen/style.scss"; @import "./components/sidebar-navigation-screen-details-footer/style.scss"; @import "./components/sidebar-navigation-screen-navigation-menu/style.scss"; -@import "components/sidebar-navigation-screen-details-panel/style.scss"; @import "./components/sidebar-navigation-screen-patterns/style.scss"; +@import "./components/sidebar-navigation-screen-navigation-menus/style.scss"; +@import "./components/sidebar-navigation-screen-main/style.scss"; +@import "./components/sidebar-navigation-screen-templates-browse/style.scss"; @import "./components/sidebar-dataviews/style.scss"; @import "./components/site-hub/style.scss"; -@import "./components/sidebar-navigation-screen-navigation-menus/style.scss"; @import "./components/site-icon/style.scss"; @import "./components/style-book/style.scss"; @import "./components/editor-canvas-container/style.scss"; @import "./components/post-edit/style.scss"; @import "./components/post-list/style.scss"; -@import "./components/post-fields/style.scss"; @import "./components/resizable-frame/style.scss"; @import "./hooks/push-changes-to-global-styles/style.scss"; @import "./components/global-styles/font-library-modal/style.scss"; @@ -49,6 +49,7 @@ mix-blend-mode: normal; display: block; } + /* stylelint-enable */ body.js #wpadminbar { diff --git a/packages/edit-site/src/utils/is-previewing-theme.js b/packages/edit-site/src/utils/is-previewing-theme.js index 1a71c441f9925e..a4c830b4b60ad7 100644 --- a/packages/edit-site/src/utils/is-previewing-theme.js +++ b/packages/edit-site/src/utils/is-previewing-theme.js @@ -4,9 +4,7 @@ import { getQueryArg } from '@wordpress/url'; export function isPreviewingTheme() { - return ( - getQueryArg( window.location.href, 'wp_theme_preview' ) !== undefined - ); + return !! getQueryArg( window.location.href, 'wp_theme_preview' ); } export function currentlyPreviewingTheme() { diff --git a/packages/edit-site/src/utils/use-activate-theme.js b/packages/edit-site/src/utils/use-activate-theme.js index 0dafd88340ba75..447ea073053492 100644 --- a/packages/edit-site/src/utils/use-activate-theme.js +++ b/packages/edit-site/src/utils/use-activate-theme.js @@ -4,6 +4,7 @@ import { store as coreStore } from '@wordpress/core-data'; import { useDispatch } from '@wordpress/data'; import { privateApis as routerPrivateApis } from '@wordpress/router'; +import { addQueryArgs } from '@wordpress/url'; /** * Internal dependencies @@ -14,7 +15,7 @@ import { currentlyPreviewingTheme, } from './is-previewing-theme'; -const { useHistory } = unlock( routerPrivateApis ); +const { useHistory, useLocation } = unlock( routerPrivateApis ); /** * This should be refactored to use the REST API, once the REST API can activate themes. @@ -23,6 +24,7 @@ const { useHistory } = unlock( routerPrivateApis ); */ export function useActivateTheme() { const history = useHistory(); + const { path } = useLocation(); const { startResolution, finishResolution } = useDispatch( coreStore ); return async () => { @@ -37,8 +39,7 @@ export function useActivateTheme() { finishResolution( 'activateTheme' ); // Remove the wp_theme_preview query param: we've finished activating // the queue and are switching to normal Site Editor. - const { params } = history.getLocationWithParams(); - history.replace( { ...params, wp_theme_preview: undefined } ); + history.navigate( addQueryArgs( path, { wp_theme_preview: '' } ) ); } }; } diff --git a/packages/edit-site/tsconfig.json b/packages/edit-site/tsconfig.json new file mode 100644 index 00000000000000..d6c82614bf534d --- /dev/null +++ b/packages/edit-site/tsconfig.json @@ -0,0 +1,53 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig.json", + "extends": "../../tsconfig.base.json", + "references": [ + { "path": "../a11y" }, + { "path": "../api-fetch" }, + { "path": "../autop" }, + { "path": "../blob" }, + { "path": "../block-library" }, + { "path": "../block-editor" }, + { "path": "../components" }, + { "path": "../compose" }, + { "path": "../core-data" }, + { "path": "../data" }, + { "path": "../dataviews" }, + { "path": "../date" }, + { "path": "../deprecated" }, + { "path": "../dom" }, + { "path": "../editor" }, + { "path": "../element" }, + { "path": "../escape-html" }, + { "path": "../fields" }, + { "path": "../hooks" }, + { "path": "../html-entities" }, + { "path": "../i18n" }, + { "path": "../icons" }, + { "path": "../interactivity" }, + { "path": "../interactivity-router" }, + { "path": "../media-utils" }, + { "path": "../notices" }, + { "path": "../keycodes" }, + { "path": "../plugins" }, + { "path": "../primitives" }, + { "path": "../private-apis" }, + { "path": "../rich-text" }, + { "path": "../router" }, + { "path": "../style-engine" }, + { "path": "../url" }, + { "path": "../wordcount" } + ], + // NOTE: This package is being progressively typed. You are encouraged to + // expand this array with files which can be type-checked. At some point in + // the future, this can be simplified to an `includes` of `src/**/*`. + "files": [ + "src/components/style-book/categories.ts", + "src/components/style-book/constants.ts", + "src/components/style-book/types.ts", + "src/components/style-book/color-examples.tsx", + "src/components/style-book/duotone-examples.tsx", + "src/components/style-book/examples.tsx" + ], + "include": [] +} diff --git a/packages/edit-widgets/CHANGELOG.md b/packages/edit-widgets/CHANGELOG.md index 316648bf952585..bde44856bf4f23 100644 --- a/packages/edit-widgets/CHANGELOG.md +++ b/packages/edit-widgets/CHANGELOG.md @@ -2,6 +2,16 @@ ## Unreleased +## 6.16.0 (2025-01-15) + +## 6.15.0 (2025-01-02) + +## 6.14.0 (2024-12-11) + +## 6.13.0 (2024-11-27) + +## 6.12.0 (2024-11-16) + ## 6.11.0 (2024-10-30) ## 6.10.0 (2024-10-16) diff --git a/packages/edit-widgets/package.json b/packages/edit-widgets/package.json index 331dbc742dbc8c..e06e1cf74aeb8b 100644 --- a/packages/edit-widgets/package.json +++ b/packages/edit-widgets/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/edit-widgets", - "version": "6.11.0", + "version": "6.16.0", "description": "Widgets Page module for WordPress..", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -29,33 +29,32 @@ "wpScript": true, "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/api-fetch": "*", - "@wordpress/block-editor": "*", - "@wordpress/block-library": "*", - "@wordpress/blocks": "*", - "@wordpress/components": "*", - "@wordpress/compose": "*", - "@wordpress/core-data": "*", - "@wordpress/data": "*", - "@wordpress/deprecated": "*", - "@wordpress/dom": "*", - "@wordpress/editor": "*", - "@wordpress/element": "*", - "@wordpress/hooks": "*", - "@wordpress/i18n": "*", - "@wordpress/icons": "*", - "@wordpress/interface": "*", - "@wordpress/keyboard-shortcuts": "*", - "@wordpress/keycodes": "*", - "@wordpress/media-utils": "*", - "@wordpress/notices": "*", - "@wordpress/patterns": "*", - "@wordpress/plugins": "*", - "@wordpress/preferences": "*", - "@wordpress/private-apis": "*", - "@wordpress/reusable-blocks": "*", - "@wordpress/url": "*", - "@wordpress/widgets": "*", + "@wordpress/api-fetch": "file:../api-fetch", + "@wordpress/block-editor": "file:../block-editor", + "@wordpress/block-library": "file:../block-library", + "@wordpress/blocks": "file:../blocks", + "@wordpress/components": "file:../components", + "@wordpress/compose": "file:../compose", + "@wordpress/core-data": "file:../core-data", + "@wordpress/data": "file:../data", + "@wordpress/deprecated": "file:../deprecated", + "@wordpress/dom": "file:../dom", + "@wordpress/element": "file:../element", + "@wordpress/hooks": "file:../hooks", + "@wordpress/i18n": "file:../i18n", + "@wordpress/icons": "file:../icons", + "@wordpress/interface": "file:../interface", + "@wordpress/keyboard-shortcuts": "file:../keyboard-shortcuts", + "@wordpress/keycodes": "file:../keycodes", + "@wordpress/media-utils": "file:../media-utils", + "@wordpress/notices": "file:../notices", + "@wordpress/patterns": "file:../patterns", + "@wordpress/plugins": "file:../plugins", + "@wordpress/preferences": "file:../preferences", + "@wordpress/private-apis": "file:../private-apis", + "@wordpress/reusable-blocks": "file:../reusable-blocks", + "@wordpress/url": "file:../url", + "@wordpress/widgets": "file:../widgets", "clsx": "^2.1.1" }, "peerDependencies": { diff --git a/packages/edit-widgets/src/components/header/style.scss b/packages/edit-widgets/src/components/header/style.scss index 642a641e6e5952..32214cb0f157ad 100644 --- a/packages/edit-widgets/src/components/header/style.scss +++ b/packages/edit-widgets/src/components/header/style.scss @@ -147,8 +147,9 @@ } svg { - transition: transform cubic-bezier(0.165, 0.84, 0.44, 1) 0.2s; - @include reduce-motion("transition"); + @media not (prefers-reduced-motion) { + transition: transform cubic-bezier(0.165, 0.84, 0.44, 1) 0.2s; + } } &.is-pressed { diff --git a/packages/edit-widgets/src/components/layout/style.scss b/packages/edit-widgets/src/components/layout/style.scss index 14d74e4db9248c..71b1049adf196d 100644 --- a/packages/edit-widgets/src/components/layout/style.scss +++ b/packages/edit-widgets/src/components/layout/style.scss @@ -7,13 +7,6 @@ } } -.edit-widgets-layout__inserter-panel-header { - padding-top: $grid-unit-10; - padding-right: $grid-unit-10; - display: flex; - justify-content: flex-end; -} - .edit-widgets-layout__inserter-panel-content { // Leave space for the close button height: calc(100% - #{$button-size} - #{$grid-unit-10}); diff --git a/packages/edit-widgets/src/components/secondary-sidebar/inserter-sidebar.js b/packages/edit-widgets/src/components/secondary-sidebar/inserter-sidebar.js index 4b26dd306ea0a3..72e04e5f62034c 100644 --- a/packages/edit-widgets/src/components/secondary-sidebar/inserter-sidebar.js +++ b/packages/edit-widgets/src/components/secondary-sidebar/inserter-sidebar.js @@ -1,8 +1,6 @@ /** * WordPress dependencies */ -import { Button, VisuallyHidden } from '@wordpress/components'; -import { close } from '@wordpress/icons'; import { __experimentalLibrary as Library } from '@wordpress/block-editor'; import { useViewportMatch, @@ -10,7 +8,6 @@ import { } from '@wordpress/compose'; import { useCallback, useRef } from '@wordpress/element'; import { useDispatch } from '@wordpress/data'; -import { __ } from '@wordpress/i18n'; /** * Internal dependencies @@ -28,7 +25,6 @@ export default function InserterSidebar() { return setIsInserterOpened( false ); }, [ setIsInserterOpened ] ); - const TagName = ! isMobileViewport ? VisuallyHidden : 'div'; const [ inserterDialogRef, inserterDialogProps ] = useDialog( { onClose: closeInserter, focusOnMount: true, @@ -42,14 +38,6 @@ export default function InserterSidebar() { { ...inserterDialogProps } className="edit-widgets-layout__inserter-panel" > - <TagName className="edit-widgets-layout__inserter-panel-header"> - <Button - __next40pxDefaultSize - icon={ close } - onClick={ closeInserter } - label={ __( 'Close Block Inserter' ) } - /> - </TagName> <div className="edit-widgets-layout__inserter-panel-content"> <Library showInserterHelpPanel @@ -57,6 +45,7 @@ export default function InserterSidebar() { rootClientId={ rootClientId } __experimentalInsertionIndex={ insertionIndex } ref={ libraryRef } + onClose={ closeInserter } /> </div> </div> diff --git a/packages/edit-widgets/src/components/welcome-guide/index.js b/packages/edit-widgets/src/components/welcome-guide/index.js index bd06234e399b58..b9967e3a6025d7 100644 --- a/packages/edit-widgets/src/components/welcome-guide/index.js +++ b/packages/edit-widgets/src/components/welcome-guide/index.js @@ -118,7 +118,7 @@ export default function WelcomeGuide() { content: ( <> <h1 className="edit-widgets-welcome-guide__heading"> - { __( 'Make each block your own' ) } + { __( 'Customize each block' ) } </h1> <p className="edit-widgets-welcome-guide__text"> { __( @@ -138,7 +138,7 @@ export default function WelcomeGuide() { content: ( <> <h1 className="edit-widgets-welcome-guide__heading"> - { __( 'Get to know the block library' ) } + { __( 'Explore all blocks' ) } </h1> <p className="edit-widgets-welcome-guide__text"> { createInterpolateElement( @@ -169,7 +169,7 @@ export default function WelcomeGuide() { content: ( <> <h1 className="edit-widgets-welcome-guide__heading"> - { __( 'Learn how to use the block editor' ) } + { __( 'Learn more' ) } </h1> <p className="edit-widgets-welcome-guide__text"> { createInterpolateElement( diff --git a/packages/edit-widgets/src/components/widget-areas-block-editor-provider/index.js b/packages/edit-widgets/src/components/widget-areas-block-editor-provider/index.js index af844fe9da4fe9..751931d858da8d 100644 --- a/packages/edit-widgets/src/components/widget-areas-block-editor-provider/index.js +++ b/packages/edit-widgets/src/components/widget-areas-block-editor-provider/index.js @@ -94,6 +94,7 @@ export default function WidgetAreasBlockEditorProvider( { __experimentalSetIsInserterOpened: setIsInserterOpened, pageOnFront, pageForPosts, + editorTool: 'edit', }; }, [ hasUploadPermissions, diff --git a/packages/edit-widgets/src/index.js b/packages/edit-widgets/src/index.js index 8788ee2b99ea1d..2f0ced0c09bd75 100644 --- a/packages/edit-widgets/src/index.js +++ b/packages/edit-widgets/src/index.js @@ -9,7 +9,6 @@ import { } from '@wordpress/blocks'; import { dispatch } from '@wordpress/data'; import deprecated from '@wordpress/deprecated'; -import { privateApis as editorPrivateApis } from '@wordpress/editor'; import { StrictMode, createRoot } from '@wordpress/element'; import { registerCoreBlocks, @@ -30,7 +29,6 @@ import { store as preferencesStore } from '@wordpress/preferences'; import './store'; import './filters'; import * as widgetArea from './blocks/widget-area'; -import { unlock } from './lock-unlock'; import Layout from './components/layout'; import { ALLOW_REUSABLE_BLOCKS, @@ -44,8 +42,6 @@ const disabledBlocks = [ ...( ALLOW_REUSABLE_BLOCKS ? [] : [ 'core/block' ] ), ]; -const { registerCoreBlockBindingsSources } = unlock( editorPrivateApis ); - /** * Initializes the block editor in the widgets screen. * @@ -75,7 +71,6 @@ export function initializeEditor( id, settings ) { dispatch( blocksStore ).reapplyBlockTypeFilters(); registerCoreBlocks( coreBlocks ); - registerCoreBlockBindingsSources(); registerLegacyWidgetBlock(); if ( globalThis.IS_GUTENBERG_PLUGIN ) { __experimentalRegisterExperimentalCoreBlocks( { diff --git a/packages/edit-widgets/src/store/transformers.js b/packages/edit-widgets/src/store/transformers.js index 3b42e3141ff5f0..12a2f9d32933a8 100644 --- a/packages/edit-widgets/src/store/transformers.js +++ b/packages/edit-widgets/src/store/transformers.js @@ -46,7 +46,7 @@ export function transformWidgetToBlock( widget ) { * Converts a block to a widget entity record. * * @param {Object} block The block. - * @param {Object?} relatedWidget A related widget entity record from the API (optional). + * @param {?Object} relatedWidget A related widget entity record from the API (optional). * @return {Object} the widget object (converted from block). */ export function transformBlockToWidget( block, relatedWidget = {} ) { diff --git a/packages/editor/CHANGELOG.md b/packages/editor/CHANGELOG.md index a3980bf4aead7a..ecad566fe77a57 100644 --- a/packages/editor/CHANGELOG.md +++ b/packages/editor/CHANGELOG.md @@ -2,6 +2,16 @@ ## Unreleased +## 14.16.0 (2025-01-15) + +## 14.15.0 (2025-01-02) + +## 14.14.0 (2024-12-11) + +## 14.13.0 (2024-11-27) + +## 14.12.0 (2024-11-16) + ## 14.11.0 (2024-10-30) ### Bug Fixes diff --git a/packages/editor/README.md b/packages/editor/README.md index e0b53362089f1b..3119f3f289637a 100644 --- a/packages/editor/README.md +++ b/packages/editor/README.md @@ -270,7 +270,7 @@ _Parameters_ _Returns_ -- `JSX.Element`: The rendered DocumentBar component. +- `React.ReactNode`: The rendered DocumentBar component. ### DocumentOutline @@ -279,13 +279,12 @@ Renders a document outline component. _Parameters_ - _props_ `Object`: Props. -- _props.onSelect_ `Function`: Function to be called when an outline item is selected. -- _props.isTitleSupported_ `boolean`: Indicates whether the title is supported. +- _props.onSelect_ `Function`: Function to be called when an outline item is selected - _props.hasOutlineItemsDisabled_ `boolean`: Indicates whether the outline items are disabled. _Returns_ -- `Component`: The component to be rendered. +- `React.ReactNode`: The rendered component. ### DocumentOutlineCheck @@ -294,11 +293,11 @@ Component check if there are any headings (core/heading blocks) present in the d _Parameters_ - _props_ `Object`: Props. -- _props.children_ `Element`: Children to be rendered. +- _props.children_ `React.ReactElement`: Children to be rendered. _Returns_ -- `Component|null`: The component to be rendered or null if there are headings. +- `React.ReactElement`: The component to be rendered or null if there are headings. ### EditorHistoryRedo @@ -311,7 +310,7 @@ _Parameters_ _Returns_ -- `Component`: The component to be rendered. +- `React.ReactNode`: The rendered component. ### EditorHistoryUndo @@ -324,7 +323,7 @@ _Parameters_ _Returns_ -- `Component`: The component to be rendered. +- `React.ReactNode`: The rendered component. ### EditorKeyboardShortcuts @@ -352,7 +351,7 @@ _Usage_ _Returns_ -- `JSX.Element`: The rendered EditorNotices component. +- `React.ReactNode`: The rendered EditorNotices component. ### EditorProvider @@ -380,11 +379,11 @@ _Parameters_ - _props.post_ `[Object]`: The post object to edit. This is required. - _props.\_\_unstableTemplate_ `[Object]`: The template object wrapper the edited post. This is optional and can only be used when the post type supports templates (like posts and pages). - _props.settings_ `[Object]`: The settings object to use for the editor. This is optional and can be used to override the default settings. -- _props.children_ `[Element]`: Children elements for which the BlockEditorProvider context should apply. This is optional. +- _props.children_ `[React.ReactNode]`: Children elements for which the BlockEditorProvider context should apply. This is optional. _Returns_ -- `JSX.Element`: The rendered EditorProvider component. +- `React.ReactNode`: The rendered EditorProvider component. ### EditorSnackbars @@ -392,7 +391,7 @@ Renders the editor snackbars component. _Returns_ -- `JSX.Element`: The rendered component. +- `React.ReactNode`: The rendered component. ### EntitiesSavedStates @@ -402,11 +401,11 @@ _Parameters_ - _props_ `Object`: The component props. - _props.close_ `Function`: The function to close the dialog. -- _props.renderDialog_ `Function`: The function to render the dialog. +- _props.renderDialog_ `boolean`: Whether to render the component with modal dialog behavior. _Returns_ -- `JSX.Element`: The rendered component. +- `React.ReactNode`: The rendered component. ### ErrorBoundary @@ -500,6 +499,7 @@ _Parameters_ - _$0.maxUploadFileSize_ `?number`: Maximum upload size in bytes allowed for the site. - _$0.onError_ `Function`: Function called when an error happens. - _$0.onFileChange_ `Function`: Function called each time a file or a temporary representation of the file is available. +- _$0.onSuccess_ `Function`: Function called after the final representation of the file is available. ### MediaUploadCheck @@ -524,11 +524,11 @@ Wrapper component that renders its children only if the post type supports page _Parameters_ - _props_ `Object`: - The component props. -- _props.children_ `Element`: - The child components to render. +- _props.children_ `React.ReactElement`: - The child components to render. _Returns_ -- `Component|null`: The rendered child components or null if page attributes are not supported. +- `React.ReactElement`: The rendered child components or null if page attributes are not supported. ### PageAttributesOrder @@ -536,7 +536,7 @@ Renders the Page Attributes Order component. A number input in an editor interfa _Returns_ -- `Component`: The component to be rendered. +- `React.ReactNode`: The rendered component. ### PageAttributesPanel @@ -544,7 +544,7 @@ Renders the Page Attributes Panel component. _Returns_ -- `Component`: The component to be rendered. +- `React.ReactNode`: The rendered component. ### PageAttributesParent @@ -552,7 +552,7 @@ Renders the Page Attributes Parent component. A dropdown menu in an editor inter _Returns_ -- `Component|null`: The component to be rendered. Return null if post type is not hierarchical. +- `React.ReactNode`: The component to be rendered. Return null if post type is not hierarchical. ### PageTemplate @@ -562,7 +562,7 @@ The dropdown menu includes a button for toggling the menu, a list of available t _Returns_ -- `JSX.Element`: The rendered ClassicThemeControl component. +- `React.ReactNode`: The rendered ClassicThemeControl component. ### PanelColorSettings @@ -628,7 +628,7 @@ _Parameters_ _Returns_ -- `Component`: The component to be rendered. +- `React.ReactNode`: The rendered component. ### PluginDocumentSettingPanel @@ -685,11 +685,11 @@ _Parameters_ - _props.className_ `[string]`: An optional class name added to the row. - _props.title_ `[string]`: The title of the panel - _props.icon_ `[WPBlockTypeIconRender]`: The [Dashicon](https://developer.wordpress.org/resource/dashicons/) icon slug string, or an SVG WP element, to be rendered when the sidebar is pinned to toolbar. -- _props.children_ `Element`: Children to be rendered +- _props.children_ `React.ReactNode`: Children to be rendered _Returns_ -- `Component`: The component to be rendered. +- `React.ReactNode`: The component to be rendered. ### PluginMoreMenuItem @@ -739,6 +739,7 @@ const MyButtonMoreMenuItem = () => ( _Parameters_ - _props_ `Object`: Component properties. +- _props.children_ `[React.ReactNode]`: Children to be rendered. - _props.href_ `[string]`: When `href` is provided then the menu item is represented as an anchor rather than button. It corresponds to the `href` attribute of the anchor. - _props.icon_ `[WPBlockTypeIconRender]`: The [Dashicon](https://developer.wordpress.org/resource/dashicons/) icon slug string, or an SVG WP element, to be rendered to the left of the menu item label. - _props.onClick_ `[Function]`: The callback function to be executed when the user clicks the menu item. @@ -746,7 +747,7 @@ _Parameters_ _Returns_ -- `Component`: The component to be rendered. +- `React.ReactNode`: The rendered component. ### PluginPostPublishPanel @@ -777,11 +778,11 @@ _Parameters_ - _props.title_ `[string]`: Title displayed at the top of the panel. - _props.initialOpen_ `[boolean]`: Whether to have the panel initially opened. When no title is provided it is always opened. - _props.icon_ `[WPBlockTypeIconRender]`: The [Dashicon](https://developer.wordpress.org/resource/dashicons/) icon slug string, or an SVG WP element, to be rendered when the sidebar is pinned to toolbar. -- _props.children_ `Element`: Children to be rendered +- _props.children_ `React.ReactNode`: Children to be rendered _Returns_ -- `Component`: The component to be rendered. +- `React.ReactNode`: The rendered component. ### PluginPostStatusInfo @@ -821,11 +822,11 @@ _Parameters_ - _props_ `Object`: Component properties. - _props.className_ `[string]`: An optional class name added to the row. -- _props.children_ `Element`: Children to be rendered. +- _props.children_ `React.ReactNode`: Children to be rendered. _Returns_ -- `Component`: The component to be rendered. +- `React.ReactNode`: The rendered component. ### PluginPrePublishPanel @@ -856,11 +857,11 @@ _Parameters_ - _props.title_ `[string]`: Title displayed at the top of the panel. - _props.initialOpen_ `[boolean]`: Whether to have the panel initially opened. When no title is provided it is always opened. - _props.icon_ `[WPBlockTypeIconRender]`: The [Dashicon](https://developer.wordpress.org/resource/dashicons/) icon slug string, or an SVG WP element, to be rendered when the sidebar is pinned to toolbar. -- _props.children_ `Element`: Children to be rendered +- _props.children_ `React.ReactNode`: Children to be rendered _Returns_ -- `Component`: The component to be rendered. +- `React.ReactNode`: The rendered component. ### PluginPreviewMenuItem @@ -890,6 +891,7 @@ registerPlugin( 'external-preview-menu-item', { _Parameters_ - _props_ `Object`: Component properties. +- _props.children_ `[React.ReactNode]`: Children to be rendered. - _props.href_ `[string]`: When `href` is provided, the menu item is rendered as an anchor instead of a button. It corresponds to the `href` attribute of the anchor. - _props.icon_ `[WPBlockTypeIconRender]`: The icon to be rendered to the left of the menu item label. Can be a Dashicon slug or an SVG WP element. - _props.onClick_ `[Function]`: The callback function to be executed when the user clicks the menu item. @@ -897,7 +899,7 @@ _Parameters_ _Returns_ -- `Component`: The rendered menu item component. +- `React.ReactNode`: The rendered menu item component. ### PluginSidebar @@ -954,6 +956,7 @@ _Parameters_ - _props_ `Object`: Element props. - _props.name_ `string`: A string identifying the sidebar. Must be unique for every sidebar registered within the scope of your plugin. +- _props.children_ `[React.ReactNode]`: Children to be rendered. - _props.className_ `[string]`: An optional class name added to the sidebar body. - _props.title_ `string`: Title displayed at the top of the sidebar. - _props.isPinnable_ `[boolean]`: Whether to allow to pin sidebar to the toolbar. When set to `true` it also automatically renders a corresponding menu item. @@ -1000,11 +1003,12 @@ _Parameters_ - _props_ `Object`: Component props. - _props.target_ `string`: A string identifying the target sidebar you wish to be activated by this menu item. Must be the same as the `name` prop you have given to that sidebar. +- _props.children_ `[React.ReactNode]`: Children to be rendered. - _props.icon_ `[WPBlockTypeIconRender]`: The [Dashicon](https://developer.wordpress.org/resource/dashicons/) icon slug string, or an SVG WP element, to be rendered to the left of the menu item label. _Returns_ -- `Component`: The component to be rendered. +- `React.ReactNode`: The rendered component. ### PostAuthor @@ -1012,7 +1016,7 @@ Renders the component for selecting the post author. _Returns_ -- `Component`: The component to be rendered. +- `React.ReactNode`: The rendered component. ### PostAuthorCheck @@ -1021,11 +1025,11 @@ Wrapper component that renders its children only if the post type supports the a _Parameters_ - _props_ `Object`: The component props. -- _props.children_ `Element`: Children to be rendered. +- _props.children_ `React.ReactNode`: Children to be rendered. _Returns_ -- `Component|null`: The component to be rendered. Return `null` if the post type doesn't supports the author or if there are no authors available. +- `React.ReactNode`: The component to be rendered. Return `null` if the post type doesn't supports the author or if there are no authors available. ### PostAuthorPanel @@ -1033,7 +1037,7 @@ Renders the Post Author Panel component. _Returns_ -- `Component`: The component to be rendered. +- `React.ReactNode`: The rendered component. ### PostComments @@ -1041,7 +1045,7 @@ A form for managing comment status. _Returns_ -- `JSX.Element`: The rendered PostComments component. +- `React.ReactNode`: The rendered PostComments component. ### PostDiscussionPanel @@ -1049,7 +1053,7 @@ This component allows to update comment and pingback settings for the current po _Returns_ -- `JSX.Element|null`: The rendered PostDiscussionPanel component. +- `React.ReactNode`: The rendered PostDiscussionPanel component. ### PostExcerpt @@ -1068,11 +1072,11 @@ Component for checking if the post type supports the excerpt field. _Parameters_ - _props_ `Object`: Props. -- _props.children_ `Element`: Children to be rendered. +- _props.children_ `React.ReactNode`: Children to be rendered. _Returns_ -- `Component`: The component to be rendered. +- `React.ReactNode`: The rendered component. ### PostExcerptPanel @@ -1080,7 +1084,7 @@ Is rendered if the post type supports excerpts and allows editing the excerpt. _Returns_ -- `JSX.Element`: The rendered PostExcerptPanel component. +- `React.ReactNode`: The rendered PostExcerptPanel component. ### PostFeaturedImage @@ -1109,11 +1113,11 @@ Wrapper component that renders its children only if the post type supports a fea _Parameters_ - _props_ `Object`: Props. -- _props.children_ `Element`: Children to be rendered. +- _props.children_ `React.ReactNode`: Children to be rendered. _Returns_ -- `Component`: The component to be rendered. +- `React.ReactNode`: The rendered component. ### PostFeaturedImagePanel @@ -1126,7 +1130,7 @@ _Parameters_ _Returns_ -- `Component|null`: The component to be rendered. Return Null if the editor panel is disabled for featured image. +- `React.ReactNode`: The component to be rendered. Return Null if the editor panel is disabled for featured image. ### PostFormat @@ -1140,7 +1144,7 @@ _Usage_ _Returns_ -- `JSX.Element`: The rendered PostFormat component. +- `React.ReactNode`: The rendered PostFormat component. ### PostFormatCheck @@ -1149,11 +1153,11 @@ Component check if there are any post formats. _Parameters_ - _props_ `Object`: The component props. -- _props.children_ `Element`: The child elements to render. +- _props.children_ `React.ReactNode`: The child elements to render. _Returns_ -- `Component|null`: The rendered component or null if post formats are disabled. +- `React.ReactNode`: The rendered component or null if post formats are disabled. ### PostLastRevision @@ -1161,7 +1165,7 @@ Renders the component for displaying the last revision of a post. _Returns_ -- `Component`: The component to be rendered. +- `React.ReactNode`: The rendered component. ### PostLastRevisionCheck @@ -1170,11 +1174,11 @@ Wrapper component that renders its children if the post has more than one revisi _Parameters_ - _props_ `Object`: Props. -- _props.children_ `Element`: Children to be rendered. +- _props.children_ `React.ReactNode`: Children to be rendered. _Returns_ -- `Component|null`: Rendered child components if post has more than one revision, otherwise null. +- `React.ReactNode`: Rendered child components if post has more than one revision, otherwise null. ### PostLastRevisionPanel @@ -1182,7 +1186,7 @@ Renders the panel for displaying the last revision of a post. _Returns_ -- `Component`: The component to be rendered. +- `React.ReactNode`: The rendered component. ### PostLockedModal @@ -1190,7 +1194,7 @@ A modal component that is displayed when a post is locked for editing by another _Returns_ -- `JSX.Element|null`: The rendered PostLockedModal component. +- `React.ReactNode`: The rendered PostLockedModal component. ### PostPendingStatus @@ -1198,7 +1202,7 @@ A component for displaying and toggling the pending status of a post. _Returns_ -- `JSX.Element`: The rendered component. +- `React.ReactNode`: The rendered component. ### PostPendingStatusCheck @@ -1207,11 +1211,11 @@ This component checks the publishing status of the current post. If the post is _Parameters_ - _props_ `Object`: Component properties. -- _props.children_ `Element`: Children to be rendered. +- _props.children_ `React.ReactElement`: Children to be rendered. _Returns_ -- `JSX.Element|null`: The rendered child elements or null if the post is already published or the user doesn't have the capability to publish. +- `React.ReactElement`: The rendered child elements or null if the post is already published or the user doesn't have the capability to publish. ### PostPingbacks @@ -1232,7 +1236,7 @@ _Parameters_ _Returns_ -- `JSX.Element|null`: The rendered button component. +- `React.ReactNode`: The rendered button component. ### PostPublishButton @@ -1274,7 +1278,7 @@ _Parameters_ _Returns_ -- `Component`: The component to be rendered. +- `React.ReactNode`: The rendered component. ### PostScheduleCheck @@ -1283,11 +1287,11 @@ Wrapper component that renders its children only if post has a publish action. _Parameters_ - _props_ `Object`: Props. -- _props.children_ `Element`: Children to be rendered. +- _props.children_ `React.ReactElement`: Children to be rendered. _Returns_ -- `Component`: - The component to be rendered or null if there is no publish action. +- `React.ReactElement`: - The component to be rendered or null if there is no publish action. ### PostScheduleLabel @@ -1299,7 +1303,7 @@ _Parameters_ _Returns_ -- `Component`: The component to be rendered. +- `React.ReactNode`: The rendered component. ### PostSchedulePanel @@ -1307,28 +1311,7 @@ Renders the Post Schedule Panel component. _Returns_ -- `Component`: The component to be rendered. - -### PostSlug - -Renders the PostSlug component. It provide a control for editing the post slug. - -_Returns_ - -- `Component`: The component to be rendered. - -### PostSlugCheck - -Wrapper component that renders its children only if the post type supports the slug. - -_Parameters_ - -- _props_ `Object`: Props. -- _props.children_ `Element`: Children to be rendered. - -_Returns_ - -- `Component`: The component to be rendered. +- `React.ReactNode`: The rendered component. ### PostSticky @@ -1336,7 +1319,7 @@ Renders the PostSticky component. It provides a checkbox control for the sticky _Returns_ -- `Component`: The component to be rendered. +- `React.ReactNode`: The rendered component. ### PostStickyCheck @@ -1345,11 +1328,11 @@ Wrapper component that renders its children only if post has a sticky action. _Parameters_ - _props_ `Object`: Props. -- _props.children_ `Element`: Children to be rendered. +- _props.children_ `React.ReactElement`: Children to be rendered. _Returns_ -- `Component`: The component to be rendered or null if post type is not 'post' or hasStickyAction is false. +- `React.ReactElement`: The component to be rendered or null if post type is not 'post' or hasStickyAction is false. ### PostSwitchToDraftButton @@ -1357,7 +1340,7 @@ Renders a button component that allows the user to switch a post to draft status _Returns_ -- `JSX.Element`: The rendered component. +- `React.ReactNode`: The rendered component. ### PostSyncStatus @@ -1365,7 +1348,7 @@ Renders the sync status of a post. _Returns_ -- `JSX.Element|null`: The rendered sync status component. +- `React.ReactNode`: The rendered sync status component. ### PostTaxonomies @@ -1387,11 +1370,11 @@ Renders the children components only if the current post type has taxonomies. _Parameters_ - _props_ `Object`: The component props. -- _props.children_ `Element`: The children components to render. +- _props.children_ `React.ReactNode`: The children components to render. _Returns_ -- `Component|null`: The rendered children components or null if the current post type has no taxonomies. +- `React.ReactElement`: The rendered children components or null if the current post type has no taxonomies. ### PostTaxonomiesFlatTermSelector @@ -1405,7 +1388,7 @@ _Parameters_ _Returns_ -- `JSX.Element`: The rendered flat term selector component. +- `React.ReactNode`: The rendered flat term selector component. ### PostTaxonomiesHierarchicalTermSelector @@ -1422,17 +1405,11 @@ _Returns_ ### PostTaxonomiesPanel -Renders a panel for a specific taxonomy. - -_Parameters_ - -- _props_ `Object`: The component props. -- _props.taxonomy_ `Object`: The taxonomy object. -- _props.children_ `Element`: The child components. +Component that renders the post taxonomies panel. _Returns_ -- `Component`: The rendered taxonomy panel. +- `React.ReactNode`: The rendered component. ### PostTemplatePanel @@ -1440,7 +1417,7 @@ Displays the template controls based on the current editor settings and user per _Returns_ -- `JSX.Element|null`: The rendered PostTemplatePanel component. +- `React.ReactNode`: The rendered PostTemplatePanel component. ### PostTextEditor @@ -1448,7 +1425,7 @@ Displays the Post Text Editor along with content in Visual and Text mode. _Returns_ -- `JSX.Element|null`: The rendered PostTextEditor component. +- `React.ReactNode`: The rendered PostTextEditor component. ### PostTitle @@ -1461,7 +1438,7 @@ _Parameters_ _Returns_ -- `Component`: The rendered PostTitle component. +- `React.ReactNode`: The rendered PostTitle component. ### PostTitleRaw @@ -1477,20 +1454,20 @@ _Parameters_ _Returns_ -- `JSX.Element|null`: The rendered PostTrash component. +- `React.ReactNode`: The rendered PostTrash component. ### PostTrashCheck -Wrapper component that renders its children only if the post can trashed. +Wrapper component that renders its children only if the post can be trashed. _Parameters_ -- _props_ `Object`: - The component props. -- _props.children_ `Element`: - The child components to render. +- _props_ `Object`: The component props. +- _props.children_ `React.ReactElement`: The child components. _Returns_ -- `Component|null`: The rendered child components or null if the post can not trashed. +- `React.ReactElement | null`: The rendered child components or null if the post can't be trashed. ### PostTypeSupportCheck @@ -1499,12 +1476,12 @@ A component which renders its own children only if the current editor post type _Parameters_ - _props_ `Object`: Props. -- _props.children_ `Element`: Children to be rendered if post type supports. +- _props.children_ `React.ReactElement`: Children to be rendered if post type supports. - _props.supportKeys_ `(string|string[])`: String or string array of keys to test. _Returns_ -- `Component`: The component to be rendered. +- `React.ReactElement`: The component to be rendered. ### PostURL @@ -1518,11 +1495,12 @@ _Usage_ _Parameters_ -- _onClose_ `Function`: Callback function to be executed when the popover is closed. +- _props_ `{ onClose: () => void }`: The props for the component. +- _props.onClose_ `() => void`: Callback function to be executed when the popover is closed. _Returns_ -- `Component`: The rendered PostURL component. +- `React.ReactNode`: The rendered PostURL component. ### PostURLCheck @@ -1531,11 +1509,11 @@ Check if the post URL is valid and visible. _Parameters_ - _props_ `Object`: The component props. -- _props.children_ `Element`: The child components. +- _props.children_ `React.ReactElement`: The child components. _Returns_ -- `Component|null`: The child components if the post URL is valid and visible, otherwise null. +- `React.ReactElement`: The child components if the post URL is valid and visible, otherwise null. ### PostURLLabel @@ -1543,7 +1521,7 @@ Represents a label component for a post URL. _Returns_ -- `Component`: The PostURLLabel component. +- `React.ReactNode`: The PostURLLabel component. ### PostURLPanel @@ -1551,7 +1529,7 @@ Renders the `PostURLPanel` component. _Returns_ -- `JSX.Element`: The rendered PostURLPanel component. +- `React.ReactNode`: The rendered PostURLPanel component. ### PostVisibility @@ -1564,7 +1542,7 @@ _Parameters_ _Returns_ -- `JSX.Element`: The rendered component. +- `React.ReactNode`: The rendered component. ### PostVisibilityCheck @@ -1577,7 +1555,7 @@ _Parameters_ _Returns_ -- `JSX.Element`: The rendered component. +- `React.ReactNode`: The rendered component. ### PostVisibilityLabel @@ -1603,6 +1581,18 @@ _Parameters_ - _name_ `string`: Entity name. - _config_ `Action`: Action configuration. +### registerEntityField + +Registers a new DataViews field. + +This is an experimental API and is subject to change. it's only available in the Gutenberg plugin for now. + +_Parameters_ + +- _kind_ `string`: Entity kind. +- _name_ `string`: Entity name. +- _config_ `Field`: Field configuration. + ### RichText > **Deprecated** since 5.3, use `wp.blockEditor.RichText` instead. @@ -1652,7 +1642,7 @@ _Parameters_ _Returns_ -- `JSX.Element`: The rendered table of contents component. +- `React.ReactNode`: The rendered table of contents component. ### TextEditorGlobalKeyboardShortcuts @@ -1667,12 +1657,12 @@ Checks if the current theme supports specific features and renders the children _Parameters_ - _props_ `Object`: The component props. -- _props.children_ `Element`: The children to render if the theme supports the specified features. +- _props.children_ `React.ReactElement`: The children to render if the theme supports the specified features. - _props.supportKeys_ `string|string[]`: The key(s) of the theme support(s) to check. _Returns_ -- `JSX.Element|null`: The rendered children if the theme supports the specified features, otherwise null. +- `React.ReactElement`: The rendered children if the theme supports the specified features, otherwise null. ### TimeToRead @@ -1680,7 +1670,7 @@ Component for showing Time To Read in Content. _Returns_ -- `JSX.Element`: The rendered TimeToRead component. +- `React.ReactNode`: The rendered TimeToRead component. ### transformStyles @@ -1698,13 +1688,25 @@ _Parameters_ - _name_ `string`: Entity name. - _actionId_ `string`: Action ID. +### unregisterEntityField + +Unregisters a DataViews field. + +This is an experimental API and is subject to change. it's only available in the Gutenberg plugin for now. + +_Parameters_ + +- _kind_ `string`: Entity kind. +- _name_ `string`: Entity name. +- _fieldId_ `string`: Field ID. + ### UnsavedChangesWarning Warns the user if there are unsaved changes before leaving the editor. Compatible with Post Editor and Site Editor. _Returns_ -- `Component`: The component. +- `React.ReactNode`: The component. ### URLInput @@ -1761,7 +1763,7 @@ A user mentions completer. _Type_ -- `WPCompleter` +- `Object` ### VisualEditorGlobalKeyboardShortcuts @@ -1791,7 +1793,7 @@ Renders the word count of the post content. _Returns_ -- `JSX.Element|null`: The rendered WordCount component. +- `React.ReactNode`: The rendered WordCount component. ### WritingFlow diff --git a/packages/editor/package.json b/packages/editor/package.json index 98e0c6d2255b74..d5a60f70eef5ab 100644 --- a/packages/editor/package.json +++ b/packages/editor/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/editor", - "version": "14.11.0", + "version": "14.16.0", "description": "Enhanced block editor for WordPress posts.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -34,41 +34,41 @@ ], "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/a11y": "*", - "@wordpress/api-fetch": "*", - "@wordpress/blob": "*", - "@wordpress/block-editor": "*", - "@wordpress/blocks": "*", - "@wordpress/commands": "*", - "@wordpress/components": "*", - "@wordpress/compose": "*", - "@wordpress/core-data": "*", - "@wordpress/data": "*", - "@wordpress/dataviews": "*", - "@wordpress/date": "*", - "@wordpress/deprecated": "*", - "@wordpress/dom": "*", - "@wordpress/element": "*", - "@wordpress/fields": "*", - "@wordpress/hooks": "*", - "@wordpress/html-entities": "*", - "@wordpress/i18n": "*", - "@wordpress/icons": "*", - "@wordpress/interface": "*", - "@wordpress/keyboard-shortcuts": "*", - "@wordpress/keycodes": "*", - "@wordpress/media-utils": "*", - "@wordpress/notices": "*", - "@wordpress/patterns": "*", - "@wordpress/plugins": "*", - "@wordpress/preferences": "*", - "@wordpress/private-apis": "*", - "@wordpress/reusable-blocks": "*", - "@wordpress/rich-text": "*", - "@wordpress/server-side-render": "*", - "@wordpress/url": "*", - "@wordpress/warning": "*", - "@wordpress/wordcount": "*", + "@wordpress/a11y": "file:../a11y", + "@wordpress/api-fetch": "file:../api-fetch", + "@wordpress/blob": "file:../blob", + "@wordpress/block-editor": "file:../block-editor", + "@wordpress/blocks": "file:../blocks", + "@wordpress/commands": "file:../commands", + "@wordpress/components": "file:../components", + "@wordpress/compose": "file:../compose", + "@wordpress/core-data": "file:../core-data", + "@wordpress/data": "file:../data", + "@wordpress/dataviews": "file:../dataviews", + "@wordpress/date": "file:../date", + "@wordpress/deprecated": "file:../deprecated", + "@wordpress/dom": "file:../dom", + "@wordpress/element": "file:../element", + "@wordpress/fields": "file:../fields", + "@wordpress/hooks": "file:../hooks", + "@wordpress/html-entities": "file:../html-entities", + "@wordpress/i18n": "file:../i18n", + "@wordpress/icons": "file:../icons", + "@wordpress/interface": "file:../interface", + "@wordpress/keyboard-shortcuts": "file:../keyboard-shortcuts", + "@wordpress/keycodes": "file:../keycodes", + "@wordpress/media-utils": "file:../media-utils", + "@wordpress/notices": "file:../notices", + "@wordpress/patterns": "file:../patterns", + "@wordpress/plugins": "file:../plugins", + "@wordpress/preferences": "file:../preferences", + "@wordpress/private-apis": "file:../private-apis", + "@wordpress/reusable-blocks": "file:../reusable-blocks", + "@wordpress/rich-text": "file:../rich-text", + "@wordpress/server-side-render": "file:../server-side-render", + "@wordpress/url": "file:../url", + "@wordpress/warning": "file:../warning", + "@wordpress/wordcount": "file:../wordcount", "change-case": "^4.1.2", "client-zip": "^2.4.5", "clsx": "^2.1.1", diff --git a/packages/editor/src/bindings/pattern-overrides.js b/packages/editor/src/bindings/pattern-overrides.js index baa1f72f47694b..57f6714e8b9f41 100644 --- a/packages/editor/src/bindings/pattern-overrides.js +++ b/packages/editor/src/bindings/pattern-overrides.js @@ -19,7 +19,7 @@ export default { currentBlockAttributes?.metadata?.name ]?.[ attributeName ]; - // If it has not been overriden, return the original value. + // If it has not been overridden, return the original value. // Check undefined because empty string is a valid value. if ( overridableValue === undefined ) { overridesValues[ attributeName ] = diff --git a/packages/editor/src/bindings/post-meta.js b/packages/editor/src/bindings/post-meta.js index a3602ce7d62076..fcd068ac21d8ab 100644 --- a/packages/editor/src/bindings/post-meta.js +++ b/packages/editor/src/bindings/post-meta.js @@ -108,11 +108,8 @@ export default { return false; } - const postType = - context?.postType || select( editorStore ).getCurrentPostType(); - - // Check that editing is happening in the post editor and not a template. - if ( postType === 'wp_template' ) { + // Lock editing when `postType` is not defined. + if ( ! context?.postType ) { return false; } diff --git a/packages/editor/src/components/autocompleters/style.scss b/packages/editor/src/components/autocompleters/style.scss index ca3159ee4ac825..295d63b8e57c77 100644 --- a/packages/editor/src/components/autocompleters/style.scss +++ b/packages/editor/src/components/autocompleters/style.scss @@ -11,7 +11,7 @@ flex-grow: 0; flex-shrink: 0; max-width: none; // we must override the gutenberg default of 100% - width: 24px; // avoid jarring resize by seting the size upfront + width: 24px; // avoid jarring resize by setting the size upfront height: 24px; } .editor-autocompleters__user-name { diff --git a/packages/editor/src/components/autocompleters/user.js b/packages/editor/src/components/autocompleters/user.js index e176d3fb6b05a2..aed226ff4a50da 100644 --- a/packages/editor/src/components/autocompleters/user.js +++ b/packages/editor/src/components/autocompleters/user.js @@ -5,8 +5,6 @@ import { useMemo } from '@wordpress/element'; import { useSelect } from '@wordpress/data'; import { store as coreStore } from '@wordpress/core-data'; -/** @typedef {import('@wordpress/components').WPCompleter} WPCompleter */ - export function getUserLabel( user ) { const avatar = user.avatar_urls && user.avatar_urls[ 24 ] ? ( @@ -35,7 +33,7 @@ export function getUserLabel( user ) { /** * A user mentions completer. * - * @type {WPCompleter} + * @type {Object} */ export default { name: 'users', diff --git a/packages/editor/src/components/block-manager/category.js b/packages/editor/src/components/block-manager/category.js deleted file mode 100644 index e7125fa151f72a..00000000000000 --- a/packages/editor/src/components/block-manager/category.js +++ /dev/null @@ -1,96 +0,0 @@ -/** - * WordPress dependencies - */ -import { useMemo, useCallback } from '@wordpress/element'; -import { useDispatch, useSelect } from '@wordpress/data'; -import { useInstanceId } from '@wordpress/compose'; -import { CheckboxControl } from '@wordpress/components'; -import { store as preferencesStore } from '@wordpress/preferences'; - -/** - * Internal dependencies - */ -import BlockTypesChecklist from './checklist'; -import { store as editorStore } from '../../store'; -import { unlock } from '../../lock-unlock'; - -function BlockManagerCategory( { title, blockTypes } ) { - const instanceId = useInstanceId( BlockManagerCategory ); - const { allowedBlockTypes, hiddenBlockTypes } = useSelect( ( select ) => { - const { getEditorSettings } = select( editorStore ); - const { get } = select( preferencesStore ); - return { - allowedBlockTypes: getEditorSettings().allowedBlockTypes, - hiddenBlockTypes: get( 'core', 'hiddenBlockTypes' ), - }; - }, [] ); - const filteredBlockTypes = useMemo( () => { - if ( allowedBlockTypes === true ) { - return blockTypes; - } - return blockTypes.filter( ( { name } ) => { - return allowedBlockTypes?.includes( name ); - } ); - }, [ allowedBlockTypes, blockTypes ] ); - const { showBlockTypes, hideBlockTypes } = unlock( - useDispatch( editorStore ) - ); - const toggleVisible = useCallback( - ( blockName, nextIsChecked ) => { - if ( nextIsChecked ) { - showBlockTypes( blockName ); - } else { - hideBlockTypes( blockName ); - } - }, - [ showBlockTypes, hideBlockTypes ] - ); - const toggleAllVisible = useCallback( - ( nextIsChecked ) => { - const blockNames = blockTypes.map( ( { name } ) => name ); - if ( nextIsChecked ) { - showBlockTypes( blockNames ); - } else { - hideBlockTypes( blockNames ); - } - }, - [ blockTypes, showBlockTypes, hideBlockTypes ] - ); - - if ( ! filteredBlockTypes.length ) { - return null; - } - - const checkedBlockNames = filteredBlockTypes - .map( ( { name } ) => name ) - .filter( ( type ) => ! ( hiddenBlockTypes ?? [] ).includes( type ) ); - - const titleId = 'editor-block-manager__category-title-' + instanceId; - - const isAllChecked = checkedBlockNames.length === filteredBlockTypes.length; - const isIndeterminate = ! isAllChecked && checkedBlockNames.length > 0; - - return ( - <div - role="group" - aria-labelledby={ titleId } - className="editor-block-manager__category" - > - <CheckboxControl - __nextHasNoMarginBottom - checked={ isAllChecked } - onChange={ toggleAllVisible } - className="editor-block-manager__category-title" - indeterminate={ isIndeterminate } - label={ <span id={ titleId }>{ title }</span> } - /> - <BlockTypesChecklist - blockTypes={ filteredBlockTypes } - value={ checkedBlockNames } - onItemChange={ toggleVisible } - /> - </div> - ); -} - -export default BlockManagerCategory; diff --git a/packages/editor/src/components/block-settings-menu/plugin-block-settings-menu-item.js b/packages/editor/src/components/block-settings-menu/plugin-block-settings-menu-item.js index 59c9e9c32d4a4b..df1e75d2d0e8b5 100644 --- a/packages/editor/src/components/block-settings-menu/plugin-block-settings-menu-item.js +++ b/packages/editor/src/components/block-settings-menu/plugin-block-settings-menu-item.js @@ -76,7 +76,7 @@ const shouldRenderItem = ( selectedBlocks, allowedBlocks ) => * ); * ``` * - * @return {Component} The component to be rendered. + * @return {React.ReactNode} The rendered component. */ const PluginBlockSettingsMenuItem = ( { allowedBlocks, diff --git a/packages/editor/src/components/collab-sidebar/add-comment.js b/packages/editor/src/components/collab-sidebar/add-comment.js index 01ee7aff0370ef..f94891ada07109 100644 --- a/packages/editor/src/components/collab-sidebar/add-comment.js +++ b/packages/editor/src/components/collab-sidebar/add-comment.js @@ -1,22 +1,19 @@ /** * WordPress dependencies */ -import { __, _x } from '@wordpress/i18n'; +import { _x } from '@wordpress/i18n'; import { useSelect } from '@wordpress/data'; -import { useState, useEffect } from '@wordpress/element'; import { __experimentalHStack as HStack, __experimentalVStack as VStack, - Button, - TextControl, } from '@wordpress/components'; import { store as blockEditorStore } from '@wordpress/block-editor'; -import { store as coreStore } from '@wordpress/core-data'; /** * Internal dependencies */ -import { sanitizeCommentString } from './utils'; +import CommentAuthorInfo from './comment-author-info'; +import CommentForm from './comment-form'; /** * Renders the UI for adding a comment in the Gutenberg editor's collaboration sidebar. @@ -25,46 +22,21 @@ import { sanitizeCommentString } from './utils'; * @param {Function} props.onSubmit - A callback function to be called when the user submits a comment. * @param {boolean} props.showCommentBoard - The function to edit the comment. * @param {Function} props.setShowCommentBoard - The function to delete the comment. - * @return {JSX.Element} The rendered comment input UI. + * @return {React.ReactNode} The rendered comment input UI. */ export function AddComment( { onSubmit, showCommentBoard, setShowCommentBoard, } ) { - // State to manage the comment thread. - const [ inputComment, setInputComment ] = useState( '' ); - - const { defaultAvatar, clientId, blockCommentId, currentUser } = useSelect( - ( select ) => { - const { getSettings, getSelectedBlock } = - select( blockEditorStore ); - const { __experimentalDiscussionSettings } = getSettings(); - const selectedBlock = getSelectedBlock(); - const userData = select( coreStore ).getCurrentUser(); - return { - defaultAvatar: __experimentalDiscussionSettings?.avatarURL, - clientId: selectedBlock?.clientId, - blockCommentId: selectedBlock?.attributes?.blockCommentId, - currentUser: userData, - }; - }, - [] - ); - - const userAvatar = - currentUser && currentUser.avatar_urls && currentUser.avatar_urls[ 48 ] - ? currentUser.avatar_urls[ 48 ] - : defaultAvatar; - - useEffect( () => { - setInputComment( '' ); - }, [ clientId ] ); - - const handleCancel = () => { - setShowCommentBoard( false ); - setInputComment( '' ); - }; + const { clientId, blockCommentId } = useSelect( ( select ) => { + const { getSelectedBlock } = select( blockEditorStore ); + const selectedBlock = getSelectedBlock(); + return { + clientId: selectedBlock?.clientId, + blockCommentId: selectedBlock?.attributes?.blockCommentId, + }; + } ); if ( ! showCommentBoard || ! clientId || undefined !== blockCommentId ) { return null; @@ -73,49 +45,20 @@ export function AddComment( { return ( <VStack spacing="3" - className="editor-collab-sidebar-panel__thread editor-collab-sidebar-panel__active-thread" + className="editor-collab-sidebar-panel__thread editor-collab-sidebar-panel__active-thread editor-collab-sidebar-panel__focus-thread" > <HStack alignment="left" spacing="3"> - <img - src={ userAvatar } - // translators: alt text for user avatar image - alt={ __( 'User Avatar' ) } - className="editor-collab-sidebar-panel__user-avatar" - width={ 32 } - height={ 32 } - /> - <span className="editor-collab-sidebar-panel__user-name"> - { currentUser?.name ?? '' } - </span> + <CommentAuthorInfo /> </HStack> - <TextControl - __next40pxDefaultSize - __nextHasNoMarginBottom - value={ inputComment } - onChange={ setInputComment } - placeholder={ _x( 'Comment', 'noun' ) } + <CommentForm + onSubmit={ ( inputComment ) => { + onSubmit( inputComment ); + } } + onCancel={ () => { + setShowCommentBoard( false ); + } } + submitButtonText={ _x( 'Comment', 'Add comment button' ) } /> - <HStack alignment="right" spacing="3"> - <Button - __next40pxDefaultSize - variant="tertiary" - text={ _x( 'Cancel', 'Cancel comment button' ) } - onClick={ handleCancel } - /> - <Button - __next40pxDefaultSize - accessibleWhenDisabled - variant="primary" - text={ _x( 'Comment', 'Add comment button' ) } - disabled={ - 0 === sanitizeCommentString( inputComment ).length - } - onClick={ () => { - onSubmit( inputComment ); - setInputComment( '' ); - } } - /> - </HStack> </VStack> ); } diff --git a/packages/editor/src/components/collab-sidebar/comment-author-info.js b/packages/editor/src/components/collab-sidebar/comment-author-info.js new file mode 100644 index 00000000000000..d8b5f72a2fc25f --- /dev/null +++ b/packages/editor/src/components/collab-sidebar/comment-author-info.js @@ -0,0 +1,68 @@ +/** + * WordPress dependencies + */ +import { __experimentalVStack as VStack } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; +import { dateI18n, getSettings as getDateSettings } from '@wordpress/date'; +import { useEntityProp, store as coreStore } from '@wordpress/core-data'; +import { useSelect } from '@wordpress/data'; +import { store as blockEditorStore } from '@wordpress/block-editor'; + +/** + * Render author information for a comment. + * + * @param {Object} props - Component properties. + * @param {string} props.avatar - URL of the author's avatar. + * @param {string} props.name - Name of the author. + * @param {string} props.date - Date of the comment. + * + * @return {React.ReactNode} The JSX element representing the author's information. + */ +function CommentAuthorInfo( { avatar, name, date } ) { + const dateSettings = getDateSettings(); + const [ dateTimeFormat = dateSettings.formats.time ] = useEntityProp( + 'root', + 'site', + 'time_format' + ); + + const { currentUserAvatar, currentUserName } = useSelect( ( select ) => { + const userData = select( coreStore ).getCurrentUser(); + + const { getSettings } = select( blockEditorStore ); + const { __experimentalDiscussionSettings } = getSettings(); + const defaultAvatar = __experimentalDiscussionSettings?.avatarURL; + return { + currentUserAvatar: userData?.avatar_urls[ 48 ] ?? defaultAvatar, + currentUserName: userData?.name, + }; + }, [] ); + + const currentDate = new Date(); + + return ( + <> + <img + src={ avatar ?? currentUserAvatar } + className="editor-collab-sidebar-panel__user-avatar" + // translators: alt text for user avatar image + alt={ __( 'User avatar' ) } + width={ 32 } + height={ 32 } + /> + <VStack spacing="0"> + <span className="editor-collab-sidebar-panel__user-name"> + { name ?? currentUserName } + </span> + <time + dateTime={ dateI18n( 'c', date ?? currentDate ) } + className="editor-collab-sidebar-panel__user-time" + > + { dateI18n( dateTimeFormat, date ?? currentDate ) } + </time> + </VStack> + </> + ); +} + +export default CommentAuthorInfo; diff --git a/packages/editor/src/components/collab-sidebar/comment-button-toolbar.js b/packages/editor/src/components/collab-sidebar/comment-button-toolbar.js index 2e378a7eaabab0..b673ee87fe86ce 100644 --- a/packages/editor/src/components/collab-sidebar/comment-button-toolbar.js +++ b/packages/editor/src/components/collab-sidebar/comment-button-toolbar.js @@ -11,18 +11,18 @@ import { privateApis as blockEditorPrivateApis } from '@wordpress/block-editor'; */ import { unlock } from '../../lock-unlock'; -const { __unstableCommentIconToolbarFill } = unlock( blockEditorPrivateApis ); +const { CommentIconToolbarSlotFill } = unlock( blockEditorPrivateApis ); const AddCommentToolbarButton = ( { onClick } ) => { return ( - <__unstableCommentIconToolbarFill> + <CommentIconToolbarSlotFill.Fill> <ToolbarButton accessibleWhenDisabled icon={ commentIcon } label={ _x( 'Comment', 'View comment' ) } onClick={ onClick } /> - </__unstableCommentIconToolbarFill> + </CommentIconToolbarSlotFill.Fill> ); }; diff --git a/packages/editor/src/components/collab-sidebar/comment-button.js b/packages/editor/src/components/collab-sidebar/comment-button.js index 3b020661816660..37300be147e1de 100644 --- a/packages/editor/src/components/collab-sidebar/comment-button.js +++ b/packages/editor/src/components/collab-sidebar/comment-button.js @@ -12,19 +12,24 @@ import { privateApis as blockEditorPrivateApis } from '@wordpress/block-editor'; */ import { unlock } from '../../lock-unlock'; -const { __unstableCommentIconFill } = unlock( blockEditorPrivateApis ); +const { CommentIconSlotFill } = unlock( blockEditorPrivateApis ); const AddCommentButton = ( { onClick } ) => { return ( - <__unstableCommentIconFill> - <MenuItem - icon={ commentIcon } - onClick={ onClick } - aria-haspopup="dialog" - > - { _x( 'Comment', 'Add comment button' ) } - </MenuItem> - </__unstableCommentIconFill> + <CommentIconSlotFill.Fill> + { ( { onClose } ) => ( + <MenuItem + icon={ commentIcon } + onClick={ () => { + onClick(); + onClose(); + } } + aria-haspopup="dialog" + > + { _x( 'Comment', 'Add comment button' ) } + </MenuItem> + ) } + </CommentIconSlotFill.Fill> ); }; diff --git a/packages/editor/src/components/collab-sidebar/comment-form.js b/packages/editor/src/components/collab-sidebar/comment-form.js new file mode 100644 index 00000000000000..bd139a682f5cb7 --- /dev/null +++ b/packages/editor/src/components/collab-sidebar/comment-form.js @@ -0,0 +1,65 @@ +/** + * WordPress dependencies + */ +import { useState } from '@wordpress/element'; +import { + __experimentalHStack as HStack, + Button, + TextareaControl, +} from '@wordpress/components'; +import { _x } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import { sanitizeCommentString } from './utils'; + +/** + * EditComment component. + * + * @param {Object} props - The component props. + * @param {Function} props.onSubmit - The function to call when updating the comment. + * @param {Function} props.onCancel - The function to call when canceling the comment update. + * @param {Object} props.thread - The comment thread object. + * @param {string} props.submitButtonText - The text to display on the submit button. + * @return {React.ReactNode} The CommentForm component. + */ +function CommentForm( { onSubmit, onCancel, thread, submitButtonText } ) { + const [ inputComment, setInputComment ] = useState( + thread?.content?.raw ?? '' + ); + + return ( + <> + <TextareaControl + __next40pxDefaultSize + __nextHasNoMarginBottom + value={ inputComment ?? '' } + onChange={ setInputComment } + /> + <HStack alignment="left" spacing="3" justify="flex-start"> + <Button + __next40pxDefaultSize + accessibleWhenDisabled + variant="primary" + onClick={ () => { + onSubmit( inputComment ); + setInputComment( '' ); + } } + disabled={ + 0 === sanitizeCommentString( inputComment ).length + } + text={ submitButtonText } + /> + <Button + __next40pxDefaultSize + variant="tertiary" + onClick={ onCancel } + text={ _x( 'Cancel', 'Cancel comment button' ) } + /> + </HStack> + </> + ); +} + +export default CommentForm; diff --git a/packages/editor/src/components/collab-sidebar/comments.js b/packages/editor/src/components/collab-sidebar/comments.js index c0b4bd3d4dc38a..d492d233956cb7 100644 --- a/packages/editor/src/components/collab-sidebar/comments.js +++ b/packages/editor/src/components/collab-sidebar/comments.js @@ -13,35 +13,31 @@ import { __experimentalConfirmDialog as ConfirmDialog, Button, DropdownMenu, - TextareaControl, Tooltip, } from '@wordpress/components'; -import { - dateI18n, - format, - getSettings as getDateSettings, -} from '@wordpress/date'; import { Icon, check, published, moreVertical } from '@wordpress/icons'; -import { __, _x } from '@wordpress/i18n'; +import { __, _x, sprintf } from '@wordpress/i18n'; import { useSelect } from '@wordpress/data'; -import { useEntityProp } from '@wordpress/core-data'; import { store as blockEditorStore } from '@wordpress/block-editor'; /** * Internal dependencies */ -import { sanitizeCommentString } from './utils'; +import CommentAuthorInfo from './comment-author-info'; +import CommentForm from './comment-form'; /** * Renders the Comments component. * - * @param {Object} props - The component props. - * @param {Array} props.threads - The array of comment threads. - * @param {Function} props.onEditComment - The function to handle comment editing. - * @param {Function} props.onAddReply - The function to add a reply to a comment. - * @param {Function} props.onCommentDelete - The function to delete a comment. - * @param {Function} props.onCommentResolve - The function to mark a comment as resolved. - * @return {JSX.Element} The rendered Comments component. + * @param {Object} props - The component props. + * @param {Array} props.threads - The array of comment threads. + * @param {Function} props.onEditComment - The function to handle comment editing. + * @param {Function} props.onAddReply - The function to add a reply to a comment. + * @param {Function} props.onCommentDelete - The function to delete a comment. + * @param {Function} props.onCommentResolve - The function to mark a comment as resolved. + * @param {boolean} props.showCommentBoard - Whether to show the comment board. + * @param {Function} props.setShowCommentBoard - The function to set the comment board visibility. + * @return {React.ReactNode} The rendered Comments component. */ export function Comments( { threads, @@ -49,128 +45,28 @@ export function Comments( { onAddReply, onCommentDelete, onCommentResolve, + showCommentBoard, + setShowCommentBoard, } ) { - const [ actionState, setActionState ] = useState( false ); - const [ isConfirmDialogOpen, setIsConfirmDialogOpen ] = useState( false ); - - const handleConfirmDelete = () => { - onCommentDelete( actionState.id ); - setActionState( false ); - setIsConfirmDialogOpen( false ); - }; + const { blockCommentId } = useSelect( ( select ) => { + const { getBlockAttributes, getSelectedBlockClientId } = + select( blockEditorStore ); + const _clientId = getSelectedBlockClientId(); - const handleConfirmResolve = () => { - onCommentResolve( actionState.id ); - setActionState( false ); - setIsConfirmDialogOpen( false ); - }; - - const handleCancelDelete = () => { - setActionState( false ); - setIsConfirmDialogOpen( false ); - }; - - const blockCommentId = useSelect( ( select ) => { - const clientID = select( blockEditorStore ).getSelectedBlockClientId(); - return ( - select( blockEditorStore ).getBlock( clientID )?.attributes - ?.blockCommentId ?? false - ); + return { + blockCommentId: _clientId + ? getBlockAttributes( _clientId )?.blockCommentId + : null, + }; }, [] ); - const CommentBoard = ( { thread, parentThread } ) => { - return ( - <> - <CommentHeader - thread={ thread } - onResolve={ () => { - setActionState( { - action: 'resolve', - id: parentThread?.id ?? thread.id, - } ); - setIsConfirmDialogOpen( true ); - } } - onEdit={ () => - setActionState( { action: 'edit', id: thread.id } ) - } - onDelete={ () => { - setActionState( { action: 'delete', id: thread.id } ); - setIsConfirmDialogOpen( true ); - } } - onReply={ - ! parentThread - ? () => - setActionState( { - action: 'reply', - id: thread.id, - } ) - : undefined - } - status={ parentThread?.status ?? thread.status } - /> - <HStack - alignment="left" - spacing="3" - justify="flex-start" - className="editor-collab-sidebar-panel__user-comment" - > - <VStack - spacing="3" - className="editor-collab-sidebar-panel__comment-field" - > - { 'edit' === actionState?.action && - thread.id === actionState?.id && ( - <CommentForm - onSubmit={ ( value ) => { - onEditComment( thread.id, value ); - setActionState( false ); - } } - onCancel={ () => setActionState( false ) } - thread={ thread } - /> - ) } - { ( ! actionState || - 'edit' !== actionState?.action ) && ( - <RawHTML>{ thread?.content?.raw }</RawHTML> - ) } - </VStack> - </HStack> - { 'resolve' === actionState?.action && - thread.id === actionState?.id && ( - <ConfirmDialog - isOpen={ isConfirmDialogOpen } - onConfirm={ handleConfirmResolve } - onCancel={ handleCancelDelete } - confirmButtonText="Yes" - cancelButtonText="No" - > - { - // translators: message displayed when confirming an action - __( - 'Are you sure you want to mark this comment as resolved?' - ) - } - </ConfirmDialog> - ) } - { 'delete' === actionState?.action && - thread.id === actionState?.id && ( - <ConfirmDialog - isOpen={ isConfirmDialogOpen } - onConfirm={ handleConfirmDelete } - onCancel={ handleCancelDelete } - confirmButtonText="Yes" - cancelButtonText="No" - > - { - // translators: message displayed when confirming an action - __( - 'Are you sure you want to delete this comment?' - ) - } - </ConfirmDialog> - ) } - </> - ); + const [ focusThread, setFocusThread ] = useState( + showCommentBoard && blockCommentId ? blockCommentId : null + ); + + const clearThreadFocus = () => { + setFocusThread( null ); + setShowCommentBoard( false ); }; return ( @@ -191,7 +87,6 @@ export function Comments( { </VStack> ) } - { Array.isArray( threads ) && threads.length > 0 && threads.map( ( thread ) => ( @@ -203,202 +98,262 @@ export function Comments( { 'editor-collab-sidebar-panel__active-thread': blockCommentId && blockCommentId === thread.id, + 'editor-collab-sidebar-panel__focus-thread': + focusThread && focusThread === thread.id, } ) } id={ thread.id } spacing="3" + onClick={ () => setFocusThread( thread.id ) } > - <CommentBoard thread={ thread } /> - { 'reply' === actionState?.action && - thread.id === actionState?.id && ( - <HStack - alignment="left" - spacing="3" - justify="flex-start" - className="editor-collab-sidebar-panel__user-comment" - > - <VStack - spacing="3" - className="editor-collab-sidebar-panel__comment-field" - > - <CommentForm - onSubmit={ ( inputComment ) => { - onAddReply( - inputComment, - thread.id - ); - setActionState( false ); - } } - onCancel={ () => - setActionState( false ) - } - /> - </VStack> - </HStack> - ) } - { 0 < thread?.reply?.length && - thread.reply.map( ( reply ) => ( - <VStack - key={ reply.id } - className="editor-collab-sidebar-panel__child-thread" - id={ reply.id } - spacing="2" - > - <CommentBoard - thread={ reply } - parentThread={ thread } - /> - </VStack> - ) ) } + <Thread + thread={ thread } + onAddReply={ onAddReply } + onCommentDelete={ onCommentDelete } + onCommentResolve={ onCommentResolve } + onEditComment={ onEditComment } + isFocused={ focusThread === thread.id } + clearThreadFocus={ clearThreadFocus } + /> </VStack> ) ) } </> ); } -/** - * EditComment component. - * - * @param {Object} props - The component props. - * @param {Function} props.onSubmit - The function to call when updating the comment. - * @param {Function} props.onCancel - The function to call when canceling the comment update. - * @param {Object} props.thread - The comment thread object. - * @return {JSX.Element} The CommentForm component. - */ -function CommentForm( { onSubmit, onCancel, thread } ) { - const [ inputComment, setInputComment ] = useState( - thread?.content?.raw ?? '' - ); - +function Thread( { + thread, + onEditComment, + onAddReply, + onCommentDelete, + onCommentResolve, + isFocused, + clearThreadFocus, +} ) { return ( <> - <TextareaControl - __nextHasNoMarginBottom - value={ inputComment ?? '' } - onChange={ setInputComment } + <CommentBoard + thread={ thread } + onResolve={ onCommentResolve } + onEdit={ onEditComment } + onDelete={ onCommentDelete } + status={ thread.status } /> - <VStack alignment="left" spacing="3" justify="flex-start"> - <HStack alignment="left" spacing="3" justify="flex-start"> - <Button - __next40pxDefaultSize - accessibleWhenDisabled - variant="primary" - onClick={ () => onSubmit( inputComment ) } - disabled={ - 0 === sanitizeCommentString( inputComment ).length - } + { 0 < thread?.reply?.length && ( + <> + { ! isFocused && ( + <VStack className="editor-collab-sidebar-panel__show-more-reply"> + { sprintf( + // translators: 1: number of replies. + _x( + '%s more replies..', + 'Show replies button' + ), + thread?.reply?.length + ) } + </VStack> + ) } + + { isFocused && + thread.reply.map( ( reply ) => ( + <VStack + key={ reply.id } + className="editor-collab-sidebar-panel__child-thread" + id={ reply.id } + spacing="2" + > + { 'approved' !== thread.status && ( + <CommentBoard + thread={ reply } + onEdit={ onEditComment } + onDelete={ onCommentDelete } + /> + ) } + { 'approved' === thread.status && ( + <CommentBoard thread={ reply } /> + ) } + </VStack> + ) ) } + </> + ) } + { 'approved' !== thread.status && isFocused && ( + <VStack + className="editor-collab-sidebar-panel__child-thread" + spacing="2" + > + <HStack alignment="left" spacing="3" justify="flex-start"> + <CommentAuthorInfo /> + </HStack> + <VStack + spacing="3" + className="editor-collab-sidebar-panel__comment-field" > - { thread - ? _x( 'Update', 'verb' ) - : _x( 'Reply', 'Add reply comment' ) } - </Button> - <Button __next40pxDefaultSize onClick={ onCancel }> - { _x( 'Cancel', 'Cancel comment edit' ) } - </Button> - </HStack> - </VStack> + <CommentForm + onSubmit={ ( inputComment ) => { + onAddReply( inputComment, thread.id ); + } } + onCancel={ ( event ) => { + event.stopPropagation(); // Prevent the parent onClick from being triggered + clearThreadFocus(); + } } + submitButtonText={ _x( + 'Reply', + 'Add reply comment' + ) } + /> + </VStack> + </VStack> + ) } </> ); } -/** - * Renders the header of a comment in the collaboration sidebar. - * - * @param {Object} props - The component props. - * @param {Object} props.thread - The comment thread object. - * @param {Function} props.onResolve - The function to resolve the comment. - * @param {Function} props.onEdit - The function to edit the comment. - * @param {Function} props.onDelete - The function to delete the comment. - * @param {Function} props.onReply - The function to reply to the comment. - * @param {string} props.status - The status of the comment. - * @return {JSX.Element} The rendered comment header. - */ -function CommentHeader( { - thread, - onResolve, - onEdit, - onDelete, - onReply, - status, -} ) { - const dateSettings = getDateSettings(); - const [ dateTimeFormat = dateSettings.formats.time ] = useEntityProp( - 'root', - 'site', - 'time_format' - ); +const CommentBoard = ( { thread, onResolve, onEdit, onDelete, status } ) => { + const [ actionState, setActionState ] = useState( false ); + const [ showConfirmDialog, setShowConfirmDialog ] = useState( false ); + + const handleConfirmDelete = () => { + onDelete( thread.id ); + setActionState( false ); + setShowConfirmDialog( false ); + }; + + const handleConfirmResolve = () => { + onResolve( thread.id ); + setActionState( false ); + setShowConfirmDialog( false ); + }; + + const handleCancel = () => { + setActionState( false ); + setShowConfirmDialog( false ); + }; const actions = [ - { + onEdit && { title: _x( 'Edit', 'Edit comment' ), - onClick: onEdit, + onClick: () => { + setActionState( 'edit' ); + }, }, - { + onDelete && { title: _x( 'Delete', 'Delete comment' ), - onClick: onDelete, - }, - { - title: _x( 'Reply', 'Reply on a comment' ), - onClick: onReply, + onClick: () => { + setActionState( 'delete' ); + setShowConfirmDialog( true ); + }, }, ]; - const moreActions = actions.filter( ( item ) => item.onClick ); + const moreActions = actions.filter( ( item ) => item?.onClick ); return ( - <HStack alignment="left" spacing="3" justify="flex-start"> - <img - src={ thread?.author_avatar_urls?.[ 48 ] } - className="editor-collab-sidebar-panel__user-avatar" - // translators: alt text for user avatar image - alt={ __( 'User avatar' ) } - width={ 32 } - height={ 32 } - /> - <VStack spacing="0"> - <span className="editor-collab-sidebar-panel__user-name"> - { thread.author_name } + <> + <HStack alignment="left" spacing="3" justify="flex-start"> + <CommentAuthorInfo + avatar={ thread?.author_avatar_urls?.[ 48 ] } + name={ thread?.author_name } + date={ thread?.date } + /> + <span className="editor-collab-sidebar-panel__comment-status"> + { status !== 'approved' && ( + <HStack + alignment="right" + justify="flex-end" + spacing="0" + > + { 0 === thread?.parent && onResolve && ( + <Button + label={ _x( + 'Resolve', + 'Mark comment as resolved' + ) } + __next40pxDefaultSize + icon={ published } + onClick={ () => { + setActionState( 'resolve' ); + setShowConfirmDialog( true ); + } } + showTooltip + /> + ) } + { 0 < moreActions.length && ( + <DropdownMenu + icon={ moreVertical } + label={ _x( + 'Select an action', + 'Select comment action' + ) } + className="editor-collab-sidebar-panel__comment-dropdown-menu" + controls={ moreActions } + /> + ) } + </HStack> + ) } + { status === 'approved' && ( + // translators: tooltip for resolved comment + <Tooltip text={ __( 'Resolved' ) }> + <Icon icon={ check } /> + </Tooltip> + ) } </span> - <time - dateTime={ format( 'h:i A', thread.date ) } - className="editor-collab-sidebar-panel__user-time" + </HStack> + <HStack + alignment="left" + spacing="3" + justify="flex-start" + className="editor-collab-sidebar-panel__user-comment" + > + <VStack + spacing="3" + className="editor-collab-sidebar-panel__comment-field" > - { dateI18n( dateTimeFormat, thread.date ) } - </time> - </VStack> - <span className="editor-collab-sidebar-panel__comment-status"> - { status !== 'approved' && ( - <HStack alignment="right" justify="flex-end" spacing="0"> - { 0 === thread.parent && onResolve && ( - <Button - label={ _x( - 'Resolve', - 'Mark comment as resolved' - ) } - __next40pxDefaultSize - icon={ published } - onClick={ onResolve } - showTooltip - /> - ) } - <DropdownMenu - icon={ moreVertical } - label={ _x( - 'Select an action', - 'Select comment action' - ) } - className="editor-collab-sidebar-panel__comment-dropdown-menu" - controls={ moreActions } + { 'edit' === actionState && ( + <CommentForm + onSubmit={ ( value ) => { + onEdit( thread.id, value ); + setActionState( false ); + } } + onCancel={ () => handleCancel() } + thread={ thread } + submitButtonText={ _x( 'Update', 'verb' ) } /> - </HStack> - ) } - { status === 'approved' && ( - // translators: tooltip for resolved comment - <Tooltip text={ __( 'Resolved' ) }> - <Icon icon={ check } /> - </Tooltip> - ) } - </span> - </HStack> + ) } + { 'edit' !== actionState && ( + <RawHTML>{ thread?.content?.raw }</RawHTML> + ) } + </VStack> + </HStack> + { 'resolve' === actionState && ( + <ConfirmDialog + isOpen={ showConfirmDialog } + onConfirm={ handleConfirmResolve } + onCancel={ handleCancel } + confirmButtonText="Yes" + cancelButtonText="No" + > + { + // translators: message displayed when confirming an action + __( + 'Are you sure you want to mark this comment as resolved?' + ) + } + </ConfirmDialog> + ) } + { 'delete' === actionState && ( + <ConfirmDialog + isOpen={ showConfirmDialog } + onConfirm={ handleConfirmDelete } + onCancel={ handleCancel } + confirmButtonText="Yes" + cancelButtonText="No" + > + { + // translators: message displayed when confirming an action + __( 'Are you sure you want to delete this comment?' ) + } + </ConfirmDialog> + ) } + </> ); -} +}; diff --git a/packages/editor/src/components/collab-sidebar/constants.js b/packages/editor/src/components/collab-sidebar/constants.js index 748c2afc26374d..b62e30c346e1f9 100644 --- a/packages/editor/src/components/collab-sidebar/constants.js +++ b/packages/editor/src/components/collab-sidebar/constants.js @@ -1 +1,2 @@ +export const collabHistorySidebarName = 'edit-post/collab-history-sidebar'; export const collabSidebarName = 'edit-post/collab-sidebar'; diff --git a/packages/editor/src/components/collab-sidebar/index.js b/packages/editor/src/components/collab-sidebar/index.js index 17a23a227424a6..dfece6c6f73190 100644 --- a/packages/editor/src/components/collab-sidebar/index.js +++ b/packages/editor/src/components/collab-sidebar/index.js @@ -2,12 +2,17 @@ * WordPress dependencies */ import { __ } from '@wordpress/i18n'; -import { useSelect, useDispatch, resolveSelect } from '@wordpress/data'; +import { + useSelect, + useDispatch, + resolveSelect, + subscribe, +} from '@wordpress/data'; import { useState, useMemo } from '@wordpress/element'; import { comment as commentIcon } from '@wordpress/icons'; import { addFilter } from '@wordpress/hooks'; import { store as noticesStore } from '@wordpress/notices'; -import { store as coreStore } from '@wordpress/core-data'; +import { store as coreStore, useEntityBlockEditor } from '@wordpress/core-data'; import { store as blockEditorStore } from '@wordpress/block-editor'; import { store as interfaceStore } from '@wordpress/interface'; @@ -15,15 +20,15 @@ import { store as interfaceStore } from '@wordpress/interface'; * Internal dependencies */ import PluginSidebar from '../plugin-sidebar'; -import { collabSidebarName } from './constants'; +import { collabHistorySidebarName, collabSidebarName } from './constants'; import { Comments } from './comments'; import { AddComment } from './add-comment'; import { store as editorStore } from '../../store'; import AddCommentButton from './comment-button'; import AddCommentToolbarButton from './comment-button-toolbar'; +import { useGlobalStylesContext } from '../global-styles-provider'; +import { getCommentIdsFromBlocks } from './utils'; -const isBlockCommentExperimentEnabled = - window?.__experimentalEnableBlockComment; const modifyBlockCommentAttributes = ( settings ) => { if ( ! settings.attributes.blockCommentId ) { settings.attributes = { @@ -44,61 +49,28 @@ addFilter( modifyBlockCommentAttributes ); -function CollabSidebarContent( { showCommentBoard, setShowCommentBoard } ) { +function CollabSidebarContent( { + showCommentBoard, + setShowCommentBoard, + styles, + comments, +} ) { const { createNotice } = useDispatch( noticesStore ); const { saveEntityRecord, deleteEntityRecord } = useDispatch( coreStore ); const { getEntityRecord } = resolveSelect( coreStore ); - const { postId, threads } = useSelect( ( select ) => { + const { postId } = useSelect( ( select ) => { const { getCurrentPostId } = select( editorStore ); const _postId = getCurrentPostId(); - const data = !! _postId - ? select( coreStore ).getEntityRecords( 'root', 'comment', { - post: _postId, - type: 'block_comment', - status: 'any', - per_page: 100, - } ) - : null; return { postId: _postId, - threads: data, }; }, [] ); const { getSelectedBlockClientId } = useSelect( blockEditorStore ); const { updateBlockAttributes } = useDispatch( blockEditorStore ); - // Process comments to build the tree structure - const resultComments = useMemo( () => { - // Create a compare to store the references to all objects by id - const compare = {}; - const result = []; - - const filteredComments = ( threads ?? [] ).filter( - ( comment ) => comment.status !== 'trash' - ); - - // Initialize each object with an empty `reply` array - filteredComments.forEach( ( item ) => { - compare[ item.id ] = { ...item, reply: [] }; - } ); - - // Iterate over the data to build the tree structure - filteredComments.forEach( ( item ) => { - if ( item.parent === 0 ) { - // If parent is 0, it's a root item, push it to the result array - result.push( compare[ item.id ] ); - } else if ( compare[ item.parent ] ) { - // Otherwise, find its parent and push it to the parent's `reply` array - compare[ item.parent ].reply.push( compare[ item.id ] ); - } - } ); - - return result; - }, [ threads ] ); - // Function to save the comment. const addNewComment = async ( comment, parentCommentId ) => { const args = { @@ -222,18 +194,21 @@ function CollabSidebarContent( { showCommentBoard, setShowCommentBoard } ) { }; return ( - <div className="editor-collab-sidebar-panel"> + <div className="editor-collab-sidebar-panel" style={ styles }> <AddComment onSubmit={ addNewComment } showCommentBoard={ showCommentBoard } setShowCommentBoard={ setShowCommentBoard } /> <Comments - threads={ resultComments } + key={ getSelectedBlockClientId() } + threads={ comments } onEditComment={ onEditComment } onAddReply={ addNewComment } onCommentDelete={ onCommentDelete } onCommentResolve={ onCommentResolve } + showCommentBoard={ showCommentBoard } + setShowCommentBoard={ setShowCommentBoard } /> </div> ); @@ -245,11 +220,26 @@ function CollabSidebarContent( { showCommentBoard, setShowCommentBoard } ) { export default function CollabSidebar() { const [ showCommentBoard, setShowCommentBoard ] = useState( false ); const { enableComplementaryArea } = useDispatch( interfaceStore ); + const { getActiveComplementaryArea } = useSelect( interfaceStore ); - const { postStatus } = useSelect( ( select ) => { + const { postId, postType, postStatus, threads } = useSelect( ( select ) => { + const { getCurrentPostId, getCurrentPostType } = select( editorStore ); + const _postId = getCurrentPostId(); + const data = + !! _postId && typeof _postId === 'number' + ? select( coreStore ).getEntityRecords( 'root', 'comment', { + post: _postId, + type: 'block_comment', + status: 'any', + per_page: 100, + } ) + : null; return { + postId: _postId, + postType: getCurrentPostType(), postStatus: select( editorStore ).getEditedPostAttribute( 'status' ), + threads: data, }; }, [] ); @@ -270,8 +260,74 @@ export default function CollabSidebar() { enableComplementaryArea( 'core', 'edit-post/collab-sidebar' ); }; - // Check if the experimental flag is enabled. - if ( ! isBlockCommentExperimentEnabled || postStatus === 'publish' ) { + const [ blocks ] = useEntityBlockEditor( 'postType', postType, { + id: postId, + } ); + + // Process comments to build the tree structure + const { resultComments, sortedThreads } = useMemo( () => { + // Create a compare to store the references to all objects by id + const compare = {}; + const result = []; + + const filteredComments = ( threads ?? [] ).filter( + ( comment ) => comment.status !== 'trash' + ); + + // Initialize each object with an empty `reply` array + filteredComments.forEach( ( item ) => { + compare[ item.id ] = { ...item, reply: [] }; + } ); + + // Iterate over the data to build the tree structure + filteredComments.forEach( ( item ) => { + if ( item.parent === 0 ) { + // If parent is 0, it's a root item, push it to the result array + result.push( compare[ item.id ] ); + } else if ( compare[ item.parent ] ) { + // Otherwise, find its parent and push it to the parent's `reply` array + compare[ item.parent ].reply.push( compare[ item.id ] ); + } + } ); + + if ( 0 === result?.length ) { + return { resultComments: [], sortedThreads: [] }; + } + + const updatedResult = result.map( ( item ) => ( { + ...item, + reply: [ ...item.reply ].reverse(), + } ) ); + + const blockCommentIds = getCommentIdsFromBlocks( blocks ); + + const threadIdMap = new Map( + updatedResult.map( ( thread ) => [ thread.id, thread ] ) + ); + + const sortedComments = blockCommentIds + .map( ( id ) => threadIdMap.get( id ) ) + .filter( ( thread ) => thread !== undefined ); + + return { resultComments: updatedResult, sortedThreads: sortedComments }; + }, [ threads, blocks ] ); + + // Get the global styles to set the background color of the sidebar. + const { merged: GlobalStyles } = useGlobalStylesContext(); + const backgroundColor = GlobalStyles?.styles?.color?.background; + + if ( 0 < resultComments.length ) { + const unsubscribe = subscribe( () => { + const activeSidebar = getActiveComplementaryArea( 'core' ); + + if ( ! activeSidebar ) { + enableComplementaryArea( 'core', collabSidebarName ); + unsubscribe(); + } + } ); + } + + if ( postStatus === 'publish' ) { return null; // or maybe return some message indicating no threads are available. } @@ -283,14 +339,31 @@ export default function CollabSidebar() { <> <AddCommentComponent onClick={ openCollabBoard } /> <PluginSidebar - identifier={ collabSidebarName } + identifier={ collabHistorySidebarName } // translators: Comments sidebar title title={ __( 'Comments' ) } icon={ commentIcon } > <CollabSidebarContent + comments={ resultComments } + showCommentBoard={ showCommentBoard } + setShowCommentBoard={ setShowCommentBoard } + /> + </PluginSidebar> + <PluginSidebar + isPinnable={ false } + header={ false } + identifier={ collabSidebarName } + className="editor-collab-sidebar" + headerClassName="editor-collab-sidebar__header" + > + <CollabSidebarContent + comments={ sortedThreads } showCommentBoard={ showCommentBoard } setShowCommentBoard={ setShowCommentBoard } + styles={ { + backgroundColor, + } } /> </PluginSidebar> </> diff --git a/packages/editor/src/components/collab-sidebar/style.scss b/packages/editor/src/components/collab-sidebar/style.scss index 2f937e3df9a25b..29fcc80779f681 100644 --- a/packages/editor/src/components/collab-sidebar/style.scss +++ b/packages/editor/src/components/collab-sidebar/style.scss @@ -1,17 +1,34 @@ +.interface-interface-skeleton__sidebar:has(.editor-collab-sidebar) { + box-shadow: none; + + .interface-complementary-area-header { + display: none; + } +} + +.editor-collab-sidebar { + height: 100%; +} + .editor-collab-sidebar-panel { padding: $grid-unit-20; + height: 100%; &__thread { position: relative; padding: $grid-unit-20; border-radius: $radius-large; - border: 1px solid $gray-300; + border: 1.5px solid $gray-300; background-color: $gray-100; margin-bottom: $grid-unit-20; } &__active-thread { border: 1.5px solid #3858e9; + } + + &__focus-thread { + border: 1.5px solid #3858e9; background-color: $white; box-shadow: 0 5.5px 7.8px -0.3px rgba(0, 0, 0, 0.102); } @@ -108,4 +125,9 @@ } } + &__show-more-reply { + font-weight: 500; + font-style: italic; + padding: 0; + } } diff --git a/packages/editor/src/components/collab-sidebar/utils.js b/packages/editor/src/components/collab-sidebar/utils.js index 7e73344c5dc0e1..51345392098ff2 100644 --- a/packages/editor/src/components/collab-sidebar/utils.js +++ b/packages/editor/src/components/collab-sidebar/utils.js @@ -7,3 +7,39 @@ export function sanitizeCommentString( str ) { return str.trim(); } + +/** + * Extracts comment IDs from an array of blocks. + * + * This function recursively traverses the blocks and their inner blocks to + * collect all comment IDs found in the block attributes. + * + * @param {Array} blocks - The array of blocks to extract comment IDs from. + * @return {Array} An array of comment IDs extracted from the blocks. + */ +export function getCommentIdsFromBlocks( blocks ) { + // Recursive function to extract comment IDs from blocks + const extractCommentIds = ( items ) => { + return items.reduce( ( commentIds, block ) => { + // Check for comment IDs in the current block's attributes + if ( + block.attributes && + block.attributes.blockCommentId && + ! commentIds.includes( block.attributes.blockCommentId ) + ) { + commentIds.push( block.attributes.blockCommentId ); + } + + // Recursively check inner blocks + if ( block.innerBlocks && block.innerBlocks.length > 0 ) { + const innerCommentIds = extractCommentIds( block.innerBlocks ); + commentIds.push( ...innerCommentIds ); + } + + return commentIds; + }, [] ); + }; + + // Extract all comment IDs recursively + return extractCommentIds( blocks ); +} diff --git a/packages/editor/src/components/commands/index.js b/packages/editor/src/components/commands/index.js index 5b3007d8bc137f..177f914e73db63 100644 --- a/packages/editor/src/components/commands/index.js +++ b/packages/editor/src/components/commands/index.js @@ -26,6 +26,7 @@ import { store as noticesStore } from '@wordpress/notices'; import { store as blockEditorStore } from '@wordpress/block-editor'; import { store as coreStore, useEntityRecord } from '@wordpress/core-data'; import { store as interfaceStore } from '@wordpress/interface'; +import { getPath } from '@wordpress/url'; import { decodeEntities } from '@wordpress/html-entities'; /** @@ -91,6 +92,19 @@ const getEditorCommandLoader = () => const { openModal, enableComplementaryArea, disableComplementaryArea } = useDispatch( interfaceStore ); const { getCurrentPostId } = useSelect( editorStore ); + const { isBlockBasedTheme, canCreateTemplate } = useSelect( + ( select ) => { + return { + isBlockBasedTheme: + select( coreStore ).getCurrentTheme()?.is_block_theme, + canCreateTemplate: select( coreStore ).canUser( 'create', { + kind: 'postType', + name: 'wp_template', + } ), + }; + }, + [] + ); const allowSwitchEditorMode = isCodeEditingEnabled && isRichEditingEnabled; @@ -272,6 +286,21 @@ const getEditorCommandLoader = () => }, } ); } + if ( canCreateTemplate && isBlockBasedTheme ) { + const isSiteEditor = getPath( window.location.href )?.includes( + 'site-editor.php' + ); + if ( ! isSiteEditor ) { + commands.push( { + name: 'core/go-to-site-editor', + label: __( 'Open Site Editor' ), + callback: ( { close } ) => { + close(); + document.location = 'site-editor.php'; + }, + } ); + } + } return { commands, diff --git a/packages/editor/src/components/create-template-part-modal/index.js b/packages/editor/src/components/create-template-part-modal/index.js deleted file mode 100644 index 5d594cd646cc10..00000000000000 --- a/packages/editor/src/components/create-template-part-modal/index.js +++ /dev/null @@ -1,209 +0,0 @@ -/** - * WordPress dependencies - */ -import { useSelect, useDispatch } from '@wordpress/data'; -import { - Icon, - BaseControl, - TextControl, - Flex, - FlexItem, - FlexBlock, - Button, - Modal, - __experimentalRadioGroup as RadioGroup, - __experimentalRadio as Radio, - __experimentalHStack as HStack, - __experimentalVStack as VStack, -} from '@wordpress/components'; -import { __ } from '@wordpress/i18n'; -import { useState } from '@wordpress/element'; -import { useInstanceId } from '@wordpress/compose'; -import { store as noticesStore } from '@wordpress/notices'; -import { store as coreStore } from '@wordpress/core-data'; -import { check } from '@wordpress/icons'; -import { serialize } from '@wordpress/blocks'; - -/** - * Internal dependencies - */ -import { store as editorStore } from '../../store'; -import { - TEMPLATE_PART_POST_TYPE, - TEMPLATE_PART_AREA_DEFAULT_CATEGORY, -} from '../../store/constants'; -import { - useExistingTemplateParts, - getUniqueTemplatePartTitle, - getCleanTemplatePartSlug, -} from './utils'; - -export default function CreateTemplatePartModal( { - modalTitle, - ...restProps -} ) { - const defaultModalTitle = useSelect( - ( select ) => - select( coreStore ).getPostType( TEMPLATE_PART_POST_TYPE )?.labels - ?.add_new_item, - [] - ); - return ( - <Modal - title={ modalTitle || defaultModalTitle } - onRequestClose={ restProps.closeModal } - overlayClassName="editor-create-template-part-modal" - focusOnMount="firstContentElement" - size="medium" - > - <CreateTemplatePartModalContents { ...restProps } /> - </Modal> - ); -} - -export function CreateTemplatePartModalContents( { - defaultArea = TEMPLATE_PART_AREA_DEFAULT_CATEGORY, - blocks = [], - confirmLabel = __( 'Add' ), - closeModal, - onCreate, - onError, - defaultTitle = '', -} ) { - const { createErrorNotice } = useDispatch( noticesStore ); - const { saveEntityRecord } = useDispatch( coreStore ); - const existingTemplateParts = useExistingTemplateParts(); - - const [ title, setTitle ] = useState( defaultTitle ); - const [ area, setArea ] = useState( defaultArea ); - const [ isSubmitting, setIsSubmitting ] = useState( false ); - const instanceId = useInstanceId( CreateTemplatePartModal ); - - const templatePartAreas = useSelect( - ( select ) => - select( editorStore ).__experimentalGetDefaultTemplatePartAreas(), - [] - ); - async function createTemplatePart() { - if ( ! title || isSubmitting ) { - return; - } - - try { - setIsSubmitting( true ); - const uniqueTitle = getUniqueTemplatePartTitle( - title, - existingTemplateParts - ); - const cleanSlug = getCleanTemplatePartSlug( uniqueTitle ); - - const templatePart = await saveEntityRecord( - 'postType', - TEMPLATE_PART_POST_TYPE, - { - slug: cleanSlug, - title: uniqueTitle, - content: serialize( blocks ), - area, - }, - { throwOnError: true } - ); - await onCreate( templatePart ); - - // TODO: Add a success notice? - } catch ( error ) { - const errorMessage = - error.message && error.code !== 'unknown_error' - ? error.message - : __( - 'An error occurred while creating the template part.' - ); - - createErrorNotice( errorMessage, { type: 'snackbar' } ); - - onError?.(); - } finally { - setIsSubmitting( false ); - } - } - return ( - <form - onSubmit={ async ( event ) => { - event.preventDefault(); - await createTemplatePart(); - } } - > - <VStack spacing="4"> - <TextControl - __next40pxDefaultSize - __nextHasNoMarginBottom - label={ __( 'Name' ) } - value={ title } - onChange={ setTitle } - required - /> - <BaseControl - __nextHasNoMarginBottom - label={ __( 'Area' ) } - id={ `editor-create-template-part-modal__area-selection-${ instanceId }` } - className="editor-create-template-part-modal__area-base-control" - > - <RadioGroup - label={ __( 'Area' ) } - className="editor-create-template-part-modal__area-radio-group" - id={ `editor-create-template-part-modal__area-selection-${ instanceId }` } - onChange={ setArea } - checked={ area } - > - { templatePartAreas.map( - ( { icon, label, area: value, description } ) => ( - <Radio - __next40pxDefaultSize - key={ label } - value={ value } - className="editor-create-template-part-modal__area-radio" - > - <Flex align="start" justify="start"> - <FlexItem> - <Icon icon={ icon } /> - </FlexItem> - <FlexBlock className="editor-create-template-part-modal__option-label"> - { label } - <div>{ description }</div> - </FlexBlock> - - <FlexItem className="editor-create-template-part-modal__checkbox"> - { area === value && ( - <Icon icon={ check } /> - ) } - </FlexItem> - </Flex> - </Radio> - ) - ) } - </RadioGroup> - </BaseControl> - <HStack justify="right"> - <Button - __next40pxDefaultSize - variant="tertiary" - onClick={ () => { - closeModal(); - } } - > - { __( 'Cancel' ) } - </Button> - <Button - __next40pxDefaultSize - variant="primary" - type="submit" - aria-disabled={ ! title || isSubmitting } - isBusy={ isSubmitting } - > - { confirmLabel } - </Button> - </HStack> - </VStack> - </form> - ); -} diff --git a/packages/editor/src/components/create-template-part-modal/style.scss b/packages/editor/src/components/create-template-part-modal/style.scss deleted file mode 100644 index be15e8d76d536e..00000000000000 --- a/packages/editor/src/components/create-template-part-modal/style.scss +++ /dev/null @@ -1,63 +0,0 @@ -.editor-create-template-part-modal { - z-index: z-index(".editor-create-template-part-modal"); -} - -.editor-create-template-part-modal__area-radio-group { - width: 100%; - border: $border-width solid $gray-700; - border-radius: $radius-small; - - .components-button.editor-create-template-part-modal__area-radio { - display: block; - width: 100%; - height: 100%; - text-align: left; - padding: $grid-unit-15; - - &, - &.is-secondary:hover, - &.is-primary:hover { - margin: 0; - background-color: inherit; - border-bottom: $border-width solid $gray-700; - border-radius: 0; - - &:not(:focus) { - box-shadow: none; - } - - &:focus { - border-bottom: $border-width solid $white; - } - - &:last-of-type { - border-bottom: none; - } - } - - &:not(:hover), - &[aria-checked="true"] { - color: $gray-900; - cursor: auto; - - .editor-create-template-part-modal__option-label div { - color: $gray-600; - } - } - - .editor-create-template-part-modal__option-label { - padding-top: $grid-unit-05; - white-space: normal; - - div { - padding-top: $grid-unit-05; - font-size: $helptext-font-size; - } - } - - .editor-create-template-part-modal__checkbox { - margin-left: auto; - min-width: $grid-unit-30; - } - } -} diff --git a/packages/editor/src/components/document-bar/index.js b/packages/editor/src/components/document-bar/index.js index 30990379fe6301..544b5024d88a89 100644 --- a/packages/editor/src/components/document-bar/index.js +++ b/packages/editor/src/components/document-bar/index.js @@ -22,6 +22,7 @@ import { store as commandsStore } from '@wordpress/commands'; import { useRef, useEffect } from '@wordpress/element'; import { useReducedMotion } from '@wordpress/compose'; import { decodeEntities } from '@wordpress/html-entities'; +import { __unstableStripHTML as stripHTML } from '@wordpress/dom'; /** * Internal dependencies @@ -29,6 +30,7 @@ import { decodeEntities } from '@wordpress/html-entities'; import { TEMPLATE_POST_TYPES } from '../../store/constants'; import { store as editorStore } from '../../store'; import usePageTypeBadge from '../../utils/pageTypeBadge'; +import { getTemplateInfo } from '../../utils/get-template-info'; /** @typedef {import("@wordpress/components").IconType} IconType */ @@ -49,10 +51,11 @@ const MotionButton = motion( Button ); * @param {IconType} props.icon An icon for the document, no default. * (A default icon indicating the document post type is no longer used.) * - * @return {JSX.Element} The rendered DocumentBar component. + * @return {React.ReactNode} The rendered DocumentBar component. */ export default function DocumentBar( props ) { const { + postId, postType, postTypeLabel, documentTitle, @@ -65,9 +68,9 @@ export default function DocumentBar( props ) { getCurrentPostType, getCurrentPostId, getEditorSettings, - __experimentalGetTemplateInfo: getTemplateInfo, getRenderingMode, } = select( editorStore ); + const { getEditedEntityRecord, getPostType, @@ -80,10 +83,19 @@ export default function DocumentBar( props ) { _postType, _postId ); - const _templateInfo = getTemplateInfo( _document ); + + const { default_template_types: templateTypes = [] } = + select( coreStore ).getEntityRecord( 'root', '__unstableBase' ) ?? + {}; + + const _templateInfo = getTemplateInfo( { + templateTypes, + template: _document, + } ); const _postTypeLabel = getPostType( _postType )?.labels?.singular_name; return { + postId: _postId, postType: _postType, postTypeLabel: _postTypeLabel, documentTitle: _document.title, @@ -111,7 +123,7 @@ export default function DocumentBar( props ) { const title = props.title || entityTitle; const icon = props.icon; - const pageTypeBadge = usePageTypeBadge(); + const pageTypeBadge = usePageTypeBadge( postId ); const mountedRef = useRef( false ); useEffect( () => { @@ -189,7 +201,7 @@ export default function DocumentBar( props ) { <Text size="body" as="h1"> <span className="editor-document-bar__post-title"> { title - ? decodeEntities( title ) + ? stripHTML( title ) : __( 'No title' ) } </span> { pageTypeBadge && ( diff --git a/packages/editor/src/components/document-outline/check.js b/packages/editor/src/components/document-outline/check.js index 7e396f45c82f1a..87864cbb34a369 100644 --- a/packages/editor/src/components/document-outline/check.js +++ b/packages/editor/src/components/document-outline/check.js @@ -7,10 +7,10 @@ import { store as blockEditorStore } from '@wordpress/block-editor'; /** * Component check if there are any headings (core/heading blocks) present in the document. * - * @param {Object} props Props. - * @param {Element} props.children Children to be rendered. + * @param {Object} props Props. + * @param {React.ReactElement} props.children Children to be rendered. * - * @return {Component|null} The component to be rendered or null if there are headings. + * @return {React.ReactElement} The component to be rendered or null if there are headings. */ export default function DocumentOutlineCheck( { children } ) { const hasHeadings = useSelect( ( select ) => { @@ -19,7 +19,7 @@ export default function DocumentOutlineCheck( { children } ) { return getGlobalBlockCount( 'core/heading' ) > 0; } ); - if ( hasHeadings ) { + if ( ! hasHeadings ) { return null; } diff --git a/packages/editor/src/components/document-outline/index.js b/packages/editor/src/components/document-outline/index.js index 2ed53f771b67b7..6c498ccc799909 100644 --- a/packages/editor/src/components/document-outline/index.js +++ b/packages/editor/src/components/document-outline/index.js @@ -3,6 +3,7 @@ */ import { __ } from '@wordpress/i18n'; import { useDispatch, useSelect } from '@wordpress/data'; +import { useRef } from '@wordpress/element'; import { create, getTextContent } from '@wordpress/rich-text'; import { store as blockEditorStore } from '@wordpress/block-editor'; import { store as coreStore } from '@wordpress/core-data'; @@ -102,19 +103,17 @@ const isEmptyHeading = ( heading ) => * Renders a document outline component. * * @param {Object} props Props. - * @param {Function} props.onSelect Function to be called when an outline item is selected. - * @param {boolean} props.isTitleSupported Indicates whether the title is supported. + * @param {Function} props.onSelect Function to be called when an outline item is selected * @param {boolean} props.hasOutlineItemsDisabled Indicates whether the outline items are disabled. * - * @return {Component} The component to be rendered. + * @return {React.ReactNode} The rendered component. */ export default function DocumentOutline( { onSelect, - isTitleSupported, hasOutlineItemsDisabled, } ) { const { selectBlock } = useDispatch( blockEditorStore ); - const { blocks, title } = useSelect( ( select ) => { + const { blocks, title, isTitleSupported } = useSelect( ( select ) => { const { getBlocks } = select( blockEditorStore ); const { getEditedPostAttribute } = select( editorStore ); const { getPostType } = select( coreStore ); @@ -127,6 +126,8 @@ export default function DocumentOutline( { }; } ); + const prevHeadingLevelRef = useRef( 1 ); + const headings = computeOutlineHeadings( blocks ); if ( headings.length < 1 ) { return ( @@ -141,8 +142,6 @@ export default function DocumentOutline( { ); } - let prevHeadingLevel = 1; - // Not great but it's the simplest way to locate the title right now. const titleNode = document.querySelector( '.editor-post-title__input' ); const hasTitle = isTitleSupported && title && titleNode; @@ -169,10 +168,11 @@ export default function DocumentOutline( { { title } </DocumentOutlineItem> ) } - { headings.map( ( item, index ) => { + { headings.map( ( item ) => { // Headings remain the same, go up by one, or down by any amount. // Otherwise there are missing levels. - const isIncorrectLevel = item.level > prevHeadingLevel + 1; + const isIncorrectLevel = + item.level > prevHeadingLevelRef.current + 1; const isValid = ! item.isEmpty && @@ -180,11 +180,11 @@ export default function DocumentOutline( { !! item.level && ( item.level !== 1 || ( ! hasMultipleH1 && ! hasTitle ) ); - prevHeadingLevel = item.level; + prevHeadingLevelRef.current = item.level; return ( <DocumentOutlineItem - key={ index } + key={ item.clientId } level={ `H${ item.level }` } isValid={ isValid } isDisabled={ hasOutlineItemsDisabled } diff --git a/packages/editor/src/components/document-tools/index.js b/packages/editor/src/components/document-tools/index.js index 74118caaf5849c..71a8b1b094a135 100644 --- a/packages/editor/src/components/document-tools/index.js +++ b/packages/editor/src/components/document-tools/index.js @@ -10,7 +10,7 @@ import { useViewportMatch } from '@wordpress/compose'; import { useSelect, useDispatch } from '@wordpress/data'; import { __, _x } from '@wordpress/i18n'; import { NavigableToolbar, ToolSelector } from '@wordpress/block-editor'; -import { Button, ToolbarItem } from '@wordpress/components'; +import { ToolbarButton, ToolbarItem } from '@wordpress/components'; import { listView, plus } from '@wordpress/icons'; import { useCallback } from '@wordpress/element'; import { store as keyboardShortcutsStore } from '@wordpress/keyboard-shortcuts'; @@ -60,8 +60,9 @@ function DocumentTools( { className, disableBlockTools = false } ) { isDistractionFree: get( 'core', 'distractionFree' ), isVisualMode: getEditorMode() === 'visual', showTools: - getRenderingMode() !== 'post-only' || - getCurrentPostType() === 'wp_template', + !! window?.__experimentalEditorWriteMode && + ( getRenderingMode() !== 'post-only' || + getCurrentPostType() === 'wp_template' ), }; }, [] ); @@ -117,9 +118,8 @@ function DocumentTools( { className, disableBlockTools = false } ) { > <div className="editor-document-tools__left"> { ! isDistractionFree && ( - <ToolbarItem + <ToolbarButton ref={ inserterSidebarToggleRef } - as={ Button } className="editor-document-tools__inserter-toggle" variant="primary" isPressed={ isInserterOpened } @@ -158,8 +158,7 @@ function DocumentTools( { className, disableBlockTools = false } ) { size="compact" /> { ! isDistractionFree && ( - <ToolbarItem - as={ Button } + <ToolbarButton className="editor-document-tools__document-overview-toggle" icon={ listView } disabled={ disableBlockTools } @@ -174,7 +173,6 @@ function DocumentTools( { className, disableBlockTools = false } ) { } aria-expanded={ isListViewOpen } ref={ listViewToggleRef } - size="compact" /> ) } </> diff --git a/packages/editor/src/components/document-tools/style.scss b/packages/editor/src/components/document-tools/style.scss index a1abfd5abd7aef..dfafff2126d66d 100644 --- a/packages/editor/src/components/document-tools/style.scss +++ b/packages/editor/src/components/document-tools/style.scss @@ -74,14 +74,8 @@ } .editor-document-tools .editor-document-tools__left > .editor-document-tools__inserter-toggle.has-icon { - min-width: $button-size-compact; - width: $button-size-compact; - height: $button-size-compact; - padding: 0; - .show-icon-labels & { width: auto; - height: $button-size-compact; padding: 0 $grid-unit-10; } } diff --git a/packages/editor/src/components/editor-help/intro-to-blocks.native.js b/packages/editor/src/components/editor-help/intro-to-blocks.native.js index 3dc23ec2619172..9e23a70936d4e9 100644 --- a/packages/editor/src/components/editor-help/intro-to-blocks.native.js +++ b/packages/editor/src/components/editor-help/intro-to-blocks.native.js @@ -71,7 +71,7 @@ const IntroToBlocks = () => { <HelpDetailSectionHeadingText text={ __( 'Build layouts' ) } /> <HelpDetailBodyText text={ __( - 'Arrange your content into columns, add Call to Action buttons, and overlay images with text.' + 'Arrange your content into columns, add Call to action buttons, and overlay images with text.' ) } /> <HelpDetailImage diff --git a/packages/editor/src/components/editor-history/redo.js b/packages/editor/src/components/editor-history/redo.js index 46a263bb89926b..b2b20555f30544 100644 --- a/packages/editor/src/components/editor-history/redo.js +++ b/packages/editor/src/components/editor-history/redo.js @@ -50,6 +50,6 @@ function EditorHistoryRedo( props, ref ) { * @param {Object} props - Props. * @param {Ref} ref - Forwarded ref. * - * @return {Component} The component to be rendered. + * @return {React.ReactNode} The rendered component. */ export default forwardRef( EditorHistoryRedo ); diff --git a/packages/editor/src/components/editor-history/undo.js b/packages/editor/src/components/editor-history/undo.js index 6ba6055706e993..fe8cce72c4197e 100644 --- a/packages/editor/src/components/editor-history/undo.js +++ b/packages/editor/src/components/editor-history/undo.js @@ -46,6 +46,6 @@ function EditorHistoryUndo( props, ref ) { * @param {Object} props - Props. * @param {Ref} ref - Forwarded ref. * - * @return {Component} The component to be rendered. + * @return {React.ReactNode} The rendered component. */ export default forwardRef( EditorHistoryUndo ); diff --git a/packages/editor/src/components/editor-interface/content-slot-fill.js b/packages/editor/src/components/editor-interface/content-slot-fill.js index 1aab394e87230f..ce1070b30da802 100644 --- a/packages/editor/src/components/editor-interface/content-slot-fill.js +++ b/packages/editor/src/components/editor-interface/content-slot-fill.js @@ -1,15 +1,10 @@ /** * WordPress dependencies */ -import { privateApis as componentsPrivateApis } from '@wordpress/components'; +import { createSlotFill } from '@wordpress/components'; -/** - * Internal dependencies - */ -import { unlock } from '../../lock-unlock'; - -const { createPrivateSlotFill } = unlock( componentsPrivateApis ); -const SLOT_FILL_NAME = 'EditCanvasContainerSlot'; -const EditorContentSlotFill = createPrivateSlotFill( SLOT_FILL_NAME ); +const EditorContentSlotFill = createSlotFill( + Symbol( 'EditCanvasContainerSlot' ) +); export default EditorContentSlotFill; diff --git a/packages/editor/src/components/editor-interface/index.js b/packages/editor/src/components/editor-interface/index.js index 3692a5ed797f5e..6f6ffbec7b9c32 100644 --- a/packages/editor/src/components/editor-interface/index.js +++ b/packages/editor/src/components/editor-interface/index.js @@ -10,11 +10,7 @@ import { InterfaceSkeleton, ComplementaryArea } from '@wordpress/interface'; import { useSelect } from '@wordpress/data'; import { __, _x } from '@wordpress/i18n'; import { store as preferencesStore } from '@wordpress/preferences'; -import { - store as blockEditorStore, - BlockBreadcrumb, - BlockToolbar, -} from '@wordpress/block-editor'; +import { BlockBreadcrumb, BlockToolbar } from '@wordpress/block-editor'; import { useViewportMatch } from '@wordpress/compose'; import { useState, useCallback } from '@wordpress/element'; @@ -31,8 +27,6 @@ import TextEditor from '../text-editor'; import VisualEditor from '../visual-editor'; import EditorContentSlotFill from './content-slot-fill'; -import { unlock } from '../../lock-unlock'; - const interfaceLabels = { /* translators: accessibility text for the editor top bar landmark region. */ header: __( 'Editor top bar' ), @@ -69,13 +63,11 @@ export default function EditorInterface( { isPreviewMode, showBlockBreadcrumbs, documentLabel, - isZoomOut, } = useSelect( ( select ) => { const { get } = select( preferencesStore ); const { getEditorSettings, getPostTypeLabel } = select( editorStore ); const editorSettings = getEditorSettings(); const postTypeLabel = getPostTypeLabel(); - const { isZoomOut: _isZoomOut } = unlock( select( blockEditorStore ) ); return { mode: select( editorStore ).getEditorMode(), @@ -88,7 +80,6 @@ export default function EditorInterface( { documentLabel: // translators: Default label for the Document in the Block Breadcrumb. postTypeLabel || _x( 'Document', 'noun, breadcrumb' ), - isZoomOut: _isZoomOut(), }; }, [] ); const isLargeViewport = useViewportMatch( 'medium' ); @@ -197,7 +188,6 @@ export default function EditorInterface( { isLargeViewport && showBlockBreadcrumbs && isRichEditingEnabled && - ! isZoomOut && mode === 'visual' && ( <BlockBreadcrumb rootLabelText={ documentLabel } /> ) diff --git a/packages/editor/src/components/editor-interface/style.scss b/packages/editor/src/components/editor-interface/style.scss index 05b23fe2304dd8..7219e03058fc4c 100644 --- a/packages/editor/src/components/editor-interface/style.scss +++ b/packages/editor/src/components/editor-interface/style.scss @@ -8,5 +8,6 @@ } .editor-visual-editor { - flex: 1 0 auto; + // Fits the height to the parent — flex-shrink ensures it doesn’t create overflow. + flex: 1 1 0%; } diff --git a/packages/editor/src/components/editor-notices/index.js b/packages/editor/src/components/editor-notices/index.js index 28341bfda3f236..5f095ef1a813c6 100644 --- a/packages/editor/src/components/editor-notices/index.js +++ b/packages/editor/src/components/editor-notices/index.js @@ -18,7 +18,7 @@ import TemplateValidationNotice from '../template-validation-notice'; * <EditorNotices /> * ``` * - * @return {JSX.Element} The rendered EditorNotices component. + * @return {React.ReactNode} The rendered EditorNotices component. */ export function EditorNotices() { const { notices } = useSelect( diff --git a/packages/editor/src/components/editor-snackbars/index.js b/packages/editor/src/components/editor-snackbars/index.js index 6530e1ec7ea902..9b781ee60dcaa5 100644 --- a/packages/editor/src/components/editor-snackbars/index.js +++ b/packages/editor/src/components/editor-snackbars/index.js @@ -11,7 +11,7 @@ const MAX_VISIBLE_NOTICES = -3; /** * Renders the editor snackbars component. * - * @return {JSX.Element} The rendered component. + * @return {React.ReactNode} The rendered component. */ export default function EditorSnackbars() { const notices = useSelect( diff --git a/packages/editor/src/components/entities-saved-states/entity-record-item.js b/packages/editor/src/components/entities-saved-states/entity-record-item.js index ca9fb2e0b169c3..e8219c4cca7ae1 100644 --- a/packages/editor/src/components/entities-saved-states/entity-record-item.js +++ b/packages/editor/src/components/entities-saved-states/entity-record-item.js @@ -12,6 +12,7 @@ import { decodeEntities } from '@wordpress/html-entities'; */ import { store as editorStore } from '../../store'; import { unlock } from '../../lock-unlock'; +import { getTemplateInfo } from '../../utils/get-template-info'; export default function EntityRecordItem( { record, checked, onChange } ) { const { name, kind, title, key } = record; @@ -33,11 +34,18 @@ export default function EntityRecordItem( { record, checked, onChange } ) { name, key ); + + const { default_template_types: templateTypes = [] } = + select( coreStore ).getEntityRecord( + 'root', + '__unstableBase' + ) ?? {}; + return { - entityRecordTitle: - select( editorStore ).__experimentalGetTemplateInfo( - template - ).title, + entityRecordTitle: getTemplateInfo( { + template, + templateTypes, + } ).title, hasPostMetaChanges: unlock( select( editorStore ) ).hasPostMetaChanges( name, key ), diff --git a/packages/editor/src/components/entities-saved-states/index.js b/packages/editor/src/components/entities-saved-states/index.js index 849bd2d0d71c8c..200473cccff706 100644 --- a/packages/editor/src/components/entities-saved-states/index.js +++ b/packages/editor/src/components/entities-saved-states/index.js @@ -31,14 +31,11 @@ function identity( values ) { * * @param {Object} props The component props. * @param {Function} props.close The function to close the dialog. - * @param {Function} props.renderDialog The function to render the dialog. + * @param {boolean} props.renderDialog Whether to render the component with modal dialog behavior. * - * @return {JSX.Element} The rendered component. + * @return {React.ReactNode} The rendered component. */ -export default function EntitiesSavedStates( { - close, - renderDialog = undefined, -} ) { +export default function EntitiesSavedStates( { close, renderDialog } ) { const isDirtyProps = useIsDirty(); return ( <EntitiesSavedStatesExtensible @@ -58,13 +55,13 @@ export default function EntitiesSavedStates( { * @param {Function} props.onSave Function to call when saving entities. * @param {boolean} props.saveEnabled Flag indicating if save is enabled. * @param {string} props.saveLabel Label for the save button. - * @param {Function} props.renderDialog Function to render a custom dialog. + * @param {boolean} props.renderDialog Whether to render the component with modal dialog behavior. * @param {Array} props.dirtyEntityRecords Array of dirty entity records. * @param {boolean} props.isDirty Flag indicating if there are dirty entities. * @param {Function} props.setUnselectedEntities Function to set unselected entities. * @param {Array} props.unselectedEntities Array of unselected entities. * - * @return {JSX.Element} The rendered component. + * @return {React.ReactNode} The rendered component. */ export function EntitiesSavedStatesExtensible( { additionalPrompt = undefined, @@ -72,7 +69,7 @@ export function EntitiesSavedStatesExtensible( { onSave = identity, saveEnabled: saveEnabledProp = undefined, saveLabel = __( 'Save' ), - renderDialog = undefined, + renderDialog, dirtyEntityRecords, isDirty, setUnselectedEntities, @@ -118,10 +115,14 @@ export function EntitiesSavedStatesExtensible( { 'description' ); + const selectItemsToSaveDescription = !! dirtyEntityRecords.length + ? __( 'Select the items you want to save.' ) + : undefined; + return ( <div - ref={ saveDialogRef } - { ...saveDialogProps } + ref={ renderDialog ? saveDialogRef : undefined } + { ...( renderDialog && saveDialogProps ) } className="entities-saved-states__panel" role={ renderDialog ? 'dialog' : undefined } aria-labelledby={ renderDialog ? dialogLabel : undefined } @@ -183,7 +184,7 @@ export function EntitiesSavedStatesExtensible( { ), { strong: <strong /> } ) - : __( 'Select the items you want to save.' ) } + : selectItemsToSaveDescription } </p> </div> diff --git a/packages/editor/src/components/error-boundary/index.js b/packages/editor/src/components/error-boundary/index.js index d43af5a556b67b..f3e6e5fd127afc 100644 --- a/packages/editor/src/components/error-boundary/index.js +++ b/packages/editor/src/components/error-boundary/index.js @@ -3,9 +3,12 @@ */ import { Component } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; -import { Button } from '@wordpress/components'; +import { + Button, + __experimentalHStack as HStack, + __experimentalText as Text, +} from '@wordpress/components'; import { select } from '@wordpress/data'; -import { Warning } from '@wordpress/block-editor'; import { useCopyToClipboard } from '@wordpress/compose'; import { doAction } from '@wordpress/hooks'; @@ -26,10 +29,10 @@ function getContent() { } catch ( error ) {} } -function CopyButton( { text, children } ) { +function CopyButton( { text, children, variant = 'secondary' } ) { const ref = useCopyToClipboard( text ); return ( - <Button __next40pxDefaultSize variant="secondary" ref={ ref }> + <Button __next40pxDefaultSize variant={ variant } ref={ ref }> { children } </Button> ); @@ -54,23 +57,34 @@ class ErrorBoundary extends Component { render() { const { error } = this.state; + const { canCopyContent = false } = this.props; if ( ! error ) { return this.props.children; } - const actions = [ - <CopyButton key="copy-post" text={ getContent }> - { __( 'Copy Post Text' ) } - </CopyButton>, - <CopyButton key="copy-error" text={ error.stack }> - { __( 'Copy Error' ) } - </CopyButton>, - ]; - return ( - <Warning className="editor-error-boundary" actions={ actions }> - { __( 'The editor has encountered an unexpected error.' ) } - </Warning> + <HStack + className="editor-error-boundary" + alignment="baseline" + spacing={ 4 } + justify="space-between" + expanded={ false } + wrap + > + <Text as="p"> + { __( 'The editor has encountered an unexpected error.' ) } + </Text> + <HStack expanded={ false }> + { canCopyContent && ( + <CopyButton text={ getContent }> + { __( 'Copy contents' ) } + </CopyButton> + ) } + <CopyButton variant="primary" text={ error?.stack }> + { __( 'Copy error' ) } + </CopyButton> + </HStack> + </HStack> ); } } diff --git a/packages/editor/src/components/error-boundary/index.native.js b/packages/editor/src/components/error-boundary/index.native.js index 0de048e8114451..4c05ceb3fc150b 100644 --- a/packages/editor/src/components/error-boundary/index.native.js +++ b/packages/editor/src/components/error-boundary/index.native.js @@ -16,7 +16,7 @@ import { usePreferredColorSchemeStyle, withPreferredColorScheme, } from '@wordpress/compose'; -import { warning } from '@wordpress/icons'; +import { cautionFilled } from '@wordpress/icons'; import { Icon } from '@wordpress/components'; /** @@ -141,7 +141,7 @@ class ErrorBoundary extends Component { <View style={ styles[ 'error-boundary__container' ] }> <View style={ iconContainerStyle }> <Icon - icon={ warning } + icon={ cautionFilled } { ...styles[ 'error-boundary__icon' ] } /> </View> diff --git a/packages/editor/src/components/error-boundary/style.scss b/packages/editor/src/components/error-boundary/style.scss index 2913a644b94576..4d59c511667088 100644 --- a/packages/editor/src/components/error-boundary/style.scss +++ b/packages/editor/src/components/error-boundary/style.scss @@ -1,7 +1,11 @@ .editor-error-boundary { + font-family: $default-font; margin: auto; max-width: 780px; - padding: 20px; + padding: 1em; margin-top: 60px; box-shadow: $elevation-large; + border: $border-width solid $gray-900; + border-radius: $radius-small; + background-color: $white; } diff --git a/packages/editor/src/components/header/index.js b/packages/editor/src/components/header/index.js index 2a5629b080caf8..b32fda6d031b14 100644 --- a/packages/editor/src/components/header/index.js +++ b/packages/editor/src/components/header/index.js @@ -30,6 +30,10 @@ import { PATTERN_POST_TYPE, NAVIGATION_POST_TYPE, } from '../../store/constants'; +import { unlock } from '../../lock-unlock'; + +const isBlockCommentExperimentEnabled = + window?.__experimentalEnableBlockComment; const toolbarVariations = { distractionFreeDisabled: { y: '-50px' }, @@ -64,6 +68,7 @@ function Header( { showIconLabels, hasFixedToolbar, hasBlockSelection, + hasSectionRootClientId, } = useSelect( ( select ) => { const { get: getPreference } = select( preferencesStore ); const { @@ -71,6 +76,9 @@ function Header( { getCurrentPostType, isPublishSidebarOpened: _isPublishSidebarOpened, } = select( editorStore ); + const { getBlockSelectionStart, getSectionRootClientId } = unlock( + select( blockEditorStore ) + ); return { postType: getCurrentPostType(), @@ -78,14 +86,14 @@ function Header( { isPublishSidebarOpened: _isPublishSidebarOpened(), showIconLabels: getPreference( 'core', 'showIconLabels' ), hasFixedToolbar: getPreference( 'core', 'fixedToolbar' ), - hasBlockSelection: - !! select( blockEditorStore ).getBlockSelectionStart(), + hasBlockSelection: !! getBlockSelectionStart(), + hasSectionRootClientId: !! getSectionRootClientId(), }; }, [] ); - const canBeZoomedOut = [ 'post', 'page', 'wp_template' ].includes( - postType - ); + const canBeZoomedOut = + [ 'post', 'page', 'wp_template' ].includes( postType ) && + hasSectionRootClientId; const disablePreviewOption = [ NAVIGATION_POST_TYPE, @@ -97,11 +105,14 @@ function Header( { useState( true ); const hasCenter = - ( ! hasBlockSelection || isBlockToolsCollapsed ) && - ! isTooNarrowForDocumentBar; + ! isTooNarrowForDocumentBar && + ( ! hasFixedToolbar || + ( hasFixedToolbar && + ( ! hasBlockSelection || isBlockToolsCollapsed ) ) ); const hasBackButton = useHasBackButton(); + /* - * The edit-post-header classname is only kept for backward compatability + * The edit-post-header classname is only kept for backward compatibility * as some plugins might be relying on its presence. */ return ( @@ -167,7 +178,7 @@ function Header( { forceIsAutosaveable={ forceIsDirty } /> - { canBeZoomedOut && isWideViewport && ( + { isWideViewport && canBeZoomedOut && ( <ZoomOutToggle disabled={ forceDisableBlockTools } /> ) } @@ -183,7 +194,10 @@ function Header( { } /> ) } - <CollabSidebar /> + + { isBlockCommentExperimentEnabled ? ( + <CollabSidebar /> + ) : undefined } { customSaveButton } <MoreMenu /> diff --git a/packages/editor/src/components/header/style.scss b/packages/editor/src/components/header/style.scss index 4259ef4b8a2084..4cbbe992e63fdb 100644 --- a/packages/editor/src/components/header/style.scss +++ b/packages/editor/src/components/header/style.scss @@ -7,7 +7,7 @@ &:has(> .editor-header__center) { grid-template: auto / $header-height min-content 1fr min-content $header-height; @include break-medium { - grid-template: auto / $header-height minmax(min-content, 1fr) 2fr minmax(min-content, 1fr) $header-height; + grid-template: auto / $header-height minmax(min-content, 2fr) 2.5fr minmax(min-content, 2fr) $header-height; } } @include break-mobile { @@ -256,7 +256,8 @@ & > .editor-header__toolbar .editor-document-tools__document-overview-toggle, & > .editor-header__settings > .editor-preview-dropdown, - & > .editor-header__settings > .interface-pinned-items { + & > .editor-header__settings > .interface-pinned-items, + & > .editor-header__settings > .editor-zoom-out-toggle { display: none; } diff --git a/packages/editor/src/components/index.js b/packages/editor/src/components/index.js index b42566aac653be..d940532be75a3d 100644 --- a/packages/editor/src/components/index.js +++ b/packages/editor/src/components/index.js @@ -68,8 +68,6 @@ export { usePostScheduleLabel, } from './post-schedule/label'; export { default as PostSchedulePanel } from './post-schedule/panel'; -export { default as PostSlug } from './post-slug'; -export { default as PostSlugCheck } from './post-slug/check'; export { default as PostSticky } from './post-sticky'; export { default as PostStickyCheck } from './post-sticky/check'; export { default as PostSwitchToDraftButton } from './post-switch-to-draft-button'; diff --git a/packages/editor/src/components/more-menu/index.js b/packages/editor/src/components/more-menu/index.js index 9e062e5e5adc50..f5eaa45e4ed696 100644 --- a/packages/editor/src/components/more-menu/index.js +++ b/packages/editor/src/components/more-menu/index.js @@ -113,7 +113,6 @@ export default function MoreMenu() { <ActionItem.Slot name="core/plugin-more-menu" label={ __( 'Plugins' ) } - as={ MenuGroup } fillProps={ { onClick: onClose } } /> <MenuGroup label={ __( 'Tools' ) }> diff --git a/packages/editor/src/components/page-attributes/check.js b/packages/editor/src/components/page-attributes/check.js index bed2b1a353842a..3c08a3d8e53514 100644 --- a/packages/editor/src/components/page-attributes/check.js +++ b/packages/editor/src/components/page-attributes/check.js @@ -12,10 +12,10 @@ import { store as editorStore } from '../../store'; /** * Wrapper component that renders its children only if the post type supports page attributes. * - * @param {Object} props - The component props. - * @param {Element} props.children - The child components to render. + * @param {Object} props - The component props. + * @param {React.ReactElement} props.children - The child components to render. * - * @return {Component|null} The rendered child components or null if page attributes are not supported. + * @return {React.ReactElement} The rendered child components or null if page attributes are not supported. */ export function PageAttributesCheck( { children } ) { const supportsPageAttributes = useSelect( ( select ) => { diff --git a/packages/editor/src/components/page-attributes/order.js b/packages/editor/src/components/page-attributes/order.js index c5f02c71b613d4..04c6ce186a9701 100644 --- a/packages/editor/src/components/page-attributes/order.js +++ b/packages/editor/src/components/page-attributes/order.js @@ -59,7 +59,7 @@ function PageAttributesOrder() { * for setting the order of a given page. * The component is now not used in core but was kept for backward compatibility. * - * @return {Component} The component to be rendered. + * @return {React.ReactNode} The rendered component. */ export default function PageAttributesOrderWithChecks() { return ( diff --git a/packages/editor/src/components/page-attributes/panel.js b/packages/editor/src/components/page-attributes/panel.js index 7fcaf4b90d9ffe..8ecf7f1642f718 100644 --- a/packages/editor/src/components/page-attributes/panel.js +++ b/packages/editor/src/components/page-attributes/panel.js @@ -33,7 +33,7 @@ function AttributesPanel() { /** * Renders the Page Attributes Panel component. * - * @return {Component} The component to be rendered. + * @return {React.ReactNode} The rendered component. */ export default function PageAttributesPanel() { return ( diff --git a/packages/editor/src/components/page-attributes/parent.js b/packages/editor/src/components/page-attributes/parent.js index 17395589cd313b..bd2861766c334a 100644 --- a/packages/editor/src/components/page-attributes/parent.js +++ b/packages/editor/src/components/page-attributes/parent.js @@ -56,7 +56,7 @@ export const getItemPriority = ( name, searchValue ) => { * Renders the Page Attributes Parent component. A dropdown menu in an editor interface * for selecting the parent page of a given page. * - * @return {Component|null} The component to be rendered. Return null if post type is not hierarchical. + * @return {React.ReactNode} The component to be rendered. Return null if post type is not hierarchical. */ export function PageAttributesParent() { const { editPost } = useDispatch( editorStore ); diff --git a/packages/editor/src/components/plugin-document-setting-panel/index.js b/packages/editor/src/components/plugin-document-setting-panel/index.js index 7466acffc0c4b1..6408d82fe7e118 100644 --- a/packages/editor/src/components/plugin-document-setting-panel/index.js +++ b/packages/editor/src/components/plugin-document-setting-panel/index.js @@ -22,7 +22,7 @@ const { Fill, Slot } = createSlotFill( 'PluginDocumentSettingPanel' ); * @param {string} [props.className] An optional class name added to the row. * @param {string} [props.title] The title of the panel * @param {WPBlockTypeIconRender} [props.icon=inherits from the plugin] The [Dashicon](https://developer.wordpress.org/resource/dashicons/) icon slug string, or an SVG WP element, to be rendered when the sidebar is pinned to toolbar. - * @param {Element} props.children Children to be rendered + * @param {React.ReactNode} props.children Children to be rendered * * @example * ```js @@ -64,7 +64,7 @@ const { Fill, Slot } = createSlotFill( 'PluginDocumentSettingPanel' ); * registerPlugin( 'document-setting-test', { render: MyDocumentSettingTest } ); * ``` * - * @return {Component} The component to be rendered. + * @return {React.ReactNode} The component to be rendered. */ const PluginDocumentSettingPanel = ( { name, diff --git a/packages/editor/src/components/plugin-more-menu-item/index.js b/packages/editor/src/components/plugin-more-menu-item/index.js index 28173c24ebcefa..1d8e124b03e604 100644 --- a/packages/editor/src/components/plugin-more-menu-item/index.js +++ b/packages/editor/src/components/plugin-more-menu-item/index.js @@ -10,6 +10,7 @@ import { ActionItem } from '@wordpress/interface'; * The text within the component appears as the menu item label. * * @param {Object} props Component properties. + * @param {React.ReactNode} [props.children] Children to be rendered. * @param {string} [props.href] When `href` is provided then the menu item is represented as an anchor rather than button. It corresponds to the `href` attribute of the anchor. * @param {WPBlockTypeIconRender} [props.icon=inherits from the plugin] The [Dashicon](https://developer.wordpress.org/resource/dashicons/) icon slug string, or an SVG WP element, to be rendered to the left of the menu item label. * @param {Function} [props.onClick=noop] The callback function to be executed when the user clicks the menu item. @@ -59,7 +60,7 @@ import { ActionItem } from '@wordpress/interface'; * ); * ``` * - * @return {Component} The component to be rendered. + * @return {React.ReactNode} The rendered component. */ export default function PluginMoreMenuItem( props ) { const context = usePluginContext(); diff --git a/packages/editor/src/components/plugin-post-publish-panel/index.js b/packages/editor/src/components/plugin-post-publish-panel/index.js index 086045b1c1fee1..b93f0a15c237f5 100644 --- a/packages/editor/src/components/plugin-post-publish-panel/index.js +++ b/packages/editor/src/components/plugin-post-publish-panel/index.js @@ -15,7 +15,7 @@ const { Fill, Slot } = createSlotFill( 'PluginPostPublishPanel' ); * @param {string} [props.title] Title displayed at the top of the panel. * @param {boolean} [props.initialOpen=false] Whether to have the panel initially opened. When no title is provided it is always opened. * @param {WPBlockTypeIconRender} [props.icon=inherits from the plugin] The [Dashicon](https://developer.wordpress.org/resource/dashicons/) icon slug string, or an SVG WP element, to be rendered when the sidebar is pinned to toolbar. - * @param {Element} props.children Children to be rendered + * @param {React.ReactNode} props.children Children to be rendered * * @example * ```jsx @@ -34,7 +34,7 @@ const { Fill, Slot } = createSlotFill( 'PluginPostPublishPanel' ); * ); * ``` * - * @return {Component} The component to be rendered. + * @return {React.ReactNode} The rendered component. */ const PluginPostPublishPanel = ( { children, diff --git a/packages/editor/src/components/plugin-post-status-info/index.js b/packages/editor/src/components/plugin-post-status-info/index.js index a4a216b78ae78b..f9f3293047ddd3 100644 --- a/packages/editor/src/components/plugin-post-status-info/index.js +++ b/packages/editor/src/components/plugin-post-status-info/index.js @@ -14,9 +14,9 @@ const { Fill, Slot } = createSlotFill( 'PluginPostStatusInfo' ); * It should be noted that this is named and implemented around the function it serves * and not its location, which may change in future iterations. * - * @param {Object} props Component properties. - * @param {string} [props.className] An optional class name added to the row. - * @param {Element} props.children Children to be rendered. + * @param {Object} props Component properties. + * @param {string} [props.className] An optional class name added to the row. + * @param {React.ReactNode} props.children Children to be rendered. * * @example * ```js @@ -50,7 +50,7 @@ const { Fill, Slot } = createSlotFill( 'PluginPostStatusInfo' ); * ); * ``` * - * @return {Component} The component to be rendered. + * @return {React.ReactNode} The rendered component. */ const PluginPostStatusInfo = ( { children, className } ) => ( <Fill> diff --git a/packages/editor/src/components/plugin-pre-publish-panel/index.js b/packages/editor/src/components/plugin-pre-publish-panel/index.js index c9f556dc534a80..412af36c5176e0 100644 --- a/packages/editor/src/components/plugin-pre-publish-panel/index.js +++ b/packages/editor/src/components/plugin-pre-publish-panel/index.js @@ -18,7 +18,7 @@ const { Fill, Slot } = createSlotFill( 'PluginPrePublishPanel' ); * @param {WPBlockTypeIconRender} [props.icon=inherits from the plugin] The [Dashicon](https://developer.wordpress.org/resource/dashicons/) * icon slug string, or an SVG WP element, to be rendered when * the sidebar is pinned to toolbar. - * @param {Element} props.children Children to be rendered + * @param {React.ReactNode} props.children Children to be rendered * * @example * ```jsx @@ -37,7 +37,7 @@ const { Fill, Slot } = createSlotFill( 'PluginPrePublishPanel' ); * ); * ``` * - * @return {Component} The component to be rendered. + * @return {React.ReactNode} The rendered component. */ const PluginPrePublishPanel = ( { children, diff --git a/packages/editor/src/components/plugin-preview-menu-item/index.js b/packages/editor/src/components/plugin-preview-menu-item/index.js index 8038da04595aae..949f02808a7b03 100644 --- a/packages/editor/src/components/plugin-preview-menu-item/index.js +++ b/packages/editor/src/components/plugin-preview-menu-item/index.js @@ -10,6 +10,7 @@ import { ActionItem } from '@wordpress/interface'; * The text within the component appears as the menu item label. * * @param {Object} props Component properties. + * @param {React.ReactNode} [props.children] Children to be rendered. * @param {string} [props.href] When `href` is provided, the menu item is rendered as an anchor instead of a button. It corresponds to the `href` attribute of the anchor. * @param {WPBlockTypeIconRender} [props.icon=inherits from the plugin] The icon to be rendered to the left of the menu item label. Can be a Dashicon slug or an SVG WP element. * @param {Function} [props.onClick] The callback function to be executed when the user clicks the menu item. @@ -38,7 +39,7 @@ import { ActionItem } from '@wordpress/interface'; * } ); * ``` * - * @return {Component} The rendered menu item component. + * @return {React.ReactNode} The rendered menu item component. */ export default function PluginPreviewMenuItem( props ) { const context = usePluginContext(); diff --git a/packages/editor/src/components/plugin-sidebar-more-menu-item/index.js b/packages/editor/src/components/plugin-sidebar-more-menu-item/index.js index 0d7695c9abfe12..379a0720dc8a91 100644 --- a/packages/editor/src/components/plugin-sidebar-more-menu-item/index.js +++ b/packages/editor/src/components/plugin-sidebar-more-menu-item/index.js @@ -10,6 +10,7 @@ import { ComplementaryAreaMoreMenuItem } from '@wordpress/interface'; * * @param {Object} props Component props. * @param {string} props.target A string identifying the target sidebar you wish to be activated by this menu item. Must be the same as the `name` prop you have given to that sidebar. + * @param {React.ReactNode} [props.children] Children to be rendered. * @param {WPBlockTypeIconRender} [props.icon=inherits from the plugin] The [Dashicon](https://developer.wordpress.org/resource/dashicons/) icon slug string, or an SVG WP element, to be rendered to the left of the menu item label. * * @example @@ -48,9 +49,8 @@ import { ComplementaryAreaMoreMenuItem } from '@wordpress/interface'; * ); * ``` * - * @return {Component} The component to be rendered. + * @return {React.ReactNode} The rendered component. */ - export default function PluginSidebarMoreMenuItem( props ) { return ( <ComplementaryAreaMoreMenuItem diff --git a/packages/editor/src/components/plugin-sidebar/index.js b/packages/editor/src/components/plugin-sidebar/index.js index 5cd61a3747e6c1..dfcc01b5e60283 100644 --- a/packages/editor/src/components/plugin-sidebar/index.js +++ b/packages/editor/src/components/plugin-sidebar/index.js @@ -16,6 +16,7 @@ import { ComplementaryArea } from '@wordpress/interface'; * * @param {Object} props Element props. * @param {string} props.name A string identifying the sidebar. Must be unique for every sidebar registered within the scope of your plugin. + * @param {React.ReactNode} [props.children] Children to be rendered. * @param {string} [props.className] An optional class name added to the sidebar body. * @param {string} props.title Title displayed at the top of the sidebar. * @param {boolean} [props.isPinnable=true] Whether to allow to pin sidebar to the toolbar. When set to `true` it also automatically renders a corresponding menu item. diff --git a/packages/editor/src/components/post-actions/actions.js b/packages/editor/src/components/post-actions/actions.js index e1c0ed1558193d..023b93d31bb511 100644 --- a/packages/editor/src/components/post-actions/actions.js +++ b/packages/editor/src/components/post-actions/actions.js @@ -3,12 +3,15 @@ */ import { useDispatch, useSelect } from '@wordpress/data'; import { useMemo, useEffect } from '@wordpress/element'; +import { store as coreStore } from '@wordpress/core-data'; /** * Internal dependencies */ import { store as editorStore } from '../../store'; import { unlock } from '../../lock-unlock'; +import { useSetAsHomepageAction } from './set-as-homepage'; +import { useSetAsPostsPageAction } from './set-as-posts-page'; export function usePostActions( { postType, onActionPerformed, context } ) { const { defaultActions } = useSelect( @@ -21,19 +24,53 @@ export function usePostActions( { postType, onActionPerformed, context } ) { [ postType ] ); - const { registerPostTypeActions } = unlock( useDispatch( editorStore ) ); + const { canManageOptions, hasFrontPageTemplate } = useSelect( + ( select ) => { + const { getEntityRecords } = select( coreStore ); + const templates = getEntityRecords( 'postType', 'wp_template', { + per_page: -1, + } ); + + return { + canManageOptions: select( coreStore ).canUser( 'update', { + kind: 'root', + name: 'site', + } ), + hasFrontPageTemplate: !! templates?.find( + ( template ) => template?.slug === 'front-page' + ), + }; + } + ); + + const setAsHomepageAction = useSetAsHomepageAction(); + const setAsPostsPageAction = useSetAsPostsPageAction(); + const shouldShowHomepageActions = + canManageOptions && ! hasFrontPageTemplate; + + const { registerPostTypeSchema } = unlock( useDispatch( editorStore ) ); useEffect( () => { - registerPostTypeActions( postType ); - }, [ registerPostTypeActions, postType ] ); + registerPostTypeSchema( postType ); + }, [ registerPostTypeSchema, postType ] ); return useMemo( () => { + let actions = [ ...defaultActions ]; + if ( shouldShowHomepageActions ) { + actions.push( setAsHomepageAction, setAsPostsPageAction ); + } + + // Ensure "Move to trash" is always the last action. + actions = actions.sort( ( a, b ) => + b.id === 'move-to-trash' ? -1 : 0 + ); + // Filter actions based on provided context. If not provided // all actions are returned. We'll have a single entry for getting the actions // and the consumer should provide the context to filter the actions, if needed. // Actions should also provide the `context` they support, if it's specific, to // compare with the provided context to get all the actions. // Right now the only supported context is `list`. - const actions = defaultActions.filter( ( action ) => { + actions = actions.filter( ( action ) => { if ( ! action.context ) { return true; } @@ -88,5 +125,12 @@ export function usePostActions( { postType, onActionPerformed, context } ) { } return actions; - }, [ defaultActions, onActionPerformed, context ] ); + }, [ + context, + defaultActions, + onActionPerformed, + setAsHomepageAction, + setAsPostsPageAction, + shouldShowHomepageActions, + ] ); } diff --git a/packages/editor/src/components/post-actions/index.js b/packages/editor/src/components/post-actions/index.js index 9f39b1f3305aeb..d6adf6c0721667 100644 --- a/packages/editor/src/components/post-actions/index.js +++ b/packages/editor/src/components/post-actions/index.js @@ -1,7 +1,7 @@ /** * WordPress dependencies */ -import { useSelect } from '@wordpress/data'; +import { useRegistry, useSelect } from '@wordpress/data'; import { useState, useMemo } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; import { @@ -20,66 +20,89 @@ import { usePostActions } from './actions'; const { Menu, kebabCase } = unlock( componentsPrivateApis ); -export default function PostActions( { postType, postId, onActionPerformed } ) { - const [ isActionsMenuOpen, setIsActionsMenuOpen ] = useState( false ); - const { item, permissions } = useSelect( +function useEditedEntityRecordsWithPermissions( postType, postIds ) { + const { items, permissions } = useSelect( ( select ) => { const { getEditedEntityRecord, getEntityRecordPermissions } = unlock( select( coreStore ) ); return { - item: getEditedEntityRecord( 'postType', postType, postId ), - permissions: getEntityRecordPermissions( - 'postType', - postType, - postId + items: postIds.map( ( postId ) => + getEditedEntityRecord( 'postType', postType, postId ) + ), + permissions: postIds.map( ( postId ) => + getEntityRecordPermissions( 'postType', postType, postId ) ), }; }, - [ postId, postType ] + [ postIds, postType ] ); - const itemWithPermissions = useMemo( () => { - return { + + return useMemo( () => { + return items.map( ( item, index ) => ( { ...item, - permissions, - }; - }, [ item, permissions ] ); + permissions: permissions[ index ], + } ) ); + }, [ items, permissions ] ); +} + +export default function PostActions( { postType, postId, onActionPerformed } ) { + const [ activeModalAction, setActiveModalAction ] = useState( null ); + const _postIds = useMemo( () => { + if ( Array.isArray( postId ) ) { + return postId; + } + return postId ? [ postId ] : []; + }, [ postId ] ); + + const itemsWithPermissions = useEditedEntityRecordsWithPermissions( + postType, + _postIds + ); const allActions = usePostActions( { postType, onActionPerformed } ); const actions = useMemo( () => { return allActions.filter( ( action ) => { return ( - ! action.isEligible || action.isEligible( itemWithPermissions ) + ( ! action.isEligible || + itemsWithPermissions.some( ( itemWithPermissions ) => + action.isEligible( itemWithPermissions ) + ) ) && + ( itemsWithPermissions.length < 2 || action.supportsBulk ) ); } ); - }, [ allActions, itemWithPermissions ] ); + }, [ allActions, itemsWithPermissions ] ); return ( - <Menu - open={ isActionsMenuOpen } - trigger={ - <Button - size="small" - icon={ moreVertical } - label={ __( 'Actions' ) } - disabled={ ! actions.length } - accessibleWhenDisabled - className="editor-all-actions-button" - onClick={ () => - setIsActionsMenuOpen( ! isActionsMenuOpen ) + <> + <Menu placement="bottom-end"> + <Menu.TriggerButton + render={ + <Button + size="small" + icon={ moreVertical } + label={ __( 'Actions' ) } + disabled={ ! actions.length } + accessibleWhenDisabled + className="editor-all-actions-button" + /> } /> - } - onOpenChange={ setIsActionsMenuOpen } - placement="bottom-end" - > - <ActionsDropdownMenuGroup - actions={ actions } - item={ itemWithPermissions } - onClose={ () => { - setIsActionsMenuOpen( false ); - } } - /> - </Menu> + <Menu.Popover> + <ActionsDropdownMenuGroup + actions={ actions } + items={ itemsWithPermissions } + setActiveModalAction={ setActiveModalAction } + /> + </Menu.Popover> + </Menu> + { !! activeModalAction && ( + <ActionModal + action={ activeModalAction } + items={ itemsWithPermissions } + closeModal={ () => setActiveModalAction( null ) } + /> + ) } + </> ); } @@ -88,79 +111,52 @@ export default function PostActions( { postType, postId, onActionPerformed } ) { // and the dataviews package should not be using the editor packages directly, // so duplicating the code here seems like the least bad option. -// Copied as is from packages/dataviews/src/item-actions.js function DropdownMenuItemTrigger( { action, onClick, items } ) { const label = typeof action.label === 'string' ? action.label : action.label( items ); return ( - <Menu.Item onClick={ onClick } hideOnClick={ ! action.RenderModal }> + <Menu.Item onClick={ onClick }> <Menu.ItemLabel>{ label }</Menu.ItemLabel> </Menu.Item> ); } -// Copied as is from packages/dataviews/src/item-actions.js -// With an added onClose prop. -function ActionWithModal( { action, item, ActionTrigger, onClose } ) { - const [ isModalOpen, setIsModalOpen ] = useState( false ); - const actionTriggerProps = { - action, - onClick: () => setIsModalOpen( true ), - items: [ item ], - }; - const { RenderModal, hideModalHeader } = action; +export function ActionModal( { action, items, closeModal } ) { + const label = + typeof action.label === 'string' ? action.label : action.label( items ); return ( - <> - <ActionTrigger { ...actionTriggerProps } /> - { isModalOpen && ( - <Modal - title={ action.modalHeader || action.label } - __experimentalHideHeader={ !! hideModalHeader } - onRequestClose={ () => { - setIsModalOpen( false ); - } } - overlayClassName={ `editor-action-modal editor-action-modal__${ kebabCase( - action.id - ) }` } - focusOnMount="firstContentElement" - size="small" - > - <RenderModal - items={ [ item ] } - closeModal={ () => { - setIsModalOpen( false ); - onClose(); - } } - /> - </Modal> - ) } - </> + <Modal + title={ action.modalHeader || label } + __experimentalHideHeader={ !! action.hideModalHeader } + onRequestClose={ closeModal ?? ( () => {} ) } + focusOnMount="firstContentElement" + size="medium" + overlayClassName={ `editor-action-modal editor-action-modal__${ kebabCase( + action.id + ) }` } + > + <action.RenderModal items={ items } closeModal={ closeModal } /> + </Modal> ); } -// Copied as is from packages/dataviews/src/item-actions.js -// With an added onClose prop. -function ActionsDropdownMenuGroup( { actions, item, onClose } ) { +function ActionsDropdownMenuGroup( { actions, items, setActiveModalAction } ) { + const registry = useRegistry(); return ( <Menu.Group> { actions.map( ( action ) => { - if ( action.RenderModal ) { - return ( - <ActionWithModal - key={ action.id } - action={ action } - item={ item } - ActionTrigger={ DropdownMenuItemTrigger } - onClose={ onClose } - /> - ); - } return ( <DropdownMenuItemTrigger key={ action.id } action={ action } - onClick={ () => action.callback( [ item ] ) } - items={ [ item ] } + onClick={ () => { + if ( 'RenderModal' in action ) { + setActiveModalAction( action ); + return; + } + action.callback( items, { registry } ); + } } + items={ items } /> ); } ) } diff --git a/packages/editor/src/components/post-actions/set-as-homepage.js b/packages/editor/src/components/post-actions/set-as-homepage.js new file mode 100644 index 00000000000000..cb67e251ed58c2 --- /dev/null +++ b/packages/editor/src/components/post-actions/set-as-homepage.js @@ -0,0 +1,167 @@ +/** + * WordPress dependencies + */ +import { __, sprintf } from '@wordpress/i18n'; +import { useMemo } from '@wordpress/element'; +import { + Button, + __experimentalText as Text, + __experimentalHStack as HStack, + __experimentalVStack as VStack, +} from '@wordpress/components'; +import { useDispatch, useSelect } from '@wordpress/data'; +import { store as coreStore } from '@wordpress/core-data'; +import { store as noticesStore } from '@wordpress/notices'; + +/** + * Internal dependencies + */ +import { getItemTitle } from '../../utils/get-item-title'; + +const SetAsHomepageModal = ( { items, closeModal } ) => { + const [ item ] = items; + const pageTitle = getItemTitle( item ); + const { showOnFront, currentHomePage, isSaving } = useSelect( + ( select ) => { + const { getEntityRecord, isSavingEntityRecord } = + select( coreStore ); + const siteSettings = getEntityRecord( 'root', 'site' ); + const currentHomePageItem = getEntityRecord( + 'postType', + 'page', + siteSettings?.page_on_front + ); + return { + showOnFront: siteSettings?.show_on_front, + currentHomePage: currentHomePageItem, + isSaving: isSavingEntityRecord( 'root', 'site' ), + }; + } + ); + + const { saveEntityRecord } = useDispatch( coreStore ); + const { createSuccessNotice, createErrorNotice } = + useDispatch( noticesStore ); + + async function onSetPageAsHomepage( event ) { + event.preventDefault(); + + try { + await saveEntityRecord( 'root', 'site', { + page_on_front: item.id, + show_on_front: 'page', + } ); + + createSuccessNotice( __( 'Homepage updated.' ), { + type: 'snackbar', + } ); + } catch ( error ) { + const errorMessage = + error.message && error.code !== 'unknown_error' + ? error.message + : __( 'An error occurred while setting the homepage.' ); + createErrorNotice( errorMessage, { type: 'snackbar' } ); + } finally { + closeModal?.(); + } + } + + let modalWarning = ''; + if ( 'posts' === showOnFront ) { + modalWarning = __( + 'This will replace the current homepage which is set to display latest posts.' + ); + } else if ( currentHomePage ) { + modalWarning = sprintf( + // translators: %s: title of the current home page. + __( 'This will replace the current homepage: "%s"' ), + getItemTitle( currentHomePage ) + ); + } + + const modalText = sprintf( + // translators: %1$s: title of the page to be set as the homepage, %2$s: homepage replacement warning message. + __( 'Set "%1$s" as the site homepage? %2$s' ), + pageTitle, + modalWarning + ).trim(); + + // translators: Button label to confirm setting the specified page as the homepage. + const modalButtonLabel = __( 'Set homepage' ); + + return ( + <form onSubmit={ onSetPageAsHomepage }> + <VStack spacing="5"> + <Text>{ modalText }</Text> + <HStack justify="right"> + <Button + __next40pxDefaultSize + variant="tertiary" + onClick={ () => { + closeModal?.(); + } } + disabled={ isSaving } + accessibleWhenDisabled + > + { __( 'Cancel' ) } + </Button> + <Button + __next40pxDefaultSize + variant="primary" + type="submit" + disabled={ isSaving } + accessibleWhenDisabled + > + { modalButtonLabel } + </Button> + </HStack> + </VStack> + </form> + ); +}; + +export const useSetAsHomepageAction = () => { + const { pageOnFront, pageForPosts } = useSelect( ( select ) => { + const { getEntityRecord, canUser } = select( coreStore ); + const siteSettings = canUser( 'read', { + kind: 'root', + name: 'site', + } ) + ? getEntityRecord( 'root', 'site' ) + : undefined; + return { + pageOnFront: siteSettings?.page_on_front, + pageForPosts: siteSettings?.page_for_posts, + }; + } ); + + return useMemo( + () => ( { + id: 'set-as-homepage', + label: __( 'Set as homepage' ), + isEligible( post ) { + if ( post.status !== 'publish' ) { + return false; + } + + if ( post.type !== 'page' ) { + return false; + } + + // Don't show the action if the page is already set as the homepage. + if ( pageOnFront === post.id ) { + return false; + } + + // Don't show the action if the page is already set as the page for posts. + if ( pageForPosts === post.id ) { + return false; + } + + return true; + }, + RenderModal: SetAsHomepageModal, + } ), + [ pageForPosts, pageOnFront ] + ); +}; diff --git a/packages/editor/src/components/post-actions/set-as-posts-page.js b/packages/editor/src/components/post-actions/set-as-posts-page.js new file mode 100644 index 00000000000000..830c2cac734f1f --- /dev/null +++ b/packages/editor/src/components/post-actions/set-as-posts-page.js @@ -0,0 +1,164 @@ +/** + * WordPress dependencies + */ +import { __, sprintf } from '@wordpress/i18n'; +import { useMemo } from '@wordpress/element'; +import { + Button, + __experimentalText as Text, + __experimentalHStack as HStack, + __experimentalVStack as VStack, +} from '@wordpress/components'; +import { useDispatch, useSelect } from '@wordpress/data'; +import { store as coreStore } from '@wordpress/core-data'; +import { store as noticesStore } from '@wordpress/notices'; + +/** + * Internal dependencies + */ +import { getItemTitle } from '../../utils/get-item-title'; + +const SetAsPostsPageModal = ( { items, closeModal } ) => { + const [ item ] = items; + const pageTitle = getItemTitle( item ); + const { currentPostsPage, isPageForPostsSet, isSaving } = useSelect( + ( select ) => { + const { getEntityRecord, isSavingEntityRecord } = + select( coreStore ); + const siteSettings = getEntityRecord( 'root', 'site' ); + const currentPostsPageItem = getEntityRecord( + 'postType', + 'page', + siteSettings?.page_for_posts + ); + return { + currentPostsPage: currentPostsPageItem, + isPageForPostsSet: siteSettings?.page_for_posts !== 0, + isSaving: isSavingEntityRecord( 'root', 'site' ), + }; + } + ); + + const { saveEntityRecord } = useDispatch( coreStore ); + const { createSuccessNotice, createErrorNotice } = + useDispatch( noticesStore ); + + async function onSetPageAsPostsPage( event ) { + event.preventDefault(); + + try { + await saveEntityRecord( 'root', 'site', { + page_for_posts: item.id, + show_on_front: 'page', + } ); + + createSuccessNotice( __( 'Posts page updated.' ), { + type: 'snackbar', + } ); + } catch ( error ) { + const errorMessage = + error.message && error.code !== 'unknown_error' + ? error.message + : __( 'An error occurred while setting the posts page.' ); + createErrorNotice( errorMessage, { type: 'snackbar' } ); + } finally { + closeModal?.(); + } + } + + const modalWarning = + isPageForPostsSet && currentPostsPage + ? sprintf( + // translators: %s: title of the current posts page. + __( 'This will replace the current posts page: "%s"' ), + getItemTitle( currentPostsPage ) + ) + : __( 'This page will show the latest posts.' ); + + const modalText = sprintf( + // translators: %1$s: title of the page to be set as the posts page, %2$s: posts page replacement warning message. + __( 'Set "%1$s" as the posts page? %2$s' ), + pageTitle, + modalWarning + ); + + // translators: Button label to confirm setting the specified page as the posts page. + const modalButtonLabel = __( 'Set posts page' ); + + return ( + <form onSubmit={ onSetPageAsPostsPage }> + <VStack spacing="5"> + <Text>{ modalText }</Text> + <HStack justify="right"> + <Button + __next40pxDefaultSize + variant="tertiary" + onClick={ () => { + closeModal?.(); + } } + disabled={ isSaving } + accessibleWhenDisabled + > + { __( 'Cancel' ) } + </Button> + <Button + __next40pxDefaultSize + variant="primary" + type="submit" + disabled={ isSaving } + accessibleWhenDisabled + > + { modalButtonLabel } + </Button> + </HStack> + </VStack> + </form> + ); +}; + +export const useSetAsPostsPageAction = () => { + const { pageOnFront, pageForPosts } = useSelect( ( select ) => { + const { getEntityRecord, canUser } = select( coreStore ); + const siteSettings = canUser( 'read', { + kind: 'root', + name: 'site', + } ) + ? getEntityRecord( 'root', 'site' ) + : undefined; + + return { + pageOnFront: siteSettings?.page_on_front, + pageForPosts: siteSettings?.page_for_posts, + }; + } ); + + return useMemo( + () => ( { + id: 'set-as-posts-page', + label: __( 'Set as posts page' ), + isEligible( post ) { + if ( post.status !== 'publish' ) { + return false; + } + + if ( post.type !== 'page' ) { + return false; + } + + // Don't show the action if the page is already set as the homepage. + if ( pageOnFront === post.id ) { + return false; + } + + // Don't show the action if the page is already set as the page for posts. + if ( pageForPosts === post.id ) { + return false; + } + + return true; + }, + RenderModal: SetAsPostsPageModal, + } ), + [ pageForPosts, pageOnFront ] + ); +}; diff --git a/packages/editor/src/components/post-author/check.js b/packages/editor/src/components/post-author/check.js index d10a0a2ccf0bd0..0ae7a3963e243f 100644 --- a/packages/editor/src/components/post-author/check.js +++ b/packages/editor/src/components/post-author/check.js @@ -14,10 +14,10 @@ import { AUTHORS_QUERY } from './constants'; /** * Wrapper component that renders its children only if the post type supports the author. * - * @param {Object} props The component props. - * @param {Element} props.children Children to be rendered. + * @param {Object} props The component props. + * @param {React.ReactNode} props.children Children to be rendered. * - * @return {Component|null} The component to be rendered. Return `null` if the post type doesn't + * @return {React.ReactNode} The component to be rendered. Return `null` if the post type doesn't * supports the author or if there are no authors available. */ export default function PostAuthorCheck( { children } ) { diff --git a/packages/editor/src/components/post-author/constants.js b/packages/editor/src/components/post-author/constants.js index 46bc2d46d1b086..517cbd9ea1dc8d 100644 --- a/packages/editor/src/components/post-author/constants.js +++ b/packages/editor/src/components/post-author/constants.js @@ -5,6 +5,6 @@ export const BASE_QUERY = { export const AUTHORS_QUERY = { who: 'authors', - per_page: 50, + per_page: 100, ...BASE_QUERY, }; diff --git a/packages/editor/src/components/post-author/hook.js b/packages/editor/src/components/post-author/hook.js index 62830cf6ea60e6..f251eba79e1806 100644 --- a/packages/editor/src/components/post-author/hook.js +++ b/packages/editor/src/components/post-author/hook.js @@ -23,6 +23,7 @@ export function useAuthorsQuery( search ) { if ( search ) { query.search = search; + query.search_columns = [ 'name' ]; } return { diff --git a/packages/editor/src/components/post-author/index.js b/packages/editor/src/components/post-author/index.js index 9ff3aaaf09a29c..7623adc10402c9 100644 --- a/packages/editor/src/components/post-author/index.js +++ b/packages/editor/src/components/post-author/index.js @@ -16,7 +16,7 @@ const minimumUsersForCombobox = 25; /** * Renders the component for selecting the post author. * - * @return {Component} The component to be rendered. + * @return {React.ReactNode} The rendered component. */ function PostAuthor() { const showCombobox = useSelect( ( select ) => { diff --git a/packages/editor/src/components/post-author/panel.js b/packages/editor/src/components/post-author/panel.js index 6c6a51918902dc..5a413aedac09cf 100644 --- a/packages/editor/src/components/post-author/panel.js +++ b/packages/editor/src/components/post-author/panel.js @@ -39,7 +39,7 @@ function PostAuthorToggle( { isOpen, onClick } ) { /** * Renders the Post Author Panel component. * - * @return {Component} The component to be rendered. + * @return {React.ReactNode} The rendered component. */ export function PostAuthor() { // Use internal state instead of a ref to make sure that the component diff --git a/packages/editor/src/components/post-card-panel/index.js b/packages/editor/src/components/post-card-panel/index.js index 410b8cfd4447d9..895545cb007f00 100644 --- a/packages/editor/src/components/post-card-panel/index.js +++ b/packages/editor/src/components/post-card-panel/index.js @@ -4,12 +4,15 @@ import { Icon, __experimentalHStack as HStack, + __experimentalVStack as VStack, __experimentalText as Text, + privateApis as componentsPrivateApis, } from '@wordpress/components'; import { store as coreStore } from '@wordpress/core-data'; import { useSelect } from '@wordpress/data'; -import { __ } from '@wordpress/i18n'; -import { decodeEntities } from '@wordpress/html-entities'; +import { useMemo } from '@wordpress/element'; +import { __, sprintf } from '@wordpress/i18n'; +import { __unstableStripHTML as stripHTML } from '@wordpress/dom'; /** * Internal dependencies @@ -22,39 +25,80 @@ import { import { unlock } from '../../lock-unlock'; import PostActions from '../post-actions'; import usePageTypeBadge from '../../utils/pageTypeBadge'; +import { getTemplateInfo } from '../../utils/get-template-info'; +const { Badge } = unlock( componentsPrivateApis ); +/** + * Renders a title of the post type and the available quick actions available within a 3-dot dropdown. + * + * @param {Object} props - Component props. + * @param {string} [props.postType] - The post type string. + * @param {string|string[]} [props.postId] - The post id or list of post ids. + * @param {Function} [props.onActionPerformed] - A callback function for when a quick action is performed. + * @return {React.ReactNode} The rendered component. + */ export default function PostCardPanel( { postType, postId, onActionPerformed, } ) { - const { title, icon } = useSelect( + const postIds = useMemo( + () => ( Array.isArray( postId ) ? postId : [ postId ] ), + [ postId ] + ); + const { postTitle, icon, labels } = useSelect( ( select ) => { - const { __experimentalGetTemplateInfo } = select( editorStore ); - const { getEditedEntityRecord } = select( coreStore ); + const { getEditedEntityRecord, getEntityRecord, getPostType } = + select( coreStore ); + const { getPostIcon } = unlock( select( editorStore ) ); + let _title = ''; const _record = getEditedEntityRecord( 'postType', postType, - postId + postIds[ 0 ] ); - const _templateInfo = - [ TEMPLATE_POST_TYPE, TEMPLATE_PART_POST_TYPE ].includes( - postType - ) && __experimentalGetTemplateInfo( _record ); + if ( postIds.length === 1 ) { + const { default_template_types: templateTypes = [] } = + getEntityRecord( 'root', '__unstableBase' ) ?? {}; + + const _templateInfo = [ + TEMPLATE_POST_TYPE, + TEMPLATE_PART_POST_TYPE, + ].includes( postType ) + ? getTemplateInfo( { + template: _record, + templateTypes, + } ) + : {}; + _title = _templateInfo?.title || _record?.title; + } + return { - title: _templateInfo?.title || _record?.title, - icon: unlock( select( editorStore ) ).getPostIcon( postType, { + postTitle: _title, + icon: getPostIcon( postType, { area: _record?.area, } ), + labels: getPostType( postType )?.labels, }; }, - [ postId, postType ] + [ postIds, postType ] ); - const pageTypeBadge = usePageTypeBadge(); + const pageTypeBadge = usePageTypeBadge( postId ); + let title = __( 'No title' ); + if ( labels?.name && postIds.length > 1 ) { + title = sprintf( + // translators: %i number of selected items %s: Name of the plural post type e.g: "Posts". + __( '%i %s' ), + postId.length, + labels?.name + ); + } else if ( postTitle ) { + title = stripHTML( postTitle ); + } return ( - <div className="editor-post-card-panel"> + <VStack spacing={ 1 } className="editor-post-card-panel"> <HStack spacing={ 2 } className="editor-post-card-panel__header" @@ -65,15 +109,13 @@ export default function PostCardPanel( { numberOfLines={ 2 } truncate className="editor-post-card-panel__title" - weight={ 500 } as="h2" - lineHeight="20px" > - { title ? decodeEntities( title ) : __( 'No title' ) } - { pageTypeBadge && ( - <span className="editor-post-card-panel__title-badge"> - { pageTypeBadge } - </span> + <span className="editor-post-card-panel__title-name"> + { title } + </span> + { pageTypeBadge && postIds.length === 1 && ( + <Badge>{ pageTypeBadge }</Badge> ) } </Text> <PostActions @@ -82,6 +124,15 @@ export default function PostCardPanel( { onActionPerformed={ onActionPerformed } /> </HStack> - </div> + { postIds.length > 1 && ( + <Text className="editor-post-card-panel__description"> + { sprintf( + // translators: %s: Name of the plural post type e.g: "Posts". + __( 'Changes will be applied to all selected %s.' ), + labels?.name.toLowerCase() + ) } + </Text> + ) } + </VStack> ); } diff --git a/packages/editor/src/components/post-card-panel/style.scss b/packages/editor/src/components/post-card-panel/style.scss index d8a2a4628e6f9c..5fa54c67f14e55 100644 --- a/packages/editor/src/components/post-card-panel/style.scss +++ b/packages/editor/src/components/post-card-panel/style.scss @@ -7,8 +7,8 @@ width: 100%; &.editor-post-card-panel__title { + @include heading-medium(); margin: 0; - padding: 2px 0; display: flex; align-items: center; flex-wrap: wrap; @@ -32,16 +32,12 @@ &.has-description &__header { margin-bottom: $grid-unit-10; } -} -.editor-post-card-panel__title-badge { - background: $gray-100; - color: $gray-800; - padding: 0 $grid-unit-05; - border-radius: $radius-small; - font-size: 12px; - font-weight: 400; - flex-shrink: 0; - line-height: $grid-unit-05 * 5; - display: inline-block; + .editor-post-card-panel__title-name { + padding: 2px 0; + } + + .editor-post-card-panel__description { + color: $gray-700; + } } diff --git a/packages/editor/src/components/post-comments/index.js b/packages/editor/src/components/post-comments/index.js index 1d331811b46b26..6e69f9bce4f838 100644 --- a/packages/editor/src/components/post-comments/index.js +++ b/packages/editor/src/components/post-comments/index.js @@ -61,6 +61,6 @@ function PostComments() { /** * A form for managing comment status. * - * @return {JSX.Element} The rendered PostComments component. + * @return {React.ReactNode} The rendered PostComments component. */ export default PostComments; diff --git a/packages/editor/src/components/post-discussion/panel.js b/packages/editor/src/components/post-discussion/panel.js index c539791d404dec..280ab11b0447e7 100644 --- a/packages/editor/src/components/post-discussion/panel.js +++ b/packages/editor/src/components/post-discussion/panel.js @@ -93,7 +93,7 @@ function PostDiscussionToggle( { isOpen, onClick } ) { * checks whether the current post has support for the * above and if the `discussion-panel` panel is enabled. * - * @return {JSX.Element|null} The rendered PostDiscussionPanel component. + * @return {React.ReactNode} The rendered PostDiscussionPanel component. */ export default function PostDiscussionPanel() { const { isEnabled } = useSelect( ( select ) => { diff --git a/packages/editor/src/components/post-excerpt/check.js b/packages/editor/src/components/post-excerpt/check.js index 77436ecfed218a..c518d90e424a9b 100644 --- a/packages/editor/src/components/post-excerpt/check.js +++ b/packages/editor/src/components/post-excerpt/check.js @@ -6,10 +6,10 @@ import PostTypeSupportCheck from '../post-type-support-check'; /** * Component for checking if the post type supports the excerpt field. * - * @param {Object} props Props. - * @param {Element} props.children Children to be rendered. + * @param {Object} props Props. + * @param {React.ReactNode} props.children Children to be rendered. * - * @return {Component} The component to be rendered. + * @return {React.ReactNode} The rendered component. */ function PostExcerptCheck( { children } ) { return ( diff --git a/packages/editor/src/components/post-excerpt/index.js b/packages/editor/src/components/post-excerpt/index.js index 2555922f4e45ae..1a64bd53bab9bd 100644 --- a/packages/editor/src/components/post-excerpt/index.js +++ b/packages/editor/src/components/post-excerpt/index.js @@ -32,7 +32,7 @@ export default function PostExcerpt( { select( editorStore ); const postType = getCurrentPostType(); // This special case is unfortunate, but the REST API of wp_template and wp_template_part - // support the excerpt field throught the "description" field rather than "excerpt". + // support the excerpt field through the "description" field rather than "excerpt". const _usedAttribute = [ 'wp_template', 'wp_template_part', diff --git a/packages/editor/src/components/post-excerpt/panel.js b/packages/editor/src/components/post-excerpt/panel.js index 9c09796f467040..d4f2b27126c7c1 100644 --- a/packages/editor/src/components/post-excerpt/panel.js +++ b/packages/editor/src/components/post-excerpt/panel.js @@ -83,7 +83,7 @@ function ExcerptPanel() { /** * Is rendered if the post type supports excerpts and allows editing the excerpt. * - * @return {JSX.Element} The rendered PostExcerptPanel component. + * @return {React.ReactNode} The rendered PostExcerptPanel component. */ export default function PostExcerptPanel() { return ( diff --git a/packages/editor/src/components/post-excerpt/plugin.js b/packages/editor/src/components/post-excerpt/plugin.js index 64861162a0f637..50c494c01cb6d8 100644 --- a/packages/editor/src/components/post-excerpt/plugin.js +++ b/packages/editor/src/components/post-excerpt/plugin.js @@ -12,9 +12,9 @@ const { Fill, Slot } = createSlotFill( 'PluginPostExcerpt' ); /** * Renders a post excerpt panel in the post sidebar. * - * @param {Object} props Component properties. - * @param {string} [props.className] An optional class name added to the row. - * @param {Element} props.children Children to be rendered. + * @param {Object} props Component properties. + * @param {string} [props.className] An optional class name added to the row. + * @param {React.ReactNode} props.children Children to be rendered. * * @example * ```js @@ -46,7 +46,7 @@ const { Fill, Slot } = createSlotFill( 'PluginPostExcerpt' ); * ); * ``` * - * @return {Component} The component to be rendered. + * @return {React.ReactNode} The rendered component. */ const PluginPostExcerpt = ( { children, className } ) => { return ( diff --git a/packages/editor/src/components/post-featured-image/check.js b/packages/editor/src/components/post-featured-image/check.js index 823559f766bc35..700a3b8edfd032 100644 --- a/packages/editor/src/components/post-featured-image/check.js +++ b/packages/editor/src/components/post-featured-image/check.js @@ -8,10 +8,10 @@ import ThemeSupportCheck from '../theme-support-check'; * Wrapper component that renders its children only if the post type supports a featured image * and the theme supports post thumbnails. * - * @param {Object} props Props. - * @param {Element} props.children Children to be rendered. + * @param {Object} props Props. + * @param {React.ReactNode} props.children Children to be rendered. * - * @return {Component} The component to be rendered. + * @return {React.ReactNode} The rendered component. */ function PostFeaturedImageCheck( { children } ) { return ( diff --git a/packages/editor/src/components/post-featured-image/index.js b/packages/editor/src/components/post-featured-image/index.js index cabd791e938bf5..acf366506cc41e 100644 --- a/packages/editor/src/components/post-featured-image/index.js +++ b/packages/editor/src/components/post-featured-image/index.js @@ -1,3 +1,8 @@ +/** + * External dependencies + */ +import clsx from 'clsx'; + /** * WordPress dependencies */ @@ -10,6 +15,7 @@ import { withNotices, withFilters, __experimentalHStack as HStack, + Notice, } from '@wordpress/components'; import { isBlobURL } from '@wordpress/blob'; import { useState, useRef } from '@wordpress/element'; @@ -94,8 +100,9 @@ function PostFeaturedImage( { postType, noticeUI, noticeOperations, + isRequestingFeaturedImageMedia, } ) { - const toggleRef = useRef(); + const returnsFocusRef = useRef( false ); const [ isLoading, setIsLoading ] = useState( false ); const { getSettings } = useSelect( blockEditorStore ); const { mediaSourceUrl } = getMediaDetails( media, currentPostId ); @@ -150,6 +157,16 @@ function PostFeaturedImage( { ); } + function returnFocus( node ) { + if ( returnsFocusRef.current && node ) { + node.focus(); + returnsFocusRef.current = false; + } + } + + const isMissingMedia = + ! isRequestingFeaturedImageMedia && !! featuredImageId && ! media; + return ( <PostFeaturedImageCheck> { noticeUI } @@ -174,52 +191,83 @@ function PostFeaturedImage( { modalClass="editor-post-featured-image__media-modal" render={ ( { open } ) => ( <div className="editor-post-featured-image__container"> - <Button - __next40pxDefaultSize - ref={ toggleRef } - className={ - ! featuredImageId - ? 'editor-post-featured-image__toggle' - : 'editor-post-featured-image__preview' - } - onClick={ open } - aria-label={ - ! featuredImageId - ? null - : __( - 'Edit or replace the featured image' - ) - } - aria-describedby={ - ! featuredImageId - ? null - : `editor-post-featured-image-${ featuredImageId }-describedby` - } - aria-haspopup="dialog" - disabled={ isLoading } - accessibleWhenDisabled - > - { !! featuredImageId && media && ( - <img - className="editor-post-featured-image__preview-image" - src={ mediaSourceUrl } - alt={ getImageDescription( media ) } - /> - ) } - { isLoading && <Spinner /> } - { ! featuredImageId && - ! isLoading && - ( postType?.labels - ?.set_featured_image || - DEFAULT_SET_FEATURE_IMAGE_LABEL ) } - </Button> + { isMissingMedia ? ( + <Notice + status="warning" + isDismissible={ false } + > + { __( + 'Could not retrieve the featured image data.' + ) } + </Notice> + ) : ( + <Button + __next40pxDefaultSize + ref={ returnFocus } + className={ + ! featuredImageId + ? 'editor-post-featured-image__toggle' + : 'editor-post-featured-image__preview' + } + onClick={ open } + aria-label={ + ! featuredImageId + ? null + : __( + 'Edit or replace the featured image' + ) + } + aria-describedby={ + ! featuredImageId + ? null + : `editor-post-featured-image-${ featuredImageId }-describedby` + } + aria-haspopup="dialog" + disabled={ isLoading } + accessibleWhenDisabled + > + { !! featuredImageId && media && ( + <img + className="editor-post-featured-image__preview-image" + src={ mediaSourceUrl } + alt={ getImageDescription( + media + ) } + /> + ) } + { ( isLoading || + isRequestingFeaturedImageMedia ) && ( + <Spinner /> + ) } + { ! featuredImageId && + ! isLoading && + ( postType?.labels + ?.set_featured_image || + DEFAULT_SET_FEATURE_IMAGE_LABEL ) } + </Button> + ) } { !! featuredImageId && ( - <HStack className="editor-post-featured-image__actions"> + <HStack + className={ clsx( + 'editor-post-featured-image__actions', + { + 'editor-post-featured-image__actions-missing-image': + isMissingMedia, + 'editor-post-featured-image__actions-is-requesting-image': + isRequestingFeaturedImageMedia, + } + ) } + > <Button __next40pxDefaultSize className="editor-post-featured-image__action" onClick={ open } aria-haspopup="dialog" + variant={ + isMissingMedia + ? 'secondary' + : undefined + } > { __( 'Replace' ) } </Button> @@ -228,8 +276,17 @@ function PostFeaturedImage( { className="editor-post-featured-image__action" onClick={ () => { onRemoveImage(); - toggleRef.current.focus(); + // Signal that the toggle button should be focused, + // when it is rendered. Can't focus it directly here + // because it's rendered conditionally. + returnsFocusRef.current = true; } } + variant={ + isMissingMedia + ? 'secondary' + : undefined + } + isDestructive={ isMissingMedia } > { __( 'Remove' ) } </Button> @@ -247,7 +304,8 @@ function PostFeaturedImage( { } const applyWithSelect = withSelect( ( select ) => { - const { getMedia, getPostType } = select( coreStore ); + const { getMedia, getPostType, hasFinishedResolution } = + select( coreStore ); const { getCurrentPostId, getEditedPostAttribute } = select( editorStore ); const featuredImageId = getEditedPostAttribute( 'featured_media' ); @@ -258,6 +316,12 @@ const applyWithSelect = withSelect( ( select ) => { currentPostId: getCurrentPostId(), postType: getPostType( getEditedPostAttribute( 'type' ) ), featuredImageId, + isRequestingFeaturedImageMedia: + !! featuredImageId && + ! hasFinishedResolution( 'getMedia', [ + featuredImageId, + { context: 'view' }, + ] ), }; } ); diff --git a/packages/editor/src/components/post-featured-image/panel.js b/packages/editor/src/components/post-featured-image/panel.js index dd2a1527152ddf..8621b2eb886337 100644 --- a/packages/editor/src/components/post-featured-image/panel.js +++ b/packages/editor/src/components/post-featured-image/panel.js @@ -21,7 +21,7 @@ const PANEL_NAME = 'featured-image'; * @param {Object} props Props. * @param {boolean} props.withPanelBody Whether to include the panel body. Default true. * - * @return {Component|null} The component to be rendered. + * @return {React.ReactNode} The component to be rendered. * Return Null if the editor panel is disabled for featured image. */ export default function PostFeaturedImagePanel( { withPanelBody = true } ) { diff --git a/packages/editor/src/components/post-featured-image/style.scss b/packages/editor/src/components/post-featured-image/style.scss index 30d5cb43403cdf..bf9433faa662d4 100644 --- a/packages/editor/src/components/post-featured-image/style.scss +++ b/packages/editor/src/components/post-featured-image/style.scss @@ -16,11 +16,16 @@ &:hover, &:focus, &:focus-within { - .editor-post-featured-image__actions { + .editor-post-featured-image__actions:not(.editor-post-featured-image__actions-is-requesting-image) { opacity: 1; } } + .editor-post-featured-image__actions.editor-post-featured-image__actions-missing-image { + opacity: 1; + margin-top: $grid-unit-20; + } + .components-drop-zone__content { border-radius: $radius-small; } @@ -72,17 +77,22 @@ } .editor-post-featured-image__actions { - @include reduce-motion("transition"); - bottom: 0; - opacity: 0; // Use opacity instead of visibility so that the buttons remain in the tab order. - padding: $grid-unit-10; - position: absolute; - transition: opacity 50ms ease-out; -} + &:not(.editor-post-featured-image__actions-missing-image) { + @include reduce-motion("transition"); + bottom: 0; + opacity: 0; // Use opacity instead of visibility so that the buttons remain in the tab order. + padding: $grid-unit-10; + position: absolute; + transition: opacity 50ms ease-out; -.editor-post-featured-image__action { - backdrop-filter: blur(16px) saturate(180%); - background: rgba(255, 255, 255, 0.75); - flex-grow: 1; - justify-content: center; + .editor-post-featured-image__action { + backdrop-filter: blur(16px) saturate(180%); + background: rgba(255, 255, 255, 0.75); + } + } + + .editor-post-featured-image__action { + flex-grow: 1; + justify-content: center; + } } diff --git a/packages/editor/src/components/post-fields/index.ts b/packages/editor/src/components/post-fields/index.ts new file mode 100644 index 00000000000000..d701bdef2284e6 --- /dev/null +++ b/packages/editor/src/components/post-fields/index.ts @@ -0,0 +1,78 @@ +/** + * WordPress dependencies + */ +import { useEffect, useMemo } from '@wordpress/element'; +import { useEntityRecords } from '@wordpress/core-data'; +import { useDispatch, useSelect } from '@wordpress/data'; +import type { Field } from '@wordpress/dataviews'; +import type { BasePostWithEmbeddedAuthor } from '@wordpress/fields'; + +/** + * Internal dependencies + */ +import { unlock } from '../../lock-unlock'; +import { store as editorStore } from '../../store'; + +interface UsePostFieldsReturn { + isLoading: boolean; + fields: Field< BasePostWithEmbeddedAuthor >[]; +} + +interface Author { + id: number; + name: string; +} + +function usePostFields( { + postType, +}: { + postType: string; +} ): UsePostFieldsReturn { + const { registerPostTypeSchema } = unlock( useDispatch( editorStore ) ); + useEffect( () => { + registerPostTypeSchema( postType ); + }, [ registerPostTypeSchema, postType ] ); + + const { defaultFields } = useSelect( + ( select ) => { + const { getEntityFields } = unlock( select( editorStore ) ); + return { + defaultFields: getEntityFields( 'postType', postType ), + }; + }, + [ postType ] + ); + + const { records: authors, isResolving: isLoadingAuthors } = + useEntityRecords< Author >( 'root', 'user', { per_page: -1 } ); + + const fields = useMemo( + () => + defaultFields.map( + ( field: Field< BasePostWithEmbeddedAuthor > ) => { + if ( field.id === 'author' ) { + return { + ...field, + elements: authors?.map( ( { id, name } ) => ( { + value: id, + label: name, + } ) ), + }; + } + + return field; + } + ) as Field< BasePostWithEmbeddedAuthor >[], + [ authors, defaultFields ] + ); + + return { + isLoading: isLoadingAuthors, + fields, + }; +} + +/** + * Hook to get the fields for a post (BasePost or BasePostWithEmbeddedAuthor). + */ +export default usePostFields; diff --git a/packages/editor/src/components/post-format/check.js b/packages/editor/src/components/post-format/check.js index 35729770b93c40..fe791862e1cebb 100644 --- a/packages/editor/src/components/post-format/check.js +++ b/packages/editor/src/components/post-format/check.js @@ -9,7 +9,15 @@ import { useSelect } from '@wordpress/data'; import PostTypeSupportCheck from '../post-type-support-check'; import { store as editorStore } from '../../store'; -function PostFormatCheck( { children } ) { +/** + * Component check if there are any post formats. + * + * @param {Object} props The component props. + * @param {React.ReactNode} props.children The child elements to render. + * + * @return {React.ReactNode} The rendered component or null if post formats are disabled. + */ +export default function PostFormatCheck( { children } ) { const disablePostFormats = useSelect( ( select ) => select( editorStore ).getEditorSettings().disablePostFormats, @@ -26,13 +34,3 @@ function PostFormatCheck( { children } ) { </PostTypeSupportCheck> ); } - -/** - * Component check if there are any post formats. - * - * @param {Object} props The component props. - * @param {Element} props.children The child elements to render. - * - * @return {Component|null} The rendered component or null if post formats are disabled. - */ -export default PostFormatCheck; diff --git a/packages/editor/src/components/post-format/index.js b/packages/editor/src/components/post-format/index.js index 8f7423239600f2..d98720cd6fa93f 100644 --- a/packages/editor/src/components/post-format/index.js +++ b/packages/editor/src/components/post-format/index.js @@ -46,7 +46,7 @@ export const POST_FORMATS = [ * <PostFormat /> * ``` * - * @return {JSX.Element} The rendered PostFormat component. + * @return {React.ReactNode} The rendered PostFormat component. */ export default function PostFormat() { const instanceId = useInstanceId( PostFormat ); diff --git a/packages/editor/src/components/post-format/panel.js b/packages/editor/src/components/post-format/panel.js index faaf88b785a4b2..18704eda6fc448 100644 --- a/packages/editor/src/components/post-format/panel.js +++ b/packages/editor/src/components/post-format/panel.js @@ -18,7 +18,7 @@ import { store as editorStore } from '../../store'; /** * Renders the Post Author Panel component. * - * @return {Component} The component to be rendered. + * @return {React.ReactNode} The rendered component. */ function PostFormat() { const { postFormat } = useSelect( ( select ) => { diff --git a/packages/editor/src/components/post-last-revision/check.js b/packages/editor/src/components/post-last-revision/check.js index c570f5e42cdc32..cb3c655e1b7cc3 100644 --- a/packages/editor/src/components/post-last-revision/check.js +++ b/packages/editor/src/components/post-last-revision/check.js @@ -12,10 +12,10 @@ import { store as editorStore } from '../../store'; /** * Wrapper component that renders its children if the post has more than one revision. * - * @param {Object} props Props. - * @param {Element} props.children Children to be rendered. + * @param {Object} props Props. + * @param {React.ReactNode} props.children Children to be rendered. * - * @return {Component|null} Rendered child components if post has more than one revision, otherwise null. + * @return {React.ReactNode} Rendered child components if post has more than one revision, otherwise null. */ function PostLastRevisionCheck( { children } ) { const { lastRevisionId, revisionsCount } = useSelect( ( select ) => { diff --git a/packages/editor/src/components/post-last-revision/index.js b/packages/editor/src/components/post-last-revision/index.js index fd68f9703cb4e2..ac25e6cb8f30d6 100644 --- a/packages/editor/src/components/post-last-revision/index.js +++ b/packages/editor/src/components/post-last-revision/index.js @@ -28,7 +28,7 @@ function usePostLastRevisionInfo() { /** * Renders the component for displaying the last revision of a post. * - * @return {Component} The component to be rendered. + * @return {React.ReactNode} The rendered component. */ function PostLastRevision() { const { lastRevisionId, revisionsCount } = usePostLastRevisionInfo(); diff --git a/packages/editor/src/components/post-last-revision/panel.js b/packages/editor/src/components/post-last-revision/panel.js index e87475cc2b34e9..55a3ba571c4b0a 100644 --- a/packages/editor/src/components/post-last-revision/panel.js +++ b/packages/editor/src/components/post-last-revision/panel.js @@ -12,7 +12,7 @@ import PostLastRevisionCheck from './check'; /** * Renders the panel for displaying the last revision of a post. * - * @return {Component} The component to be rendered. + * @return {React.ReactNode} The rendered component. */ function PostLastRevisionPanel() { return ( diff --git a/packages/editor/src/components/post-locked-modal/index.js b/packages/editor/src/components/post-locked-modal/index.js index 7bfa2d23fd9808..16cff5af976841 100644 --- a/packages/editor/src/components/post-locked-modal/index.js +++ b/packages/editor/src/components/post-locked-modal/index.js @@ -24,7 +24,7 @@ import { store as editorStore } from '../../store'; * A modal component that is displayed when a post is locked for editing by another user. * The modal provides information about the lock status and options to take over or exit the editor. * - * @return {JSX.Element|null} The rendered PostLockedModal component. + * @return {React.ReactNode} The rendered PostLockedModal component. */ export default function PostLockedModal() { const instanceId = useInstanceId( PostLockedModal ); diff --git a/packages/editor/src/components/post-pending-status/check.js b/packages/editor/src/components/post-pending-status/check.js index 7a4ff5195041c6..9f407d8c8cd82a 100644 --- a/packages/editor/src/components/post-pending-status/check.js +++ b/packages/editor/src/components/post-pending-status/check.js @@ -13,10 +13,10 @@ import { store as editorStore } from '../../store'; * If the post is already published or the user doesn't have the * capability to publish, it returns null. * - * @param {Object} props Component properties. - * @param {Element} props.children Children to be rendered. + * @param {Object} props Component properties. + * @param {React.ReactElement} props.children Children to be rendered. * - * @return {JSX.Element|null} The rendered child elements or null if the post is already published or the user doesn't have the capability to publish. + * @return {React.ReactElement} The rendered child elements or null if the post is already published or the user doesn't have the capability to publish. */ export function PostPendingStatusCheck( { children } ) { const { hasPublishAction, isPublished } = useSelect( ( select ) => { diff --git a/packages/editor/src/components/post-pending-status/index.js b/packages/editor/src/components/post-pending-status/index.js index 8363ebc715891b..352570c44a6630 100644 --- a/packages/editor/src/components/post-pending-status/index.js +++ b/packages/editor/src/components/post-pending-status/index.js @@ -14,7 +14,7 @@ import { store as editorStore } from '../../store'; /** * A component for displaying and toggling the pending status of a post. * - * @return {JSX.Element} The rendered component. + * @return {React.ReactNode} The rendered component. */ export function PostPendingStatus() { const status = useSelect( diff --git a/packages/editor/src/components/post-preview-button/index.js b/packages/editor/src/components/post-preview-button/index.js index d57143cd355d80..0429ce691b4f17 100644 --- a/packages/editor/src/components/post-preview-button/index.js +++ b/packages/editor/src/components/post-preview-button/index.js @@ -112,7 +112,7 @@ function writeInterstitialMessage( targetDocument ) { * @param {string} props.role The role attribute for the button. * @param {Function} props.onPreview The callback function for preview event. * - * @return {JSX.Element|null} The rendered button component. + * @return {React.ReactNode} The rendered button component. */ export default function PostPreviewButton( { className, @@ -129,13 +129,17 @@ export default function PostPreviewButton( { const postType = core.getPostType( editor.getCurrentPostType( 'type' ) ); + const canView = postType?.viewable ?? false; + if ( ! canView ) { + return { isViewable: canView }; + } return { postId: editor.getCurrentPostId(), currentPostLink: editor.getCurrentPostAttribute( 'link' ), previewLink: editor.getEditedPostPreviewLink(), isSaveable: editor.isEditedPostSaveable(), - isViewable: postType?.viewable ?? false, + isViewable: canView, }; }, [] ); diff --git a/packages/editor/src/components/post-publish-button/index.js b/packages/editor/src/components/post-publish-button/index.js index 71e18a4d6a9c82..db8e0588848515 100644 --- a/packages/editor/src/components/post-publish-button/index.js +++ b/packages/editor/src/components/post-publish-button/index.js @@ -114,10 +114,10 @@ export class PostPublishButton extends Component { ( ! isPublishable && ! forceIsDirty ) ) && ( ! hasNonPostEntityChanges || isSavingNonPostEntityChanges ); - // If the new status has not changed explicitely, we derive it from + // If the new status has not changed explicitly, we derive it from // other factors, like having a publish action, etc.. We need to preserve // this because it affects when to show the pre and post publish panels. - // If it has changed though explicitely, we need to respect that. + // If it has changed though explicitly, we need to respect that. let publishStatus = 'publish'; if ( postStatusHasChanged ) { publishStatus = postStatus; @@ -151,6 +151,7 @@ export class PostPublishButton extends Component { isBusy: ! isAutoSaving && isSaving, variant: 'primary', onClick: this.createOnClick( onClickButton ), + 'aria-haspopup': hasNonPostEntityChanges ? 'dialog' : undefined, }; const toggleProps = { @@ -161,6 +162,7 @@ export class PostPublishButton extends Component { variant: 'primary', size: 'compact', onClick: this.createOnClick( onClickToggle ), + 'aria-haspopup': hasNonPostEntityChanges ? 'dialog' : undefined, }; const componentProps = isToggle ? toggleProps : buttonProps; return ( diff --git a/packages/editor/src/components/post-publish-button/post-publish-button-or-toggle.js b/packages/editor/src/components/post-publish-button/post-publish-button-or-toggle.js index bf742bef1429bb..5c0ff90df5736d 100644 --- a/packages/editor/src/components/post-publish-button/post-publish-button-or-toggle.js +++ b/packages/editor/src/components/post-publish-button/post-publish-button-or-toggle.js @@ -1,8 +1,8 @@ /** * WordPress dependencies */ -import { useViewportMatch, compose } from '@wordpress/compose'; -import { withDispatch, withSelect } from '@wordpress/data'; +import { useViewportMatch } from '@wordpress/compose'; +import { useDispatch, useSelect } from '@wordpress/data'; /** * Internal dependencies @@ -10,24 +10,46 @@ import { withDispatch, withSelect } from '@wordpress/data'; import PostPublishButton from './index'; import { store as editorStore } from '../../store'; -export function PostPublishButtonOrToggle( { +const IS_TOGGLE = 'toggle'; +const IS_BUTTON = 'button'; + +export default function PostPublishButtonOrToggle( { forceIsDirty, - hasPublishAction, - isBeingScheduled, - isPending, - isPublished, - isPublishSidebarEnabled, - isPublishSidebarOpened, - isScheduled, - togglePublishSidebar, setEntitiesSavedStatesCallback, - postStatusHasChanged, - postStatus, } ) { - const IS_TOGGLE = 'toggle'; - const IS_BUTTON = 'button'; - const isSmallerThanMediumViewport = useViewportMatch( 'medium', '<' ); let component; + const isSmallerThanMediumViewport = useViewportMatch( 'medium', '<' ); + const { togglePublishSidebar } = useDispatch( editorStore ); + const { + hasPublishAction, + isBeingScheduled, + isPending, + isPublished, + isPublishSidebarEnabled, + isPublishSidebarOpened, + isScheduled, + postStatus, + postStatusHasChanged, + } = useSelect( ( select ) => { + return { + hasPublishAction: + !! select( editorStore ).getCurrentPost()?._links?.[ + 'wp:action-publish' + ] ?? false, + isBeingScheduled: + select( editorStore ).isEditedPostBeingScheduled(), + isPending: select( editorStore ).isCurrentPostPending(), + isPublished: select( editorStore ).isCurrentPostPublished(), + isPublishSidebarEnabled: + select( editorStore ).isPublishSidebarEnabled(), + isPublishSidebarOpened: + select( editorStore ).isPublishSidebarOpened(), + isScheduled: select( editorStore ).isCurrentPostScheduled(), + postStatus: + select( editorStore ).getEditedPostAttribute( 'status' ), + postStatusHasChanged: select( editorStore ).getPostEdits()?.status, + }; + }, [] ); /** * Conditions to show a BUTTON (publish directly) or a TOGGLE (open publish sidebar): @@ -36,7 +58,7 @@ export function PostPublishButtonOrToggle( { * for a particular role (see https://wordpress.org/documentation/article/post-status/): * * - is published - * - post status has changed explicitely to something different than 'future' or 'publish' + * - post status has changed explicitly to something different than 'future' or 'publish' * - is scheduled to be published * - is pending and can't be published (but only for viewports >= medium). * Originally, we considered showing a button for pending posts that couldn't be published @@ -76,27 +98,3 @@ export function PostPublishButtonOrToggle( { /> ); } - -export default compose( - withSelect( ( select ) => ( { - hasPublishAction: - select( editorStore ).getCurrentPost()?._links?.[ - 'wp:action-publish' - ] ?? false, - isBeingScheduled: select( editorStore ).isEditedPostBeingScheduled(), - isPending: select( editorStore ).isCurrentPostPending(), - isPublished: select( editorStore ).isCurrentPostPublished(), - isPublishSidebarEnabled: - select( editorStore ).isPublishSidebarEnabled(), - isPublishSidebarOpened: select( editorStore ).isPublishSidebarOpened(), - isScheduled: select( editorStore ).isCurrentPostScheduled(), - postStatus: select( editorStore ).getEditedPostAttribute( 'status' ), - postStatusHasChanged: select( editorStore ).getPostEdits()?.status, - } ) ), - withDispatch( ( dispatch ) => { - const { togglePublishSidebar } = dispatch( editorStore ); - return { - togglePublishSidebar, - }; - } ) -)( PostPublishButtonOrToggle ); diff --git a/packages/editor/src/components/post-publish-button/test/post-publish-button-or-toggle.js b/packages/editor/src/components/post-publish-button/test/post-publish-button-or-toggle.js index 0794c3c8995a1f..a8fa8b72db9c7b 100644 --- a/packages/editor/src/components/post-publish-button/test/post-publish-button-or-toggle.js +++ b/packages/editor/src/components/post-publish-button/test/post-publish-button-or-toggle.js @@ -7,13 +7,15 @@ import { render, screen } from '@testing-library/react'; * WordPress dependencies */ import { useViewportMatch } from '@wordpress/compose'; +import { useSelect } from '@wordpress/data'; /** * Internal dependencies */ -import { PostPublishButtonOrToggle } from '../post-publish-button-or-toggle'; +import PostPublishButtonOrToggle from '../post-publish-button-or-toggle'; jest.mock( '@wordpress/compose/src/hooks/use-viewport-match' ); +jest.mock( '@wordpress/data/src/components/use-select', () => jest.fn() ); describe( 'PostPublishButtonOrToggle should render a', () => { afterEach( () => { @@ -21,23 +23,32 @@ describe( 'PostPublishButtonOrToggle should render a', () => { } ); it( 'button when the post is published (1)', () => { - render( <PostPublishButtonOrToggle isPublished /> ); + useSelect.mockImplementation( () => ( { + isPublished: true, + } ) ); + render( <PostPublishButtonOrToggle /> ); expect( screen.getByRole( 'button', { name: 'Submit for Review' } ) ).toBeVisible(); } ); it( 'button when the post is scheduled (2)', () => { - render( <PostPublishButtonOrToggle isScheduled isBeingScheduled /> ); + useSelect.mockImplementation( () => ( { + isScheduled: true, + isBeingScheduled: true, + } ) ); + render( <PostPublishButtonOrToggle /> ); expect( screen.getByRole( 'button', { name: 'Submit for Review' } ) ).toBeVisible(); } ); it( 'button when the post is pending and cannot be published but the viewport is >= medium (3)', () => { - render( - <PostPublishButtonOrToggle isPending hasPublishAction={ false } /> - ); + useSelect.mockImplementation( () => ( { + isPending: true, + hasPublishAction: false, + } ) ); + render( <PostPublishButtonOrToggle /> ); expect( screen.getByRole( 'button', { name: 'Submit for Review' } ) @@ -46,6 +57,9 @@ describe( 'PostPublishButtonOrToggle should render a', () => { it( 'toggle when post is not (1), (2), (3), the viewport is <= medium, and the publish sidebar is enabled', () => { useViewportMatch.mockReturnValue( true ); + useSelect.mockImplementation( () => ( { + isPublishSidebarEnabled: true, + } ) ); render( <PostPublishButtonOrToggle isPublishSidebarEnabled /> ); expect( screen.getByRole( 'button', { name: 'Publish' } ) @@ -53,9 +67,10 @@ describe( 'PostPublishButtonOrToggle should render a', () => { } ); it( 'button when post is not (1), (2), (3), the viewport is >= medium, and the publish sidebar is disabled', () => { - render( - <PostPublishButtonOrToggle isPublishSidebarEnabled={ false } /> - ); + useSelect.mockImplementation( () => ( { + isPublishSidebarEnabled: false, + } ) ); + render( <PostPublishButtonOrToggle /> ); expect( screen.getByRole( 'button', { name: 'Submit for Review' } ) ).toBeVisible(); diff --git a/packages/editor/src/components/post-publish-panel/index.js b/packages/editor/src/components/post-publish-panel/index.js index 6a2d393e20c4ba..1fd36f9f69d7a2 100644 --- a/packages/editor/src/components/post-publish-panel/index.js +++ b/packages/editor/src/components/post-publish-panel/index.js @@ -47,9 +47,10 @@ export class PostPublishPanel extends Component { // Automatically collapse the publish sidebar when a post // is published and the user makes an edit. if ( - prevProps.isPublished && - ! this.props.isSaving && - this.props.isDirty + ( prevProps.isPublished && + ! this.props.isSaving && + this.props.isDirty ) || + this.props.currentPostId !== prevProps.currentPostId ) { this.props.onClose(); } @@ -75,6 +76,7 @@ export class PostPublishPanel extends Component { onTogglePublishSidebar, PostPublishExtension, PrePublishExtension, + currentPostId, ...additionalProps } = this.props; const { @@ -154,6 +156,7 @@ export default compose( [ const { getPostType } = select( coreStore ); const { getCurrentPost, + getCurrentPostId, getEditedPostAttribute, isCurrentPostPublished, isCurrentPostScheduled, @@ -177,6 +180,7 @@ export default compose( [ isSaving: isSavingPost() && ! isAutosavingPost(), isSavingNonPostEntityChanges: isSavingNonPostEntityChanges(), isScheduled: isCurrentPostScheduled(), + currentPostId: getCurrentPostId(), }; } ), withDispatch( ( dispatch, { isPublishSidebarEnabled } ) => { diff --git a/packages/editor/src/components/post-publish-panel/maybe-upload-media.js b/packages/editor/src/components/post-publish-panel/maybe-upload-media.js index 32ea69c425e0b5..a92a4794154344 100644 --- a/packages/editor/src/components/post-publish-panel/maybe-upload-media.js +++ b/packages/editor/src/components/post-publish-panel/maybe-upload-media.js @@ -9,7 +9,7 @@ import { __unstableAnimatePresence as AnimatePresence, } from '@wordpress/components'; import { useSelect, useDispatch } from '@wordpress/data'; -import { __ } from '@wordpress/i18n'; +import { __, _x } from '@wordpress/i18n'; import { store as blockEditorStore } from '@wordpress/block-editor'; import { useState } from '@wordpress/element'; import { isBlobURL } from '@wordpress/blob'; @@ -260,7 +260,7 @@ export default function MaybeUploadMediaPanel() { variant="primary" onClick={ uploadImages } > - { __( 'Upload' ) } + { _x( 'Upload', 'verb' ) } </Button> ) } </div> diff --git a/packages/editor/src/components/post-publish-panel/postpublish.js b/packages/editor/src/components/post-publish-panel/postpublish.js index 29ee1871057ea1..98afb4d1d573e7 100644 --- a/packages/editor/src/components/post-publish-panel/postpublish.js +++ b/packages/editor/src/components/post-publish-panel/postpublish.js @@ -3,8 +3,8 @@ */ import { PanelBody, Button, TextControl } from '@wordpress/components'; import { __, sprintf } from '@wordpress/i18n'; -import { Component, createRef } from '@wordpress/element'; -import { withSelect } from '@wordpress/data'; +import { useCallback, useEffect, useState, useRef } from '@wordpress/element'; +import { useSelect } from '@wordpress/data'; import { addQueryArgs, safeDecodeURIComponent } from '@wordpress/url'; import { decodeEntities } from '@wordpress/html-entities'; import { useCopyToClipboard } from '@wordpress/compose'; @@ -41,142 +41,132 @@ const getFuturePostUrl = ( post ) => { return post.permalink_template; }; -function CopyButton( { text, onCopy, children } ) { - const ref = useCopyToClipboard( text, onCopy ); +function CopyButton( { text } ) { + const [ showCopyConfirmation, setShowCopyConfirmation ] = useState( false ); + const timeoutIdRef = useRef(); + const ref = useCopyToClipboard( text, () => { + setShowCopyConfirmation( true ); + if ( timeoutIdRef.current ) { + clearTimeout( timeoutIdRef.current ); + } + timeoutIdRef.current = setTimeout( () => { + setShowCopyConfirmation( false ); + }, 4000 ); + } ); + + useEffect( () => { + return () => { + if ( timeoutIdRef.current ) { + clearTimeout( timeoutIdRef.current ); + } + }; + }, [] ); + return ( <Button __next40pxDefaultSize variant="secondary" ref={ ref }> - { children } + { showCopyConfirmation ? __( 'Copied!' ) : __( 'Copy' ) } </Button> ); } -class PostPublishPanelPostpublish extends Component { - constructor() { - super( ...arguments ); - this.state = { - showCopyConfirmation: false, +export default function PostPublishPanelPostpublish( { + focusOnMount, + children, +} ) { + const { post, postType, isScheduled } = useSelect( ( select ) => { + const { + getEditedPostAttribute, + getCurrentPost, + isCurrentPostScheduled, + } = select( editorStore ); + const { getPostType } = select( coreStore ); + + return { + post: getCurrentPost(), + postType: getPostType( getEditedPostAttribute( 'type' ) ), + isScheduled: isCurrentPostScheduled(), }; - this.onCopy = this.onCopy.bind( this ); - this.onSelectInput = this.onSelectInput.bind( this ); - this.postLink = createRef(); - } - - componentDidMount() { - if ( this.props.focusOnMount ) { - this.postLink.current.focus(); - } - } - - componentWillUnmount() { - clearTimeout( this.dismissCopyConfirmation ); - } - - onCopy() { - this.setState( { - showCopyConfirmation: true, - } ); + }, [] ); + + const postLabel = postType?.labels?.singular_name; + const viewPostLabel = postType?.labels?.view_item; + const addNewPostLabel = postType?.labels?.add_new_item; + const link = + post.status === 'future' ? getFuturePostUrl( post ) : post.link; + const addLink = addQueryArgs( 'post-new.php', { + post_type: post.type, + } ); + + const postLinkRef = useCallback( + ( node ) => { + if ( focusOnMount && node ) { + node.focus(); + } + }, + [ focusOnMount ] + ); - clearTimeout( this.dismissCopyConfirmation ); - this.dismissCopyConfirmation = setTimeout( () => { - this.setState( { - showCopyConfirmation: false, - } ); - }, 4000 ); - } + const postPublishNonLinkHeader = isScheduled ? ( + <> + { __( 'is now scheduled. It will go live on' ) }{ ' ' } + <PostScheduleLabel />. + </> + ) : ( + __( 'is now live.' ) + ); - onSelectInput( event ) { - event.target.select(); - } + return ( + <div className="post-publish-panel__postpublish"> + <PanelBody className="post-publish-panel__postpublish-header"> + <a ref={ postLinkRef } href={ link }> + { decodeEntities( post.title ) || __( '(no title)' ) } + </a>{ ' ' } + { postPublishNonLinkHeader } + </PanelBody> + <PanelBody> + <p className="post-publish-panel__postpublish-subheader"> + <strong>{ __( 'What’s next?' ) }</strong> + </p> + <div className="post-publish-panel__postpublish-post-address-container"> + <TextControl + __next40pxDefaultSize + __nextHasNoMarginBottom + className="post-publish-panel__postpublish-post-address" + readOnly + label={ sprintf( + /* translators: %s: post type singular name */ + __( '%s address' ), + postLabel + ) } + value={ safeDecodeURIComponent( link ) } + onFocus={ ( event ) => event.target.select() } + /> - render() { - const { children, isScheduled, post, postType } = this.props; - const postLabel = postType?.labels?.singular_name; - const viewPostLabel = postType?.labels?.view_item; - const addNewPostLabel = postType?.labels?.add_new_item; - const link = - post.status === 'future' ? getFuturePostUrl( post ) : post.link; - const addLink = addQueryArgs( 'post-new.php', { - post_type: post.type, - } ); - - const postPublishNonLinkHeader = isScheduled ? ( - <> - { __( 'is now scheduled. It will go live on' ) }{ ' ' } - <PostScheduleLabel />. - </> - ) : ( - __( 'is now live.' ) - ); - - return ( - <div className="post-publish-panel__postpublish"> - <PanelBody className="post-publish-panel__postpublish-header"> - <a ref={ this.postLink } href={ link }> - { decodeEntities( post.title ) || __( '(no title)' ) } - </a>{ ' ' } - { postPublishNonLinkHeader } - </PanelBody> - <PanelBody> - <p className="post-publish-panel__postpublish-subheader"> - <strong>{ __( 'What’s next?' ) }</strong> - </p> - <div className="post-publish-panel__postpublish-post-address-container"> - <TextControl - __next40pxDefaultSize - __nextHasNoMarginBottom - className="post-publish-panel__postpublish-post-address" - readOnly - label={ sprintf( - /* translators: %s: post type singular name */ - __( '%s address' ), - postLabel - ) } - value={ safeDecodeURIComponent( link ) } - onFocus={ this.onSelectInput } - /> - - <div className="post-publish-panel__postpublish-post-address__copy-button-wrap"> - <CopyButton text={ link } onCopy={ this.onCopy }> - { this.state.showCopyConfirmation - ? __( 'Copied!' ) - : __( 'Copy' ) } - </CopyButton> - </div> + <div className="post-publish-panel__postpublish-post-address__copy-button-wrap"> + <CopyButton text={ link } /> </div> + </div> - <div className="post-publish-panel__postpublish-buttons"> - { ! isScheduled && ( - <Button - variant="primary" - href={ link } - __next40pxDefaultSize - > - { viewPostLabel } - </Button> - ) } + <div className="post-publish-panel__postpublish-buttons"> + { ! isScheduled && ( <Button - variant={ isScheduled ? 'primary' : 'secondary' } + variant="primary" + href={ link } __next40pxDefaultSize - href={ addLink } > - { addNewPostLabel } + { viewPostLabel } </Button> - </div> - </PanelBody> - { children } - </div> - ); - } + ) } + <Button + variant={ isScheduled ? 'primary' : 'secondary' } + __next40pxDefaultSize + href={ addLink } + > + { addNewPostLabel } + </Button> + </div> + </PanelBody> + { children } + </div> + ); } - -export default withSelect( ( select ) => { - const { getEditedPostAttribute, getCurrentPost, isCurrentPostScheduled } = - select( editorStore ); - const { getPostType } = select( coreStore ); - - return { - post: getCurrentPost(), - postType: getPostType( getEditedPostAttribute( 'type' ) ), - isScheduled: isCurrentPostScheduled(), - }; -} )( PostPublishPanelPostpublish ); diff --git a/packages/editor/src/components/post-publish-panel/prepublish.js b/packages/editor/src/components/post-publish-panel/prepublish.js index 193960b9cc8345..e8d5c69b769302 100644 --- a/packages/editor/src/components/post-publish-panel/prepublish.js +++ b/packages/editor/src/components/post-publish-panel/prepublish.js @@ -75,7 +75,7 @@ function PostPublishPanelPrepublish( { children } ) { if ( ! hasPublishAction ) { prePublishTitle = __( 'Are you ready to submit for review?' ); prePublishBodyText = __( - 'When you’re ready, submit your work for review, and an Editor will be able to approve it for you.' + 'Your work will be reviewed and then approved.' ); } else if ( isBeingScheduled ) { prePublishTitle = __( 'Are you ready to schedule?' ); diff --git a/packages/editor/src/components/post-publish-panel/test/__snapshots__/index.js.snap b/packages/editor/src/components/post-publish-panel/test/__snapshots__/index.js.snap index b074159ac423d4..9fb3d24cd2931f 100644 --- a/packages/editor/src/components/post-publish-panel/test/__snapshots__/index.js.snap +++ b/packages/editor/src/components/post-publish-panel/test/__snapshots__/index.js.snap @@ -466,7 +466,7 @@ exports[`PostPublishPanel should render the pre-publish panel if post status is </strong> </div> <p> - When you’re ready, submit your work for review, and an Editor will be able to approve it for you. + Your work will be reviewed and then approved. </p> <div class="components-site-card" @@ -619,7 +619,7 @@ exports[`PostPublishPanel should render the pre-publish panel if the post is not </strong> </div> <p> - When you’re ready, submit your work for review, and an Editor will be able to approve it for you. + Your work will be reviewed and then approved. </p> <div class="components-site-card" diff --git a/packages/editor/src/components/post-schedule/check.js b/packages/editor/src/components/post-schedule/check.js index 28456b90371cc3..32526a977f94fa 100644 --- a/packages/editor/src/components/post-schedule/check.js +++ b/packages/editor/src/components/post-schedule/check.js @@ -11,10 +11,10 @@ import { store as editorStore } from '../../store'; /** * Wrapper component that renders its children only if post has a publish action. * - * @param {Object} props Props. - * @param {Element} props.children Children to be rendered. + * @param {Object} props Props. + * @param {React.ReactElement} props.children Children to be rendered. * - * @return {Component} - The component to be rendered or null if there is no publish action. + * @return {React.ReactElement} - The component to be rendered or null if there is no publish action. */ export default function PostScheduleCheck( { children } ) { const hasPublishAction = useSelect( ( select ) => { diff --git a/packages/editor/src/components/post-schedule/index.js b/packages/editor/src/components/post-schedule/index.js index 94387ed4267fa9..e324e40896d379 100644 --- a/packages/editor/src/components/post-schedule/index.js +++ b/packages/editor/src/components/post-schedule/index.js @@ -27,7 +27,7 @@ const { PrivatePublishDateTimePicker } = unlock( blockEditorPrivateApis ); * @param {Object} props Props. * @param {Function} props.onClose Function to close the component. * - * @return {Component} The component to be rendered. + * @return {React.ReactNode} The rendered component. */ export default function PostSchedule( props ) { return ( @@ -59,7 +59,7 @@ export function PrivatePostSchedule( { startOfMonth( new Date( postDate ) ) ); - // Pick up published and schduled site posts. + // Pick up published and scheduled site posts. const eventsByPostType = useSelect( ( select ) => select( coreStore ).getEntityRecords( 'postType', postType, { diff --git a/packages/editor/src/components/post-schedule/label.js b/packages/editor/src/components/post-schedule/label.js index f6cf3811db7916..2b8819747e7e0c 100644 --- a/packages/editor/src/components/post-schedule/label.js +++ b/packages/editor/src/components/post-schedule/label.js @@ -15,7 +15,7 @@ import { store as editorStore } from '../../store'; * * @param {Object} props Props. * - * @return {Component} The component to be rendered. + * @return {React.ReactNode} The rendered component. */ export default function PostScheduleLabel( props ) { return usePostScheduleLabel( props ); diff --git a/packages/editor/src/components/post-schedule/panel.js b/packages/editor/src/components/post-schedule/panel.js index 5d63da5e30c910..fd453a4667417f 100644 --- a/packages/editor/src/components/post-schedule/panel.js +++ b/packages/editor/src/components/post-schedule/panel.js @@ -31,7 +31,7 @@ const DESIGN_POST_TYPES = [ /** * Renders the Post Schedule Panel component. * - * @return {Component} The component to be rendered. + * @return {React.ReactNode} The rendered component. */ export default function PostSchedulePanel() { const [ popoverAnchor, setPopoverAnchor ] = useState( null ); diff --git a/packages/editor/src/components/post-slug/check.js b/packages/editor/src/components/post-slug/check.js deleted file mode 100644 index 86bf04814c934d..00000000000000 --- a/packages/editor/src/components/post-slug/check.js +++ /dev/null @@ -1,20 +0,0 @@ -/** - * Internal dependencies - */ -import PostTypeSupportCheck from '../post-type-support-check'; - -/** - * Wrapper component that renders its children only if the post type supports the slug. - * - * @param {Object} props Props. - * @param {Element} props.children Children to be rendered. - * - * @return {Component} The component to be rendered. - */ -export default function PostSlugCheck( { children } ) { - return ( - <PostTypeSupportCheck supportKeys="slug"> - { children } - </PostTypeSupportCheck> - ); -} diff --git a/packages/editor/src/components/post-slug/index.js b/packages/editor/src/components/post-slug/index.js deleted file mode 100644 index 1a4f8e93d7565c..00000000000000 --- a/packages/editor/src/components/post-slug/index.js +++ /dev/null @@ -1,73 +0,0 @@ -/** - * WordPress dependencies - */ -import { useDispatch, useSelect } from '@wordpress/data'; -import { useState } from '@wordpress/element'; -import { __ } from '@wordpress/i18n'; -import { safeDecodeURIComponent, cleanForSlug } from '@wordpress/url'; -import { TextControl } from '@wordpress/components'; - -/** - * Internal dependencies - */ -import PostSlugCheck from './check'; -import { store as editorStore } from '../../store'; - -function PostSlugControl() { - const postSlug = useSelect( ( select ) => { - return safeDecodeURIComponent( - select( editorStore ).getEditedPostSlug() - ); - }, [] ); - const { editPost } = useDispatch( editorStore ); - const [ forceEmptyField, setForceEmptyField ] = useState( false ); - - return ( - <TextControl - __next40pxDefaultSize - __nextHasNoMarginBottom - label={ __( 'Slug' ) } - autoComplete="off" - spellCheck="false" - value={ forceEmptyField ? '' : postSlug } - onChange={ ( newValue ) => { - editPost( { slug: newValue } ); - // When we delete the field the permalink gets - // reverted to the original value. - // The forceEmptyField logic allows the user to have - // the field temporarily empty while typing. - if ( ! newValue ) { - if ( ! forceEmptyField ) { - setForceEmptyField( true ); - } - return; - } - if ( forceEmptyField ) { - setForceEmptyField( false ); - } - } } - onBlur={ ( event ) => { - editPost( { - slug: cleanForSlug( event.target.value ), - } ); - if ( forceEmptyField ) { - setForceEmptyField( false ); - } - } } - className="editor-post-slug" - /> - ); -} - -/** - * Renders the PostSlug component. It provide a control for editing the post slug. - * - * @return {Component} The component to be rendered. - */ -export default function PostSlug() { - return ( - <PostSlugCheck> - <PostSlugControl /> - </PostSlugCheck> - ); -} diff --git a/packages/editor/src/components/post-slug/panel.js b/packages/editor/src/components/post-slug/panel.js deleted file mode 100644 index 6ab97a28b251c3..00000000000000 --- a/packages/editor/src/components/post-slug/panel.js +++ /dev/null @@ -1,22 +0,0 @@ -/** - * WordPress dependencies - */ -import { PanelRow } from '@wordpress/components'; - -/** - * Internal dependencies - */ -import PostSlugForm from './'; -import PostSlugCheck from './check'; - -export function PostSlug() { - return ( - <PostSlugCheck> - <PanelRow className="editor-post-slug"> - <PostSlugForm /> - </PanelRow> - </PostSlugCheck> - ); -} - -export default PostSlug; diff --git a/packages/editor/src/components/post-slug/style.scss b/packages/editor/src/components/post-slug/style.scss deleted file mode 100644 index 551450582128e0..00000000000000 --- a/packages/editor/src/components/post-slug/style.scss +++ /dev/null @@ -1,5 +0,0 @@ -.editor-post-slug { - display: flex; - flex-direction: column; - align-items: stretch; -} diff --git a/packages/editor/src/components/post-slug/test/index.js b/packages/editor/src/components/post-slug/test/index.js deleted file mode 100644 index fb40055111b77a..00000000000000 --- a/packages/editor/src/components/post-slug/test/index.js +++ /dev/null @@ -1,53 +0,0 @@ -/** - * External dependencies - */ -import { act, render, screen } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; - -/** - * WordPress dependencies - */ -import { useSelect, useDispatch } from '@wordpress/data'; - -/** - * Internal dependencies - */ -import PostSlug from '../'; - -jest.mock( '@wordpress/data/src/components/use-select', () => jest.fn() ); -jest.mock( '@wordpress/data/src/components/use-dispatch/use-dispatch', () => - jest.fn() -); - -describe( 'PostSlug', () => { - it( 'should update slug with sanitized input', async () => { - const user = userEvent.setup(); - const editPost = jest.fn(); - - useSelect.mockImplementation( ( mapSelect ) => - mapSelect( () => ( { - getPostType: () => ( { - supports: { - slug: true, - }, - } ), - getEditedPostAttribute: () => 'post', - getEditedPostSlug: () => '1', - } ) ) - ); - useDispatch.mockImplementation( () => ( { - editPost, - } ) ); - - render( <PostSlug /> ); - - const input = screen.getByRole( 'textbox', { name: 'Slug' } ); - await user.type( input, '2', { - initialSelectionStart: 0, - initialSelectionEnd: 1, - } ); - act( () => input.blur() ); - - expect( editPost ).toHaveBeenCalledWith( { slug: '2' } ); - } ); -} ); diff --git a/packages/editor/src/components/post-sticky/check.js b/packages/editor/src/components/post-sticky/check.js index f504effca82c6b..ede5c22f3c3e32 100644 --- a/packages/editor/src/components/post-sticky/check.js +++ b/packages/editor/src/components/post-sticky/check.js @@ -11,10 +11,10 @@ import { store as editorStore } from '../../store'; /** * Wrapper component that renders its children only if post has a sticky action. * - * @param {Object} props Props. - * @param {Element} props.children Children to be rendered. + * @param {Object} props Props. + * @param {React.ReactElement} props.children Children to be rendered. * - * @return {Component} The component to be rendered or null if post type is not 'post' or hasStickyAction is false. + * @return {React.ReactElement} The component to be rendered or null if post type is not 'post' or hasStickyAction is false. */ export default function PostStickyCheck( { children } ) { const { hasStickyAction, postType } = useSelect( ( select ) => { diff --git a/packages/editor/src/components/post-sticky/index.js b/packages/editor/src/components/post-sticky/index.js index 43a4bea98de26e..6f5b914cb2f352 100644 --- a/packages/editor/src/components/post-sticky/index.js +++ b/packages/editor/src/components/post-sticky/index.js @@ -14,7 +14,7 @@ import { store as editorStore } from '../../store'; /** * Renders the PostSticky component. It provides a checkbox control for the sticky post feature. * - * @return {Component} The component to be rendered. + * @return {React.ReactNode} The rendered component. */ export default function PostSticky() { const postSticky = useSelect( ( select ) => { diff --git a/packages/editor/src/components/post-switch-to-draft-button/index.js b/packages/editor/src/components/post-switch-to-draft-button/index.js index a743c7a2991ffb..6a41e1fad597a2 100644 --- a/packages/editor/src/components/post-switch-to-draft-button/index.js +++ b/packages/editor/src/components/post-switch-to-draft-button/index.js @@ -18,7 +18,7 @@ import { store as editorStore } from '../../store'; /** * Renders a button component that allows the user to switch a post to draft status. * - * @return {JSX.Element} The rendered component. + * @return {React.ReactNode} The rendered component. */ export default function PostSwitchToDraftButton() { deprecated( 'wp.editor.PostSwitchToDraftButton', { diff --git a/packages/editor/src/components/post-sync-status/index.js b/packages/editor/src/components/post-sync-status/index.js index d3e2a1a5522e89..5f965c01503f5c 100644 --- a/packages/editor/src/components/post-sync-status/index.js +++ b/packages/editor/src/components/post-sync-status/index.js @@ -13,7 +13,7 @@ import { store as editorStore } from '../../store'; /** * Renders the sync status of a post. * - * @return {JSX.Element|null} The rendered sync status component. + * @return {React.ReactNode} The rendered sync status component. */ export default function PostSyncStatus() { const { syncStatus, postType } = useSelect( ( select ) => { diff --git a/packages/editor/src/components/post-taxonomies/check.js b/packages/editor/src/components/post-taxonomies/check.js index 401b1adad7cad4..ce3db319ae2e8a 100644 --- a/packages/editor/src/components/post-taxonomies/check.js +++ b/packages/editor/src/components/post-taxonomies/check.js @@ -12,10 +12,10 @@ import { store as editorStore } from '../../store'; /** * Renders the children components only if the current post type has taxonomies. * - * @param {Object} props The component props. - * @param {Element} props.children The children components to render. + * @param {Object} props The component props. + * @param {React.ReactNode} props.children The children components to render. * - * @return {Component|null} The rendered children components or null if the current post type has no taxonomies. + * @return {React.ReactElement} The rendered children components or null if the current post type has no taxonomies. */ export default function PostTaxonomiesCheck( { children } ) { const hasTaxonomies = useSelect( ( select ) => { diff --git a/packages/editor/src/components/post-taxonomies/flat-term-selector.js b/packages/editor/src/components/post-taxonomies/flat-term-selector.js index cd9377766af503..890175534c8b4a 100644 --- a/packages/editor/src/components/post-taxonomies/flat-term-selector.js +++ b/packages/editor/src/components/post-taxonomies/flat-term-selector.js @@ -71,7 +71,7 @@ const Wrapper = ( { children, __nextHasNoMarginBottom } ) => * @param {string} props.slug The slug of the taxonomy. * @param {boolean} props.__nextHasNoMarginBottom Start opting into the new margin-free styles that will become the default in a future version, currently scheduled to be WordPress 7.0. (The prop can be safely removed once this happens.) * - * @return {JSX.Element} The rendered flat term selector component. + * @return {React.ReactNode} The rendered flat term selector component. */ export function FlatTermSelector( { slug, __nextHasNoMarginBottom } ) { const [ values, setValues ] = useState( [] ); diff --git a/packages/editor/src/components/post-taxonomies/panel.js b/packages/editor/src/components/post-taxonomies/panel.js index 760626f984db36..f75fa74cc3d2e9 100644 --- a/packages/editor/src/components/post-taxonomies/panel.js +++ b/packages/editor/src/components/post-taxonomies/panel.js @@ -11,6 +11,15 @@ import { store as editorStore } from '../../store'; import PostTaxonomiesForm from './index'; import PostTaxonomiesCheck from './check'; +/** + * Renders a panel for a specific taxonomy. + * + * @param {Object} props The component props. + * @param {Object} props.taxonomy The taxonomy object. + * @param {React.ReactNode} props.children The child components. + * + * @return {React.ReactNode} The rendered taxonomy panel. + */ function TaxonomyPanel( { taxonomy, children } ) { const slug = taxonomy?.slug; const panelName = slug ? `taxonomy-panel-${ slug }` : ''; @@ -47,7 +56,12 @@ function TaxonomyPanel( { taxonomy, children } ) { ); } -function PostTaxonomies() { +/** + * Component that renders the post taxonomies panel. + * + * @return {React.ReactNode} The rendered component. + */ +export default function PostTaxonomies() { return ( <PostTaxonomiesCheck> <PostTaxonomiesForm @@ -62,14 +76,3 @@ function PostTaxonomies() { </PostTaxonomiesCheck> ); } - -/** - * Renders a panel for a specific taxonomy. - * - * @param {Object} props The component props. - * @param {Object} props.taxonomy The taxonomy object. - * @param {Element} props.children The child components. - * - * @return {Component} The rendered taxonomy panel. - */ -export default PostTaxonomies; diff --git a/packages/editor/src/components/post-template/classic-theme.js b/packages/editor/src/components/post-template/classic-theme.js index 4a65a9b4c75636..4345e06211c661 100644 --- a/packages/editor/src/components/post-template/classic-theme.js +++ b/packages/editor/src/components/post-template/classic-theme.js @@ -63,7 +63,7 @@ function PostTemplateToggle( { isOpen, onClick } ) { * @param {Object} props The component props. * @param {Function} props.onClose The function to close the dropdown. * - * @return {JSX.Element} The rendered dropdown content. + * @return {React.ReactNode} The rendered dropdown content. */ function PostTemplateDropdownContent( { onClose } ) { const allowSwitchingTemplate = useAllowSwitchingTemplates(); @@ -235,6 +235,6 @@ function ClassicThemeControl() { * * The dropdown menu includes a button for toggling the menu, a list of available templates, and options for creating and editing templates. * - * @return {JSX.Element} The rendered ClassicThemeControl component. + * @return {React.ReactNode} The rendered ClassicThemeControl component. */ export default ClassicThemeControl; diff --git a/packages/editor/src/components/post-template/panel.js b/packages/editor/src/components/post-template/panel.js index b5f0d34197c686..903612ef11ed15 100644 --- a/packages/editor/src/components/post-template/panel.js +++ b/packages/editor/src/components/post-template/panel.js @@ -16,7 +16,7 @@ import PostPanelRow from '../post-panel-row'; /** * Displays the template controls based on the current editor settings and user permissions. * - * @return {JSX.Element|null} The rendered PostTemplatePanel component. + * @return {React.ReactNode} The rendered PostTemplatePanel component. */ export default function PostTemplatePanel() { const { templateId, isBlockTheme } = useSelect( ( select ) => { diff --git a/packages/editor/src/components/post-template/swap-template-button.js b/packages/editor/src/components/post-template/swap-template-button.js index bdda349398406b..f85b18e3559697 100644 --- a/packages/editor/src/components/post-template/swap-template-button.js +++ b/packages/editor/src/components/post-template/swap-template-button.js @@ -37,7 +37,7 @@ export default function SwapTemplateButton( { onClick } ) { return ( <> <MenuItem onClick={ () => setShowModal( true ) }> - { __( 'Swap template' ) } + { __( 'Change template' ) } </MenuItem> { showModal && ( <Modal diff --git a/packages/editor/src/components/post-text-editor/index.js b/packages/editor/src/components/post-text-editor/index.js index c3dc61a0b4a2ef..0ae569c3e15301 100644 --- a/packages/editor/src/components/post-text-editor/index.js +++ b/packages/editor/src/components/post-text-editor/index.js @@ -22,7 +22,7 @@ import { store as editorStore } from '../../store'; /** * Displays the Post Text Editor along with content in Visual and Text mode. * - * @return {JSX.Element|null} The rendered PostTextEditor component. + * @return {React.ReactNode} The rendered PostTextEditor component. */ export default function PostTextEditor() { const instanceId = useInstanceId( PostTextEditor ); diff --git a/packages/editor/src/components/post-title/index.js b/packages/editor/src/components/post-title/index.js index 50595d936b36d9..090beb57f6170e 100644 --- a/packages/editor/src/components/post-title/index.js +++ b/packages/editor/src/components/post-title/index.js @@ -28,7 +28,7 @@ import usePostTitleFocus from './use-post-title-focus'; import usePostTitle from './use-post-title'; import PostTypeSupportCheck from '../post-type-support-check'; -function PostTitle( _, forwardedRef ) { +const PostTitle = forwardRef( ( _, forwardedRef ) => { const { placeholder } = useSelect( ( select ) => { const { getSettings } = select( blockEditorStore ); const { titlePlaceholder } = getSettings(); @@ -171,23 +171,21 @@ function PostTitle( _, forwardedRef ) { return ( /* eslint-disable jsx-a11y/heading-has-content, jsx-a11y/no-noninteractive-element-to-interactive-role */ - <PostTypeSupportCheck supportKeys="title"> - <h1 - ref={ useMergeRefs( [ richTextRef, focusRef ] ) } - contentEditable - className={ className } - aria-label={ decodedPlaceholder } - role="textbox" - aria-multiline="true" - onFocus={ onSelect } - onBlur={ onUnselect } - onKeyDown={ onKeyDown } - onPaste={ onPaste } - /> - </PostTypeSupportCheck> + <h1 + ref={ useMergeRefs( [ richTextRef, focusRef ] ) } + contentEditable + className={ className } + aria-label={ decodedPlaceholder } + role="textbox" + aria-multiline="true" + onFocus={ onSelect } + onBlur={ onUnselect } + onKeyDown={ onKeyDown } + onPaste={ onPaste } + /> /* eslint-enable jsx-a11y/heading-has-content, jsx-a11y/no-noninteractive-element-to-interactive-role */ ); -} +} ); /** * Renders the `PostTitle` component. @@ -195,6 +193,10 @@ function PostTitle( _, forwardedRef ) { * @param {Object} _ Unused parameter. * @param {Element} forwardedRef Forwarded ref for the component. * - * @return {Component} The rendered PostTitle component. + * @return {React.ReactNode} The rendered PostTitle component. */ -export default forwardRef( PostTitle ); +export default forwardRef( ( _, forwardedRef ) => ( + <PostTypeSupportCheck supportKeys="title"> + <PostTitle ref={ forwardedRef } /> + </PostTypeSupportCheck> +) ); diff --git a/packages/editor/src/components/post-title/post-title-raw.js b/packages/editor/src/components/post-title/post-title-raw.js index 66c944b45871ab..9beba1068f8ef7 100644 --- a/packages/editor/src/components/post-title/post-title-raw.js +++ b/packages/editor/src/components/post-title/post-title-raw.js @@ -26,7 +26,7 @@ import usePostTitle from './use-post-title'; * @param {Object} _ Unused parameter. * @param {Element} forwardedRef Reference to the component's DOM node. * - * @return {Component} The rendered component. + * @return {React.ReactNode} The rendered component. */ function PostTitleRaw( _, forwardedRef ) { const { placeholder } = useSelect( ( select ) => { diff --git a/packages/editor/src/components/post-trash/check.js b/packages/editor/src/components/post-trash/check.js index 7edc7c0f18c273..590aa7cd4c390b 100644 --- a/packages/editor/src/components/post-trash/check.js +++ b/packages/editor/src/components/post-trash/check.js @@ -11,12 +11,12 @@ import { store as editorStore } from '../../store'; import { GLOBAL_POST_TYPES } from '../../store/constants'; /** - * Wrapper component that renders its children only if the post can trashed. + * Wrapper component that renders its children only if the post can be trashed. * - * @param {Object} props - The component props. - * @param {Element} props.children - The child components to render. + * @param {Object} props The component props. + * @param {React.ReactElement} props.children The child components. * - * @return {Component|null} The rendered child components or null if the post can not trashed. + * @return {React.ReactElement | null} The rendered child components or null if the post can't be trashed. */ export default function PostTrashCheck( { children } ) { const { canTrashPost } = useSelect( ( select ) => { diff --git a/packages/editor/src/components/post-trash/index.js b/packages/editor/src/components/post-trash/index.js index 743512e9efd7d4..2d3dd8bcb0c4b3 100644 --- a/packages/editor/src/components/post-trash/index.js +++ b/packages/editor/src/components/post-trash/index.js @@ -19,7 +19,7 @@ import PostTrashCheck from './check'; * Displays the Post Trash Button and Confirm Dialog in the Editor. * * @param {?{onActionPerformed: Object}} An object containing the onActionPerformed function. - * @return {JSX.Element|null} The rendered PostTrash component. + * @return {React.ReactNode} The rendered PostTrash component. */ export default function PostTrash( { onActionPerformed } ) { const registry = useRegistry(); diff --git a/packages/editor/src/components/post-type-support-check/index.js b/packages/editor/src/components/post-type-support-check/index.js index 613fda8eb82da3..f04aea84b0e411 100644 --- a/packages/editor/src/components/post-type-support-check/index.js +++ b/packages/editor/src/components/post-type-support-check/index.js @@ -13,13 +13,13 @@ import { store as editorStore } from '../../store'; * A component which renders its own children only if the current editor post * type supports one of the given `supportKeys` prop. * - * @param {Object} props Props. - * @param {Element} props.children Children to be rendered if post - * type supports. - * @param {(string|string[])} props.supportKeys String or string array of keys - * to test. + * @param {Object} props Props. + * @param {React.ReactElement} props.children Children to be rendered if post + * type supports. + * @param {(string|string[])} props.supportKeys String or string array of keys + * to test. * - * @return {Component} The component to be rendered. + * @return {React.ReactElement} The component to be rendered. */ function PostTypeSupportCheck( { children, supportKeys } ) { const postType = useSelect( ( select ) => { diff --git a/packages/editor/src/components/post-url/check.js b/packages/editor/src/components/post-url/check.js index 7eb390472bdd7d..a80d829de23221 100644 --- a/packages/editor/src/components/post-url/check.js +++ b/packages/editor/src/components/post-url/check.js @@ -12,10 +12,10 @@ import { store as editorStore } from '../../store'; /** * Check if the post URL is valid and visible. * - * @param {Object} props The component props. - * @param {Element} props.children The child components. + * @param {Object} props The component props. + * @param {React.ReactElement} props.children The child components. * - * @return {Component|null} The child components if the post URL is valid and visible, otherwise null. + * @return {React.ReactElement} The child components if the post URL is valid and visible, otherwise null. */ export default function PostURLCheck( { children } ) { const isVisible = useSelect( ( select ) => { diff --git a/packages/editor/src/components/post-url/index.js b/packages/editor/src/components/post-url/index.js index f03bdd59752a83..f55ac973be50e6 100644 --- a/packages/editor/src/components/post-url/index.js +++ b/packages/editor/src/components/post-url/index.js @@ -32,9 +32,10 @@ import { store as editorStore } from '../../store'; * <PostURL /> * ``` * - * @param {Function} onClose Callback function to be executed when the popover is closed. + * @param {{ onClose: () => void }} props The props for the component. + * @param {() => void} props.onClose Callback function to be executed when the popover is closed. * - * @return {Component} The rendered PostURL component. + * @return {React.ReactNode} The rendered PostURL component. */ export default function PostURL( { onClose } ) { const { diff --git a/packages/editor/src/components/post-url/label.js b/packages/editor/src/components/post-url/label.js index 4f03e2bce0d05f..277cc6cfc715d3 100644 --- a/packages/editor/src/components/post-url/label.js +++ b/packages/editor/src/components/post-url/label.js @@ -12,7 +12,7 @@ import { store as editorStore } from '../../store'; /** * Represents a label component for a post URL. * - * @return {Component} The PostURLLabel component. + * @return {React.ReactNode} The PostURLLabel component. */ export default function PostURLLabel() { return usePostURLLabel(); diff --git a/packages/editor/src/components/post-url/panel.js b/packages/editor/src/components/post-url/panel.js index 786a12cb8e6b54..97eaa8ccb77f8d 100644 --- a/packages/editor/src/components/post-url/panel.js +++ b/packages/editor/src/components/post-url/panel.js @@ -19,7 +19,7 @@ import { store as editorStore } from '../../store'; /** * Renders the `PostURLPanel` component. * - * @return {JSX.Element} The rendered PostURLPanel component. + * @return {React.ReactNode} The rendered PostURLPanel component. */ export default function PostURLPanel() { const { isFrontPage } = useSelect( ( select ) => { diff --git a/packages/editor/src/components/post-visibility/check.js b/packages/editor/src/components/post-visibility/check.js index 19a241ae1110ae..ea04a6b739617a 100644 --- a/packages/editor/src/components/post-visibility/check.js +++ b/packages/editor/src/components/post-visibility/check.js @@ -15,7 +15,7 @@ import { store as editorStore } from '../../store'; * @param {Object} props The component props. * @param {Function} props.render Function to render the component. * Receives an object with a `canEdit` property. - * @return {JSX.Element} The rendered component. + * @return {React.ReactNode} The rendered component. */ export default function PostVisibilityCheck( { render } ) { const canEdit = useSelect( ( select ) => { diff --git a/packages/editor/src/components/post-visibility/index.js b/packages/editor/src/components/post-visibility/index.js index e47f2acd664434..3eb0c157c337ca 100644 --- a/packages/editor/src/components/post-visibility/index.js +++ b/packages/editor/src/components/post-visibility/index.js @@ -22,7 +22,7 @@ import { store as editorStore } from '../../store'; * * @param {Object} props The component props. * @param {Function} props.onClose Function to call when the popover is closed. - * @return {JSX.Element} The rendered component. + * @return {React.ReactNode} The rendered component. */ export default function PostVisibility( { onClose } ) { const instanceId = useInstanceId( PostVisibility ); diff --git a/packages/editor/src/components/preferences-modal/block-visibility.js b/packages/editor/src/components/preferences-modal/block-visibility.js new file mode 100644 index 00000000000000..8726b114d97480 --- /dev/null +++ b/packages/editor/src/components/preferences-modal/block-visibility.js @@ -0,0 +1,96 @@ +/** + * WordPress dependencies + */ +import { useSelect, useDispatch } from '@wordpress/data'; +import { store as preferencesStore } from '@wordpress/preferences'; +import { hasBlockSupport, store as blocksStore } from '@wordpress/blocks'; +import { useMemo } from '@wordpress/element'; +import { privateApis as blockEditorPrivateApis } from '@wordpress/block-editor'; + +/** + * Internal dependencies + */ +import { store as editorStore } from '../../store'; +import { unlock } from '../../lock-unlock'; + +const { BlockManager } = unlock( blockEditorPrivateApis ); + +export default function BlockVisibility() { + const { showBlockTypes, hideBlockTypes } = unlock( + useDispatch( editorStore ) + ); + + const { + blockTypes, + allowedBlockTypes: _allowedBlockTypes, + hiddenBlockTypes: _hiddenBlockTypes, + } = useSelect( ( select ) => { + return { + blockTypes: select( blocksStore ).getBlockTypes(), + allowedBlockTypes: + select( editorStore ).getEditorSettings().allowedBlockTypes, + hiddenBlockTypes: + select( preferencesStore ).get( 'core', 'hiddenBlockTypes' ) ?? + [], + }; + }, [] ); + + const allowedBlockTypes = useMemo( () => { + if ( _allowedBlockTypes === true ) { + return blockTypes; + } + return blockTypes.filter( ( { name } ) => { + return _allowedBlockTypes?.includes( name ); + } ); + }, [ _allowedBlockTypes, blockTypes ] ); + + const filteredBlockTypes = allowedBlockTypes.filter( + ( blockType ) => + hasBlockSupport( blockType, 'inserter', true ) && + ( ! blockType.parent || + blockType.parent.includes( 'core/post-content' ) ) + ); + + // Some hidden blocks become unregistered + // by removing for instance the plugin that registered them, yet + // they're still remain as hidden by the user's action. + // We consider "hidden", blocks which were hidden and + // are still registered. + const hiddenBlockTypes = _hiddenBlockTypes.filter( ( hiddenBlock ) => { + return filteredBlockTypes.some( + ( registeredBlock ) => registeredBlock.name === hiddenBlock + ); + } ); + + const selectedBlockTypes = filteredBlockTypes.filter( + ( blockType ) => ! hiddenBlockTypes.includes( blockType.name ) + ); + + const onChangeSelectedBlockTypes = ( newSelectedBlockTypes ) => { + if ( selectedBlockTypes.length > newSelectedBlockTypes.length ) { + const blockTypesToHide = selectedBlockTypes.filter( + ( blockType ) => + ! newSelectedBlockTypes.find( + ( { name } ) => name === blockType.name + ) + ); + hideBlockTypes( blockTypesToHide.map( ( { name } ) => name ) ); + } else if ( selectedBlockTypes.length < newSelectedBlockTypes.length ) { + const blockTypesToShow = newSelectedBlockTypes.filter( + ( blockType ) => + ! selectedBlockTypes.find( + ( { name } ) => name === blockType.name + ) + ); + showBlockTypes( blockTypesToShow.map( ( { name } ) => name ) ); + } + }; + + return ( + <BlockManager + blockTypes={ filteredBlockTypes } + selectedBlockTypes={ selectedBlockTypes } + onChange={ onChangeSelectedBlockTypes } + /> + ); +} diff --git a/packages/editor/src/components/preferences-modal/index.js b/packages/editor/src/components/preferences-modal/index.js index 7ea7ea456ce28e..fba60405e7e4b5 100644 --- a/packages/editor/src/components/preferences-modal/index.js +++ b/packages/editor/src/components/preferences-modal/index.js @@ -18,7 +18,7 @@ import { store as interfaceStore } from '@wordpress/interface'; import EnablePanelOption from './enable-panel'; import EnablePluginDocumentSettingPanelOption from './enable-plugin-document-setting-panel'; import EnablePublishSidebarOption from './enable-publish-sidebar'; -import BlockManager from '../block-manager'; +import BlockVisibility from './block-visibility'; import PostTaxonomies from '../post-taxonomies'; import PostFeaturedImageCheck from '../post-featured-image/check'; import PostExcerptCheck from '../post-excerpt/check'; @@ -26,7 +26,6 @@ import PageAttributesCheck from '../page-attributes/check'; import PostTypeSupportCheck from '../post-type-support-check'; import { store as editorStore } from '../../store'; import { unlock } from '../../lock-unlock'; -import { useStartPatterns } from '../start-page-options'; const { PreferencesModal, @@ -73,7 +72,6 @@ function PreferencesModalContents( { extraSections = {} } ) { const { setIsListViewOpened, setIsInserterOpened } = useDispatch( editorStore ); const { set: setPreference } = useDispatch( preferencesStore ); - const hasStarterPatterns = !! useStartPatterns().length; const sections = useMemo( () => @@ -114,16 +112,14 @@ function PreferencesModalContents( { extraSections = {} } ) { 'Allow right-click contextual menus' ) } /> - { hasStarterPatterns && ( - <PreferenceToggleControl - scope="core" - featureName="enableChoosePatternModal" - help={ __( - 'Shows starter patterns when creating a new page.' - ) } - label={ __( 'Show starter patterns' ) } - /> - ) } + <PreferenceToggleControl + scope="core" + featureName="enableChoosePatternModal" + help={ __( + 'Shows starter patterns when creating a new page.' + ) } + label={ __( 'Show starter patterns' ) } + /> </PreferencesModalSection> <PreferencesModalSection title={ __( 'Document settings' ) } @@ -297,7 +293,7 @@ function PreferencesModalContents( { extraSections = {} } ) { "Disable blocks that you don't want to appear in the inserter. They can always be toggled back on later." ) } > - <BlockManager /> + <BlockVisibility /> </PreferencesModalSection> </> ), @@ -341,7 +337,6 @@ function PreferencesModalContents( { extraSections = {} } ) { setIsListViewOpened, setPreference, isLargeViewport, - hasStarterPatterns, ] ); diff --git a/packages/editor/src/components/preview-dropdown/index.js b/packages/editor/src/components/preview-dropdown/index.js index 6fa35c673430cc..a081564e48ea8d 100644 --- a/packages/editor/src/components/preview-dropdown/index.js +++ b/packages/editor/src/components/preview-dropdown/index.js @@ -190,7 +190,6 @@ export default function PreviewDropdown( { forceIsAutosaveable, disabled } ) { ) } <ActionItem.Slot name="core/plugin-preview-menu" - as={ MenuGroup } fillProps={ { onClick: onClose } } /> </> diff --git a/packages/editor/src/components/provider/disable-non-page-content-blocks.js b/packages/editor/src/components/provider/disable-non-page-content-blocks.js index ae4fd1075fc261..ffbf1ac0625463 100644 --- a/packages/editor/src/components/provider/disable-non-page-content-blocks.js +++ b/packages/editor/src/components/provider/disable-non-page-content-blocks.js @@ -16,9 +16,13 @@ import usePostContentBlocks from './use-post-content-blocks'; */ export default function DisableNonPageContentBlocks() { const contentOnlyIds = usePostContentBlocks(); - const templateParts = useSelect( ( select ) => { - const { getBlocksByName } = select( blockEditorStore ); - return getBlocksByName( 'core/template-part' ); + const { templateParts, isNavigationMode } = useSelect( ( select ) => { + const { getBlocksByName, isNavigationMode: _isNavigationMode } = + select( blockEditorStore ); + return { + templateParts: getBlocksByName( 'core/template-part' ), + isNavigationMode: _isNavigationMode(), + }; }, [] ); const disabledIds = useSelect( ( select ) => { @@ -32,38 +36,85 @@ export default function DisableNonPageContentBlocks() { const registry = useRegistry(); + // The code here is split into multiple `useEffects` calls. + // This is done to avoid setting/unsetting block editing modes multiple times unnecessarily. + // + // For example, the block editing mode of the root block (clientId: '') only + // needs to be set once, not when `contentOnlyIds` or `disabledIds` change. + // + // It's also unlikely that these different types of blocks are being inserted + // or removed at the same time, so using different effects reflects that. + useEffect( () => { + const { setBlockEditingMode, unsetBlockEditingMode } = + registry.dispatch( blockEditorStore ); + + setBlockEditingMode( '', 'disabled' ); + + return () => { + unsetBlockEditingMode( '' ); + }; + }, [ registry ] ); + useEffect( () => { const { setBlockEditingMode, unsetBlockEditingMode } = registry.dispatch( blockEditorStore ); registry.batch( () => { - setBlockEditingMode( '', 'disabled' ); for ( const clientId of contentOnlyIds ) { setBlockEditingMode( clientId, 'contentOnly' ); } - for ( const clientId of templateParts ) { - setBlockEditingMode( clientId, 'contentOnly' ); - } - for ( const clientId of disabledIds ) { - setBlockEditingMode( clientId, 'disabled' ); - } } ); return () => { registry.batch( () => { - unsetBlockEditingMode( '' ); for ( const clientId of contentOnlyIds ) { unsetBlockEditingMode( clientId ); } + } ); + }; + }, [ contentOnlyIds, registry ] ); + + useEffect( () => { + const { setBlockEditingMode, unsetBlockEditingMode } = + registry.dispatch( blockEditorStore ); + + registry.batch( () => { + if ( ! isNavigationMode ) { for ( const clientId of templateParts ) { - unsetBlockEditingMode( clientId ); + setBlockEditingMode( clientId, 'contentOnly' ); } + } + } ); + + return () => { + registry.batch( () => { + if ( ! isNavigationMode ) { + for ( const clientId of templateParts ) { + unsetBlockEditingMode( clientId ); + } + } + } ); + }; + }, [ templateParts, isNavigationMode, registry ] ); + + useEffect( () => { + const { setBlockEditingMode, unsetBlockEditingMode } = + registry.dispatch( blockEditorStore ); + + registry.batch( () => { + for ( const clientId of disabledIds ) { + setBlockEditingMode( clientId, 'disabled' ); + } + } ); + + return () => { + registry.batch( () => { for ( const clientId of disabledIds ) { unsetBlockEditingMode( clientId ); } } ); }; - }, [ templateParts, contentOnlyIds, disabledIds, registry ] ); + }, [ disabledIds, registry ] ); return null; } diff --git a/packages/editor/src/components/provider/index.js b/packages/editor/src/components/provider/index.js index 50d02610062c00..dfeaf19a5bd21e 100644 --- a/packages/editor/src/components/provider/index.js +++ b/packages/editor/src/components/provider/index.js @@ -56,6 +56,11 @@ const NON_CONTEXTUAL_POST_TYPES = [ 'wp_template_part', ]; +/** + * These are rendering modes that the editor supports. + */ +const RENDERING_MODES = [ 'post-only', 'template-locked' ]; + /** * Depending on the post, template and template mode, * returns the appropriate blocks and change handlers for the block editor provider. @@ -72,8 +77,7 @@ const NON_CONTEXTUAL_POST_TYPES = [ * @return {Array} Block editor props. */ function useBlockEditorProps( post, template, mode ) { - const rootLevelPost = - mode === 'post-only' || ! template ? 'post' : 'template'; + const rootLevelPost = mode === 'template-locked' ? 'template' : 'post'; const [ postBlocks, onInput, onChange ] = useEntityBlockEditor( 'postType', post.type, @@ -164,30 +168,59 @@ export const ExperimentalEditorProvider = withRegistryProvider( BlockEditorProviderComponent = ExperimentalBlockEditorProvider, __unstableTemplate: template, } ) => { - const { editorSettings, selection, isReady, mode, postTypeEntities } = - useSelect( - ( select ) => { - const { - getEditorSettings, - getEditorSelection, - getRenderingMode, - __unstableIsEditorReady, - } = select( editorStore ); - const { getEntitiesConfig } = select( coreStore ); + const hasTemplate = !! template; + const { + editorSettings, + selection, + isReady, + mode, + defaultMode, + postTypeEntities, + } = useSelect( + ( select ) => { + const { + getEditorSettings, + getEditorSelection, + getRenderingMode, + __unstableIsEditorReady, + } = select( editorStore ); + const { + getEntitiesConfig, + getPostType, + hasFinishedResolution, + } = select( coreStore ); + + const postTypeSupports = getPostType( post.type )?.supports; + const hasLoadedPostObject = hasFinishedResolution( + 'getPostType', + [ post.type ] + ); + + const _defaultMode = Array.isArray( postTypeSupports?.editor ) + ? postTypeSupports.editor.find( + ( features ) => 'default-mode' in features + )?.[ 'default-mode' ] + : undefined; + const hasDefaultMode = RENDERING_MODES.includes( _defaultMode ); + + return { + editorSettings: getEditorSettings(), + isReady: __unstableIsEditorReady() && hasLoadedPostObject, + mode: getRenderingMode(), + defaultMode: + hasTemplate && hasDefaultMode + ? _defaultMode + : 'post-only', + selection: getEditorSelection(), + postTypeEntities: + post.type === 'wp_template' + ? getEntitiesConfig( 'postType' ) + : null, + }; + }, + [ post.type, hasTemplate ] + ); - return { - editorSettings: getEditorSettings(), - isReady: __unstableIsEditorReady(), - mode: getRenderingMode(), - selection: getEditorSelection(), - postTypeEntities: - post.type === 'wp_template' - ? getEntitiesConfig( 'postType' ) - : null, - }; - }, - [ post.type ] - ); const shouldRenderTemplate = !! template && mode !== 'post-only'; const rootLevelPost = shouldRenderTemplate ? template : post; const defaultBlockContext = useMemo( () => { @@ -282,6 +315,10 @@ export const ExperimentalEditorProvider = withRegistryProvider( } ); } + + // The dependencies of the hook are omitted deliberately + // We only want to run setupEditor (with initialEdits) only once per post. + // A better solution in the future would be to split this effect into multiple ones. }, [] ); // Synchronizes the active post with the state @@ -301,15 +338,15 @@ export const ExperimentalEditorProvider = withRegistryProvider( // Sets the right rendering mode when loading the editor. useEffect( () => { - setRenderingMode( settings.defaultRenderingMode ?? 'post-only' ); - }, [ settings.defaultRenderingMode, setRenderingMode ] ); + setRenderingMode( defaultMode ); + }, [ defaultMode, setRenderingMode ] ); useHideBlocksFromInserter( post.type, mode ); // Register the editor commands. useCommands(); - if ( ! isReady ) { + if ( ! isReady || ! mode ) { return null; } @@ -366,14 +403,14 @@ export const ExperimentalEditorProvider = withRegistryProvider( * * All modification and changes are performed to the `@wordpress/core-data` store. * - * @param {Object} props The component props. - * @param {Object} [props.post] The post object to edit. This is required. - * @param {Object} [props.__unstableTemplate] The template object wrapper the edited post. - * This is optional and can only be used when the post type supports templates (like posts and pages). - * @param {Object} [props.settings] The settings object to use for the editor. - * This is optional and can be used to override the default settings. - * @param {Element} [props.children] Children elements for which the BlockEditorProvider context should apply. - * This is optional. + * @param {Object} props The component props. + * @param {Object} [props.post] The post object to edit. This is required. + * @param {Object} [props.__unstableTemplate] The template object wrapper the edited post. + * This is optional and can only be used when the post type supports templates (like posts and pages). + * @param {Object} [props.settings] The settings object to use for the editor. + * This is optional and can be used to override the default settings. + * @param {React.ReactNode} [props.children] Children elements for which the BlockEditorProvider context should apply. + * This is optional. * * @example * ```jsx @@ -386,7 +423,7 @@ export const ExperimentalEditorProvider = withRegistryProvider( * </EditorProvider> * ``` * - * @return {JSX.Element} The rendered EditorProvider component. + * @return {React.ReactNode} The rendered EditorProvider component. */ export function EditorProvider( props ) { return ( diff --git a/packages/editor/src/components/provider/use-block-editor-settings.js b/packages/editor/src/components/provider/use-block-editor-settings.js index f5c45f431e2c85..d0c2e36d474433 100644 --- a/packages/editor/src/components/provider/use-block-editor-settings.js +++ b/packages/editor/src/components/provider/use-block-editor-settings.js @@ -23,6 +23,7 @@ import { */ import inserterMediaCategories from '../media-categories'; import { mediaUpload } from '../../utils'; +import { default as mediaSideload } from '../../utils/media-sideload'; import { store as editorStore } from '../../store'; import { unlock } from '../../lock-unlock'; import { useGlobalStylesContext } from '../global-styles-provider'; @@ -45,6 +46,7 @@ const BLOCK_EDITOR_SETTINGS = [ '__experimentalGlobalStylesBaseStyles', 'alignWide', 'blockInspectorTabs', + 'maxUploadFileSize', 'allowedMimeTypes', 'bodyPlaceholder', 'canLockBlocks', @@ -290,6 +292,7 @@ function useBlockEditorSettings( settings, postType, postId, renderingMode ) { isDistractionFree, keepCaretInsideBlock, mediaUpload: hasUploadPermissions ? mediaUpload : undefined, + mediaSideload: hasUploadPermissions ? mediaSideload : undefined, __experimentalBlockPatterns: blockPatterns, [ selectBlockPatternsKey ]: ( select ) => { const { hasFinishedResolution, getBlockPatternsForPostType } = diff --git a/packages/editor/src/components/save-publish-panels/index.js b/packages/editor/src/components/save-publish-panels/index.js index d95d9f35928906..2a37357e27100e 100644 --- a/packages/editor/src/components/save-publish-panels/index.js +++ b/packages/editor/src/components/save-publish-panels/index.js @@ -88,6 +88,7 @@ export default function SavePublishPanels( { variant="secondary" onClick={ openEntitiesSavedStates } aria-expanded={ false } + aria-haspopup="dialog" disabled={ ! isDirty } accessibleWhenDisabled > @@ -102,7 +103,10 @@ export default function SavePublishPanels( { return ( <> { isEntitiesSavedStatesOpen && ( - <EntitiesSavedStates close={ closeEntitiesSavedStates } /> + <EntitiesSavedStates + close={ closeEntitiesSavedStates } + renderDialog + /> ) } <Slot bubblesVirtually /> { ! isEntitiesSavedStatesOpen && unmountableContent } diff --git a/packages/editor/src/components/sidebar/post-summary.js b/packages/editor/src/components/sidebar/post-summary.js index 3539f7ba964ec7..58e9e3e6ee61be 100644 --- a/packages/editor/src/components/sidebar/post-summary.js +++ b/packages/editor/src/components/sidebar/post-summary.js @@ -38,7 +38,7 @@ const PANEL_NAME = 'post-status'; export default function PostSummary( { onActionPerformed } ) { const { isRemovedPostStatusPanel, postType, postId } = useSelect( ( select ) => { - // We use isEditorPanelRemoved to hide the panel if it was programatically removed. We do + // 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, diff --git a/packages/editor/src/components/start-page-options/index.js b/packages/editor/src/components/start-page-options/index.js index 783a4a224fa378..d7874000ffc420 100644 --- a/packages/editor/src/components/start-page-options/index.js +++ b/packages/editor/src/components/start-page-options/index.js @@ -1,16 +1,8 @@ /** * WordPress dependencies */ -import { Modal } from '@wordpress/components'; -import { __ } from '@wordpress/i18n'; -import { useState, useMemo } from '@wordpress/element'; -import { - store as blockEditorStore, - __experimentalBlockPatternsList as BlockPatternsList, -} from '@wordpress/block-editor'; +import { useEffect } from '@wordpress/element'; import { useSelect, useDispatch } from '@wordpress/data'; -import { store as coreStore } from '@wordpress/core-data'; -import { __unstableSerializeAndClean } from '@wordpress/blocks'; import { store as preferencesStore } from '@wordpress/preferences'; import { store as interfaceStore } from '@wordpress/interface'; @@ -18,124 +10,41 @@ import { store as interfaceStore } from '@wordpress/interface'; * Internal dependencies */ import { store as editorStore } from '../../store'; -import { TEMPLATE_POST_TYPE } from '../../store/constants'; - -export function useStartPatterns() { - // A pattern is a start pattern if it includes 'core/post-content' in its blockTypes, - // and it has no postTypes declared and the current post type is page or if - // the current post type is part of the postTypes declared. - const { blockPatternsWithPostContentBlockType, postType } = useSelect( - ( select ) => { - const { getPatternsByBlockTypes, getBlocksByName } = - select( blockEditorStore ); - const { getCurrentPostType, getRenderingMode } = - select( editorStore ); - const rootClientId = - getRenderingMode() === 'post-only' - ? '' - : getBlocksByName( 'core/post-content' )?.[ 0 ]; - return { - blockPatternsWithPostContentBlockType: getPatternsByBlockTypes( - 'core/post-content', - rootClientId - ), - postType: getCurrentPostType(), - }; - }, - [] - ); - - return useMemo( () => { - if ( ! blockPatternsWithPostContentBlockType?.length ) { - return []; - } - - /* - * Filter patterns without postTypes declared if the current postType is page - * or patterns that declare the current postType in its post type array. - */ - return blockPatternsWithPostContentBlockType.filter( ( pattern ) => { - return ( - ( postType === 'page' && ! pattern.postTypes ) || - ( Array.isArray( pattern.postTypes ) && - pattern.postTypes.includes( postType ) ) - ); - } ); - }, [ postType, blockPatternsWithPostContentBlockType ] ); -} - -function PatternSelection( { blockPatterns, onChoosePattern } ) { - const { editEntityRecord } = useDispatch( coreStore ); - const { postType, postId } = useSelect( ( select ) => { - const { getCurrentPostType, getCurrentPostId } = select( editorStore ); - - return { - postType: getCurrentPostType(), - postId: getCurrentPostId(), - }; - }, [] ); - return ( - <BlockPatternsList - blockPatterns={ blockPatterns } - onClickPattern={ ( _pattern, blocks ) => { - editEntityRecord( 'postType', postType, postId, { - blocks, - content: ( { blocks: blocksForSerialization = [] } ) => - __unstableSerializeAndClean( blocksForSerialization ), - } ); - onChoosePattern(); - } } - /> - ); -} - -function StartPageOptionsModal( { onClose } ) { - const startPatterns = useStartPatterns(); - const hasStartPattern = startPatterns.length > 0; - - if ( ! hasStartPattern ) { - return null; - } - - return ( - <Modal - title={ __( 'Choose a pattern' ) } - isFullScreen - onRequestClose={ onClose } - > - <div className="editor-start-page-options__modal-content"> - <PatternSelection - blockPatterns={ startPatterns } - onChoosePattern={ onClose } - /> - </div> - </Modal> - ); -} export default function StartPageOptions() { - const [ isClosed, setIsClosed ] = useState( false ); - const shouldEnableModal = useSelect( ( select ) => { - const { isEditedPostDirty, isEditedPostEmpty, getCurrentPostType } = - select( editorStore ); + const { postId, shouldEnable } = useSelect( ( select ) => { + const { + isEditedPostDirty, + isEditedPostEmpty, + getCurrentPostId, + getCurrentPostType, + } = select( editorStore ); const preferencesModalActive = select( interfaceStore ).isModalActive( 'editor/preferences' ); const choosePatternModalEnabled = select( preferencesStore ).get( 'core', 'enableChoosePatternModal' ); - return ( - choosePatternModalEnabled && - ! preferencesModalActive && - ! isEditedPostDirty() && - isEditedPostEmpty() && - TEMPLATE_POST_TYPE !== getCurrentPostType() - ); + return { + postId: getCurrentPostId(), + shouldEnable: + choosePatternModalEnabled && + ! preferencesModalActive && + ! isEditedPostDirty() && + isEditedPostEmpty() && + 'page' === getCurrentPostType(), + }; }, [] ); + const { setIsInserterOpened } = useDispatch( editorStore ); + + useEffect( () => { + if ( shouldEnable ) { + setIsInserterOpened( { + tab: 'patterns', + category: 'core/starter-content', + } ); + } + }, [ postId, shouldEnable, setIsInserterOpened ] ); - if ( ! shouldEnableModal || isClosed ) { - return null; - } - - return <StartPageOptionsModal onClose={ () => setIsClosed( true ) } />; + return null; } diff --git a/packages/editor/src/components/table-of-contents/index.js b/packages/editor/src/components/table-of-contents/index.js index 6fd83557b13ab7..47de10b66ebdd1 100644 --- a/packages/editor/src/components/table-of-contents/index.js +++ b/packages/editor/src/components/table-of-contents/index.js @@ -61,6 +61,6 @@ function TableOfContents( * @param {boolean} props.repositionDropdown Whether to reposition the dropdown. * @param {Element.ref} ref The component's ref. * - * @return {JSX.Element} The rendered table of contents component. + * @return {React.ReactNode} The rendered table of contents component. */ export default forwardRef( TableOfContents ); diff --git a/packages/editor/src/components/template-part-menu-items/convert-to-template-part.js b/packages/editor/src/components/template-part-menu-items/convert-to-template-part.js index 4ae15d1dd178c0..9ce11772a34b7a 100644 --- a/packages/editor/src/components/template-part-menu-items/convert-to-template-part.js +++ b/packages/editor/src/components/template-part-menu-items/convert-to-template-part.js @@ -13,7 +13,7 @@ import { symbolFilled } from '@wordpress/icons'; /** * Internal dependencies */ -import CreateTemplatePartModal from '../create-template-part-modal'; +import { CreateTemplatePartModal } from '@wordpress/fields'; export default function ConvertToTemplatePart( { clientIds, blocks } ) { const [ isModalOpen, setIsModalOpen ] = useState( false ); diff --git a/packages/editor/src/components/template-part-menu-items/index.js b/packages/editor/src/components/template-part-menu-items/index.js index 0e126644d49938..52c50f91b3933c 100644 --- a/packages/editor/src/components/template-part-menu-items/index.js +++ b/packages/editor/src/components/template-part-menu-items/index.js @@ -27,25 +27,16 @@ export default function TemplatePartMenuItems() { } function TemplatePartConverterMenuItem( { clientIds, onClose } ) { - const { isContentOnly, blocks } = useSelect( + const { blocks } = useSelect( ( select ) => { - const { getBlocksByClientId, getBlockEditingMode } = - select( blockEditorStore ); + const { getBlocksByClientId } = select( blockEditorStore ); return { blocks: getBlocksByClientId( clientIds ), - isContentOnly: - clientIds.length === 1 && - getBlockEditingMode( clientIds[ 0 ] ) === 'contentOnly', }; }, [ clientIds ] ); - // Do not show the convert button if the block is in content-only mode. - if ( isContentOnly ) { - return null; - } - // Allow converting a single template part to standard blocks. if ( blocks.length === 1 && blocks[ 0 ]?.name === 'core/template-part' ) { return ( diff --git a/packages/editor/src/components/theme-support-check/index.js b/packages/editor/src/components/theme-support-check/index.js index 78fbde809a7088..1f29370d32199c 100644 --- a/packages/editor/src/components/theme-support-check/index.js +++ b/packages/editor/src/components/theme-support-check/index.js @@ -12,11 +12,11 @@ import { store as editorStore } from '../../store'; /** * Checks if the current theme supports specific features and renders the children if supported. * - * @param {Object} props The component props. - * @param {Element} props.children The children to render if the theme supports the specified features. - * @param {string|string[]} props.supportKeys The key(s) of the theme support(s) to check. + * @param {Object} props The component props. + * @param {React.ReactElement} props.children The children to render if the theme supports the specified features. + * @param {string|string[]} props.supportKeys The key(s) of the theme support(s) to check. * - * @return {JSX.Element|null} The rendered children if the theme supports the specified features, otherwise null. + * @return {React.ReactElement} The rendered children if the theme supports the specified features, otherwise null. */ export default function ThemeSupportCheck( { children, supportKeys } ) { const { postType, themeSupports } = useSelect( ( select ) => { diff --git a/packages/editor/src/components/time-to-read/index.js b/packages/editor/src/components/time-to-read/index.js index 5d748abc3049cb..21891273991a2c 100644 --- a/packages/editor/src/components/time-to-read/index.js +++ b/packages/editor/src/components/time-to-read/index.js @@ -23,7 +23,7 @@ const AVERAGE_READING_RATE = 189; /** * Component for showing Time To Read in Content. * - * @return {JSX.Element} The rendered TimeToRead component. + * @return {React.ReactNode} The rendered TimeToRead component. */ export default function TimeToRead() { const content = useSelect( diff --git a/packages/editor/src/components/unsaved-changes-warning/index.js b/packages/editor/src/components/unsaved-changes-warning/index.js index 49e2b7edf1f293..d04b1f36abcbb3 100644 --- a/packages/editor/src/components/unsaved-changes-warning/index.js +++ b/packages/editor/src/components/unsaved-changes-warning/index.js @@ -10,7 +10,7 @@ import { store as coreStore } from '@wordpress/core-data'; * Warns the user if there are unsaved changes before leaving the editor. * Compatible with Post Editor and Site Editor. * - * @return {Component} The component. + * @return {React.ReactNode} The component. */ export default function UnsavedChangesWarning() { const { __experimentalGetDirtyEntityRecords } = useSelect( coreStore ); diff --git a/packages/editor/src/components/visual-editor/edit-template-blocks-notification.js b/packages/editor/src/components/visual-editor/edit-template-blocks-notification.js index bacf1beb1abecd..b3f1f0dfc4bbff 100644 --- a/packages/editor/src/components/visual-editor/edit-template-blocks-notification.js +++ b/packages/editor/src/components/visual-editor/edit-template-blocks-notification.js @@ -19,7 +19,7 @@ import { store as editorStore } from '../../store'; * user is focusing on editing page content and clicks on a disabled template * block. * - Displays a 'Edit your template to edit this block' dialog when the user - * is focusing on editing page conetnt and double clicks on a disabled + * is focusing on editing page content and double clicks on a disabled * template block. * * @param {Object} props diff --git a/packages/editor/src/components/visual-editor/index.js b/packages/editor/src/components/visual-editor/index.js index 795a4f983f1536..c726339d1e0495 100644 --- a/packages/editor/src/components/visual-editor/index.js +++ b/packages/editor/src/components/visual-editor/index.js @@ -337,7 +337,7 @@ function VisualEditor( { ! isPreview && // Disable resizing in mobile viewport. ! isMobileViewport && - // Dsiable resizing in zoomed-out mode. + // Disable resizing in zoomed-out mode. ! isZoomedOut; const iframeStyles = useMemo( () => { @@ -385,6 +385,7 @@ function VisualEditor( { 'has-padding': isFocusedEntity || enableResizing, 'is-resizable': enableResizing, 'is-iframed': ! disableIframe, + 'is-scrollable': disableIframe || deviceType !== 'Desktop', } ) } > @@ -433,7 +434,7 @@ function VisualEditor( { <div className={ clsx( 'editor-visual-editor__post-title-wrapper', - // The following class is only here for backward comapatibility + // The following class is only here for backward compatibility // some themes might be using it to style the post title. 'edit-post-visual-editor__post-title-wrapper', { diff --git a/packages/editor/src/components/visual-editor/style.scss b/packages/editor/src/components/visual-editor/style.scss index 63df28f0f1ba5a..04ca0215e4fc66 100644 --- a/packages/editor/src/components/visual-editor/style.scss +++ b/packages/editor/src/components/visual-editor/style.scss @@ -6,6 +6,9 @@ // when the iframe doesn't cover the whole canvas // like the "focused entities". background-color: $gray-300; + // Allows the height to fit the parent container and avoids parent scrolling contexts from + // having overflow due to popovers of block tools. + overflow: hidden; // This overrides the iframe background since it's applied again here // It also prevents some style glitches if `editor-visual-editor` @@ -25,12 +28,6 @@ padding: $grid-unit-30 $grid-unit-30 0; } - // In the iframed canvas this keeps extra scrollbars from appearing (when block toolbars overflow). In the - // legacy (non-iframed) canvas, overflow must not be hidden in order to maintain support for sticky positioning. - &.is-iframed { - overflow: hidden; - } - // The button element easily inherits styles that are meant for the editor style. // These rules enhance the specificity to reduce that inheritance. // This is duplicated in edit-site. @@ -44,4 +41,30 @@ padding: 6px; } } + + // The cases for this are non-iframed editor canvas or previewing devices. The block canvas is + // made the scrolling context. + &.is-scrollable .block-editor-block-canvas { + overflow: auto; + + // Applicable only when legacy (non-iframed). + > .block-editor-writing-flow { + display: flow-root; + min-height: 100%; + box-sizing: border-box; // Ensures that 100% min-height doesn’t create overflow. + } + + // Applicable only when iframed. These styles ensure that if the the iframe is + // given a fixed height and it’s taller than the viewport then scrolling is + // allowed. This is needed for device previews. + > .block-editor-iframe__container { + display: flex; + flex-direction: column; + + > .block-editor-iframe__scale-container { + flex: 1 0 fit-content; + display: flow-root; + } + } + } } diff --git a/packages/editor/src/components/word-count/index.js b/packages/editor/src/components/word-count/index.js index aab562b46b89ff..31eb4d6bfd8c35 100644 --- a/packages/editor/src/components/word-count/index.js +++ b/packages/editor/src/components/word-count/index.js @@ -13,7 +13,7 @@ import { store as editorStore } from '../../store'; /** * Renders the word count of the post content. * - * @return {JSX.Element|null} The rendered WordCount component. + * @return {React.ReactNode} The rendered WordCount component. */ export default function WordCount() { const content = useSelect( diff --git a/packages/editor/src/components/zoom-out-toggle/index.js b/packages/editor/src/components/zoom-out-toggle/index.js index 080a4c58578069..2646898a066fb9 100644 --- a/packages/editor/src/components/zoom-out-toggle/index.js +++ b/packages/editor/src/components/zoom-out-toggle/index.js @@ -20,13 +20,19 @@ import { isAppleOS } from '@wordpress/keycodes'; import { unlock } from '../../lock-unlock'; const ZoomOutToggle = ( { disabled } ) => { - const { isZoomOut, showIconLabels } = useSelect( ( select ) => ( { - isZoomOut: unlock( select( blockEditorStore ) ).isZoomOut(), - showIconLabels: select( preferencesStore ).get( - 'core', - 'showIconLabels' - ), - } ) ); + const { isZoomOut, showIconLabels, isDistractionFree } = useSelect( + ( select ) => ( { + isZoomOut: unlock( select( blockEditorStore ) ).isZoomOut(), + showIconLabels: select( preferencesStore ).get( + 'core', + 'showIconLabels' + ), + isDistractionFree: select( preferencesStore ).get( + 'core', + 'distractionFree' + ), + } ) + ); const { resetZoomLevel, setZoomLevel } = unlock( useDispatch( blockEditorStore ) @@ -52,13 +58,19 @@ const ZoomOutToggle = ( { disabled } ) => { }; }, [ registerShortcut, unregisterShortcut ] ); - useShortcut( 'core/editor/zoom', () => { - if ( isZoomOut ) { - resetZoomLevel(); - } else { - setZoomLevel( 'auto-scaled' ); + useShortcut( + 'core/editor/zoom', + () => { + if ( isZoomOut ) { + resetZoomLevel(); + } else { + setZoomLevel( 'auto-scaled' ); + } + }, + { + isDisabled: isDistractionFree, } - } ); + ); const handleZoomOut = () => { if ( isZoomOut ) { @@ -78,6 +90,7 @@ const ZoomOutToggle = ( { disabled } ) => { isPressed={ isZoomOut } size="compact" showTooltip={ ! showIconLabels } + className="editor-zoom-out-toggle" /> ); }; diff --git a/packages/editor/src/dataviews/actions/utils.ts b/packages/editor/src/dataviews/actions/utils.ts deleted file mode 100644 index 33a2be16397f3f..00000000000000 --- a/packages/editor/src/dataviews/actions/utils.ts +++ /dev/null @@ -1,64 +0,0 @@ -/** - * WordPress dependencies - */ -import { decodeEntities } from '@wordpress/html-entities'; - -/** - * Internal dependencies - */ -import { - TEMPLATE_ORIGINS, - TEMPLATE_PART_POST_TYPE, - TEMPLATE_POST_TYPE, -} from '../../store/constants'; - -import type { Post, TemplatePart, Template } from '../types'; - -export function isTemplate( post: Post ): post is Template { - return post.type === TEMPLATE_POST_TYPE; -} - -export function isTemplatePart( post: Post ): post is TemplatePart { - return post.type === TEMPLATE_PART_POST_TYPE; -} - -export function isTemplateOrTemplatePart( - p: Post -): p is Template | TemplatePart { - return p.type === TEMPLATE_POST_TYPE || p.type === TEMPLATE_PART_POST_TYPE; -} - -export function getItemTitle( item: Post ) { - if ( typeof item.title === 'string' ) { - return decodeEntities( item.title ); - } - if ( 'rendered' in item.title ) { - return decodeEntities( item.title.rendered ); - } - if ( 'raw' in item.title ) { - return decodeEntities( item.title.raw ); - } - return ''; -} - -/** - * Check if a template is removable. - * - * @param template The template entity to check. - * @return Whether the template is removable. - */ -export function isTemplateRemovable( template: Template | TemplatePart ) { - if ( ! template ) { - return false; - } - // In patterns list page we map the templates parts to a different object - // than the one returned from the endpoint. This is why we need to check for - // two props whether is custom or has a theme file. - return ( - [ template.source, template.source ].includes( - TEMPLATE_ORIGINS.custom - ) && - ! Boolean( template.type === 'wp_template' && template?.plugin ) && - ! template.has_theme_file - ); -} diff --git a/packages/editor/src/dataviews/api.js b/packages/editor/src/dataviews/api.js index 130a69bba754c7..e03b9ef35ac758 100644 --- a/packages/editor/src/dataviews/api.js +++ b/packages/editor/src/dataviews/api.js @@ -11,6 +11,7 @@ import { store as editorStore } from '../store'; /** * @typedef {import('@wordpress/dataviews').Action} Action + * @typedef {import('@wordpress/dataviews').Field} Field */ /** @@ -53,3 +54,43 @@ export function unregisterEntityAction( kind, name, actionId ) { _unregisterEntityAction( kind, name, actionId ); } } + +/** + * Registers a new DataViews field. + * + * This is an experimental API and is subject to change. + * it's only available in the Gutenberg plugin for now. + * + * @param {string} kind Entity kind. + * @param {string} name Entity name. + * @param {Field} config Field configuration. + */ +export function registerEntityField( kind, name, config ) { + const { registerEntityField: _registerEntityField } = unlock( + dispatch( editorStore ) + ); + + if ( globalThis.IS_GUTENBERG_PLUGIN ) { + _registerEntityField( kind, name, config ); + } +} + +/** + * Unregisters a DataViews field. + * + * This is an experimental API and is subject to change. + * it's only available in the Gutenberg plugin for now. + * + * @param {string} kind Entity kind. + * @param {string} name Entity name. + * @param {string} fieldId Field ID. + */ +export function unregisterEntityField( kind, name, fieldId ) { + const { unregisterEntityField: _unregisterEntityField } = unlock( + dispatch( editorStore ) + ); + + if ( globalThis.IS_GUTENBERG_PLUGIN ) { + _unregisterEntityField( kind, name, fieldId ); + } +} diff --git a/packages/editor/src/dataviews/fields/content-preview/content-preview-view.tsx b/packages/editor/src/dataviews/fields/content-preview/content-preview-view.tsx new file mode 100644 index 00000000000000..0a5b8387163083 --- /dev/null +++ b/packages/editor/src/dataviews/fields/content-preview/content-preview-view.tsx @@ -0,0 +1,108 @@ +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; +import { + BlockPreview, + privateApis as blockEditorPrivateApis, + // @ts-ignore +} from '@wordpress/block-editor'; +import type { BasePost } from '@wordpress/fields'; +import { useSelect } from '@wordpress/data'; +import { useEntityBlockEditor, store as coreStore } from '@wordpress/core-data'; + +/** + * Internal dependencies + */ +import { EditorProvider } from '../../../components/provider'; +import { unlock } from '../../../lock-unlock'; +// @ts-ignore +import { store as editorStore } from '../../../store'; + +const { useGlobalStyle } = unlock( blockEditorPrivateApis ); + +function PostPreviewContainer( { + template, + post, +}: { + template: any; + post: any; +} ) { + const [ backgroundColor = 'white' ] = useGlobalStyle( 'color.background' ); + const [ postBlocks ] = useEntityBlockEditor( 'postType', post.type, { + id: post.id, + } ); + const [ templateBlocks ] = useEntityBlockEditor( + 'postType', + template?.type, + { + id: template?.id, + } + ); + const blocks = template && templateBlocks ? templateBlocks : postBlocks; + const isEmpty = ! blocks?.length; + return ( + <div + className="editor-fields-content-preview" + style={ { + backgroundColor, + } } + > + { isEmpty && ( + <span className="editor-fields-content-preview__empty"> + { __( 'Empty content' ) } + </span> + ) } + { ! isEmpty && ( + <BlockPreview.Async> + <BlockPreview blocks={ blocks } /> + </BlockPreview.Async> + ) } + </div> + ); +} + +export default function PostPreviewView( { item }: { item: BasePost } ) { + const { settings, template } = useSelect( + ( select ) => { + const { canUser, getPostType, getTemplateId, getEntityRecord } = + unlock( select( coreStore ) ); + const canViewTemplate = canUser( 'read', { + kind: 'postType', + name: 'wp_template', + } ); + const _settings = select( editorStore ).getEditorSettings(); + // @ts-ignore + const supportsTemplateMode = _settings.supportsTemplateMode; + const isViewable = getPostType( item.type )?.viewable ?? false; + + const templateId = + supportsTemplateMode && isViewable && canViewTemplate + ? getTemplateId( item.type, item.id ) + : null; + return { + settings: _settings, + template: templateId + ? getEntityRecord( 'postType', 'wp_template', templateId ) + : undefined, + }; + }, + [ item.type, item.id ] + ); + // Wrap everything in a block editor provider to ensure 'styles' that are needed + // for the previews are synced between the site editor store and the block editor store. + // Additionally we need to have the `__experimentalBlockPatterns` setting in order to + // render patterns inside the previews. + // TODO: Same approach is used in the patterns list and it becomes obvious that some of + // the block editor settings are needed in context where we don't have the block editor. + // Explore how we can solve this in a better way. + return ( + <EditorProvider + post={ item } + settings={ settings } + __unstableTemplate={ template } + > + <PostPreviewContainer template={ template } post={ item } /> + </EditorProvider> + ); +} diff --git a/packages/editor/src/dataviews/fields/content-preview/index.tsx b/packages/editor/src/dataviews/fields/content-preview/index.tsx new file mode 100644 index 00000000000000..5dadc599ea232e --- /dev/null +++ b/packages/editor/src/dataviews/fields/content-preview/index.tsx @@ -0,0 +1,21 @@ +/** + * WordPress dependencies + */ +import type { Field } from '@wordpress/dataviews'; +import type { BasePost } from '@wordpress/fields'; +import { __ } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import PostPreviewView from './content-preview-view'; + +const postPreviewField: Field< BasePost > = { + type: 'media', + id: 'content-preview', + label: __( 'Content preview' ), + render: PostPreviewView, + enableSorting: false, +}; + +export default postPreviewField; diff --git a/packages/editor/src/dataviews/fields/content-preview/style.scss b/packages/editor/src/dataviews/fields/content-preview/style.scss new file mode 100644 index 00000000000000..4f204dc5108c9b --- /dev/null +++ b/packages/editor/src/dataviews/fields/content-preview/style.scss @@ -0,0 +1,21 @@ +.editor-fields-content-preview { + display: flex; + flex-direction: column; + height: 100%; + border-radius: $radius-medium; + + .dataviews-view-table & { + width: 96px; + flex-grow: 0; + } + + .block-editor-block-preview__container, + .editor-fields-content-preview__empty { + margin-top: auto; + margin-bottom: auto; + } +} + +.editor-fields-content-preview__empty { + text-align: center; +} diff --git a/packages/editor/src/dataviews/store/private-actions.ts b/packages/editor/src/dataviews/store/private-actions.ts index 10f2b9ce872d5a..82c2c8911c7c96 100644 --- a/packages/editor/src/dataviews/store/private-actions.ts +++ b/packages/editor/src/dataviews/store/private-actions.ts @@ -2,15 +2,9 @@ * WordPress dependencies */ import { store as coreStore } from '@wordpress/core-data'; -import type { Action } from '@wordpress/dataviews'; +import type { Action, Field } from '@wordpress/dataviews'; import { doAction } from '@wordpress/hooks'; - -/** - * Internal dependencies - */ -import type { PostType } from '../types'; -import { store as editorStore } from '../../store'; -import { unlock } from '../../lock-unlock'; +import type { PostType } from '@wordpress/fields'; import { viewPost, viewPostRevisions, @@ -24,8 +18,28 @@ import { renamePost, resetPost, deletePost, + duplicateTemplatePart, + featuredImageField, + dateField, + parentField, + passwordField, + commentStatusField, + slugField, + statusField, + authorField, + titleField, + templateField, + templateTitleField, + pageTitleField, + patternTitleField, } from '@wordpress/fields'; -import duplicateTemplatePart from '../actions/duplicate-template-part'; + +/** + * Internal dependencies + */ +import { store as editorStore } from '../../store'; +import postPreviewField from '../fields/content-preview'; +import { unlock } from '../../lock-unlock'; export function registerEntityAction< Item >( kind: string, @@ -53,6 +67,32 @@ export function unregisterEntityAction( }; } +export function registerEntityField< Item >( + kind: string, + name: string, + config: Field< Item > +) { + return { + type: 'REGISTER_ENTITY_FIELD' as const, + kind, + name, + config, + }; +} + +export function unregisterEntityField( + kind: string, + name: string, + fieldId: string +) { + return { + type: 'UNREGISTER_ENTITY_FIELD' as const, + kind, + name, + fieldId, + }; +} + export function setIsReady( kind: string, name: string ) { return { type: 'SET_IS_READY' as const, @@ -61,7 +101,7 @@ export function setIsReady( kind: string, name: string ) { }; } -export const registerPostTypeActions = +export const registerPostTypeSchema = ( postType: string ) => async ( { registry }: { registry: any } ) => { const isReady = unlock( registry.select( editorStore ) ).isEntityReady( @@ -93,7 +133,7 @@ export const registerPostTypeActions = const actions = [ postTypeConfig.viewable ? viewPost : undefined, - !! postTypeConfig?.supports?.revisions + !! postTypeConfig.supports?.revisions ? viewPostRevisions : undefined, // @ts-ignore @@ -113,7 +153,7 @@ export const registerPostTypeActions = ? duplicatePattern : undefined, postTypeConfig.supports?.title ? renamePost : undefined, - postTypeConfig?.supports?.[ 'page-attributes' ] + postTypeConfig.supports?.[ 'page-attributes' ] ? reorderPage : undefined, postTypeConfig.slug === 'wp_block' ? exportPattern : undefined, @@ -122,20 +162,54 @@ export const registerPostTypeActions = deletePost, trashPost, permanentlyDeletePost, - ]; + ].filter( Boolean ); + + const fields = [ + postTypeConfig.supports?.thumbnail && + currentTheme?.theme_supports?.[ 'post-thumbnails' ] && + featuredImageField, + postTypeConfig.supports?.author && authorField, + statusField, + dateField, + slugField, + postTypeConfig.supports?.[ 'page-attributes' ] && parentField, + postTypeConfig.supports?.comments && commentStatusField, + templateField, + passwordField, + postTypeConfig.supports?.editor && + postTypeConfig.viewable && + postPreviewField, + ].filter( Boolean ); + if ( postTypeConfig.supports?.title ) { + let _titleField; + if ( postType === 'page' ) { + _titleField = pageTitleField; + } else if ( postType === 'wp_template' ) { + _titleField = templateTitleField; + } else if ( postType === 'wp_block' ) { + _titleField = patternTitleField; + } else { + _titleField = titleField; + } + fields.push( _titleField ); + } registry.batch( () => { actions.forEach( ( action ) => { - if ( ! action ) { - return; - } unlock( registry.dispatch( editorStore ) ).registerEntityAction( 'postType', postType, action ); } ); + fields.forEach( ( field ) => { + unlock( registry.dispatch( editorStore ) ).registerEntityField( + 'postType', + postType, + field + ); + } ); } ); - doAction( 'core.registerPostTypeActions', postType ); + doAction( 'core.registerPostTypeSchema', postType ); }; diff --git a/packages/editor/src/dataviews/store/private-selectors.ts b/packages/editor/src/dataviews/store/private-selectors.ts index 117c5b30966a39..e1daeb4032fc21 100644 --- a/packages/editor/src/dataviews/store/private-selectors.ts +++ b/packages/editor/src/dataviews/store/private-selectors.ts @@ -9,6 +9,10 @@ export function getEntityActions( state: State, kind: string, name: string ) { return state.actions[ kind ]?.[ name ] ?? EMPTY_ARRAY; } +export function getEntityFields( state: State, kind: string, name: string ) { + return state.fields[ kind ]?.[ name ] ?? EMPTY_ARRAY; +} + export function isEntityReady( state: State, kind: string, name: string ) { return state.isReady[ kind ]?.[ name ]; } diff --git a/packages/editor/src/dataviews/store/reducer.ts b/packages/editor/src/dataviews/store/reducer.ts index 9124b74f02860a..94d7f2e6c4f190 100644 --- a/packages/editor/src/dataviews/store/reducer.ts +++ b/packages/editor/src/dataviews/store/reducer.ts @@ -2,17 +2,21 @@ * WordPress dependencies */ import { combineReducers } from '@wordpress/data'; -import type { Action } from '@wordpress/dataviews'; +import type { Action, Field } from '@wordpress/dataviews'; type ReduxAction = | ReturnType< typeof import('./private-actions').registerEntityAction > | ReturnType< typeof import('./private-actions').unregisterEntityAction > + | ReturnType< typeof import('./private-actions').registerEntityField > + | ReturnType< typeof import('./private-actions').unregisterEntityField > | ReturnType< typeof import('./private-actions').setIsReady >; export type ActionState = Record< string, Record< string, Action< any >[] > >; +export type FieldsState = Record< string, Record< string, Field< any >[] > >; export type ReadyState = Record< string, Record< string, boolean > >; export type State = { actions: ActionState; + fields: FieldsState; isReady: ReadyState; }; @@ -64,7 +68,40 @@ function actions( state: ActionState = {}, action: ReduxAction ) { return state; } +function fields( state: FieldsState = {}, action: ReduxAction ) { + switch ( action.type ) { + case 'REGISTER_ENTITY_FIELD': + return { + ...state, + [ action.kind ]: { + ...state[ action.kind ], + [ action.name ]: [ + ...( + state[ action.kind ]?.[ action.name ] ?? [] + ).filter( + ( _field ) => _field.id !== action.config.id + ), + action.config, + ], + }, + }; + case 'UNREGISTER_ENTITY_FIELD': + return { + ...state, + [ action.kind ]: { + ...state[ action.kind ], + [ action.name ]: ( + state[ action.kind ]?.[ action.name ] ?? [] + ).filter( ( _field ) => _field.id !== action.fieldId ), + }, + }; + } + + return state; +} + export default combineReducers( { actions, + fields, isReady, } ); diff --git a/packages/editor/src/dataviews/types.ts b/packages/editor/src/dataviews/types.ts deleted file mode 100644 index 664c2dd417201c..00000000000000 --- a/packages/editor/src/dataviews/types.ts +++ /dev/null @@ -1,85 +0,0 @@ -type PostStatus = - | 'published' - | 'draft' - | 'pending' - | 'private' - | 'future' - | 'auto-draft' - | 'trash'; - -export interface CommonPost { - status?: PostStatus; - title: string | { rendered: string } | { raw: string }; - content: string | { raw: string; rendered: string }; - type: string; - id: string | number; - blocks?: Object[]; - _links?: Links; -} - -interface Links { - 'predecessor-version'?: { href: string; id: number }[]; - 'version-history'?: { href: string; count: number }[]; - [ key: string ]: { href: string }[] | undefined; -} - -export interface BasePost extends CommonPost { - comment_status?: 'open' | 'closed'; - excerpt?: string | { raw: string; rendered: string }; - meta?: Record< string, any >; - parent?: number; - password?: string; - template?: string; - format?: string; - featured_media?: number; - menu_order?: number; - ping_status?: 'open' | 'closed'; - link?: string; -} - -export interface Template extends CommonPost { - type: 'wp_template'; - is_custom: boolean; - source: string; - origin: string; - plugin?: string; - has_theme_file: boolean; - id: string; -} - -export interface TemplatePart extends CommonPost { - type: 'wp_template_part'; - source: string; - origin: string; - has_theme_file: boolean; - id: string; - area: string; -} - -export interface Pattern extends CommonPost { - slug: string; - title: { raw: string }; - wp_pattern_sync_status: string; -} - -export type Post = Template | TemplatePart | Pattern | BasePost; - -export type PostWithPermissions = Post & { - permissions: { - delete: boolean; - update: boolean; - }; -}; - -export interface PostType { - slug: string; - viewable: boolean; - supports?: { - 'page-attributes'?: boolean; - title?: boolean; - revisions?: boolean; - }; -} - -// Will be unnecessary after typescript 5.0 upgrade. -export type CoreDataError = { message?: string; code?: string }; diff --git a/packages/editor/src/private-apis.js b/packages/editor/src/private-apis.js index f9a6d4d17904ee..11083eb6ab8a45 100644 --- a/packages/editor/src/private-apis.js +++ b/packages/editor/src/private-apis.js @@ -10,12 +10,12 @@ import { lock } from './lock-unlock'; import { EntitiesSavedStatesExtensible } from './components/entities-saved-states'; import EditorContentSlotFill from './components/editor-interface/content-slot-fill'; import BackButton from './components/header/back-button'; -import CreateTemplatePartModal from './components/create-template-part-modal'; import Editor from './components/editor'; import PluginPostExcerpt from './components/post-excerpt/plugin'; import PostCardPanel from './components/post-card-panel'; import PreferencesModal from './components/preferences-modal'; import { usePostActions } from './components/post-actions/actions'; +import usePostFields from './components/post-fields'; import ToolsMoreMenuGroup from './components/more-menu/tools-more-menu-group'; import ViewMoreMenuGroup from './components/more-menu/view-more-menu-group'; import ResizableEditor from './components/resizable-editor'; @@ -23,7 +23,9 @@ import { mergeBaseAndUserConfigs, GlobalStylesProvider, } from './components/global-styles-provider'; +import { CreateTemplatePartModal } from '@wordpress/fields'; import { registerCoreBlockBindingsSources } from './bindings/api'; +import { getTemplateInfo } from './utils/get-template-info'; const { store: interfaceStore, ...remainingInterfaceApis } = interfaceApis; @@ -40,11 +42,12 @@ lock( privateApis, { PostCardPanel, PreferencesModal, usePostActions, + usePostFields, ToolsMoreMenuGroup, ViewMoreMenuGroup, ResizableEditor, registerCoreBlockBindingsSources, - + getTemplateInfo, // This is a temporary private API while we're updating the site editor to use EditorProvider. interfaceStore, ...remainingInterfaceApis, diff --git a/packages/editor/src/store/actions.js b/packages/editor/src/store/actions.js index 7ff23cb49f4983..6a628512f62bf7 100644 --- a/packages/editor/src/store/actions.js +++ b/packages/editor/src/store/actions.js @@ -23,7 +23,6 @@ import { __ } from '@wordpress/i18n'; /** * Internal dependencies */ -import { TRASH_POST_NOTICE_ID } from './constants'; import { localAutosaveSet } from './local-autosave'; import { getNotificationArgumentsForSaveSuccess, @@ -35,9 +34,9 @@ import { unlock } from '../lock-unlock'; * Returns an action generator used in signalling that editor has initialized with * the specified post object and editor settings. * - * @param {Object} post Post object. - * @param {Object} edits Initial edited attributes object. - * @param {Array?} template Block Template. + * @param {Object} post Post object. + * @param {Object} edits Initial edited attributes object. + * @param {Array} [template] Block Template. */ export const setupEditor = ( post, edits, template ) => @@ -157,8 +156,8 @@ export function setEditedPost( postType, postId ) { * Returns an action object used in signalling that attributes of the post have * been edited. * - * @param {Object} edits Post attributes to edit. - * @param {Object} options Options for the edit. + * @param {Object} edits Post attributes to edit. + * @param {Object} [options] Options for the edit. * * @example * ```js @@ -195,7 +194,7 @@ export const editPost = /** * Action for saving the current post in the editor. * - * @param {Object} options + * @param {Object} [options] */ export const savePost = ( options = {} ) => @@ -347,7 +346,6 @@ export const trashPost = const postType = await registry .resolveSelect( coreStore ) .getPostType( postTypeSlug ); - registry.dispatch( noticesStore ).removeNotice( TRASH_POST_NOTICE_ID ); const { rest_base: restBase, rest_namespace: restNamespace = 'wp/v2' } = postType; dispatch( { type: 'REQUEST_POST_DELETE_START' } ); @@ -375,7 +373,8 @@ export const trashPost = * autosaving (e.g. on the Web, the post might be committed to Session * Storage). * - * @param {Object?} options Extra flags to identify the autosave. + * @param {Object} [options] Extra flags to identify the autosave. + * @param {boolean} [options.local] Whether to perform a local autosave. */ export const autosave = ( { local = false, ...options } = {} ) => @@ -598,8 +597,8 @@ export function unlockPostAutosaving( lockName ) { /** * Returns an action object used to signal that the blocks have been updated. * - * @param {Array} blocks Block Array. - * @param {?Object} options Optional options. + * @param {Array} blocks Block Array. + * @param {Object} [options] Optional options. */ export const resetEditorBlocks = ( blocks, options = {} ) => @@ -834,6 +833,9 @@ export const toggleDistractionFree = .set( 'core', 'fixedToolbar', true ); dispatch.setIsInserterOpened( false ); dispatch.setIsListViewOpened( false ); + unlock( + registry.dispatch( blockEditorStore ) + ).resetZoomLevel(); } ); } registry.batch( () => { diff --git a/packages/editor/src/store/constants.ts b/packages/editor/src/store/constants.ts index 73d6a104370c37..2cb0903453466e 100644 --- a/packages/editor/src/store/constants.ts +++ b/packages/editor/src/store/constants.ts @@ -11,8 +11,6 @@ export const EDIT_MERGE_PROPERTIES = new Set( [ 'meta' ] ); */ export const STORE_NAME = 'core/editor'; -export const SAVE_POST_NOTICE_ID = 'SAVE_POST_NOTICE_ID'; -export const TRASH_POST_NOTICE_ID = 'TRASH_POST_NOTICE_ID'; export const PERMALINK_POSTNAME_REGEX = /%(?:postname|pagename)%/; export const ONE_MINUTE_IN_MS = 60 * 1000; export const AUTOSAVE_PROPERTIES = [ 'title', 'excerpt', 'content' ]; diff --git a/packages/editor/src/store/private-actions.js b/packages/editor/src/store/private-actions.js index 74c1f1ea100b37..6a83b3ca0b4032 100644 --- a/packages/editor/src/store/private-actions.js +++ b/packages/editor/src/store/private-actions.js @@ -34,7 +34,7 @@ export function setCurrentTemplateId( id ) { /** * Create a block based template. * - * @param {Object?} template Template to create and assign. + * @param {?Object} template Template to create and assign. */ export const createTemplate = ( template ) => @@ -388,7 +388,7 @@ export const removeTemplates = } ) ); - // If all the promises were fulfilled with sucess. + // If all the promises were fulfilled with success. if ( promiseResult.every( ( { status } ) => status === 'fulfilled' ) ) { let successMessage; diff --git a/packages/editor/src/store/private-selectors.js b/packages/editor/src/store/private-selectors.js index 9bc065436c10bb..73bf19f3d99937 100644 --- a/packages/editor/src/store/private-selectors.js +++ b/packages/editor/src/store/private-selectors.js @@ -20,15 +20,13 @@ import { store as coreStore } from '@wordpress/core-data'; /** * Internal dependencies */ -import { - getRenderingMode, - getCurrentPost, - __experimentalGetDefaultTemplatePartAreas, -} from './selectors'; +import { getRenderingMode, getCurrentPost } from './selectors'; import { getEntityActions as _getEntityActions, + getEntityFields as _getEntityFields, isEntityReady as _isEntityReady, } from '../dataviews/store/private-selectors'; +import { getTemplatePartIcon } from '../utils'; const EMPTY_INSERTION_POINT = { rootClientId: undefined, @@ -100,11 +98,21 @@ export const getPostIcon = createRegistrySelector( postType === 'wp_template_part' || postType === 'wp_template' ) { - return ( - __experimentalGetDefaultTemplatePartAreas( state ).find( - ( item ) => options.area === item.area - )?.icon || layout + const templateAreas = + select( coreStore ).getEntityRecord( + 'root', + '__unstableBase' + )?.default_template_part_areas || []; + + const areaData = templateAreas.find( + ( item ) => options.area === item.area ); + + if ( areaData?.icon ) { + return getTemplatePartIcon( areaData.icon ); + } + + return layout; } if ( CARD_ICONS[ postType ] ) { return CARD_ICONS[ postType ]; @@ -171,6 +179,10 @@ export function isEntityReady( state, ...args ) { return _isEntityReady( state.dataviews, ...args ); } +export function getEntityFields( state, ...args ) { + return _getEntityFields( state.dataviews, ...args ); +} + /** * Similar to getBlocksByName in @wordpress/block-editor, but only returns the top-most * blocks that aren't descendants of the query block. diff --git a/packages/editor/src/store/reducer.native.js b/packages/editor/src/store/reducer.native.js index 7566dfc5dfd038..fbf6c968f57d08 100644 --- a/packages/editor/src/store/reducer.native.js +++ b/packages/editor/src/store/reducer.native.js @@ -9,6 +9,7 @@ import { combineReducers } from '@wordpress/data'; import { postId, postType, + renderingMode, saving, postLock, postSavingLock, @@ -82,6 +83,7 @@ export default combineReducers( { postId, postType, postTitle, + renderingMode, saving, postLock, postSavingLock, diff --git a/packages/editor/src/store/selectors.js b/packages/editor/src/store/selectors.js index db452be5c17abb..4ab0a47210353b 100644 --- a/packages/editor/src/store/selectors.js +++ b/packages/editor/src/store/selectors.js @@ -12,7 +12,6 @@ import { addQueryArgs, cleanForSlug } from '@wordpress/url'; import { createSelector, createRegistrySelector } from '@wordpress/data'; import deprecated from '@wordpress/deprecated'; import { Platform } from '@wordpress/element'; -import { layout } from '@wordpress/icons'; import { store as blockEditorStore } from '@wordpress/block-editor'; import { store as coreStore } from '@wordpress/core-data'; import { store as preferencesStore } from '@wordpress/preferences'; @@ -29,6 +28,7 @@ import { import { getPostRawValue } from './reducer'; import { getTemplatePartIcon } from '../utils/get-template-part-icon'; import { unlock } from '../lock-unlock'; +import { getTemplateInfo } from '../utils/get-template-info'; /** * Shared reference to an empty object for cases where it is important to avoid @@ -206,7 +206,7 @@ export function getCurrentPostId( state ) { * * @param {Object} state Global application state. * - * @return {string?} Template ID. + * @return {?string} Template ID. */ export function getCurrentTemplateId( state ) { return state.templateId; @@ -1702,16 +1702,20 @@ export const getBlockListSettings = getBlockEditorSelector( 'getBlockListSettings' ); -/** - * Returns the default template types. - * - * @param {Object} state Global application state. - * - * @return {Object} The template types. - */ -export function __experimentalGetDefaultTemplateTypes( state ) { - return getEditorSettings( state )?.defaultTemplateTypes; -} +export const __experimentalGetDefaultTemplateTypes = createRegistrySelector( + ( select ) => () => { + deprecated( + "select('core/editor').__experimentalGetDefaultTemplateTypes", + { + since: '6.8', + alternative: + "select('core/core-data').getEntityRecord( 'root', '__unstableBase' )?.default_template_types", + } + ); + return select( coreStore ).getEntityRecord( 'root', '__unstableBase' ) + ?.default_template_types; + } +); /** * Returns the default template part areas. @@ -1720,15 +1724,26 @@ export function __experimentalGetDefaultTemplateTypes( state ) { * * @return {Array} The template part areas. */ -export const __experimentalGetDefaultTemplatePartAreas = createSelector( - ( state ) => { - const areas = - getEditorSettings( state )?.defaultTemplatePartAreas ?? []; - return areas.map( ( item ) => { - return { ...item, icon: getTemplatePartIcon( item.icon ) }; - } ); - }, - ( state ) => [ getEditorSettings( state )?.defaultTemplatePartAreas ] +export const __experimentalGetDefaultTemplatePartAreas = createRegistrySelector( + ( select ) => + createSelector( () => { + deprecated( + "select('core/editor').__experimentalGetDefaultTemplatePartAreas", + { + since: '6.8', + alternative: + "select('core/core-data').getEntityRecord( 'root', '__unstableBase' )?.default_template_part_areas", + } + ); + + const areas = + select( coreStore ).getEntityRecord( 'root', '__unstableBase' ) + ?.default_template_part_areas || []; + + return areas.map( ( item ) => { + return { ...item, icon: getTemplatePartIcon( item.icon ) }; + } ); + } ) ); /** @@ -1739,20 +1754,30 @@ export const __experimentalGetDefaultTemplatePartAreas = createSelector( * * @return {Object} The template type. */ -export const __experimentalGetDefaultTemplateType = createSelector( - ( state, slug ) => { - const templateTypes = __experimentalGetDefaultTemplateTypes( state ); - if ( ! templateTypes ) { - return EMPTY_OBJECT; - } +export const __experimentalGetDefaultTemplateType = createRegistrySelector( + ( select ) => + createSelector( ( state, slug ) => { + deprecated( + "select('core/editor').__experimentalGetDefaultTemplateType", + { + since: '6.8', + } + ); + const templateTypes = select( coreStore ).getEntityRecord( + 'root', + '__unstableBase' + )?.default_template_types; + + if ( ! templateTypes ) { + return EMPTY_OBJECT; + } - return ( - Object.values( templateTypes ).find( - ( type ) => type.slug === slug - ) ?? EMPTY_OBJECT - ); - }, - ( state ) => [ __experimentalGetDefaultTemplateTypes( state ) ] + return ( + Object.values( templateTypes ).find( + ( type ) => type.slug === slug + ) ?? EMPTY_OBJECT + ); + } ) ); /** @@ -1763,38 +1788,31 @@ export const __experimentalGetDefaultTemplateType = createSelector( * @param {Object} template The template for which we need information. * @return {Object} Information about the template, including title, description, and icon. */ -export const __experimentalGetTemplateInfo = createSelector( - ( state, template ) => { - if ( ! template ) { - return EMPTY_OBJECT; - } +export const __experimentalGetTemplateInfo = createRegistrySelector( + ( select ) => + createSelector( ( state, template ) => { + deprecated( "select('core/editor').__experimentalGetTemplateInfo", { + since: '6.8', + } ); - const { description, slug, title, area } = template; - const { title: defaultTitle, description: defaultDescription } = - __experimentalGetDefaultTemplateType( state, slug ); + if ( ! template ) { + return EMPTY_OBJECT; + } - const templateTitle = - typeof title === 'string' ? title : title?.rendered; - const templateDescription = - typeof description === 'string' ? description : description?.raw; - const templateIcon = - __experimentalGetDefaultTemplatePartAreas( state ).find( - ( item ) => area === item.area - )?.icon || layout; + const templateTypes = + select( coreStore ).getEntityRecord( 'root', '__unstableBase' ) + ?.default_template_types || []; - return { - title: - templateTitle && templateTitle !== slug - ? templateTitle - : defaultTitle || slug, - description: templateDescription || defaultDescription, - icon: templateIcon, - }; - }, - ( state ) => [ - __experimentalGetDefaultTemplateTypes( state ), - __experimentalGetDefaultTemplatePartAreas( state ), - ] + const templateAreas = + select( coreStore ).getEntityRecord( 'root', '__unstableBase' ) + ?.default_template_part_areas || []; + + return getTemplateInfo( { + template, + templateAreas, + templateTypes, + } ); + } ) ); /** diff --git a/packages/editor/src/store/test/selectors.js b/packages/editor/src/store/test/selectors.js index 1de25604ebd7e3..2a36fabcd2ec97 100644 --- a/packages/editor/src/store/test/selectors.js +++ b/packages/editor/src/store/test/selectors.js @@ -16,7 +16,6 @@ import { getBlockTypes, } from '@wordpress/blocks'; import { RawHTML } from '@wordpress/element'; -import { layout, footer, header } from '@wordpress/icons'; /** * Internal dependencies @@ -188,43 +187,11 @@ const { isPostAutosavingLocked, canUserUseUnfilteredHTML, getPostTypeLabel, - __experimentalGetDefaultTemplateType, - __experimentalGetDefaultTemplateTypes, - __experimentalGetTemplateInfo, - __experimentalGetDefaultTemplatePartAreas, isEditorPanelRemoved, isInserterOpened, isListViewOpened, } = selectors; -const defaultTemplateTypes = [ - { - title: 'Default (Index)', - description: 'Main template', - slug: 'index', - }, - { - title: '404 (Not Found)', - description: 'Applied when content cannot be found', - slug: '404', - }, -]; - -const defaultTemplatePartAreas = [ - { - area: 'header', - label: 'Header', - description: 'Some description of a header', - icon: 'header', - }, - { - area: 'footer', - label: 'Footer', - description: 'Some description of a footer', - icon: 'footer', - }, -]; - describe( 'selectors', () => { let cachedSelectors; @@ -2766,228 +2733,6 @@ describe( 'selectors', () => { } ); } ); - describe( '__experimentalGetDefaultTemplateTypes', () => { - const state = { editorSettings: { defaultTemplateTypes } }; - - it( 'returns undefined if there are no default template types', () => { - const emptyState = { editorSettings: {} }; - expect( - __experimentalGetDefaultTemplateTypes( emptyState ) - ).toBeUndefined(); - } ); - - it( 'returns a list of default template types if present in state', () => { - expect( - __experimentalGetDefaultTemplateTypes( state ) - ).toHaveLength( 2 ); - } ); - } ); - - describe( '__experimentalGetDefaultTemplatePartAreas', () => { - const state = { editorSettings: { defaultTemplatePartAreas } }; - - it( 'returns empty array if there are no default template part areas', () => { - const emptyState = { editorSettings: {} }; - expect( - __experimentalGetDefaultTemplatePartAreas( emptyState ) - ).toHaveLength( 0 ); - } ); - - it( 'returns a list of default template part areas if present in state', () => { - expect( - __experimentalGetDefaultTemplatePartAreas( state ) - ).toHaveLength( 2 ); - } ); - - it( 'assigns an icon to each area', () => { - const templatePartAreas = - __experimentalGetDefaultTemplatePartAreas( state ); - templatePartAreas.forEach( ( area ) => - expect( area.icon ).not.toBeNull() - ); - } ); - } ); - - describe( '__experimentalGetDefaultTemplateType', () => { - const state = { editorSettings: { defaultTemplateTypes } }; - - it( 'returns an empty object if there are no default template types', () => { - const emptyState = { editorSettings: {} }; - expect( - __experimentalGetDefaultTemplateType( emptyState, 'slug' ) - ).toEqual( {} ); - } ); - - it( 'returns an empty object if the requested slug is not found', () => { - expect( - __experimentalGetDefaultTemplateType( state, 'foobar' ) - ).toEqual( {} ); - } ); - - it( 'returns the requested default template type', () => { - expect( - __experimentalGetDefaultTemplateType( state, 'index' ) - ).toEqual( { - title: 'Default (Index)', - description: 'Main template', - slug: 'index', - } ); - } ); - - it( 'returns the requested default template type even when the slug is numeric', () => { - expect( - __experimentalGetDefaultTemplateType( state, '404' ) - ).toEqual( { - title: '404 (Not Found)', - description: 'Applied when content cannot be found', - slug: '404', - } ); - } ); - } ); - - describe( '__experimentalGetTemplateInfo', () => { - const state = { - editorSettings: { defaultTemplateTypes, defaultTemplatePartAreas }, - }; - - it( 'should return an empty object if no template is passed', () => { - expect( __experimentalGetTemplateInfo( state, null ) ).toEqual( - {} - ); - expect( __experimentalGetTemplateInfo( state, undefined ) ).toEqual( - {} - ); - expect( __experimentalGetTemplateInfo( state, false ) ).toEqual( - {} - ); - } ); - - it( 'should return the default title if none is defined on the template', () => { - expect( - __experimentalGetTemplateInfo( state, { slug: 'index' } ).title - ).toEqual( 'Default (Index)' ); - } ); - - it( 'should return the rendered title if defined on the template', () => { - expect( - __experimentalGetTemplateInfo( state, { - slug: 'index', - title: { rendered: 'test title' }, - } ).title - ).toEqual( 'test title' ); - } ); - - it( 'should return the slug if no title is found', () => { - expect( - __experimentalGetTemplateInfo( state, { - slug: 'not a real template', - } ).title - ).toEqual( 'not a real template' ); - } ); - - it( 'should return the default description if none is defined on the template', () => { - expect( - __experimentalGetTemplateInfo( state, { slug: 'index' } ) - .description - ).toEqual( 'Main template' ); - } ); - - it( 'should return the raw excerpt as description if defined on the template', () => { - expect( - __experimentalGetTemplateInfo( state, { - slug: 'index', - description: { raw: 'test description' }, - } ).description - ).toEqual( 'test description' ); - } ); - - it( 'should return a title, description, and icon', () => { - expect( - __experimentalGetTemplateInfo( state, { slug: 'index' } ) - ).toEqual( { - title: 'Default (Index)', - description: 'Main template', - icon: layout, - } ); - - expect( - __experimentalGetTemplateInfo( state, { - slug: 'index', - title: { rendered: 'test title' }, - } ) - ).toEqual( { - title: 'test title', - description: 'Main template', - icon: layout, - } ); - - expect( - __experimentalGetTemplateInfo( state, { - slug: 'index', - description: { raw: 'test description' }, - } ) - ).toEqual( { - title: 'Default (Index)', - description: 'test description', - icon: layout, - } ); - - expect( - __experimentalGetTemplateInfo( state, { - slug: 'index', - title: { rendered: 'test title' }, - description: { raw: 'test description' }, - } ) - ).toEqual( { - title: 'test title', - description: 'test description', - icon: layout, - } ); - } ); - - it( 'should return correct icon based on area', () => { - expect( - __experimentalGetTemplateInfo( state, { - slug: 'template part, area = uncategorized', - area: 'uncategorized', - } ) - ).toEqual( { - title: 'template part, area = uncategorized', - icon: layout, - } ); - - expect( - __experimentalGetTemplateInfo( state, { - slug: 'template part, area = invalid', - area: 'invalid', - } ) - ).toEqual( { - title: 'template part, area = invalid', - icon: layout, - } ); - - expect( - __experimentalGetTemplateInfo( state, { - slug: 'template part, area = header', - area: 'header', - } ) - ).toEqual( { - title: 'template part, area = header', - icon: header, - } ); - - expect( - __experimentalGetTemplateInfo( state, { - slug: 'template part, area = footer', - area: 'footer', - } ) - ).toEqual( { - title: 'template part, area = footer', - icon: footer, - } ); - } ); - } ); - describe( 'getPostTypeLabel', () => { it( 'should return the correct label for the current post type', () => { const postTypes = [ diff --git a/packages/editor/src/store/utils/notice-builder.js b/packages/editor/src/store/utils/notice-builder.js index 58fc9ca0d747eb..9e1230b2ea88c5 100644 --- a/packages/editor/src/store/utils/notice-builder.js +++ b/packages/editor/src/store/utils/notice-builder.js @@ -3,11 +3,6 @@ */ import { __ } from '@wordpress/i18n'; -/** - * Internal dependencies - */ -import { SAVE_POST_NOTICE_ID, TRASH_POST_NOTICE_ID } from '../constants'; - /** * Builds the arguments for a success notification dispatch. * @@ -68,7 +63,7 @@ export function getNotificationArgumentsForSaveSuccess( data ) { return [ noticeMessage, { - id: SAVE_POST_NOTICE_ID, + id: 'editor-save', type: 'snackbar', actions, }, @@ -113,7 +108,7 @@ export function getNotificationArgumentsForSaveFail( data ) { return [ noticeMessage, { - id: SAVE_POST_NOTICE_ID, + id: 'editor-save', }, ]; } @@ -131,7 +126,7 @@ export function getNotificationArgumentsForTrashFail( data ) { ? data.error.message : __( 'Trashing failed' ), { - id: TRASH_POST_NOTICE_ID, + id: 'editor-trash-fail', }, ]; } diff --git a/packages/editor/src/store/utils/test/notice-builder.js b/packages/editor/src/store/utils/test/notice-builder.js index e66a96259680f7..d97ec0f9f9483b 100644 --- a/packages/editor/src/store/utils/test/notice-builder.js +++ b/packages/editor/src/store/utils/test/notice-builder.js @@ -6,7 +6,6 @@ import { getNotificationArgumentsForSaveFail, getNotificationArgumentsForTrashFail, } from '../notice-builder'; -import { SAVE_POST_NOTICE_ID, TRASH_POST_NOTICE_ID } from '../../constants'; describe( 'getNotificationArgumentsForSaveSuccess()', () => { const postType = { @@ -27,7 +26,7 @@ describe( 'getNotificationArgumentsForSaveSuccess()', () => { }; const post = { ...previousPost }; const defaultExpectedAction = { - id: SAVE_POST_NOTICE_ID, + id: 'editor-save', actions: [], type: 'snackbar', }; @@ -106,7 +105,7 @@ describe( 'getNotificationArgumentsForSaveFail()', () => { const error = { code: '42', message: 'Something went wrong.' }; const post = { status: 'publish' }; const edits = { status: 'publish' }; - const defaultExpectedAction = { id: SAVE_POST_NOTICE_ID }; + const defaultExpectedAction = { id: 'editor-save' }; [ [ 'when error code is `rest_autosave_no_changes`', @@ -190,7 +189,7 @@ describe( 'getNotificationArgumentsForTrashFail()', () => { ].forEach( ( [ description, error, message ] ) => { // eslint-disable-next-line jest/valid-title it( description, () => { - const expectedValue = [ message, { id: TRASH_POST_NOTICE_ID } ]; + const expectedValue = [ message, { id: 'editor-trash-fail' } ]; expect( getNotificationArgumentsForTrashFail( { error } ) ).toEqual( expectedValue ); diff --git a/packages/editor/src/style.scss b/packages/editor/src/style.scss index b62ccd2c8ac71d..c3366d6aa2266f 100644 --- a/packages/editor/src/style.scss +++ b/packages/editor/src/style.scss @@ -1,10 +1,8 @@ @import "../../interface/src/style.scss"; @import "./components/autocompleters/style.scss"; -@import "./components/block-manager/style.scss"; @import "./components/collab-sidebar/style.scss"; @import "./components/collapsible-block-toolbar/style.scss"; -@import "./components/create-template-part-modal/style.scss"; @import "./components/block-settings-menu/style.scss"; @import "./components/blog-title/style.scss"; @import "./components/document-bar/style.scss"; @@ -35,7 +33,6 @@ @import "./components/post-publish-panel/style.scss"; @import "./components/post-saved-state/style.scss"; @import "./components/post-schedule/style.scss"; -@import "./components/post-slug/style.scss"; @import "./components/post-status/style.scss"; @import "./components/post-sticky/style.scss"; @import "./components/post-sync-status/style.scss"; @@ -57,3 +54,4 @@ @import "./components/table-of-contents/style.scss"; @import "./components/text-editor/style.scss"; @import "./components/visual-editor/style.scss"; +@import "./dataviews/fields/content-preview/style.scss"; diff --git a/packages/editor/src/utils/get-item-title.js b/packages/editor/src/utils/get-item-title.js new file mode 100644 index 00000000000000..86929c27408a81 --- /dev/null +++ b/packages/editor/src/utils/get-item-title.js @@ -0,0 +1,25 @@ +/** + * WordPress dependencies + */ +import { decodeEntities } from '@wordpress/html-entities'; + +/** + * Helper function to get the title of a post item. + * This is duplicated from the `@wordpress/fields` package. + * `packages/fields/src/actions/utils.ts` + * + * @param {Object} item The post item. + * @return {string} The title of the item, or an empty string if the title is not found. + */ +export function getItemTitle( item ) { + if ( typeof item.title === 'string' ) { + return decodeEntities( item.title ); + } + if ( item.title && 'rendered' in item.title ) { + return decodeEntities( item.title.rendered ); + } + if ( item.title && 'raw' in item.title ) { + return decodeEntities( item.title.raw ); + } + return ''; +} diff --git a/packages/editor/src/utils/get-template-info.js b/packages/editor/src/utils/get-template-info.js new file mode 100644 index 00000000000000..bc84c06c9399d4 --- /dev/null +++ b/packages/editor/src/utils/get-template-info.js @@ -0,0 +1,52 @@ +/** + * WordPress dependencies + */ +import { layout } from '@wordpress/icons'; +/** + * Internal dependencies + */ +import { getTemplatePartIcon } from './get-template-part-icon'; +const EMPTY_OBJECT = {}; + +/** + * Helper function to retrieve the corresponding template info for a given template. + * @param {Object} params + * @param {Array} params.templateTypes + * @param {Array} [params.templateAreas] + * @param {Object} params.template + */ +export const getTemplateInfo = ( params ) => { + if ( ! params ) { + return EMPTY_OBJECT; + } + + const { templateTypes, templateAreas, template } = params; + + const { description, slug, title, area } = template; + + const { title: defaultTitle, description: defaultDescription } = + Object.values( templateTypes ).find( ( type ) => type.slug === slug ) ?? + EMPTY_OBJECT; + + const templateTitle = typeof title === 'string' ? title : title?.rendered; + const templateDescription = + typeof description === 'string' ? description : description?.raw; + + const templateAreasWithIcon = templateAreas?.map( ( item ) => ( { + ...item, + icon: getTemplatePartIcon( item.icon ), + } ) ); + + const templateIcon = + templateAreasWithIcon?.find( ( item ) => area === item.area )?.icon || + layout; + + return { + title: + templateTitle && templateTitle !== slug + ? templateTitle + : defaultTitle || slug, + description: templateDescription || defaultDescription, + icon: templateIcon, + }; +}; diff --git a/packages/editor/src/utils/media-sideload/index.js b/packages/editor/src/utils/media-sideload/index.js new file mode 100644 index 00000000000000..86fcdc688abf8f --- /dev/null +++ b/packages/editor/src/utils/media-sideload/index.js @@ -0,0 +1,13 @@ +/** + * WordPress dependencies + */ +import { privateApis } from '@wordpress/media-utils'; + +/** + * Internal dependencies + */ +import { unlock } from '../../lock-unlock'; + +const { sideloadMedia: mediaSideload } = unlock( privateApis ); + +export default mediaSideload; diff --git a/packages/editor/src/utils/media-sideload/index.native.js b/packages/editor/src/utils/media-sideload/index.native.js new file mode 100644 index 00000000000000..d84a912ec92de0 --- /dev/null +++ b/packages/editor/src/utils/media-sideload/index.native.js @@ -0,0 +1 @@ +export default function mediaSideload() {} diff --git a/packages/editor/src/utils/media-upload/index.js b/packages/editor/src/utils/media-upload/index.js index 6b39db2443cc33..0d970d91ce745c 100644 --- a/packages/editor/src/utils/media-upload/index.js +++ b/packages/editor/src/utils/media-upload/index.js @@ -27,6 +27,7 @@ const noop = () => {}; * @param {?number} $0.maxUploadFileSize Maximum upload size in bytes allowed for the site. * @param {Function} $0.onError Function called when an error happens. * @param {Function} $0.onFileChange Function called each time a file or a temporary representation of the file is available. + * @param {Function} $0.onSuccess Function called after the final representation of the file is available. */ export default function mediaUpload( { additionalData = {}, @@ -35,6 +36,7 @@ export default function mediaUpload( { maxUploadFileSize, onError = noop, onFileChange, + onSuccess, } ) { const { getCurrentPost, getEditorSettings } = select( editorStore ); const { @@ -77,8 +79,9 @@ export default function mediaUpload( { } else { clearSaveLock(); } - onFileChange( file ); + onFileChange?.( file ); }, + onSuccess, additionalData: { ...postData, ...additionalData, diff --git a/packages/editor/src/utils/pageTypeBadge.js b/packages/editor/src/utils/pageTypeBadge.js index 321b9caa17769e..3dc7d4750be739 100644 --- a/packages/editor/src/utils/pageTypeBadge.js +++ b/packages/editor/src/utils/pageTypeBadge.js @@ -5,19 +5,14 @@ import { __ } from '@wordpress/i18n'; import { useSelect } from '@wordpress/data'; import { store as coreStore } from '@wordpress/core-data'; -/** - * Internal dependencies - */ -import { store as editorStore } from '../store'; - /** * Custom hook to get the page type badge for the current post on edit site view. + * + * @param {number|string} postId postId of the current post being edited. */ -export default function usePageTypeBadge() { +export default function usePageTypeBadge( postId ) { const { isFrontPage, isPostsPage } = useSelect( ( select ) => { - const { getCurrentPostId } = select( editorStore ); const { canUser, getEditedEntityRecord } = select( coreStore ); - const postId = getCurrentPostId(); const siteSettings = canUser( 'read', { kind: 'root', name: 'site', @@ -25,9 +20,11 @@ export default function usePageTypeBadge() { ? getEditedEntityRecord( 'root', 'site' ) : undefined; + const _postId = parseInt( postId, 10 ); + return { - isFrontPage: siteSettings?.page_on_front === postId, - isPostsPage: siteSettings?.page_for_posts === postId, + isFrontPage: siteSettings?.page_on_front === _postId, + isPostsPage: siteSettings?.page_for_posts === _postId, }; } ); diff --git a/packages/editor/src/utils/test/get-template-info.js b/packages/editor/src/utils/test/get-template-info.js new file mode 100644 index 00000000000000..d0ffe15aa902ad --- /dev/null +++ b/packages/editor/src/utils/test/get-template-info.js @@ -0,0 +1,224 @@ +/** + * WordPress dependencies + */ +import { footer, header, layout } from '@wordpress/icons'; +/** + * Internal dependencies + */ +import { getTemplateInfo } from '../get-template-info'; + +describe( '__experimentalGetTemplateInfo', () => { + const defaultTemplateTypes = [ + { + title: 'Default (Index)', + description: 'Main template', + slug: 'index', + }, + { + title: '404 (Not Found)', + description: 'Applied when content cannot be found', + slug: '404', + }, + ]; + + const defaultTemplatePartAreas = [ + { + area: 'header', + label: 'Header', + description: 'Some description of a header', + icon: 'header', + }, + { + area: 'footer', + label: 'Footer', + description: 'Some description of a footer', + icon: 'footer', + }, + ]; + + it( 'should return an empty object if no template is passed', () => { + expect( getTemplateInfo( undefined ) ).toEqual( {} ); + expect( getTemplateInfo( null ) ).toEqual( {} ); + expect( getTemplateInfo( false ) ).toEqual( {} ); + } ); + + it( 'should return the default title if none is defined on the template', () => { + expect( + getTemplateInfo( { + templateAreas: defaultTemplatePartAreas, + templateTypes: defaultTemplateTypes, + template: { + slug: 'index', + }, + } ).title + ).toEqual( 'Default (Index)' ); + } ); + + it( 'should return the rendered title if defined on the template', () => { + expect( + getTemplateInfo( { + templateAreas: defaultTemplatePartAreas, + templateTypes: defaultTemplateTypes, + template: { + slug: 'index', + title: { rendered: 'test title' }, + }, + } ).title + ).toEqual( 'test title' ); + } ); + + it( 'should return the slug if no title is found', () => { + expect( + getTemplateInfo( { + templateAreas: defaultTemplatePartAreas, + templateTypes: defaultTemplateTypes, + template: { + slug: 'not a real template', + }, + } ).title + ).toEqual( 'not a real template' ); + } ); + + it( 'should return the default description if none is defined on the template', () => { + expect( + getTemplateInfo( { + templateAreas: defaultTemplatePartAreas, + templateTypes: defaultTemplateTypes, + template: { + slug: 'index', + }, + } ).description + ).toEqual( 'Main template' ); + } ); + + it( 'should return the raw excerpt as description if defined on the template', () => { + expect( + getTemplateInfo( { + templateAreas: defaultTemplatePartAreas, + templateTypes: defaultTemplateTypes, + template: { + slug: 'index', + description: { raw: 'test description' }, + }, + } ).description + ).toEqual( 'test description' ); + } ); + + it( 'should return a title, description, and icon', () => { + expect( + getTemplateInfo( { + templateAreas: defaultTemplatePartAreas, + templateTypes: defaultTemplateTypes, + template: { slug: 'index' }, + } ) + ).toEqual( { + title: 'Default (Index)', + description: 'Main template', + icon: layout, + } ); + + expect( + getTemplateInfo( { + templateAreas: defaultTemplatePartAreas, + templateTypes: defaultTemplateTypes, + template: { + slug: 'index', + title: { rendered: 'test title' }, + }, + } ) + ).toEqual( { + title: 'test title', + description: 'Main template', + icon: layout, + } ); + + expect( + getTemplateInfo( { + templateAreas: defaultTemplatePartAreas, + templateTypes: defaultTemplateTypes, + template: { + slug: 'index', + description: { raw: 'test description' }, + }, + } ) + ).toEqual( { + title: 'Default (Index)', + description: 'test description', + icon: layout, + } ); + + expect( + getTemplateInfo( { + templateAreas: defaultTemplatePartAreas, + templateTypes: defaultTemplateTypes, + template: { + slug: 'index', + title: { rendered: 'test title' }, + description: { raw: 'test description' }, + }, + } ) + ).toEqual( { + title: 'test title', + description: 'test description', + icon: layout, + } ); + } ); + + it( 'should return correct icon based on area', () => { + expect( + getTemplateInfo( { + templateAreas: defaultTemplatePartAreas, + templateTypes: defaultTemplateTypes, + template: { + slug: 'template part, area = uncategorized', + area: 'uncategorized', + }, + } ) + ).toEqual( { + title: 'template part, area = uncategorized', + icon: layout, + } ); + + expect( + getTemplateInfo( { + templateAreas: defaultTemplatePartAreas, + templateTypes: defaultTemplateTypes, + template: { + slug: 'template part, area = invalid', + area: 'invalid', + }, + } ) + ).toEqual( { + title: 'template part, area = invalid', + icon: layout, + } ); + + expect( + getTemplateInfo( { + templateAreas: defaultTemplatePartAreas, + templateTypes: defaultTemplateTypes, + template: { + slug: 'template part, area = header', + area: 'header', + }, + } ) + ).toEqual( { + title: 'template part, area = header', + icon: header, + } ); + + expect( + getTemplateInfo( { + templateAreas: defaultTemplatePartAreas, + templateTypes: defaultTemplateTypes, + template: { + slug: 'template part, area = footer', + area: 'footer', + }, + } ) + ).toEqual( { + title: 'template part, area = footer', + icon: footer, + } ); + } ); +} ); diff --git a/packages/editor/tsconfig.json b/packages/editor/tsconfig.json index 3c45fbcb10db3d..00a8f3860e2925 100644 --- a/packages/editor/tsconfig.json +++ b/packages/editor/tsconfig.json @@ -2,8 +2,6 @@ "$schema": "https://json.schemastore.org/tsconfig.json", "extends": "../../tsconfig.base.json", "compilerOptions": { - "rootDir": "src", - "declarationDir": "build-types", "checkJs": false }, "references": [ @@ -34,6 +32,5 @@ { "path": "../url" }, { "path": "../warning" }, { "path": "../wordcount" } - ], - "include": [ "src" ] + ] } diff --git a/packages/element/CHANGELOG.md b/packages/element/CHANGELOG.md index cf73e03c74f6a8..5283a083658a59 100644 --- a/packages/element/CHANGELOG.md +++ b/packages/element/CHANGELOG.md @@ -2,6 +2,16 @@ ## Unreleased +## 6.16.0 (2025-01-15) + +## 6.15.0 (2025-01-02) + +## 6.14.0 (2024-12-11) + +## 6.13.0 (2024-11-27) + +## 6.12.0 (2024-11-16) + ## 6.11.0 (2024-10-30) ## 6.10.0 (2024-10-16) @@ -241,7 +251,7 @@ ### New Features -- Added `lazy` feautre (see: https://reactjs.org/docs/react-api.html#reactlazy). +- Added `lazy` feature (see: https://reactjs.org/docs/react-api.html#reactlazy). - Added `Suspense` component (see: https://reactjs.org/docs/react-api.html#reactsuspense). ## 2.3.0 (2019-03-06) diff --git a/packages/element/README.md b/packages/element/README.md index 86f3a6214df0e0..eeed217ab6e90c 100755 --- a/packages/element/README.md +++ b/packages/element/README.md @@ -241,7 +241,7 @@ _Related_ ### Platform -Component used to detect the current Platform being used. Use Platform.OS === 'web' to detect if running on web enviroment. +Component used to detect the current Platform being used. Use Platform.OS === 'web' to detect if running on web environment. This is the same concept as the React Native implementation. diff --git a/packages/element/package.json b/packages/element/package.json index 4a196255971cfb..11473adcef65d8 100644 --- a/packages/element/package.json +++ b/packages/element/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/element", - "version": "6.11.0", + "version": "6.16.0", "description": "Element React module for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -33,7 +33,7 @@ "@babel/runtime": "7.25.7", "@types/react": "^18.2.79", "@types/react-dom": "^18.2.25", - "@wordpress/escape-html": "*", + "@wordpress/escape-html": "file:../escape-html", "change-case": "^4.1.2", "is-plain-object": "^5.0.0", "react": "^18.3.0", diff --git a/packages/element/src/platform.js b/packages/element/src/platform.js index 841cd06e4cabb5..37960103b75464 100644 --- a/packages/element/src/platform.js +++ b/packages/element/src/platform.js @@ -13,7 +13,7 @@ const Platform = { }; /** * Component used to detect the current Platform being used. - * Use Platform.OS === 'web' to detect if running on web enviroment. + * Use Platform.OS === 'web' to detect if running on web environment. * * This is the same concept as the React Native implementation. * diff --git a/packages/element/tsconfig.json b/packages/element/tsconfig.json index ad6a489d33e9a5..a1df062eb218b3 100644 --- a/packages/element/tsconfig.json +++ b/packages/element/tsconfig.json @@ -2,12 +2,8 @@ "$schema": "https://json.schemastore.org/tsconfig.json", "extends": "../../tsconfig.base.json", "compilerOptions": { - "rootDir": "src", - "declarationDir": "build-types", - "noImplicitAny": false, "strictNullChecks": false }, - "references": [ { "path": "../escape-html" } ], - "include": [ "src/**/*" ] + "references": [ { "path": "../escape-html" } ] } diff --git a/packages/env/CHANGELOG.md b/packages/env/CHANGELOG.md index c8b49abebfaaa9..f972ba1355755c 100644 --- a/packages/env/CHANGELOG.md +++ b/packages/env/CHANGELOG.md @@ -2,6 +2,32 @@ ## Unreleased +## 10.16.0 (2025-01-15) + +## 10.15.0 (2025-01-02) + +### Enhancements + +- Add support for WordPress multisite installations. Enabled via the new `multisite` environment config ([#67845](https://github.com/WordPress/gutenberg/pull/67845)). + +### Internal + +- Refactored the code to use new API introduced together with `@inquirer/prompts` instead of legacy `inquirer` package ([#67877](https://github.com/WordPress/gutenberg/pull/67877)). + +## 10.14.0 (2024-12-11) + +### Enhancements + +- Add phpMyAdmin as an optional service. Enabled via the new `phpmyadminPort` environment config, as well as env vars `WP_ENV_PHPMYADMIN_PORT` and `WP_ENV_TESTS_PHPMYADMIN_PORT` ([#67588](https://github.com/WordPress/gutenberg/pull/67588)). + +### Internal + +- The bundled `rimraf` dependency has been updated from `^3.0.2` to `^5.0.10` ([#67708](https://github.com/WordPress/gutenberg/pull/67708)). + +## 10.13.0 (2024-11-27) + +## 10.12.0 (2024-11-16) + ## 10.11.0 (2024-10-30) ## 10.10.0 (2024-10-16) @@ -298,7 +324,7 @@ ### Breaking Changes -- `wp-env start` is now the only command which writes to the docker configuration files. Previously, running any command would also parse the config and then write it to the correct location. Now, other commands still parse the config, but they will not overwrite the confugiration which was set by wp-env start. This allows parameters to be passed to wp-env start which can affect the configuration. +- `wp-env start` is now the only command which writes to the docker configuration files. Previously, running any command would also parse the config and then write it to the correct location. Now, other commands still parse the config, but they will not overwrite the configuration which was set by wp-env start. This allows parameters to be passed to wp-env start which can affect the configuration. ### Enhancements diff --git a/packages/env/README.md b/packages/env/README.md index 35b00fe83e8f4d..467e8d44e7135d 100644 --- a/packages/env/README.md +++ b/packages/env/README.md @@ -349,7 +349,7 @@ containers. Positionals: container The Docker service to run the command on. [string] [required] [choices: "mysql", "tests-mysql", "wordpress", - "tests-wordpress", "cli", "tests-cli", "composer", "phpunit"] + "tests-wordpress", "cli", "tests-cli", "composer", "phpmyadmin"] command The command to run. [required] Options: @@ -479,17 +479,19 @@ You can customize the WordPress installation, plugins and themes that the develo `.wp-env.json` supports fields for options applicable to both the tests and development instances. -| Field | Type | Default | Description | -|----------------|----------------|----------------------------------------|----------------------------------------------------------------------------------------------------------------------------------| -| `"core"` | `string\|null` | `null` | The WordPress installation to use. If `null` is specified, `wp-env` will use the latest production release of WordPress. | -| `"phpVersion"` | `string\|null` | `null` | The PHP version to use. If `null` is specified, `wp-env` will use the default version used with production release of WordPress. | -| `"plugins"` | `string[]` | `[]` | A list of plugins to install and activate in the environment. | -| `"themes"` | `string[]` | `[]` | A list of themes to install in the environment. | -| `"port"` | `integer` | `8888` (`8889` for the tests instance) | The primary port number to use for the installation. You'll access the instance through the port: 'http://localhost:8888'. | -| `"testsPort"` | `integer` | `8889` | The port number for the test site. You'll access the instance through the port: 'http://localhost:8889'. | -| `"config"` | `Object` | See below. | Mapping of wp-config.php constants to their desired values. | -| `"mappings"` | `Object` | `"{}"` | Mapping of WordPress directories to local directories to be mounted in the WordPress instance. | -| `"mysqlPort"` | `integer` | `null` (randomly assigned) | The MySQL port number to expose. The setting is only available in the `env.development` and `env.tests` objects. | +| Field | Type | Default | Description | +|--------------------|----------------|----------------------------------------|----------------------------------------------------------------------------------------------------------------------------------| +| `"core"` | `string\|null` | `null` | The WordPress installation to use. If `null` is specified, `wp-env` will use the latest production release of WordPress. | +| `"phpVersion"` | `string\|null` | `null` | The PHP version to use. If `null` is specified, `wp-env` will use the default version used with production release of WordPress. | +| `"plugins"` | `string[]` | `[]` | A list of plugins to install and activate in the environment. | +| `"themes"` | `string[]` | `[]` | A list of themes to install in the environment. | +| `"port"` | `integer` | `8888` (`8889` for the tests instance) | The primary port number to use for the installation. You'll access the instance through the port: 'http://localhost:8888'. | +| `"testsPort"` | `integer` | `8889` | The port number for the test site. You'll access the instance through the port: 'http://localhost:8889'. | +| `"config"` | `Object` | See below. | Mapping of wp-config.php constants to their desired values. | +| `"mappings"` | `Object` | `"{}"` | Mapping of WordPress directories to local directories to be mounted in the WordPress instance. | +| `"mysqlPort"` | `integer` | `null` (randomly assigned) | The MySQL port number to expose. The setting is only available in the `env.development` and `env.tests` objects. | +| `"phpmyadminPort"` | `integer` | `null` | The port number for phpMyAdmin. If provided, you'll access phpMyAdmin through: http://localhost:<port> | +| `"multisite"` | `boolean` | `false` | Whether to set up a multisite installation. | _Note: the port number environment variables (`WP_ENV_PORT` and `WP_ENV_TESTS_PORT`) take precedent over the .wp-env.json values._ @@ -523,7 +525,8 @@ Additionally, the key `env` is available to override any of the above options on "KEY_1": false }, "port": 3000, - "mysqlPort": 13306 + "mysqlPort": 13306, + "phpmyadminPort": 9001 } } } @@ -688,7 +691,12 @@ You can tell `wp-env` to use a custom port number so that your instance does not } ``` -These can also be set via the environment variables `WP_ENV_PORT`, `WP_ENV_TESTS_PORT`, `WP_ENV_MYSQL_PORT` and `WP_ENV_TESTS_MYSQL_PORT`. +These can also be set via environment variables: + +- `WP_ENV_PORT` to override the development environment's web server's port. +- `WP_ENV_TESTS_PORT` to override the testing environment's web server's port. +- phpMyAdmin is not enabled by default, but its port can also be overridden for the development and testing environments via `WP_ENV_PHPMYADMIN_PORT` and `WP_ENV_TESTS_PHPMYADMIN_PORT`, respectively. +- By default, MySQL aren't exposed to the host, which means no chance of port conflicts. But these can also be overridden for the development and testing environments via `WP_ENV_MYSQL_PORT` and `WP_ENV_TESTS_MYSQL_PORT`, respectively. ### Specific PHP Version diff --git a/packages/env/lib/build-docker-compose-config.js b/packages/env/lib/build-docker-compose-config.js index a394537e360872..a1a4f68256b688 100644 --- a/packages/env/lib/build-docker-compose-config.js +++ b/packages/env/lib/build-docker-compose-config.js @@ -174,6 +174,13 @@ module.exports = function buildDockerComposeConfig( config ) { config.env.tests.mysqlPort ?? '' }}:3306`; + const developmentPhpmyadminPorts = `\${WP_ENV_PHPMYADMIN_PORT:-${ + config.env.development.phpmyadminPort ?? '' + }}:80`; + const testsPhpmyadminPorts = `\${WP_ENV_TESTS_PHPMYADMIN_PORT:-${ + config.env.tests.phpmyadminPort ?? '' + }}:80`; + return { services: { mysql: { @@ -266,6 +273,26 @@ module.exports = function buildDockerComposeConfig( config ) { }, extra_hosts: [ 'host.docker.internal:host-gateway' ], }, + phpmyadmin: { + image: 'phpmyadmin', + ports: [ developmentPhpmyadminPorts ], + environment: { + PMA_PORT: 3306, + PMA_HOST: 'mysql', + PMA_USER: 'root', + PMA_PASSWORD: 'password', + }, + }, + 'tests-phpmyadmin': { + image: 'phpmyadmin', + ports: [ testsPhpmyadminPorts ], + environment: { + PMA_PORT: 3306, + PMA_HOST: 'tests-mysql', + PMA_USER: 'root', + PMA_PASSWORD: 'password', + }, + }, }, volumes: { ...( ! config.env.development.coreSource && { wordpress: {} } ), diff --git a/packages/env/lib/commands/destroy.js b/packages/env/lib/commands/destroy.js index 20f76250271a9e..016838ea218442 100644 --- a/packages/env/lib/commands/destroy.js +++ b/packages/env/lib/commands/destroy.js @@ -3,15 +3,14 @@ * External dependencies */ const { v2: dockerCompose } = require( 'docker-compose' ); -const util = require( 'util' ); const fs = require( 'fs' ).promises; const path = require( 'path' ); -const inquirer = require( 'inquirer' ); +const { confirm } = require( '@inquirer/prompts' ); /** * Promisified dependencies */ -const rimraf = util.promisify( require( 'rimraf' ) ); +const { rimraf } = require( 'rimraf' ); /** * Internal dependencies @@ -41,14 +40,19 @@ module.exports = async function destroy( { spinner, scripts, debug } ) { 'WARNING! This will remove Docker containers, volumes, networks, and images associated with the WordPress instance.' ); - const { yesDelete } = await inquirer.prompt( [ - { - type: 'confirm', - name: 'yesDelete', + let yesDelete = false; + try { + yesDelete = await confirm( { message: 'Are you sure you want to continue?', default: false, - }, - ] ); + } ); + } catch ( error ) { + if ( error.name === 'ExitPromptError' ) { + console.log( 'Cancelled.' ); + process.exit( 1 ); + } + throw error; + } spinner.start(); diff --git a/packages/env/lib/commands/start.js b/packages/env/lib/commands/start.js index 4203ac74632287..db05b82060d2c5 100644 --- a/packages/env/lib/commands/start.js +++ b/packages/env/lib/commands/start.js @@ -6,13 +6,13 @@ const { v2: dockerCompose } = require( 'docker-compose' ); const util = require( 'util' ); const path = require( 'path' ); const fs = require( 'fs' ).promises; -const inquirer = require( 'inquirer' ); +const { confirm } = require( '@inquirer/prompts' ); /** * Promisified dependencies */ const sleep = util.promisify( setTimeout ); -const rimraf = util.promisify( require( 'rimraf' ) ); +const { rimraf } = require( 'rimraf' ); const exec = util.promisify( require( 'child_process' ).exec ); /** @@ -180,6 +180,24 @@ module.exports = async function start( { } ); + if ( config.env.development.phpmyadminPort ) { + await dockerCompose.upOne( 'phpmyadmin', { + ...dockerComposeConfig, + commandOptions: shouldConfigureWp + ? [ '--build', '--force-recreate' ] + : [], + } ); + } + + if ( config.env.tests.phpmyadminPort ) { + await dockerCompose.upOne( 'tests-phpmyadmin', { + ...dockerComposeConfig, + commandOptions: shouldConfigureWp + ? [ '--build', '--force-recreate' ] + : [], + } ); + } + // Make sure we've consumed the custom CLI dockerfile. if ( shouldConfigureWp ) { await dockerCompose.buildOne( [ 'cli' ], { ...dockerComposeConfig } ); @@ -225,35 +243,61 @@ module.exports = async function start( { const siteUrl = config.env.development.config.WP_SITEURL; const testsSiteUrl = config.env.tests.config.WP_SITEURL; - const { out: mySQLAddress } = await dockerCompose.port( + const mySQLPort = await getPublicDockerPort( 'mysql', 3306, dockerComposeConfig ); - const mySQLPort = mySQLAddress.split( ':' ).pop(); - const { out: testsMySQLAddress } = await dockerCompose.port( + const testsMySQLPort = await getPublicDockerPort( 'tests-mysql', 3306, dockerComposeConfig ); - const testsMySQLPort = testsMySQLAddress.split( ':' ).pop(); - - spinner.prefixText = 'WordPress development site started' - .concat( siteUrl ? ` at ${ siteUrl }` : '.' ) - .concat( '\n' ) - .concat( 'WordPress test site started' ) - .concat( testsSiteUrl ? ` at ${ testsSiteUrl }` : '.' ) - .concat( '\n' ) - .concat( `MySQL is listening on port ${ mySQLPort }` ) - .concat( - `MySQL for automated testing is listening on port ${ testsMySQLPort }` - ) - .concat( '\n' ); + const phpmyadminPort = config.env.development.phpmyadminPort + ? await getPublicDockerPort( 'phpmyadmin', 80, dockerComposeConfig ) + : null; + + const testsPhpmyadminPort = config.env.tests.phpmyadminPort + ? await getPublicDockerPort( + 'tests-phpmyadmin', + 80, + dockerComposeConfig + ) + : null; + + spinner.prefixText = [ + 'WordPress development site started' + + ( siteUrl ? ` at ${ siteUrl }` : '.' ), + 'WordPress test site started' + + ( testsSiteUrl ? ` at ${ testsSiteUrl }` : '.' ), + `MySQL is listening on port ${ mySQLPort }`, + `MySQL for automated testing is listening on port ${ testsMySQLPort }`, + phpmyadminPort && + `phpMyAdmin started at http://localhost:${ phpmyadminPort }`, + testsPhpmyadminPort && + `phpMyAdmin for automated testing started at http://localhost:${ testsPhpmyadminPort }`, + ] + .filter( Boolean ) + .join( '\n' ); + spinner.prefixText += '\n\n'; spinner.text = 'Done!'; }; +async function getPublicDockerPort( + service, + containerPort, + dockerComposeConfig +) { + const { out: address } = await dockerCompose.port( + service, + containerPort, + dockerComposeConfig + ); + return address.split( ':' ).pop().trim(); +} + /** * Checks for legacy installs and provides * the user the option to delete them. @@ -284,15 +328,21 @@ async function checkForLegacyInstall( spinner ) { ' and ' ) }. Installs are now in your home folder.\n` ); - const { yesDelete } = await inquirer.prompt( [ - { - type: 'confirm', - name: 'yesDelete', + let yesDelete = false; + try { + yesDelete = confirm( { message: 'Do you wish to delete these old installs to reclaim disk space?', default: true, - }, - ] ); + } ); + } catch ( error ) { + if ( error.name === 'ExitPromptError' ) { + console.log( 'Cancelled.' ); + process.exit( 1 ); + } + throw error; + } + if ( yesDelete ) { await Promise.all( installs.map( ( install ) => rimraf( install ) ) ); spinner.info( 'Old installs deleted successfully.' ); diff --git a/packages/env/lib/config/get-config-from-environment-vars.js b/packages/env/lib/config/get-config-from-environment-vars.js index beaf721ea1c0c1..618b5fff257920 100644 --- a/packages/env/lib/config/get-config-from-environment-vars.js +++ b/packages/env/lib/config/get-config-from-environment-vars.js @@ -20,6 +20,7 @@ const { checkPort, checkVersion, checkString } = require( './validate-config' ); * @property {?number} mysqlPort An override for the development environment's MySQL port. * @property {?number} testsPort An override for the testing environment's port. * @property {?number} testsMysqlPort An override for the testing environment's MySQL port. + * @property {?number} phpmyadminPort An override for the development environment's phpMyAdmin port. * @property {?WPSource} coreSource An override for all environment's coreSource. * @property {?string} phpVersion An override for all environment's PHP version. * @property {?Object.<string, string>} lifecycleScripts An override for various lifecycle scripts. @@ -40,6 +41,12 @@ module.exports = function getConfigFromEnvironmentVars( cacheDirectoryPath ) { testsMysqlPort: getPortFromEnvironmentVariable( 'WP_ENV_TESTS_MYSQL_PORT' ), + phpmyadminPort: getPortFromEnvironmentVariable( + 'WP_ENV_PHPMYADMIN_PORT' + ), + testsPhpmyadminPort: getPortFromEnvironmentVariable( + 'WP_ENV_TESTS_PHPMYADMIN_PORT' + ), lifecycleScripts: getLifecycleScriptOverrides(), }; diff --git a/packages/env/lib/config/parse-config.js b/packages/env/lib/config/parse-config.js index 1fc7e949251490..f501ab672e6edf 100644 --- a/packages/env/lib/config/parse-config.js +++ b/packages/env/lib/config/parse-config.js @@ -46,14 +46,16 @@ const mergeConfigs = require( './merge-configs' ); * The environment-specific configuration options. (development/tests/etc) * * @typedef WPEnvironmentConfig - * @property {WPSource} coreSource The WordPress installation to load in the environment. - * @property {WPSource[]} pluginSources Plugins to load in the environment. - * @property {WPSource[]} themeSources Themes to load in the environment. - * @property {number} port The port to use. - * @property {number} mysqlPort The port to use for MySQL. Random if empty. - * @property {Object} config Mapping of wp-config.php constants to their desired values. - * @property {Object.<string, WPSource>} mappings Mapping of WordPress directories to local directories which should be mounted. - * @property {string|null} phpVersion Version of PHP to use in the environments, of the format 0.0. + * @property {WPSource} coreSource The WordPress installation to load in the environment. + * @property {WPSource[]} pluginSources Plugins to load in the environment. + * @property {WPSource[]} themeSources Themes to load in the environment. + * @property {number} port The port to use. + * @property {number} mysqlPort The port to use for MySQL. Random if empty. + * @property {number} phpmyadminPort The port to use for phpMyAdmin. If empty, disabled phpMyAdmin. + * @property {boolean} multisite Whether to set up a multisite installation. + * @property {Object} config Mapping of wp-config.php constants to their desired values. + * @property {Object.<string, WPSource>} mappings Mapping of WordPress directories to local directories which should be mounted. + * @property {string|null} phpVersion Version of PHP to use in the environments, of the format 0.0. */ /** @@ -87,6 +89,8 @@ const DEFAULT_ENVIRONMENT_CONFIG = { port: 8888, testsPort: 8889, mysqlPort: null, + phpmyadminPort: null, + multisite: false, mappings: {}, config: { FS_METHOD: 'direct', @@ -140,7 +144,7 @@ async function parseConfig( configDirectoryPath, cacheDirectoryPath ) { } ); // Users can provide overrides in environment - // variables that supercede all other options. + // variables that supersede all other options. const environmentVarOverrides = getEnvironmentVarOverrides( cacheDirectoryPath ); @@ -282,6 +286,11 @@ function getEnvironmentVarOverrides( cacheDirectoryPath ) { overrideConfig.env.development.mysqlPort = overrides.mysqlPort; } + if ( overrides.phpmyadminPort ) { + overrideConfig.env.development.phpmyadminPort = + overrides.phpmyadminPort; + } + if ( overrides.testsPort ) { overrideConfig.testsPort = overrides.testsPort; overrideConfig.env.tests.port = overrides.testsPort; @@ -455,6 +464,14 @@ async function parseEnvironmentConfig( parsedConfig.mysqlPort = config.mysqlPort; } + if ( config.phpmyadminPort !== undefined ) { + parsedConfig.phpmyadminPort = config.phpmyadminPort; + } + + if ( config.multisite !== undefined ) { + parsedConfig.multisite = config.multisite; + } + if ( config.phpVersion !== undefined ) { // Support null as a valid input. if ( config.phpVersion !== null ) { diff --git a/packages/env/lib/config/post-process-config.js b/packages/env/lib/config/post-process-config.js index d09843893ea69f..15fd2cbd8c0723 100644 --- a/packages/env/lib/config/post-process-config.js +++ b/packages/env/lib/config/post-process-config.js @@ -129,7 +129,7 @@ function appendPortToWPConfigs( config ) { */ function validatePortUniqueness( config ) { // We're going to build a map of the environments and their port - // so we can accomodate root-level config options more easily. + // so we can accommodate root-level config options more easily. const environmentPorts = {}; // Add all of the environments to the map. This will @@ -179,7 +179,7 @@ function validate( config ) { * @return {WPRootConfig} A deep copy of the root config object. */ function deepCopyRootOptions( config ) { - // Create a shallow clone of the object first so we can operate on it safetly. + // Create a shallow clone of the object first so we can operate on it safely. const rootConfig = Object.assign( {}, config ); // Since we're only dealing with the root options we don't want the environments. diff --git a/packages/env/lib/config/test/__snapshots__/config-integration.js.snap b/packages/env/lib/config/test/__snapshots__/config-integration.js.snap index 6c3618f4724cb0..833a8a54d7749a 100644 --- a/packages/env/lib/config/test/__snapshots__/config-integration.js.snap +++ b/packages/env/lib/config/test/__snapshots__/config-integration.js.snap @@ -29,8 +29,10 @@ exports[`Config Integration should load local and override configuration files 1 "url": "https://github.com/WordPress/WordPress.git", }, "mappings": {}, + "multisite": false, "mysqlPort": 23306, "phpVersion": null, + "phpmyadminPort": null, "pluginSources": [], "port": 999, "themeSources": [], @@ -58,8 +60,10 @@ exports[`Config Integration should load local and override configuration files 1 "url": "https://github.com/WordPress/WordPress.git", }, "mappings": {}, + "multisite": false, "mysqlPort": 23307, "phpVersion": null, + "phpmyadminPort": null, "pluginSources": [], "port": 456, "themeSources": [], @@ -104,8 +108,10 @@ exports[`Config Integration should load local configuration file 1`] = ` "url": "https://github.com/WordPress/WordPress.git", }, "mappings": {}, + "multisite": false, "mysqlPort": 13306, "phpVersion": null, + "phpmyadminPort": null, "pluginSources": [], "port": 123, "themeSources": [], @@ -133,8 +139,10 @@ exports[`Config Integration should load local configuration file 1`] = ` "url": "https://github.com/WordPress/WordPress.git", }, "mappings": {}, + "multisite": false, "mysqlPort": 23307, "phpVersion": null, + "phpmyadminPort": null, "pluginSources": [], "port": 8889, "themeSources": [], @@ -179,8 +187,10 @@ exports[`Config Integration should use default configuration 1`] = ` "url": "https://github.com/WordPress/WordPress.git", }, "mappings": {}, + "multisite": false, "mysqlPort": null, "phpVersion": null, + "phpmyadminPort": null, "pluginSources": [], "port": 8888, "themeSources": [], @@ -208,8 +218,10 @@ exports[`Config Integration should use default configuration 1`] = ` "url": "https://github.com/WordPress/WordPress.git", }, "mappings": {}, + "multisite": false, "mysqlPort": null, "phpVersion": null, + "phpmyadminPort": null, "pluginSources": [], "port": 8889, "themeSources": [], @@ -254,8 +266,10 @@ exports[`Config Integration should use environment variables over local and over "url": "https://github.com/WordPress/WordPress.git", }, "mappings": {}, + "multisite": false, "mysqlPort": 23306, "phpVersion": null, + "phpmyadminPort": null, "pluginSources": [], "port": 12345, "testsPort": 61234, @@ -284,8 +298,10 @@ exports[`Config Integration should use environment variables over local and over "url": "https://github.com/WordPress/WordPress.git", }, "mappings": {}, + "multisite": false, "mysqlPort": 23307, "phpVersion": null, + "phpmyadminPort": null, "pluginSources": [], "port": 61234, "testsPort": 61234, diff --git a/packages/env/lib/config/test/parse-config.js b/packages/env/lib/config/test/parse-config.js index 38e4db9860cb3c..968c8b66a4ec15 100644 --- a/packages/env/lib/config/test/parse-config.js +++ b/packages/env/lib/config/test/parse-config.js @@ -22,6 +22,8 @@ const DEFAULT_CONFIG = { port: 8888, testsPort: 8889, mysqlPort: null, + phpmyadminPort: null, + multisite: false, phpVersion: null, coreSource: { type: 'git', @@ -400,4 +402,57 @@ describe( 'parseConfig', () => { ) ); } ); + + it( 'should parse phpmyadmin configuration for a given environment', async () => { + readRawConfigFile.mockImplementation( async ( configFile ) => { + if ( configFile === '/test/gutenberg/.wp-env.json' ) { + return { + core: 'WordPress/WordPress#Test', + phpVersion: '1.0', + lifecycleScripts: { + afterStart: 'test', + }, + env: { + development: { + phpmyadminPort: 9001, + }, + }, + }; + } + } ); + + const parsed = await parseConfig( '/test/gutenberg', '/cache' ); + + const expected = { + development: { + ...DEFAULT_CONFIG.env.development, + phpmyadminPort: 9001, + }, + tests: DEFAULT_CONFIG.env.tests, + }; + expect( parsed.env ).toEqual( expected ); + } ); + + it( 'should ignore root-level configuration for phpmyadmin', async () => { + readRawConfigFile.mockImplementation( async ( configFile ) => { + if ( configFile === '/test/gutenberg/.wp-env.json' ) { + return { + core: 'WordPress/WordPress#Test', + phpVersion: '1.0', + lifecycleScripts: { + afterStart: 'test', + }, + phpmyadminPort: 9001, + }; + } + } ); + + const parsed = await parseConfig( '/test/gutenberg', '/cache' ); + + const expected = { + development: DEFAULT_CONFIG.env.development, + tests: DEFAULT_CONFIG.env.tests, + }; + expect( parsed.env ).toEqual( expected ); + } ); } ); diff --git a/packages/env/lib/config/test/validate-config.js b/packages/env/lib/config/test/validate-config.js index bb1decfd53dfb7..a4c16e579e5e67 100644 --- a/packages/env/lib/config/test/validate-config.js +++ b/packages/env/lib/config/test/validate-config.js @@ -306,7 +306,7 @@ describe( 'validate-config', () => { } ); describe( 'checkValidURL', () => { - it( 'throws for invaid URLs', () => { + it( 'throws for invalid URLs', () => { expect( () => checkValidURL( 'test.json', 'test', 'localhost' ) ).toThrow( diff --git a/packages/env/lib/config/validate-config.js b/packages/env/lib/config/validate-config.js index 4aa62cb4571557..36c454ac5a6c0e 100644 --- a/packages/env/lib/config/validate-config.js +++ b/packages/env/lib/config/validate-config.js @@ -5,7 +5,7 @@ */ /** - * Error subtype which indicates that an expected validation erorr occurred + * Error subtype which indicates that an expected validation error occurred * while reading wp-env configuration. */ class ValidationError extends Error {} diff --git a/packages/env/lib/download-sources.js b/packages/env/lib/download-sources.js index 45c65fe715de46..01a41e5e8ae576 100644 --- a/packages/env/lib/download-sources.js +++ b/packages/env/lib/download-sources.js @@ -13,7 +13,7 @@ const path = require( 'path' ); */ const pipeline = util.promisify( require( 'stream' ).pipeline ); const extractZip = util.promisify( require( 'extract-zip' ) ); -const rimraf = util.promisify( require( 'rimraf' ) ); +const { rimraf } = require( 'rimraf' ); /** * @typedef {import('./config').WPConfig} WPConfig diff --git a/packages/env/lib/validate-run-container.js b/packages/env/lib/validate-run-container.js index 77e68d2a3a4dc7..fbb6670f6c1500 100644 --- a/packages/env/lib/validate-run-container.js +++ b/packages/env/lib/validate-run-container.js @@ -10,6 +10,7 @@ const RUN_CONTAINERS = [ 'tests-wordpress', 'cli', 'tests-cli', + 'phpmyadmin', ]; /** diff --git a/packages/env/lib/wordpress.js b/packages/env/lib/wordpress.js index bd3c4a23f8ff5d..8c08fb1f20ec78 100644 --- a/packages/env/lib/wordpress.js +++ b/packages/env/lib/wordpress.js @@ -86,11 +86,40 @@ async function configureWordPress( environment, config, spinner ) { // Ignore error. } - const installCommand = `wp core install --url="${ config.env[ environment ].config.WP_SITEURL }" --title="${ config.name }" --admin_user=admin --admin_password=password --admin_email=wordpress@example.com --skip-email`; + const isMultisite = config.env[ environment ].multisite; + + const installMethod = isMultisite ? 'multisite-install' : 'install'; + const installCommand = `wp core ${ installMethod } --url="${ config.env[ environment ].config.WP_SITEURL }" --title="${ config.name }" --admin_user=admin --admin_password=password --admin_email=wordpress@example.com --skip-email`; // -eo pipefail exits the command as soon as anything fails in bash. const setupCommands = [ 'set -eo pipefail', installCommand ]; + // Bootstrap .htaccess for multisite + if ( isMultisite ) { + // Using a subshell with `exec` was the best tradeoff I could come up + // with between readability of this source and compatibility with the + // way that all strings in `setupCommands` are later joined with '&&'. + setupCommands.push( + `( +exec > /var/www/html/.htaccess +echo 'RewriteEngine On' +echo 'RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]' +echo 'RewriteBase /' +echo 'RewriteRule ^index\.php$ - [L]' +echo '' +echo '# add a trailing slash to /wp-admin' +echo 'RewriteRule ^([_0-9a-zA-Z-]+/)?wp-admin$ $1wp-admin/ [R=301,L]' +echo '' +echo 'RewriteCond %{REQUEST_FILENAME} -f [OR]' +echo 'RewriteCond %{REQUEST_FILENAME} -d' +echo 'RewriteRule ^ - [L]' +echo 'RewriteRule ^([_0-9a-zA-Z-]+/)?(wp-(content|admin|includes).*) $2 [L]' +echo 'RewriteRule ^([_0-9a-zA-Z-]+/)?(.*\.php)$ $2 [L]' +echo 'RewriteRule . index.php [L]' +)` + ); + } + // WordPress versions below 5.1 didn't use proper spacing in wp-config. const configAnchor = wpVersion && isWPMajorMinorVersionLower( wpVersion, '5.1' ) diff --git a/packages/env/package.json b/packages/env/package.json index a03004abb83283..f06e0c00e160c9 100644 --- a/packages/env/package.json +++ b/packages/env/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/env", - "version": "10.11.0", + "version": "10.16.0", "description": "A zero-config, self contained local WordPress environment for development and testing.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -36,15 +36,15 @@ "wp-env": "bin/wp-env" }, "dependencies": { + "@inquirer/prompts": "^7.2.0", "chalk": "^4.0.0", "copy-dir": "^1.3.0", "docker-compose": "^0.24.3", "extract-zip": "^1.6.7", "got": "^11.8.5", - "inquirer": "^7.1.0", "js-yaml": "^3.13.1", "ora": "^4.0.2", - "rimraf": "^3.0.2", + "rimraf": "^5.0.10", "simple-git": "^3.5.0", "terminal-link": "^2.0.0", "yargs": "^17.3.0" diff --git a/packages/escape-html/CHANGELOG.md b/packages/escape-html/CHANGELOG.md index f92f40cce1a8cf..a6c0b0973dd15a 100644 --- a/packages/escape-html/CHANGELOG.md +++ b/packages/escape-html/CHANGELOG.md @@ -2,6 +2,16 @@ ## Unreleased +## 3.16.0 (2025-01-15) + +## 3.15.0 (2025-01-02) + +## 3.14.0 (2024-12-11) + +## 3.13.0 (2024-11-27) + +## 3.12.0 (2024-11-16) + ## 3.11.0 (2024-10-30) ## 3.10.0 (2024-10-16) diff --git a/packages/escape-html/package.json b/packages/escape-html/package.json index 135876460bd05e..d292d6ee6f90b4 100644 --- a/packages/escape-html/package.json +++ b/packages/escape-html/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/escape-html", - "version": "3.11.0", + "version": "3.16.0", "description": "Escape HTML utils.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/escape-html/tsconfig.json b/packages/escape-html/tsconfig.json index 6e33d8ff82d47e..7ff060ab6ce105 100644 --- a/packages/escape-html/tsconfig.json +++ b/packages/escape-html/tsconfig.json @@ -1,9 +1,4 @@ { "$schema": "https://json.schemastore.org/tsconfig.json", - "extends": "../../tsconfig.base.json", - "compilerOptions": { - "rootDir": "src", - "declarationDir": "build-types" - }, - "include": [ "src/**/*" ] + "extends": "../../tsconfig.base.json" } diff --git a/packages/eslint-plugin/CHANGELOG.md b/packages/eslint-plugin/CHANGELOG.md index 3f753fb9b6b8b0..12e43687c324b3 100644 --- a/packages/eslint-plugin/CHANGELOG.md +++ b/packages/eslint-plugin/CHANGELOG.md @@ -2,6 +2,20 @@ ## Unreleased +## 22.2.0 (2025-01-15) + +## 22.1.0 (2025-01-02) + +## 22.0.0 (2024-12-11) + +### Breaking Changes + +- The minimum required TypeScript version changed to 5 ([#67461](https://github.com/WordPress/gutenberg/pull/67461)) + +## 21.6.0 (2024-11-27) + +## 21.5.0 (2024-11-16) + ## 21.4.0 (2024-10-30) ## 21.3.0 (2024-10-16) @@ -14,8 +28,8 @@ ### Breaking Changes -- Add [`@wordpress/i18n-no-flanking-whitespace`](https://github.com/WordPress/gutenberg/blob/HEAD/packages/eslint-plugin/docs/rules/i18n-no-flanking-whitespace.md) to the recommended i18n ruleset ([#64710](https://github.com/WordPress/gutenberg/pull/64710). -- Add [`@wordpress/i18n-hyphenated-range`](https://github.com/WordPress/gutenberg/blob/HEAD/packages/eslint-plugin/docs/rules/i18n-hyphenated-range.md) to the recommended i18n ruleset ([#64710](https://github.com/WordPress/gutenberg/pull/64710). +- Add [`@wordpress/i18n-no-flanking-whitespace`](https://github.com/WordPress/gutenberg/blob/HEAD/packages/eslint-plugin/docs/rules/i18n-no-flanking-whitespace.md) to the recommended i18n ruleset ([#64710](https://github.com/WordPress/gutenberg/pull/64710). +- Add [`@wordpress/i18n-hyphenated-range`](https://github.com/WordPress/gutenberg/blob/HEAD/packages/eslint-plugin/docs/rules/i18n-hyphenated-range.md) to the recommended i18n ruleset ([#64710](https://github.com/WordPress/gutenberg/pull/64710). ## 20.3.0 (2024-08-21) @@ -26,8 +40,9 @@ ## 20.0.0 (2024-07-10) ### Breaking Changes -- Add [`@typescript-eslint/no-unused-vars` rule](https://typescript-eslint.io/rules/no-unused-vars) to the recommended TypeScript ruleset ([#62925](https://github.com/WordPress/gutenberg/pull/62925)). -- Add [`@typescript-eslint/method-signature-style` rule](https://typescript-eslint.io/rules/method-signature-style) to the recommended TypeScript ruleset ([#62718](https://github.com/WordPress/gutenberg/pull/62718)). + +- Add [`@typescript-eslint/no-unused-vars` rule](https://typescript-eslint.io/rules/no-unused-vars) to the recommended TypeScript ruleset ([#62925](https://github.com/WordPress/gutenberg/pull/62925)). +- Add [`@typescript-eslint/method-signature-style` rule](https://typescript-eslint.io/rules/method-signature-style) to the recommended TypeScript ruleset ([#62718](https://github.com/WordPress/gutenberg/pull/62718)). ## 19.2.0 (2024-06-26) diff --git a/packages/eslint-plugin/package.json b/packages/eslint-plugin/package.json index 8e2d3e2c97e2bf..dd28f372ad7dc6 100644 --- a/packages/eslint-plugin/package.json +++ b/packages/eslint-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/eslint-plugin", - "version": "21.4.0", + "version": "22.2.0", "description": "ESLint plugin for WordPress development.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -34,12 +34,12 @@ "@babel/eslint-parser": "7.25.7", "@typescript-eslint/eslint-plugin": "^6.4.1", "@typescript-eslint/parser": "^6.4.1", - "@wordpress/babel-preset-default": "*", - "@wordpress/prettier-config": "*", + "@wordpress/babel-preset-default": "file:../babel-preset-default", + "@wordpress/prettier-config": "file:../prettier-config", "cosmiconfig": "^7.0.0", "eslint-config-prettier": "^8.3.0", "eslint-plugin-import": "^2.25.2", - "eslint-plugin-jest": "^27.2.3", + "eslint-plugin-jest": "^27.4.3", "eslint-plugin-jsdoc": "^46.4.6", "eslint-plugin-jsx-a11y": "^6.5.1", "eslint-plugin-playwright": "^0.15.3", @@ -53,7 +53,7 @@ "@babel/core": ">=7", "eslint": ">=8", "prettier": ">=3", - "typescript": ">=4" + "typescript": ">=5" }, "peerDependenciesMeta": { "prettier": { diff --git a/packages/eslint-plugin/rules/__tests__/valid-sprintf.js b/packages/eslint-plugin/rules/__tests__/valid-sprintf.js index 9b2b7de255d47b..8f5b77458fbaeb 100644 --- a/packages/eslint-plugin/rules/__tests__/valid-sprintf.js +++ b/packages/eslint-plugin/rules/__tests__/valid-sprintf.js @@ -71,6 +71,18 @@ sprintf( { code: `sprintf( '%(greeting)s %(toWhom)s', 'Hello', 'World' )`, }, + { + code: `sprintf( 'Rotated at %d %% degrees', 90 )`, + }, + { + code: `sprintf( 'Rotated at %d%% degrees', 90 )`, + }, + { + code: `sprintf( __( 'Rotated at %d%% degrees' ), 90 )`, + }, + { + code: `sprintf( 'Rotated at %1$d %% degrees, %2$d %% angles', 90, 180 )`, + }, ], invalid: [ { diff --git a/packages/eslint-plugin/rules/no-wp-process-env.js b/packages/eslint-plugin/rules/no-wp-process-env.js index 55aca44b92ba74..be7c37e76d204b 100644 --- a/packages/eslint-plugin/rules/no-wp-process-env.js +++ b/packages/eslint-plugin/rules/no-wp-process-env.js @@ -17,7 +17,7 @@ module.exports = { useGlobalThis: '`{{ name }}` should not be accessed from process.env. Use `globalThis.{{name}}`.', noGutenbergPhase: - 'The GUTENBERG_PHASE environement variable is no longer available. Use IS_GUTENBERG_PLUGIN (boolean).', + 'The GUTENBERG_PHASE environment variable is no longer available. Use IS_GUTENBERG_PLUGIN (boolean).', }, }, create( context ) { diff --git a/packages/eslint-plugin/rules/wp-global-usage.js b/packages/eslint-plugin/rules/wp-global-usage.js index c6c75d99331238..b2c395e29b98c1 100644 --- a/packages/eslint-plugin/rules/wp-global-usage.js +++ b/packages/eslint-plugin/rules/wp-global-usage.js @@ -25,7 +25,7 @@ function isUsedInConditional( node ) { /** @type {import('estree').Node|undefined} */ let current = node; - // Simple negation is the only expresion allowed in the conditional: + // Simple negation is the only expression allowed in the conditional: // if ( ! globalThis.SCRIPT_DEBUG ) {} // const D = ! globalThis.SCRIPT_DEBUG ? 'yes' : 'no'; if ( diff --git a/packages/eslint-plugin/tsconfig.json b/packages/eslint-plugin/tsconfig.json index e17815f78a6a16..a769c50a12df4c 100644 --- a/packages/eslint-plugin/tsconfig.json +++ b/packages/eslint-plugin/tsconfig.json @@ -3,12 +3,12 @@ "extends": "../../tsconfig.base.json", "compilerOptions": { "module": "CommonJS", - "rootDir": "rules", - "declarationDir": "build-types" + "rootDir": "rules" }, "references": [ { "path": "../prettier-config" } ], // NOTE: This package is being progressively typed. You are encouraged to // expand this array with files which can be type-checked. At some point in // the future, this can be simplified to an `includes` of `src/**/*`. - "files": [ "rules/dependency-group.js", "rules/no-unsafe-wp-apis.js" ] + "files": [ "rules/dependency-group.js", "rules/no-unsafe-wp-apis.js" ], + "include": [] } diff --git a/packages/eslint-plugin/utils/constants.js b/packages/eslint-plugin/utils/constants.js index a19add74964c0e..44e881fb867c78 100644 --- a/packages/eslint-plugin/utils/constants.js +++ b/packages/eslint-plugin/utils/constants.js @@ -37,13 +37,13 @@ const TRANSLATION_FUNCTIONS = new Set( [ '__', '_x', '_n', '_nx' ] ); * @type {RegExp} */ const REGEXP_SPRINTF_PLACEHOLDER = - /%(((\d+)\$)|(\(([$_a-zA-Z][$_a-zA-Z0-9]*)\)))?[ +0#-]*\d*(\.(\d+|\*))?(ll|[lhqL])?([cduxXefgsp%])/g; -// ▲ ▲ ▲ ▲ ▲ ▲ ▲ type -// │ │ │ │ │ └ Length (unsupported) -// │ │ │ │ └ Precision / max width -// │ │ │ └ Min width (unsupported) -// │ │ └ Flags (unsupported) -// └ Index └ Name (for named arguments) + /(?<!%)%(((\d+)\$)|(\(([$_a-zA-Z][$_a-zA-Z0-9]*)\)))?[ +0#-]*\d*(\.(\d+|\*))?(ll|[lhqL])?([cduxXefgsp])/g; +// ▲ ▲ ▲ ▲ ▲ ▲ ▲ type +// │ │ │ │ │ └ Length (unsupported) +// │ │ │ │ └ Precision / max width +// │ │ │ └ Min width (unsupported) +// │ │ └ Flags (unsupported) +// └ Index └ Name (for named arguments) /** * "Unordered" means there's no position specifier: '%s', not '%2$s'. diff --git a/packages/fields/CHANGELOG.md b/packages/fields/CHANGELOG.md index 6f2bf69b653585..4b6515d00ea62a 100644 --- a/packages/fields/CHANGELOG.md +++ b/packages/fields/CHANGELOG.md @@ -2,6 +2,16 @@ ## Unreleased +## 0.8.0 (2025-01-15) + +## 0.7.0 (2025-01-02) + +## 0.6.0 (2024-12-11) + +## 0.5.0 (2024-11-27) + +## 0.4.0 (2024-11-16) + ## 0.3.0 (2024-10-30) ## 0.2.0 (2024-10-16) diff --git a/packages/fields/README.md b/packages/fields/README.md index e6cf6d3007ed97..e8224a1e4849ac 100644 --- a/packages/fields/README.md +++ b/packages/fields/README.md @@ -18,101 +18,130 @@ npm install @wordpress/fields --save Author field for BasePost. +### BasePost + +Undocumented declaration. + +### BasePostWithEmbeddedAuthor + +Undocumented declaration. + ### commentStatusField Comment status field for BasePost. +### CreateTemplatePartModal + +A React component that renders a modal for creating a template part. The modal displays a title and the contents for creating the template part. This component should not live in this package, it should be moved to a dedicated package responsible for managing template. + +_Parameters_ + +- _props_ `Object`: The component props. +- _props.modalTitle_ `{ modalTitle: string; } & CreateTemplatePartModalContentsProps[ 'modalTitle' ]`: + ### dateField Date field for BasePost. ### deletePost -Undocumented declaration. +Delete action for Templates, Patterns and Template Parts. ### duplicatePattern -Undocumented declaration. +Duplicate action for Pattern. ### duplicatePost -Undocumented declaration. +Duplicate action for BasePost. -### duplicatePostNative +### duplicateTemplatePart -Undocumented declaration. +Duplicate action for TemplatePart. ### exportPattern -Undocumented declaration. - -### exportPatternNative - -Undocumented declaration. +Export action as JSON for Pattern. ### featuredImageField -Undocumented declaration. +Featured Image field for BasePost. ### orderField -Undocumented declaration. +Order field for BasePost. + +### pageTitleField + +Title for the page entity. ### parentField -This field is used to display the post parent. +Parent field for BasePost. ### passwordField -This field is used to display the post password. +Password field for BasePost. + +### patternTitleField + +Title for the pattern entity. ### permanentlyDeletePost -Undocumented declaration. +Delete action for PostWithPermissions. -### renamePost +### PostType Undocumented declaration. -### reorderPage +### renamePost -Undocumented declaration. +Rename action for PostWithPermissions. -### reorderPageNative +### reorderPage -Undocumented declaration. +Reorder action for BasePost. ### resetPost -Undocumented declaration. +Reset action for Template and TemplatePart. ### restorePost -Undocumented declaration. +Restore action for PostWithPermissions. ### slugField -Undocumented declaration. +Slug field for BasePost. ### statusField Status field for BasePost. +### templateField + +Template field for BasePost. + +### templateTitleField + +Title for the template entity. + ### titleField -Undocumented declaration. +Title for the any entity with a `title` property. For patterns, pages or templates you should use the respective field because there are some differences in the rendering, labels, etc. ### trashPost -Undocumented declaration. +Trash action for PostWithPermissions. ### viewPost -Undocumented declaration. +View post action for BasePost. ### viewPostRevisions -Undocumented declaration. +View post revisions action for Post. <!-- END TOKEN(Autogenerated API docs) --> diff --git a/packages/fields/package.json b/packages/fields/package.json index cee1060223767a..70e7d4c4edc663 100644 --- a/packages/fields/package.json +++ b/packages/fields/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/fields", - "version": "0.3.0", + "version": "0.8.0", "description": "DataViews is a component that provides an API to render datasets using different types of layouts (table, grid, list, etc.).", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -33,28 +33,29 @@ ], "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/api-fetch": "*", - "@wordpress/blob": "*", - "@wordpress/blocks": "*", - "@wordpress/components": "*", - "@wordpress/compose": "*", - "@wordpress/core-data": "*", - "@wordpress/data": "*", - "@wordpress/dataviews": "*", - "@wordpress/date": "*", - "@wordpress/element": "*", - "@wordpress/hooks": "*", - "@wordpress/html-entities": "*", - "@wordpress/i18n": "*", - "@wordpress/icons": "*", - "@wordpress/media-utils": "*", - "@wordpress/notices": "*", - "@wordpress/patterns": "*", - "@wordpress/primitives": "*", - "@wordpress/private-apis": "*", - "@wordpress/router": "*", - "@wordpress/url": "*", - "@wordpress/warning": "*", + "@wordpress/api-fetch": "file:../api-fetch", + "@wordpress/blob": "file:../blob", + "@wordpress/block-editor": "file:../block-editor", + "@wordpress/blocks": "file:../blocks", + "@wordpress/components": "file:../components", + "@wordpress/compose": "file:../compose", + "@wordpress/core-data": "file:../core-data", + "@wordpress/data": "file:../data", + "@wordpress/dataviews": "file:../dataviews", + "@wordpress/date": "file:../date", + "@wordpress/element": "file:../element", + "@wordpress/hooks": "file:../hooks", + "@wordpress/html-entities": "file:../html-entities", + "@wordpress/i18n": "file:../i18n", + "@wordpress/icons": "file:../icons", + "@wordpress/media-utils": "file:../media-utils", + "@wordpress/notices": "file:../notices", + "@wordpress/patterns": "file:../patterns", + "@wordpress/primitives": "file:../primitives", + "@wordpress/private-apis": "file:../private-apis", + "@wordpress/router": "file:../router", + "@wordpress/url": "file:../url", + "@wordpress/warning": "file:../warning", "change-case": "4.1.2", "client-zip": "^2.4.5", "clsx": "2.1.1", diff --git a/packages/fields/src/actions/delete-post.tsx b/packages/fields/src/actions/delete-post.tsx index d3a469d7f83ab4..4a94796b6f0bdf 100644 --- a/packages/fields/src/actions/delete-post.tsx +++ b/packages/fields/src/actions/delete-post.tsx @@ -203,4 +203,7 @@ const deletePostAction: Action< Template | TemplatePart | Pattern > = { }, }; +/** + * Delete action for Templates, Patterns and Template Parts. + */ export default deletePostAction; diff --git a/packages/fields/src/actions/duplicate-pattern.tsx b/packages/fields/src/actions/duplicate-pattern.tsx index bf2820f951dbad..274f57b0fe19dc 100644 --- a/packages/fields/src/actions/duplicate-pattern.tsx +++ b/packages/fields/src/actions/duplicate-pattern.tsx @@ -37,4 +37,7 @@ const duplicatePattern: Action< Pattern > = { }, }; +/** + * Duplicate action for Pattern. + */ export default duplicatePattern; diff --git a/packages/fields/src/actions/duplicate-post.native.tsx b/packages/fields/src/actions/duplicate-post.native.tsx deleted file mode 100644 index 5468aa649abbd4..00000000000000 --- a/packages/fields/src/actions/duplicate-post.native.tsx +++ /dev/null @@ -1,3 +0,0 @@ -const duplicatePost = undefined; - -export default duplicatePost; diff --git a/packages/fields/src/actions/duplicate-post.tsx b/packages/fields/src/actions/duplicate-post.tsx index fd7e0ae9de4ad1..37cb8af049cc8c 100644 --- a/packages/fields/src/actions/duplicate-post.tsx +++ b/packages/fields/src/actions/duplicate-post.tsx @@ -37,8 +37,8 @@ const duplicatePost: Action< BasePost > = { const [ item, setItem ] = useState< BasePost >( { ...items[ 0 ], title: sprintf( - /* translators: %s: Existing template title */ - _x( '%s (Copy)', 'template' ), + /* translators: %s: Existing post title */ + _x( '%s (Copy)', 'post' ), getItemTitle( items[ 0 ] ) ), } ); @@ -55,7 +55,7 @@ const duplicatePost: Action< BasePost > = { return; } - const newItemOject = { + const newItemObject = { status: 'draft', title: item.title, slug: item.title || __( 'No title' ), @@ -90,7 +90,7 @@ const duplicatePost: Action< BasePost > = { assignableProperties.forEach( ( property ) => { if ( item.hasOwnProperty( property ) ) { // @ts-ignore - newItemOject[ property ] = item[ property ]; + newItemObject[ property ] = item[ property ]; } } ); setIsCreatingPage( true ); @@ -98,13 +98,13 @@ const duplicatePost: Action< BasePost > = { const newItem = await saveEntityRecord( 'postType', item.type, - newItemOject, + newItemObject, { throwOnError: true } ); createSuccessNotice( sprintf( - // translators: %s: Title of the created post or template, e.g: "Hello world". + // translators: %s: Title of the created post, e.g: "Hello world". __( '"%s" successfully created.' ), decodeEntities( newItem.title?.rendered || item.title ) ), @@ -171,4 +171,7 @@ const duplicatePost: Action< BasePost > = { }, }; +/** + * Duplicate action for BasePost. + */ export default duplicatePost; diff --git a/packages/editor/src/dataviews/actions/duplicate-template-part.tsx b/packages/fields/src/actions/duplicate-template-part.tsx similarity index 79% rename from packages/editor/src/dataviews/actions/duplicate-template-part.tsx rename to packages/fields/src/actions/duplicate-template-part.tsx index 95e7e6bb672fcc..795e40638a47a7 100644 --- a/packages/editor/src/dataviews/actions/duplicate-template-part.tsx +++ b/packages/fields/src/actions/duplicate-template-part.tsx @@ -12,15 +12,17 @@ import type { Action } from '@wordpress/dataviews'; /** * Internal dependencies */ -import { TEMPLATE_PART_POST_TYPE } from '../../store/constants'; -import { CreateTemplatePartModalContents } from '../../components/create-template-part-modal'; +import type { Post, TemplatePart } from '../types'; +import { CreateTemplatePartModalContents } from '../components/create-template-part-modal'; import { getItemTitle } from './utils'; -import type { TemplatePart } from '../types'; +/** + * This action is used to duplicate a template part. + */ const duplicateTemplatePart: Action< TemplatePart > = { id: 'duplicate-template-part', label: _x( 'Duplicate', 'action label' ), - isEligible: ( item ) => item.type === TEMPLATE_PART_POST_TYPE, + isEligible: ( item ) => item.type === 'wp_template_part', modalHeader: _x( 'Duplicate template part', 'action label' ), RenderModal: ( { items, closeModal } ) => { const [ item ] = items; @@ -38,12 +40,12 @@ const duplicateTemplatePart: Action< TemplatePart > = { ); }, [ item.content, item.blocks ] ); const { createSuccessNotice } = useDispatch( noticesStore ); - function onTemplatePartSuccess() { + function onTemplatePartSuccess( templatePart: Post ) { createSuccessNotice( sprintf( // translators: %s: The new template part's title e.g. 'Call to action (copy)'. _x( '"%s" duplicated.', 'template part' ), - getItemTitle( item ) + getItemTitle( templatePart ) ), { type: 'snackbar', id: 'edit-site-patterns-success' } ); @@ -61,10 +63,12 @@ const duplicateTemplatePart: Action< TemplatePart > = { onCreate={ onTemplatePartSuccess } onError={ closeModal } confirmLabel={ _x( 'Duplicate', 'action label' ) } - closeModal={ closeModal } + closeModal={ closeModal ?? ( () => {} ) } /> ); }, }; - +/** + * Duplicate action for TemplatePart. + */ export default duplicateTemplatePart; diff --git a/packages/fields/src/actions/export-pattern.native.tsx b/packages/fields/src/actions/export-pattern.native.tsx deleted file mode 100644 index c58cffcbd79e89..00000000000000 --- a/packages/fields/src/actions/export-pattern.native.tsx +++ /dev/null @@ -1,3 +0,0 @@ -const exportPattern = undefined; - -export default exportPattern; diff --git a/packages/fields/src/actions/export-pattern.tsx b/packages/fields/src/actions/export-pattern.tsx index b6be83eeda84b4..4a041e6ac73c3f 100644 --- a/packages/fields/src/actions/export-pattern.tsx +++ b/packages/fields/src/actions/export-pattern.tsx @@ -73,4 +73,7 @@ const exportPattern: Action< Pattern > = { }, }; +/** + * Export action as JSON for Pattern. + */ export default exportPattern; diff --git a/packages/fields/src/actions/index.ts b/packages/fields/src/actions/index.ts index 08e22836e68fd1..cede3f1b59d1b3 100644 --- a/packages/fields/src/actions/index.ts +++ b/packages/fields/src/actions/index.ts @@ -1,15 +1,13 @@ export { default as viewPost } from './view-post'; export { default as reorderPage } from './reorder-page'; -export { default as reorderPageNative } from './reorder-page.native'; export { default as duplicatePost } from './duplicate-post'; -export { default as duplicatePostNative } from './duplicate-post.native'; export { default as renamePost } from './rename-post'; export { default as resetPost } from './reset-post'; export { default as duplicatePattern } from './duplicate-pattern'; export { default as exportPattern } from './export-pattern'; -export { default as exportPatternNative } from './export-pattern.native'; export { default as viewPostRevisions } from './view-post-revisions'; export { default as permanentlyDeletePost } from './permanently-delete-post'; export { default as restorePost } from './restore-post'; export { default as trashPost } from './trash-post'; export { default as deletePost } from './delete-post'; +export { default as duplicateTemplatePart } from './duplicate-template-part'; diff --git a/packages/fields/src/actions/permanently-delete-post.tsx b/packages/fields/src/actions/permanently-delete-post.tsx index afbb84ae12c74c..136fcdda9a3e68 100644 --- a/packages/fields/src/actions/permanently-delete-post.tsx +++ b/packages/fields/src/actions/permanently-delete-post.tsx @@ -2,10 +2,19 @@ * WordPress dependencies */ import { store as coreStore } from '@wordpress/core-data'; -import { __, sprintf } from '@wordpress/i18n'; +import { __, _n, sprintf } from '@wordpress/i18n'; import { store as noticesStore } from '@wordpress/notices'; import type { Action } from '@wordpress/dataviews'; import { trash } from '@wordpress/icons'; +import { useState } from '@wordpress/element'; +import { useDispatch } from '@wordpress/data'; +import { + Button, + __experimentalText as Text, + __experimentalHStack as HStack, + __experimentalVStack as VStack, +} from '@wordpress/components'; +import { decodeEntities } from '@wordpress/html-entities'; /** * Internal dependencies @@ -25,94 +34,159 @@ const permanentlyDeletePost: Action< PostWithPermissions > = { const { status, permissions } = item; return status === 'trash' && permissions?.delete; }, - async callback( posts, { registry, onActionPerformed } ) { + hideModalHeader: true, + RenderModal: ( { items, closeModal, onActionPerformed } ) => { + const [ isBusy, setIsBusy ] = useState( false ); const { createSuccessNotice, createErrorNotice } = - registry.dispatch( noticesStore ); - const { deleteEntityRecord } = registry.dispatch( coreStore ); - const promiseResult = await Promise.allSettled( - posts.map( ( post ) => { - return deleteEntityRecord( - 'postType', - post.type, - post.id, - { force: true }, - { throwOnError: true } - ); - } ) + useDispatch( noticesStore ); + const { deleteEntityRecord } = useDispatch( coreStore ); + + return ( + <VStack spacing="5"> + <Text> + { items.length > 1 + ? sprintf( + // translators: %d: number of items to delete. + _n( + 'Are you sure you want to permanently delete %d item?', + 'Are you sure you want to permanently delete %d items?', + items.length + ), + items.length + ) + : sprintf( + // translators: %s: The post's title + __( + 'Are you sure you want to permanently delete "%s"?' + ), + decodeEntities( getItemTitle( items[ 0 ] ) ) + ) } + </Text> + <HStack justify="right"> + <Button + variant="tertiary" + onClick={ closeModal } + disabled={ isBusy } + accessibleWhenDisabled + __next40pxDefaultSize + > + { __( 'Cancel' ) } + </Button> + <Button + variant="primary" + onClick={ async () => { + setIsBusy( true ); + const promiseResult = await Promise.allSettled( + items.map( ( post ) => + deleteEntityRecord( + 'postType', + post.type, + post.id, + { force: true }, + { throwOnError: true } + ) + ) + ); + + // If all the promises were fulfilled with success. + if ( + promiseResult.every( + ( { status } ) => status === 'fulfilled' + ) + ) { + let successMessage; + if ( promiseResult.length === 1 ) { + successMessage = sprintf( + /* translators: The posts's title. */ + __( '"%s" permanently deleted.' ), + getItemTitle( items[ 0 ] ) + ); + } else { + successMessage = __( + 'The items were permanently deleted.' + ); + } + createSuccessNotice( successMessage, { + type: 'snackbar', + id: 'permanently-delete-post-action', + } ); + onActionPerformed?.( items ); + } else { + // If there was at lease one failure. + let errorMessage; + // If we were trying to permanently delete a single post. + if ( promiseResult.length === 1 ) { + const typedError = promiseResult[ 0 ] as { + reason?: CoreDataError; + }; + if ( typedError.reason?.message ) { + errorMessage = + typedError.reason.message; + } else { + errorMessage = __( + 'An error occurred while permanently deleting the item.' + ); + } + // If we were trying to permanently delete multiple posts + } else { + const errorMessages = new Set(); + const failedPromises = promiseResult.filter( + ( { status } ) => status === 'rejected' + ); + for ( const failedPromise of failedPromises ) { + const typedError = failedPromise as { + reason?: CoreDataError; + }; + if ( typedError.reason?.message ) { + errorMessages.add( + typedError.reason.message + ); + } + } + if ( errorMessages.size === 0 ) { + errorMessage = __( + 'An error occurred while permanently deleting the items.' + ); + } else if ( errorMessages.size === 1 ) { + errorMessage = sprintf( + /* translators: %s: an error message */ + __( + 'An error occurred while permanently deleting the items: %s' + ), + [ ...errorMessages ][ 0 ] + ); + } else { + errorMessage = sprintf( + /* translators: %s: a list of comma separated error messages */ + __( + 'Some errors occurred while permanently deleting the items: %s' + ), + [ ...errorMessages ].join( ',' ) + ); + } + } + createErrorNotice( errorMessage, { + type: 'snackbar', + } ); + } + + setIsBusy( false ); + closeModal?.(); + } } + isBusy={ isBusy } + disabled={ isBusy } + accessibleWhenDisabled + __next40pxDefaultSize + > + { __( 'Delete permanently' ) } + </Button> + </HStack> + </VStack> ); - // If all the promises were fulfilled with success. - if ( promiseResult.every( ( { status } ) => status === 'fulfilled' ) ) { - let successMessage; - if ( promiseResult.length === 1 ) { - successMessage = sprintf( - /* translators: The posts's title. */ - __( '"%s" permanently deleted.' ), - getItemTitle( posts[ 0 ] ) - ); - } else { - successMessage = __( 'The items were permanently deleted.' ); - } - createSuccessNotice( successMessage, { - type: 'snackbar', - id: 'permanently-delete-post-action', - } ); - onActionPerformed?.( posts ); - } else { - // If there was at lease one failure. - let errorMessage; - // If we were trying to permanently delete a single post. - if ( promiseResult.length === 1 ) { - const typedError = promiseResult[ 0 ] as { - reason?: CoreDataError; - }; - if ( typedError.reason?.message ) { - errorMessage = typedError.reason.message; - } else { - errorMessage = __( - 'An error occurred while permanently deleting the item.' - ); - } - // If we were trying to permanently delete multiple posts - } else { - const errorMessages = new Set(); - const failedPromises = promiseResult.filter( - ( { status } ) => status === 'rejected' - ); - for ( const failedPromise of failedPromises ) { - const typedError = failedPromise as { - reason?: CoreDataError; - }; - if ( typedError.reason?.message ) { - errorMessages.add( typedError.reason.message ); - } - } - if ( errorMessages.size === 0 ) { - errorMessage = __( - 'An error occurred while permanently deleting the items.' - ); - } else if ( errorMessages.size === 1 ) { - errorMessage = sprintf( - /* translators: %s: an error message */ - __( - 'An error occurred while permanently deleting the items: %s' - ), - [ ...errorMessages ][ 0 ] - ); - } else { - errorMessage = sprintf( - /* translators: %s: a list of comma separated error messages */ - __( - 'Some errors occurred while permanently deleting the items: %s' - ), - [ ...errorMessages ].join( ',' ) - ); - } - } - createErrorNotice( errorMessage, { - type: 'snackbar', - } ); - } }, }; +/** + * Delete action for PostWithPermissions. + */ export default permanentlyDeletePost; diff --git a/packages/fields/src/actions/rename-post.tsx b/packages/fields/src/actions/rename-post.tsx index da1fd46669f0df..be0e69db26cd80 100644 --- a/packages/fields/src/actions/rename-post.tsx +++ b/packages/fields/src/actions/rename-post.tsx @@ -26,9 +26,6 @@ import { isTemplateRemovable, isTemplate, isTemplatePart, - TEMPLATE_ORIGINS, - TEMPLATE_PART_POST_TYPE, - TEMPLATE_POST_TYPE, } from './utils'; import type { CoreDataError, PostWithPermissions } from '../types'; @@ -45,8 +42,8 @@ const renamePost: Action< PostWithPermissions > = { // Templates, template parts and patterns have special checks for renaming. if ( ! [ - TEMPLATE_POST_TYPE, - TEMPLATE_PART_POST_TYPE, + 'wp_template', + 'wp_template_part', ...Object.values( PATTERN_TYPES ), ].includes( post.type ) ) { @@ -64,7 +61,7 @@ const renamePost: Action< PostWithPermissions > = { if ( isTemplatePart( post ) ) { return ( - post.source === TEMPLATE_ORIGINS.custom && + post.source === 'custom' && ! post?.has_theme_file && post.permissions?.update ); @@ -142,4 +139,7 @@ const renamePost: Action< PostWithPermissions > = { }, }; +/** + * Rename action for PostWithPermissions. + */ export default renamePost; diff --git a/packages/fields/src/actions/reorder-page.native.tsx b/packages/fields/src/actions/reorder-page.native.tsx deleted file mode 100644 index 61e4733b6c6bd9..00000000000000 --- a/packages/fields/src/actions/reorder-page.native.tsx +++ /dev/null @@ -1,3 +0,0 @@ -const reorderPage = undefined; - -export default reorderPage; diff --git a/packages/fields/src/actions/reorder-page.tsx b/packages/fields/src/actions/reorder-page.tsx index 1820884d8d8c73..cffc4b924c0a5d 100644 --- a/packages/fields/src/actions/reorder-page.tsx +++ b/packages/fields/src/actions/reorder-page.tsx @@ -122,4 +122,7 @@ const reorderPage: Action< BasePost > = { RenderModal: ReorderModal, }; +/** + * Reorder action for BasePost. + */ export default reorderPage; diff --git a/packages/fields/src/actions/reset-post.tsx b/packages/fields/src/actions/reset-post.tsx index 105d7b283b8334..d8ac5730d6a166 100644 --- a/packages/fields/src/actions/reset-post.tsx +++ b/packages/fields/src/actions/reset-post.tsx @@ -22,12 +22,7 @@ import apiFetch from '@wordpress/api-fetch'; /** * Internal dependencies */ -import { - getItemTitle, - isTemplateOrTemplatePart, - TEMPLATE_ORIGINS, - TEMPLATE_POST_TYPE, -} from './utils'; +import { getItemTitle, isTemplateOrTemplatePart } from './utils'; import type { CoreDataError, Template, TemplatePart } from '../types'; const isTemplateRevertable = ( @@ -38,7 +33,7 @@ const isTemplateRevertable = ( } return ( - templateOrTemplatePart.source === TEMPLATE_ORIGINS.custom && + templateOrTemplatePart.source === 'custom' && ( Boolean( templateOrTemplatePart?.plugin ) || templateOrTemplatePart?.has_theme_file ) ); @@ -186,7 +181,7 @@ const resetPostAction: Action< Template | TemplatePart > = { isEligible: ( item ) => { return ( isTemplateOrTemplatePart( item ) && - item?.source === TEMPLATE_ORIGINS.custom && + item?.source === 'custom' && ( Boolean( item.type === 'wp_template' && item?.plugin ) || item?.has_theme_file ) ); @@ -231,7 +226,7 @@ const resetPostAction: Action< Template | TemplatePart > = { ); } catch ( error ) { let fallbackErrorMessage; - if ( items[ 0 ].type === TEMPLATE_POST_TYPE ) { + if ( items[ 0 ].type === 'wp_template' ) { fallbackErrorMessage = items.length === 1 ? __( @@ -297,4 +292,7 @@ const resetPostAction: Action< Template | TemplatePart > = { }, }; +/** + * Reset action for Template and TemplatePart. + */ export default resetPostAction; diff --git a/packages/fields/src/actions/restore-post.tsx b/packages/fields/src/actions/restore-post.tsx index 874eca91fee113..6ea493a619ae80 100644 --- a/packages/fields/src/actions/restore-post.tsx +++ b/packages/fields/src/actions/restore-post.tsx @@ -131,4 +131,7 @@ const restorePost: Action< PostWithPermissions > = { }, }; +/** + * Restore action for PostWithPermissions. + */ export default restorePost; diff --git a/packages/fields/src/actions/trash-post.tsx b/packages/fields/src/actions/trash-post.tsx index befbfb0ee7ffba..c0227996b5e866 100644 --- a/packages/fields/src/actions/trash-post.tsx +++ b/packages/fields/src/actions/trash-post.tsx @@ -195,4 +195,7 @@ const trashPost: Action< PostWithPermissions > = { }, }; +/** + * Trash action for PostWithPermissions. + */ export default trashPost; diff --git a/packages/fields/src/actions/utils.ts b/packages/fields/src/actions/utils.ts index 60d3d00e82766a..7bc08573f0b9f8 100644 --- a/packages/fields/src/actions/utils.ts +++ b/packages/fields/src/actions/utils.ts @@ -8,29 +8,23 @@ import { decodeEntities } from '@wordpress/html-entities'; */ import type { Post, TemplatePart, Template } from '../types'; -export const TEMPLATE_POST_TYPE = 'wp_template'; -export const TEMPLATE_PART_POST_TYPE = 'wp_template_part'; -export const TEMPLATE_ORIGINS = { - custom: 'custom', - theme: 'theme', - plugin: 'plugin', -}; - export function isTemplate( post: Post ): post is Template { - return post.type === TEMPLATE_POST_TYPE; + return post.type === 'wp_template'; } export function isTemplatePart( post: Post ): post is TemplatePart { - return post.type === TEMPLATE_PART_POST_TYPE; + return post.type === 'wp_template_part'; } export function isTemplateOrTemplatePart( p: Post ): p is Template | TemplatePart { - return p.type === TEMPLATE_POST_TYPE || p.type === TEMPLATE_PART_POST_TYPE; + return p.type === 'wp_template' || p.type === 'wp_template_part'; } -export function getItemTitle( item: Post ) { +export function getItemTitle( item: { + title: string | { rendered: string } | { raw: string }; +} ) { if ( typeof item.title === 'string' ) { return decodeEntities( item.title ); } @@ -57,9 +51,7 @@ export function isTemplateRemovable( template: Template | TemplatePart ) { // than the one returned from the endpoint. This is why we need to check for // two props whether is custom or has a theme file. return ( - [ template.source, template.source ].includes( - TEMPLATE_ORIGINS.custom - ) && + [ template.source, template.source ].includes( 'custom' ) && ! Boolean( template.type === 'wp_template' && template?.plugin ) && ! template.has_theme_file ); diff --git a/packages/fields/src/actions/view-post-revisions.tsx b/packages/fields/src/actions/view-post-revisions.tsx index 75d4edfd73e18d..40eecde5771311 100644 --- a/packages/fields/src/actions/view-post-revisions.tsx +++ b/packages/fields/src/actions/view-post-revisions.tsx @@ -44,4 +44,7 @@ const viewPostRevisions: Action< Post > = { }, }; +/** + * View post revisions action for Post. + */ export default viewPostRevisions; diff --git a/packages/fields/src/actions/view-post.tsx b/packages/fields/src/actions/view-post.tsx index 187faffafb5d3c..7517ab1f37cb33 100644 --- a/packages/fields/src/actions/view-post.tsx +++ b/packages/fields/src/actions/view-post.tsx @@ -27,4 +27,7 @@ const viewPost: Action< BasePost > = { }, }; +/** + * View post action for BasePost. + */ export default viewPost; diff --git a/packages/fields/src/components/create-template-part-modal/index.tsx b/packages/fields/src/components/create-template-part-modal/index.tsx new file mode 100644 index 00000000000000..927192eee17fcd --- /dev/null +++ b/packages/fields/src/components/create-template-part-modal/index.tsx @@ -0,0 +1,281 @@ +/** + * WordPress dependencies + */ +import { + Icon, + BaseControl, + TextControl, + Button, + Modal, + __experimentalHStack as HStack, + __experimentalVStack as VStack, +} from '@wordpress/components'; +import { useInstanceId } from '@wordpress/compose'; +import type { TemplatePartArea } from '@wordpress/core-data'; +import { store as coreStore } from '@wordpress/core-data'; +import { useDispatch, useSelect } from '@wordpress/data'; +import { useState } from '@wordpress/element'; +import { __ } from '@wordpress/i18n'; +import { + check, + footer as footerIcon, + header as headerIcon, + sidebar as sidebarIcon, + symbolFilled as symbolFilledIcon, +} from '@wordpress/icons'; +import { store as noticesStore } from '@wordpress/notices'; +// @ts-expect-error serialize is not typed +import { serialize } from '@wordpress/blocks'; + +/** + * Internal dependencies + */ +import { + getCleanTemplatePartSlug, + getUniqueTemplatePartTitle, + useExistingTemplateParts, +} from './utils'; + +function getAreaRadioId( value: string, instanceId: number ) { + return `fields-create-template-part-modal__area-option-${ value }-${ instanceId }`; +} +function getAreaRadioDescriptionId( value: string, instanceId: number ) { + return `fields-create-template-part-modal__area-option-description-${ value }-${ instanceId }`; +} + +type CreateTemplatePartModalContentsProps = { + defaultArea?: string; + blocks: any[]; + confirmLabel?: string; + closeModal: () => void; + onCreate: ( templatePart: any ) => void; + onError?: () => void; + defaultTitle?: string; +}; + +/** + * A React component that renders a modal for creating a template part. The modal displays a title and the contents for creating the template part. + * This component should not live in this package, it should be moved to a dedicated package responsible for managing template. + * @param {Object} props The component props. + * @param props.modalTitle + */ +export default function CreateTemplatePartModal( { + modalTitle, + ...restProps +}: { + modalTitle: string; +} & CreateTemplatePartModalContentsProps ) { + const defaultModalTitle = useSelect( + ( select ) => + select( coreStore ).getPostType( 'wp_template_part' )?.labels + ?.add_new_item, + [] + ); + return ( + <Modal + title={ modalTitle || defaultModalTitle } + onRequestClose={ restProps.closeModal } + overlayClassName="fields-create-template-part-modal" + focusOnMount="firstContentElement" + size="medium" + > + <CreateTemplatePartModalContents { ...restProps } /> + </Modal> + ); +} + +const getTemplatePartIcon = ( iconName: string ) => { + if ( 'header' === iconName ) { + return headerIcon; + } else if ( 'footer' === iconName ) { + return footerIcon; + } else if ( 'sidebar' === iconName ) { + return sidebarIcon; + } + return symbolFilledIcon; +}; + +/** + * A React component that renders the content of a model for creating a template part. + * This component should not live in this package; it should be moved to a dedicated package responsible for managing template. + * + * @param {Object} props - The component props. + * @param {string} [props.defaultArea=uncategorized] - The default area for the template part. + * @param {Array} [props.blocks=[]] - The blocks to be included in the template part. + * @param {string} [props.confirmLabel='Add'] - The label for the confirm button. + * @param {Function} props.closeModal - Function to close the modal. + * @param {Function} props.onCreate - Function to call when the template part is successfully created. + * @param {Function} [props.onError] - Function to call when there is an error creating the template part. + * @param {string} [props.defaultTitle=''] - The default title for the template part. + */ +export function CreateTemplatePartModalContents( { + defaultArea = 'uncategorized', + blocks = [], + confirmLabel = __( 'Add' ), + closeModal, + onCreate, + onError, + defaultTitle = '', +}: CreateTemplatePartModalContentsProps ) { + const { createErrorNotice } = useDispatch( noticesStore ); + const { saveEntityRecord } = useDispatch( coreStore ); + const existingTemplateParts = useExistingTemplateParts(); + + const [ title, setTitle ] = useState( defaultTitle ); + const [ area, setArea ] = useState( defaultArea ); + const [ isSubmitting, setIsSubmitting ] = useState( false ); + const instanceId = useInstanceId( CreateTemplatePartModal ); + + const defaultTemplatePartAreas = useSelect( + ( select ) => + select( coreStore ).getEntityRecord< { + default_template_part_areas: Array< TemplatePartArea >; + } >( 'root', '__unstableBase' )?.default_template_part_areas, + [] + ); + + async function createTemplatePart() { + if ( ! title || isSubmitting ) { + return; + } + + try { + setIsSubmitting( true ); + const uniqueTitle = getUniqueTemplatePartTitle( + title, + existingTemplateParts + ); + const cleanSlug = getCleanTemplatePartSlug( uniqueTitle ); + + const templatePart = await saveEntityRecord( + 'postType', + 'wp_template_part', + { + slug: cleanSlug, + title: uniqueTitle, + content: serialize( blocks ), + area, + }, + { throwOnError: true } + ); + await onCreate( templatePart ); + + // TODO: Add a success notice? + } catch ( error ) { + const errorMessage = + error instanceof Error && + 'code' in error && + error.message && + error.code !== 'unknown_error' + ? error.message + : __( + 'An error occurred while creating the template part.' + ); + + createErrorNotice( errorMessage, { type: 'snackbar' } ); + + onError?.(); + } finally { + setIsSubmitting( false ); + } + } + return ( + <form + onSubmit={ async ( event ) => { + event.preventDefault(); + await createTemplatePart(); + } } + > + <VStack spacing="4"> + <TextControl + __next40pxDefaultSize + __nextHasNoMarginBottom + label={ __( 'Name' ) } + value={ title } + onChange={ setTitle } + required + /> + <fieldset> + <BaseControl.VisualLabel as="legend"> + { __( 'Area' ) } + </BaseControl.VisualLabel> + <div className="fields-create-template-part-modal__area-radio-group"> + { ( defaultTemplatePartAreas ?? [] ).map( ( item ) => { + const icon = getTemplatePartIcon( item.icon ); + return ( + <div + key={ item.area } + className="fields-create-template-part-modal__area-radio-wrapper" + > + <input + type="radio" + id={ getAreaRadioId( + item.area, + instanceId + ) } + name={ `fields-create-template-part-modal__area-${ instanceId }` } + value={ item.area } + checked={ area === item.area } + onChange={ () => { + setArea( item.area ); + } } + aria-describedby={ getAreaRadioDescriptionId( + item.area, + instanceId + ) } + /> + <Icon + icon={ icon } + className="fields-create-template-part-modal__area-radio-icon" + /> + <label + htmlFor={ getAreaRadioId( + item.area, + instanceId + ) } + className="fields-create-template-part-modal__area-radio-label" + > + { item.label } + </label> + <Icon + icon={ check } + className="fields-create-template-part-modal__area-radio-checkmark" + /> + <p + className="fields-create-template-part-modal__area-radio-description" + id={ getAreaRadioDescriptionId( + item.area, + instanceId + ) } + > + { item.description } + </p> + </div> + ); + } ) } + </div> + </fieldset> + <HStack justify="right"> + <Button + __next40pxDefaultSize + variant="tertiary" + onClick={ () => { + closeModal(); + } } + > + { __( 'Cancel' ) } + </Button> + <Button + __next40pxDefaultSize + variant="primary" + type="submit" + aria-disabled={ ! title || isSubmitting } + isBusy={ isSubmitting } + > + { confirmLabel } + </Button> + </HStack> + </VStack> + </form> + ); +} diff --git a/packages/fields/src/components/create-template-part-modal/style.scss b/packages/fields/src/components/create-template-part-modal/style.scss new file mode 100644 index 00000000000000..bba250b8f3a262 --- /dev/null +++ b/packages/fields/src/components/create-template-part-modal/style.scss @@ -0,0 +1,88 @@ +.fields-create-template-part-modal { + z-index: z-index(".fields-create-template-part-modal"); +} + +.fields-create-template-part-modal__area-radio-group { + border: $border-width solid $gray-600; + border-radius: $radius-small; +} + +.fields-create-template-part-modal__area-radio-wrapper { + position: relative; + padding: $grid-unit-15; + + display: grid; + align-items: center; + grid-template-columns: min-content 1fr min-content; + grid-gap: $grid-unit-05 $grid-unit-10; + + color: $gray-900; + + & + & { + border-top: $border-width solid $gray-600; + } + + input[type="radio"] { + position: absolute; + opacity: 0; + } + + &:has(input[type="radio"]:checked) { + // This is needed to make sure that the focus ring always renders on top + // of the sibling radio "wrapper"'s borders. + z-index: 1; + } + + &:has(input[type="radio"]:not(:checked)):hover { + color: var(--wp-admin-theme-color); + } + + // Pass-through pointer events, so that the corresponding radio input + // gets checked when clicking on the underlying label + > *:not(.fields-create-template-part-modal__area-radio-label) { + pointer-events: none; + } +} + +.fields-create-template-part-modal__area-radio-label { + // Capture pointer clicks for the whole radio wrapper + &::before { + content: ""; + position: absolute; + inset: 0; + } + + input[type="radio"]:not(:checked) ~ &::before { + cursor: pointer; + } + + input[type="radio"]:focus-visible ~ &::before { + outline: 4px solid transparent; + box-shadow: 0 0 0 var(--wp-admin-border-width-focus) var(--wp-admin-theme-color); + } +} + +.fields-create-template-part-modal__area-radio-icon, +.fields-create-template-part-modal__area-radio-checkmark { + fill: currentColor; +} + +.fields-create-template-part-modal__area-radio-checkmark { + input[type="radio"]:not(:checked) ~ & { + opacity: 0; + } +} + +.fields-create-template-part-modal__area-radio-description { + grid-column: 2 / 3; + margin: 0; + + color: $gray-700; + font-size: $helptext-font-size; + line-height: normal; + text-wrap: pretty; + + input[type="radio"]:not(:checked):hover ~ & { + color: inherit; + } +} diff --git a/packages/editor/src/components/create-template-part-modal/test/utils.js b/packages/fields/src/components/create-template-part-modal/test/utils.js similarity index 100% rename from packages/editor/src/components/create-template-part-modal/test/utils.js rename to packages/fields/src/components/create-template-part-modal/test/utils.js diff --git a/packages/editor/src/components/create-template-part-modal/utils.js b/packages/fields/src/components/create-template-part-modal/utils.js similarity index 86% rename from packages/editor/src/components/create-template-part-modal/utils.js rename to packages/fields/src/components/create-template-part-modal/utils.js index 02f24cf17d2be9..9ecf3efd03f918 100644 --- a/packages/editor/src/components/create-template-part-modal/utils.js +++ b/packages/fields/src/components/create-template-part-modal/utils.js @@ -12,19 +12,20 @@ import { store as coreStore } from '@wordpress/core-data'; /** * Internal dependencies */ -import { TEMPLATE_PART_POST_TYPE } from '../../store/constants'; export const useExistingTemplateParts = () => { - return useSelect( - ( select ) => - select( coreStore ).getEntityRecords( - 'postType', - TEMPLATE_PART_POST_TYPE, - { - per_page: -1, - } - ), - [] + return ( + useSelect( + ( select ) => + select( coreStore ).getEntityRecords( + 'postType', + 'wp_template_part', + { + per_page: -1, + } + ), + [] + ) ?? [] ); }; diff --git a/packages/fields/src/fields/featured-image/index.ts b/packages/fields/src/fields/featured-image/index.ts index 62d7e8240aded0..7e17fb482e01c9 100644 --- a/packages/fields/src/fields/featured-image/index.ts +++ b/packages/fields/src/fields/featured-image/index.ts @@ -13,11 +13,14 @@ import { FeaturedImageView } from './featured-image-view'; const featuredImageField: Field< BasePost > = { id: 'featured_media', - type: 'text', + type: 'media', label: __( 'Featured Image' ), Edit: FeaturedImageEdit, render: FeaturedImageView, enableSorting: false, }; +/** + * Featured Image field for BasePost. + */ export default featuredImageField; diff --git a/packages/fields/src/fields/featured-image/style.scss b/packages/fields/src/fields/featured-image/style.scss index 46d37960199ead..918f69172f3023 100644 --- a/packages/fields/src/fields/featured-image/style.scss +++ b/packages/fields/src/fields/featured-image/style.scss @@ -83,13 +83,10 @@ fieldset.fields-controls__featured-image { } .dataviews-view-table__cell-content-wrapper { - .fields-controls__featured-image-image { - width: 32px; - height: 32px; - } - + .fields-controls__featured-image-image, .fields-controls__featured-image-placeholder { width: 32px; height: 32px; + display: block; } } diff --git a/packages/fields/src/fields/index.ts b/packages/fields/src/fields/index.ts index 5ea4235af1d964..410b0bf51a2232 100644 --- a/packages/fields/src/fields/index.ts +++ b/packages/fields/src/fields/index.ts @@ -1,7 +1,11 @@ export { default as slugField } from './slug'; export { default as titleField } from './title'; +export { default as pageTitleField } from './page-title'; +export { default as templateTitleField } from './template-title'; +export { default as patternTitleField } from './pattern-title'; export { default as orderField } from './order'; export { default as featuredImageField } from './featured-image'; +export { default as templateField } from './template'; export { default as parentField } from './parent'; export { default as passwordField } from './password'; export { default as statusField } from './status'; diff --git a/packages/fields/src/fields/order/index.ts b/packages/fields/src/fields/order/index.ts index 984a94c6427fc6..121fcb9d0f3bdc 100644 --- a/packages/fields/src/fields/order/index.ts +++ b/packages/fields/src/fields/order/index.ts @@ -16,4 +16,7 @@ const orderField: Field< BasePost > = { description: __( 'Determines the order of pages.' ), }; +/** + * Order field for BasePost. + */ export default orderField; diff --git a/packages/fields/src/fields/page-title/index.ts b/packages/fields/src/fields/page-title/index.ts new file mode 100644 index 00000000000000..8bbf051f2b5839 --- /dev/null +++ b/packages/fields/src/fields/page-title/index.ts @@ -0,0 +1,28 @@ +/** + * WordPress dependencies + */ +import type { Field } from '@wordpress/dataviews'; +import { __ } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import type { BasePost } from '../../types'; +import { getItemTitle } from '../../actions/utils'; +import PageTitleView from './view'; + +const pageTitleField: Field< BasePost > = { + type: 'text', + id: 'title', + label: __( 'Title' ), + placeholder: __( 'No title' ), + getValue: ( { item } ) => getItemTitle( item ), + render: PageTitleView, + enableHiding: false, + enableGlobalSearch: true, +}; + +/** + * Title for the page entity. + */ +export default pageTitleField; diff --git a/packages/fields/src/fields/page-title/view.tsx b/packages/fields/src/fields/page-title/view.tsx new file mode 100644 index 00000000000000..eb5184362ec82b --- /dev/null +++ b/packages/fields/src/fields/page-title/view.tsx @@ -0,0 +1,41 @@ +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; +import { useSelect } from '@wordpress/data'; +import { store as coreStore } from '@wordpress/core-data'; +import type { Settings } from '@wordpress/core-data'; +import { privateApis as componentsPrivateApis } from '@wordpress/components'; + +/** + * Internal dependencies + */ +import type { CommonPost } from '../../types'; +import { BaseTitleView } from '../title/view'; +import { unlock } from '../../lock-unlock'; +const { Badge } = unlock( componentsPrivateApis ); + +export default function PageTitleView( { item }: { item: CommonPost } ) { + const { frontPageId, postsPageId } = useSelect( ( select ) => { + const { getEntityRecord } = select( coreStore ); + const siteSettings = getEntityRecord( + 'root', + 'site' + ) as Partial< Settings >; + return { + frontPageId: siteSettings?.page_on_front, + postsPageId: siteSettings?.page_for_posts, + }; + }, [] ); + return ( + <BaseTitleView item={ item } className="fields-field__page-title"> + { [ frontPageId, postsPageId ].includes( item.id as number ) && ( + <Badge> + { item.id === frontPageId + ? __( 'Homepage' ) + : __( 'Posts Page' ) } + </Badge> + ) } + </BaseTitleView> + ); +} diff --git a/packages/fields/src/fields/parent/index.ts b/packages/fields/src/fields/parent/index.ts index 8b833e1d9369df..f974608710bf7e 100644 --- a/packages/fields/src/fields/parent/index.ts +++ b/packages/fields/src/fields/parent/index.ts @@ -21,6 +21,6 @@ const parentField: Field< BasePost > = { }; /** - * This field is used to display the post parent. + * Parent field for BasePost. */ export default parentField; diff --git a/packages/fields/src/fields/parent/parent-edit.tsx b/packages/fields/src/fields/parent/parent-edit.tsx index 21cdbee7a365a4..60830b02c4ade1 100644 --- a/packages/fields/src/fields/parent/parent-edit.tsx +++ b/packages/fields/src/fields/parent/parent-edit.tsx @@ -122,7 +122,6 @@ export function PageAttributesParent( { const { parentPostTitle, pageItems, isHierarchical } = useSelect( ( select ) => { - // @ts-expect-error getPostType is not typed const { getEntityRecord, getEntityRecords, getPostType } = select( coreStore ); @@ -289,7 +288,6 @@ export const ParentEdit = ( { const { id } = field; const homeUrl = useSelect( ( select ) => { - // @ts-expect-error getEntityRecord is not typed with unstableBase as argument. return select( coreStore ).getEntityRecord< { home: string; } >( 'root', '__unstableBase' )?.home as string; diff --git a/packages/fields/src/fields/password/index.tsx b/packages/fields/src/fields/password/index.tsx index dacd0d7435998a..3d45641b0efd0e 100644 --- a/packages/fields/src/fields/password/index.tsx +++ b/packages/fields/src/fields/password/index.tsx @@ -19,6 +19,6 @@ const passwordField: Field< BasePost > = { }; /** - * This field is used to display the post password. + * Password field for BasePost. */ export default passwordField; diff --git a/packages/fields/src/fields/pattern-title/index.ts b/packages/fields/src/fields/pattern-title/index.ts new file mode 100644 index 00000000000000..a3e54eea6f28d1 --- /dev/null +++ b/packages/fields/src/fields/pattern-title/index.ts @@ -0,0 +1,28 @@ +/** + * WordPress dependencies + */ +import type { Field } from '@wordpress/dataviews'; +import { __ } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import type { Pattern } from '../../types'; +import { getItemTitle } from '../../actions/utils'; +import PatternTitleView from './view'; + +const patternTitleField: Field< Pattern > = { + type: 'text', + id: 'title', + label: __( 'Title' ), + placeholder: __( 'No title' ), + getValue: ( { item } ) => getItemTitle( item ), + render: PatternTitleView, + enableHiding: false, + enableGlobalSearch: true, +}; + +/** + * Title for the pattern entity. + */ +export default patternTitleField; diff --git a/packages/fields/src/fields/pattern-title/style.scss b/packages/fields/src/fields/pattern-title/style.scss new file mode 100644 index 00000000000000..fa4d03c850beac --- /dev/null +++ b/packages/fields/src/fields/pattern-title/style.scss @@ -0,0 +1,3 @@ +.fields-field__pattern-title span:first-child { + flex: 1; +} diff --git a/packages/fields/src/fields/pattern-title/view.tsx b/packages/fields/src/fields/pattern-title/view.tsx new file mode 100644 index 00000000000000..b59360d30e0be2 --- /dev/null +++ b/packages/fields/src/fields/pattern-title/view.tsx @@ -0,0 +1,32 @@ +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; +import { Icon, lockSmall } from '@wordpress/icons'; +import { Tooltip } from '@wordpress/components'; +// @ts-ignore +import { privateApis as patternPrivateApis } from '@wordpress/patterns'; + +/** + * Internal dependencies + */ +import type { CommonPost } from '../../types'; +import { BaseTitleView } from '../title/view'; +import { unlock } from '../../lock-unlock'; + +export const { PATTERN_TYPES } = unlock( patternPrivateApis ); + +export default function PatternTitleView( { item }: { item: CommonPost } ) { + return ( + <BaseTitleView item={ item } className="fields-field__pattern-title"> + { item.type === PATTERN_TYPES.theme && ( + <Tooltip + placement="top" + text={ __( 'This pattern cannot be edited.' ) } + > + <Icon icon={ lockSmall } size={ 24 } /> + </Tooltip> + ) } + </BaseTitleView> + ); +} diff --git a/packages/fields/src/fields/slug/index.ts b/packages/fields/src/fields/slug/index.ts index c43fcc679622ac..7311b1d10a66f6 100644 --- a/packages/fields/src/fields/slug/index.ts +++ b/packages/fields/src/fields/slug/index.ts @@ -19,4 +19,7 @@ const slugField: Field< BasePost > = { render: SlugView, }; +/** + * Slug field for BasePost. + */ export default slugField; diff --git a/packages/fields/src/fields/slug/slug-view.tsx b/packages/fields/src/fields/slug/slug-view.tsx index c418fafd1a9af9..58f6881060c2db 100644 --- a/packages/fields/src/fields/slug/slug-view.tsx +++ b/packages/fields/src/fields/slug/slug-view.tsx @@ -10,7 +10,7 @@ import type { BasePost } from '../../types'; import { getSlug } from './utils'; const SlugView = ( { item }: { item: BasePost } ) => { - const slug = typeof item === 'object' ? getSlug( item ) : ''; + const slug = getSlug( item ); const originalSlugRef = useRef( slug ); useEffect( () => { diff --git a/packages/fields/src/fields/slug/utils.ts b/packages/fields/src/fields/slug/utils.ts index a422afaf898f96..b414146ba17819 100644 --- a/packages/fields/src/fields/slug/utils.ts +++ b/packages/fields/src/fields/slug/utils.ts @@ -9,6 +9,10 @@ import type { BasePost } from '../../types'; import { getItemTitle } from '../../actions/utils'; export const getSlug = ( item: BasePost ): string => { + if ( typeof item !== 'object' ) { + return ''; + } + return ( item.slug || cleanForSlug( getItemTitle( item ) ) || item.id.toString() ); diff --git a/packages/fields/src/fields/template-title/index.ts b/packages/fields/src/fields/template-title/index.ts new file mode 100644 index 00000000000000..e0d83d6ffda8f6 --- /dev/null +++ b/packages/fields/src/fields/template-title/index.ts @@ -0,0 +1,28 @@ +/** + * WordPress dependencies + */ +import type { Field } from '@wordpress/dataviews'; +import { __ } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import type { Template } from '../../types'; +import { getItemTitle } from '../../actions/utils'; +import TitleView from '../title/view'; + +const templateTitleField: Field< Template > = { + type: 'text', + label: __( 'Template' ), + placeholder: __( 'No title' ), + id: 'title', + getValue: ( { item } ) => getItemTitle( item ), + render: TitleView, + enableHiding: false, + enableGlobalSearch: true, +}; + +/** + * Title for the template entity. + */ +export default templateTitleField; diff --git a/packages/fields/src/fields/template/index.ts b/packages/fields/src/fields/template/index.ts new file mode 100644 index 00000000000000..c419adedb537dd --- /dev/null +++ b/packages/fields/src/fields/template/index.ts @@ -0,0 +1,24 @@ +/** + * WordPress dependencies + */ +import type { Field } from '@wordpress/dataviews'; + +/** + * Internal dependencies + */ +import { __ } from '@wordpress/i18n'; +import type { BasePost } from '../../types'; +import { TemplateEdit } from './template-edit'; + +const templateField: Field< BasePost > = { + id: 'template', + type: 'text', + label: __( 'Template' ), + Edit: TemplateEdit, + enableSorting: false, +}; + +/** + * Template field for BasePost. + */ +export default templateField; diff --git a/packages/fields/src/fields/template/style.scss b/packages/fields/src/fields/template/style.scss new file mode 100644 index 00000000000000..a0c2fafec73893 --- /dev/null +++ b/packages/fields/src/fields/template/style.scss @@ -0,0 +1,23 @@ +.fields-controls__template-modal { + z-index: z-index(".fields-controls__template-modal"); +} + +.fields-controls__template-content .block-editor-block-patterns-list { + column-count: 2; + column-gap: $grid-unit-30; + + // Small top padding required to avoid cutting off the visible outline when hovering items + padding-top: $border-width-focus-fallback; + + @include break-medium() { + column-count: 3; + } + + @include break-wide() { + column-count: 4; + } + + .block-editor-block-patterns-list__list-item { + break-inside: avoid-column; + } +} diff --git a/packages/fields/src/fields/template/template-edit.tsx b/packages/fields/src/fields/template/template-edit.tsx new file mode 100644 index 00000000000000..1a96f5d0cab747 --- /dev/null +++ b/packages/fields/src/fields/template/template-edit.tsx @@ -0,0 +1,210 @@ +/** + * WordPress dependencies + */ +import { useCallback, useMemo, useState } from '@wordpress/element'; +// @ts-ignore +import { parse } from '@wordpress/blocks'; +import type { WpTemplate } from '@wordpress/core-data'; +import { store as coreStore } from '@wordpress/core-data'; +import type { DataFormControlProps } from '@wordpress/dataviews'; + +/** + * Internal dependencies + */ +// @ts-expect-error block-editor is not typed correctly. +import { __experimentalBlockPatternsList as BlockPatternsList } from '@wordpress/block-editor'; +import { + Button, + Dropdown, + MenuGroup, + MenuItem, + Modal, +} from '@wordpress/components'; +import { useAsyncList } from '@wordpress/compose'; +import { useSelect } from '@wordpress/data'; +import { decodeEntities } from '@wordpress/html-entities'; +import { __ } from '@wordpress/i18n'; +import { getItemTitle } from '../../actions/utils'; +import type { BasePost } from '../../types'; +import { unlock } from '../../lock-unlock'; + +export const TemplateEdit = ( { + data, + field, + onChange, +}: DataFormControlProps< BasePost > ) => { + const { id } = field; + const postType = data.type; + const postId = + typeof data.id === 'number' ? data.id : parseInt( data.id, 10 ); + const slug = data.slug; + + const { availableTemplates, templates } = useSelect( + ( select ) => { + const allTemplates = + select( coreStore ).getEntityRecords< WpTemplate >( + 'postType', + 'wp_template', + { + per_page: -1, + post_type: postType, + } + ) ?? []; + + const { getHomePage, getPostsPageId } = unlock( + select( coreStore ) + ); + + const isPostsPage = getPostsPageId() === +postId; + const isFrontPage = + postType === 'page' && getHomePage()?.postId === +postId; + + const allowSwitchingTemplate = ! isPostsPage && ! isFrontPage; + + return { + templates: allTemplates, + availableTemplates: allowSwitchingTemplate + ? allTemplates.filter( + ( template ) => + template.is_custom && + template.slug !== data.template && + !! template.content.raw // Skip empty templates. + ) + : [], + }; + }, + [ data.template, postId, postType ] + ); + + const templatesAsPatterns = useMemo( + () => + availableTemplates.map( ( template ) => ( { + name: template.slug, + blocks: parse( template.content.raw ), + title: decodeEntities( template.title.rendered ), + id: template.id, + } ) ), + [ availableTemplates ] + ); + + const shownTemplates = useAsyncList( templatesAsPatterns ); + + const value = field.getValue( { item: data } ); + + const currentTemplate = useSelect( + ( select ) => { + const foundTemplate = templates?.find( + ( template ) => template.slug === value + ); + + if ( foundTemplate ) { + return foundTemplate; + } + + let slugToCheck; + // In `draft` status we might not have a slug available, so we use the `single` + // post type templates slug(ex page, single-post, single-product etc..). + // Pages do not need the `single` prefix in the slug to be prioritized + // through template hierarchy. + if ( slug ) { + slugToCheck = + postType === 'page' + ? `${ postType }-${ slug }` + : `single-${ postType }-${ slug }`; + } else { + slugToCheck = + postType === 'page' ? 'page' : `single-${ postType }`; + } + + if ( postType ) { + const templateId = select( coreStore ).getDefaultTemplateId( { + slug: slugToCheck, + } ); + + return select( coreStore ).getEntityRecord( + 'postType', + 'wp_template', + templateId + ); + } + }, + [ postType, slug, templates, value ] + ); + + const [ showModal, setShowModal ] = useState( false ); + + const onChangeControl = useCallback( + ( newValue: string ) => + onChange( { + [ id ]: newValue, + } ), + [ id, onChange ] + ); + + return ( + <fieldset className="fields-controls__template"> + <Dropdown + popoverProps={ { placement: 'bottom-start' } } + renderToggle={ ( { onToggle } ) => ( + <Button + __next40pxDefaultSize + variant="tertiary" + size="compact" + onClick={ onToggle } + > + { currentTemplate + ? getItemTitle( currentTemplate ) + : '' } + </Button> + ) } + renderContent={ ( { onToggle } ) => ( + <MenuGroup> + <MenuItem + onClick={ () => { + setShowModal( true ); + onToggle(); + } } + > + { __( 'Change template' ) } + </MenuItem> + { + // The default template in a post is indicated by an empty string + value !== '' && ( + <MenuItem + onClick={ () => { + onChangeControl( '' ); + onToggle(); + } } + > + { __( 'Use default template' ) } + </MenuItem> + ) + } + </MenuGroup> + ) } + /> + { showModal && ( + <Modal + title={ __( 'Choose a template' ) } + onRequestClose={ () => setShowModal( false ) } + overlayClassName="fields-controls__template-modal" + isFullScreen + > + <div className="fields-controls__template-content"> + <BlockPatternsList + label={ __( 'Templates' ) } + blockPatterns={ templatesAsPatterns } + shownPatterns={ shownTemplates } + onClickPattern={ ( + template: ( typeof templatesAsPatterns )[ 0 ] + ) => { + onChangeControl( template.name ); + setShowModal( false ); + } } + /> + </div> + </Modal> + ) } + </fieldset> + ); +}; diff --git a/packages/fields/src/fields/title/index.ts b/packages/fields/src/fields/title/index.ts index d8e6f25276d6b8..b4a2399526fdd9 100644 --- a/packages/fields/src/fields/title/index.ts +++ b/packages/fields/src/fields/title/index.ts @@ -7,11 +7,11 @@ import { __ } from '@wordpress/i18n'; /** * Internal dependencies */ -import type { BasePost } from '../../types'; +import type { CommonPost } from '../../types'; import { getItemTitle } from '../../actions/utils'; -import TitleView from './title-view'; +import TitleView from './view'; -const titleField: Field< BasePost > = { +const titleField: Field< CommonPost > = { type: 'text', id: 'title', label: __( 'Title' ), @@ -19,6 +19,12 @@ const titleField: Field< BasePost > = { getValue: ( { item } ) => getItemTitle( item ), render: TitleView, enableHiding: false, + enableGlobalSearch: true, }; +/** + * Title for the any entity with a `title` property. + * For patterns, pages or templates you should use the respective field + * because there are some differences in the rendering, labels, etc. + */ export default titleField; diff --git a/packages/fields/src/fields/title/style.scss b/packages/fields/src/fields/title/style.scss new file mode 100644 index 00000000000000..5b9977dcaa398c --- /dev/null +++ b/packages/fields/src/fields/title/style.scss @@ -0,0 +1,8 @@ +.fields-field__title span:first-child { + text-overflow: ellipsis; + overflow: hidden; + text-decoration: none; + white-space: nowrap; + display: block; + flex-grow: 0; +} diff --git a/packages/fields/src/fields/title/title-view.tsx b/packages/fields/src/fields/title/title-view.tsx deleted file mode 100644 index c15ed96b89b73b..00000000000000 --- a/packages/fields/src/fields/title/title-view.tsx +++ /dev/null @@ -1,62 +0,0 @@ -/** - * WordPress dependencies - */ -import { __experimentalHStack as HStack } from '@wordpress/components'; -import { decodeEntities } from '@wordpress/html-entities'; -import { __ } from '@wordpress/i18n'; -import { useSelect } from '@wordpress/data'; -import { store as coreStore } from '@wordpress/core-data'; -import type { Settings } from '@wordpress/core-data'; - -/** - * Internal dependencies - */ -import type { BasePost } from '../../types'; -import { getItemTitle } from '../../actions/utils'; - -const TitleView = ( { item }: { item: BasePost } ) => { - const { frontPageId, postsPageId } = useSelect( ( select ) => { - const { getEntityRecord } = select( coreStore ); - const siteSettings: Settings | undefined = getEntityRecord( - 'root', - 'site', - '' - ); - return { - frontPageId: siteSettings?.page_on_front, - postsPageId: siteSettings?.page_for_posts, - }; - }, [] ); - - const renderedTitle = getItemTitle( item ); - - let suffix; - if ( item.id === frontPageId ) { - suffix = ( - <span className="edit-site-post-list__title-badge"> - { __( 'Homepage' ) } - </span> - ); - } else if ( item.id === postsPageId ) { - suffix = ( - <span className="edit-site-post-list__title-badge"> - { __( 'Posts Page' ) } - </span> - ); - } - - return ( - <HStack - className="edit-site-post-list__title" - alignment="center" - justify="flex-start" - > - <span> - { decodeEntities( renderedTitle ) || __( '(no title)' ) } - </span> - { suffix } - </HStack> - ); -}; - -export default TitleView; diff --git a/packages/fields/src/fields/title/view.tsx b/packages/fields/src/fields/title/view.tsx new file mode 100644 index 00000000000000..b6f39796a57f05 --- /dev/null +++ b/packages/fields/src/fields/title/view.tsx @@ -0,0 +1,43 @@ +/** + * External dependencies + */ +import clsx from 'clsx'; +import type { ReactNode } from 'react'; + +/** + * WordPress dependencies + */ +import { __experimentalHStack as HStack } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import type { CommonPost } from '../../types'; +import { getItemTitle } from '../../actions/utils'; + +export function BaseTitleView( { + item, + className, + children, +}: { + item: CommonPost; + className?: string; + children?: ReactNode; +} ) { + const renderedTitle = getItemTitle( item ); + return ( + <HStack + className={ clsx( 'fields-field__title', className ) } + alignment="center" + justify="flex-start" + > + <span>{ renderedTitle || __( '(no title)' ) }</span> + { children } + </HStack> + ); +} + +export default function TitleView( { item }: { item: CommonPost } ) { + return <BaseTitleView item={ item } />; +} diff --git a/packages/fields/src/index.native.ts b/packages/fields/src/index.native.ts index 33a26e3c2e6e27..e69de29bb2d1d6 100644 --- a/packages/fields/src/index.native.ts +++ b/packages/fields/src/index.native.ts @@ -1,2 +0,0 @@ -export * from './actions/duplicate-post.native'; -export * from './actions/reorder-page.native'; diff --git a/packages/fields/src/index.ts b/packages/fields/src/index.ts index 4c721b85b61a40..bf1e4dfda2ddfd 100644 --- a/packages/fields/src/index.ts +++ b/packages/fields/src/index.ts @@ -1,2 +1,4 @@ export * from './fields'; export * from './actions'; +export { default as CreateTemplatePartModal } from './components/create-template-part-modal'; +export type { BasePostWithEmbeddedAuthor, BasePost, PostType } from './types'; diff --git a/packages/fields/src/style.scss b/packages/fields/src/style.scss index 1639f455ba093e..96b1f816de5b61 100644 --- a/packages/fields/src/style.scss +++ b/packages/fields/src/style.scss @@ -1,2 +1,6 @@ +@import "./components/create-template-part-modal/style.scss"; @import "./fields/slug/style.scss"; @import "./fields/featured-image/style.scss"; +@import "./fields/template/style.scss"; +@import "./fields/title/style.scss"; +@import "./fields/pattern-title/style.scss"; diff --git a/packages/fields/src/types.ts b/packages/fields/src/types.ts index e457ec699554cd..d9594c58e09793 100644 --- a/packages/fields/src/types.ts +++ b/packages/fields/src/types.ts @@ -32,6 +32,9 @@ interface EmbeddedAuthor { author: Author[]; } +/** + * BasePost interface used for all post types. + */ export interface BasePost extends CommonPost { comment_status?: 'open' | 'closed'; excerpt?: string | { raw: string; rendered: string }; @@ -97,6 +100,10 @@ export interface PostType { 'page-attributes'?: boolean; title?: boolean; revisions?: boolean; + author?: string; + thumbnail?: string; + comments?: string; + editor?: boolean; }; } diff --git a/packages/fields/tsconfig.json b/packages/fields/tsconfig.json index 5c4b91e88f895a..552aa73b8e5cce 100644 --- a/packages/fields/tsconfig.json +++ b/packages/fields/tsconfig.json @@ -2,8 +2,6 @@ "$schema": "https://json.schemastore.org/tsconfig.json", "extends": "../../tsconfig.base.json", "compilerOptions": { - "rootDir": "src", - "declarationDir": "build-types", "checkJs": false }, "references": [ @@ -12,6 +10,7 @@ { "path": "../components" }, { "path": "../compose" }, { "path": "../core-data" }, + { "path": "../block-editor" }, { "path": "../data" }, { "path": "../dataviews" }, { "path": "../date" }, @@ -26,7 +25,7 @@ { "path": "../private-apis" }, { "path": "../router" }, { "path": "../url" }, + { "path": "../block-editor" }, { "path": "../warning" } - ], - "include": [ "src" ] + ] } diff --git a/packages/format-library/CHANGELOG.md b/packages/format-library/CHANGELOG.md index f44865c4325e23..87790c5a971cec 100644 --- a/packages/format-library/CHANGELOG.md +++ b/packages/format-library/CHANGELOG.md @@ -2,6 +2,16 @@ ## Unreleased +## 5.16.0 (2025-01-15) + +## 5.15.0 (2025-01-02) + +## 5.14.0 (2024-12-11) + +## 5.13.0 (2024-11-27) + +## 5.12.0 (2024-11-16) + ## 5.11.0 (2024-10-30) ## 5.10.0 (2024-10-16) diff --git a/packages/format-library/package.json b/packages/format-library/package.json index 41852910ff5389..58d84ff93522b4 100644 --- a/packages/format-library/package.json +++ b/packages/format-library/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/format-library", - "version": "5.11.0", + "version": "5.16.0", "description": "Format library for the WordPress editor.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -28,18 +28,18 @@ "wpScript": true, "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/a11y": "*", - "@wordpress/block-editor": "*", - "@wordpress/components": "*", - "@wordpress/compose": "*", - "@wordpress/data": "*", - "@wordpress/element": "*", - "@wordpress/html-entities": "*", - "@wordpress/i18n": "*", - "@wordpress/icons": "*", - "@wordpress/private-apis": "*", - "@wordpress/rich-text": "*", - "@wordpress/url": "*" + "@wordpress/a11y": "file:../a11y", + "@wordpress/block-editor": "file:../block-editor", + "@wordpress/components": "file:../components", + "@wordpress/compose": "file:../compose", + "@wordpress/data": "file:../data", + "@wordpress/element": "file:../element", + "@wordpress/html-entities": "file:../html-entities", + "@wordpress/i18n": "file:../i18n", + "@wordpress/icons": "file:../icons", + "@wordpress/private-apis": "file:../private-apis", + "@wordpress/rich-text": "file:../rich-text", + "@wordpress/url": "file:../url" }, "peerDependencies": { "react": "^18.0.0", diff --git a/packages/format-library/src/link/inline.js b/packages/format-library/src/link/inline.js index 3d2ee57567dc13..e4e3e29d2a4e3c 100644 --- a/packages/format-library/src/link/inline.js +++ b/packages/format-library/src/link/inline.js @@ -19,7 +19,7 @@ import { useAnchor, } from '@wordpress/rich-text'; import { - __experimentalLinkControl as LinkControl, + LinkControl, store as blockEditorStore, } from '@wordpress/block-editor'; import { useDispatch, useSelect } from '@wordpress/data'; @@ -181,7 +181,7 @@ function InlineLinkUI( { // As "replace" will operate on the first match only, it is // run only against the second half of the value which was // split at the active format's boundary. This avoids a bug - // with incorrectly targetted replacements. + // with incorrectly targeted replacements. // See: https://github.com/WordPress/gutenberg/issues/41771. // Note original formats will be lost when applying this change. // That is expected behaviour. diff --git a/packages/format-library/src/link/utils.js b/packages/format-library/src/link/utils.js index 314c8118713a43..4cf611f7c51fb3 100644 --- a/packages/format-library/src/link/utils.js +++ b/packages/format-library/src/link/utils.js @@ -202,7 +202,7 @@ export function getFormatBoundary( // Safe guard: start index cannot be less than 0. startIndex = startIndex < 0 ? 0 : startIndex; - // // Return the indicies of the "edges" as the boundaries. + // // Return the indices of the "edges" as the boundaries. return { start: startIndex, end: endIndex, diff --git a/packages/format-library/src/text-color/index.js b/packages/format-library/src/text-color/index.js index 9d8241544760bf..04fd8ee6ce55aa 100644 --- a/packages/format-library/src/text-color/index.js +++ b/packages/format-library/src/text-color/index.js @@ -75,7 +75,7 @@ function TextColorEdit( { [ contentRef, value, colors ] ); - const hasColorsToChoose = colors.length || ! allowCustomControl; + const hasColorsToChoose = !! colors.length || allowCustomControl; if ( ! hasColorsToChoose && ! isActive ) { return null; } diff --git a/packages/hooks/CHANGELOG.md b/packages/hooks/CHANGELOG.md index 284fa3c5098904..b1a1d223d76cfd 100644 --- a/packages/hooks/CHANGELOG.md +++ b/packages/hooks/CHANGELOG.md @@ -2,6 +2,16 @@ ## Unreleased +## 4.16.0 (2025-01-15) + +## 4.15.0 (2025-01-02) + +## 4.14.0 (2024-12-11) + +## 4.13.0 (2024-11-27) + +## 4.12.0 (2024-11-16) + ## 4.11.0 (2024-10-30) ## 4.10.0 (2024-10-16) @@ -177,7 +187,7 @@ ### New Features -- Enable an optional namespace parameter for `hasAction` & `hasFilter`. When checking if an action or filter exists, `hasAction` and `hasFilter` now accept an optional paramter to limit matches by namespace. +- Enable an optional namespace parameter for `hasAction` & `hasFilter`. When checking if an action or filter exists, `hasAction` and `hasFilter` now accept an optional parameter to limit matches by namespace. ## 2.4.0 (2019-06-12) diff --git a/packages/hooks/package.json b/packages/hooks/package.json index 857cb24a877468..3ff2bc1acfa317 100644 --- a/packages/hooks/package.json +++ b/packages/hooks/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/hooks", - "version": "4.11.0", + "version": "4.16.0", "description": "WordPress hooks library.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/hooks/src/test/index.test.js b/packages/hooks/src/test/index.test.js index 5fdaf5fc7207a1..343b148c334fbf 100644 --- a/packages/hooks/src/test/index.test.js +++ b/packages/hooks/src/test/index.test.js @@ -67,7 +67,7 @@ function actionC() { beforeEach( () => { window.actionValue = ''; // Reset state in between tests (clear all callbacks, `didAction` counts, - // etc.) Just reseting actions and filters is not enough + // etc.) Just resetting actions and filters is not enough // because the internal functions have references to the original objects. [ actions, filters ].forEach( ( hooks ) => { for ( const k in hooks ) { diff --git a/packages/hooks/tsconfig.json b/packages/hooks/tsconfig.json index 9e3edfe0ae443c..f197b56919708b 100644 --- a/packages/hooks/tsconfig.json +++ b/packages/hooks/tsconfig.json @@ -2,9 +2,6 @@ "$schema": "https://json.schemastore.org/tsconfig.json", "extends": "../../tsconfig.base.json", "compilerOptions": { - "rootDir": "src", - "declarationDir": "build-types", "types": [ "gutenberg-env" ] - }, - "include": [ "src/**/*" ] + } } diff --git a/packages/html-entities/CHANGELOG.md b/packages/html-entities/CHANGELOG.md index e2d20a85313008..2c8c8b8adfe516 100644 --- a/packages/html-entities/CHANGELOG.md +++ b/packages/html-entities/CHANGELOG.md @@ -2,6 +2,16 @@ ## Unreleased +## 4.16.0 (2025-01-15) + +## 4.15.0 (2025-01-02) + +## 4.14.0 (2024-12-11) + +## 4.13.0 (2024-11-27) + +## 4.12.0 (2024-11-16) + ## 4.11.0 (2024-10-30) ## 4.10.0 (2024-10-16) diff --git a/packages/html-entities/package.json b/packages/html-entities/package.json index 4ecab2056c9c3a..310c2c95caf891 100644 --- a/packages/html-entities/package.json +++ b/packages/html-entities/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/html-entities", - "version": "4.11.0", + "version": "4.16.0", "description": "HTML entity utilities for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/html-entities/tsconfig.json b/packages/html-entities/tsconfig.json index 6e33d8ff82d47e..7ff060ab6ce105 100644 --- a/packages/html-entities/tsconfig.json +++ b/packages/html-entities/tsconfig.json @@ -1,9 +1,4 @@ { "$schema": "https://json.schemastore.org/tsconfig.json", - "extends": "../../tsconfig.base.json", - "compilerOptions": { - "rootDir": "src", - "declarationDir": "build-types" - }, - "include": [ "src/**/*" ] + "extends": "../../tsconfig.base.json" } diff --git a/packages/i18n/CHANGELOG.md b/packages/i18n/CHANGELOG.md index 045589f3d7a3c4..f19f1addd8514a 100644 --- a/packages/i18n/CHANGELOG.md +++ b/packages/i18n/CHANGELOG.md @@ -2,6 +2,16 @@ ## Unreleased +## 5.16.0 (2025-01-15) + +## 5.15.0 (2025-01-02) + +## 5.14.0 (2024-12-11) + +## 5.13.0 (2024-11-27) + +## 5.12.0 (2024-11-16) + ## 5.11.0 (2024-10-30) ## 5.10.0 (2024-10-16) diff --git a/packages/i18n/package.json b/packages/i18n/package.json index 53e81b64a65349..c79faf5d754137 100644 --- a/packages/i18n/package.json +++ b/packages/i18n/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/i18n", - "version": "5.11.0", + "version": "5.16.0", "description": "WordPress internationalization (i18n) library.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -32,7 +32,7 @@ }, "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/hooks": "*", + "@wordpress/hooks": "file:../hooks", "gettext-parser": "^1.3.1", "memize": "^2.1.0", "sprintf-js": "^1.1.1", diff --git a/packages/i18n/src/test/create-i18n.js b/packages/i18n/src/test/create-i18n.js index 51e280a358b34c..d588f5e37a30e7 100644 --- a/packages/i18n/src/test/create-i18n.js +++ b/packages/i18n/src/test/create-i18n.js @@ -359,7 +359,7 @@ describe( 'createI18n', () => { 'translated_plural_2' ); - // Reset the locale data and fallback to the defualt plural forms function. + // Reset the locale data and fallback to the default plural forms function. locale.resetLocaleData( { singular: [ diff --git a/packages/i18n/tsconfig.json b/packages/i18n/tsconfig.json index f90e327f124d7e..b2186db14f4cc4 100644 --- a/packages/i18n/tsconfig.json +++ b/packages/i18n/tsconfig.json @@ -1,10 +1,5 @@ { "$schema": "https://json.schemastore.org/tsconfig.json", "extends": "../../tsconfig.base.json", - "compilerOptions": { - "rootDir": "src", - "declarationDir": "build-types" - }, - "references": [ { "path": "../hooks" } ], - "include": [ "src/**/*" ] + "references": [ { "path": "../hooks" } ] } diff --git a/packages/icons/CHANGELOG.md b/packages/icons/CHANGELOG.md index 0649fe97ca5a46..5e0ceaf9552e5c 100644 --- a/packages/icons/CHANGELOG.md +++ b/packages/icons/CHANGELOG.md @@ -2,6 +2,20 @@ ## Unreleased +## 10.16.0 (2025-01-15) + +## 10.15.0 (2025-01-02) + +- Add new `caution` icon ([#66555](https://github.com/WordPress/gutenberg/pull/66555)). +- Add new `error` icon ([#66555](https://github.com/WordPress/gutenberg/pull/66555)). +- Deprecate `warning` icon and rename to `cautionFilled` ([#67895](https://github.com/WordPress/gutenberg/pull/67895)). + +## 10.14.0 (2024-12-11) + +## 10.13.0 (2024-11-27) + +## 10.12.0 (2024-11-16) + ## 10.11.0 (2024-10-30) ## 10.10.0 (2024-10-16) diff --git a/packages/icons/package.json b/packages/icons/package.json index 895f2e184b4e48..1452f17f600989 100644 --- a/packages/icons/package.json +++ b/packages/icons/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/icons", - "version": "10.11.0", + "version": "10.16.0", "description": "WordPress Icons package, based on dashicon.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -31,8 +31,8 @@ "sideEffects": false, "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/element": "*", - "@wordpress/primitives": "*" + "@wordpress/element": "file:../element", + "@wordpress/primitives": "file:../primitives" }, "publishConfig": { "access": "public" diff --git a/packages/icons/src/icon/index.js b/packages/icons/src/icon/index.js index c83a5179a41b8e..221970bd21b930 100644 --- a/packages/icons/src/icon/index.js +++ b/packages/icons/src/icon/index.js @@ -9,7 +9,7 @@ import { cloneElement, forwardRef } from '@wordpress/element'; * Return an SVG icon. * * @param {IconProps} props icon is the SVG component to render - * size is a number specifiying the icon size in pixels + * size is a number specifying the icon size in pixels * Other props will be passed to wrapped SVG component * @param {import('react').ForwardedRef<HTMLElement>} ref The forwarded ref to the SVG element. * diff --git a/packages/icons/src/icon/stories/index.story.js b/packages/icons/src/icon/stories/index.story.js index 092434de43b4dc..406f986e6ef5dc 100644 --- a/packages/icons/src/icon/stories/index.story.js +++ b/packages/icons/src/icon/stories/index.story.js @@ -9,8 +9,16 @@ import { useState, Fragment } from '@wordpress/element'; import Icon from '../'; import check from '../../library/check'; import * as icons from '../../'; +import keywords from './keywords'; -const { Icon: _Icon, ...availableIcons } = icons; +const { + Icon: _Icon, + + // Deprecated aliases + warning: _warning, + + ...availableIcons +} = icons; const meta = { component: Icon, @@ -46,14 +54,23 @@ const LibraryExample = () => { const [ filter, setFilter ] = useState( '' ); const filteredIcons = filter.length ? Object.fromEntries( - Object.entries( availableIcons ).filter( ( [ name ] ) => - name.toLowerCase().includes( filter.toLowerCase() ) - ) + Object.entries( availableIcons ).filter( ( [ name ] ) => { + const normalizedName = name.toLowerCase(); + const normalizedFilter = filter.toLowerCase(); + + return ( + normalizedName.includes( normalizedFilter ) || + // @ts-expect-error - Not worth the effort to cast `name` + keywords[ name ]?.some( ( keyword ) => + keyword.toLowerCase().includes( normalizedFilter ) + ) + ); + } ) ) : availableIcons; return ( <div style={ { padding: 20 } }> - <label htmlFor="filter-icon" style={ { paddingRight: 10 } }> + <label htmlFor="filter-icons" style={ { paddingRight: 10 } }> Filter Icons </label> <input diff --git a/packages/icons/src/icon/stories/keywords.ts b/packages/icons/src/icon/stories/keywords.ts new file mode 100644 index 00000000000000..4de5ae9a7dae93 --- /dev/null +++ b/packages/icons/src/icon/stories/keywords.ts @@ -0,0 +1,15 @@ +const keywords: Partial< Record< keyof typeof import('../../'), string[] > > = { + cancelCircleFilled: [ 'close' ], + caution: [ 'alert', 'warning' ], + cautionFilled: [ 'alert', 'warning' ], + create: [ 'add' ], + error: [ 'alert', 'caution', 'warning' ], + file: [ 'folder' ], + seen: [ 'show' ], + thumbsDown: [ 'dislike' ], + thumbsUp: [ 'like' ], + trash: [ 'delete' ], + unseen: [ 'hide' ], +}; + +export default keywords; diff --git a/packages/icons/src/index.js b/packages/icons/src/index.js index 14eaec92b78c4d..e82b09e5d5afe9 100644 --- a/packages/icons/src/index.js +++ b/packages/icons/src/index.js @@ -37,6 +37,12 @@ export { default as caption } from './library/caption'; export { default as capturePhoto } from './library/capture-photo'; export { default as captureVideo } from './library/capture-video'; export { default as category } from './library/category'; +export { default as caution } from './library/caution'; +export { + /** @deprecated Import `cautionFilled` instead. */ + default as warning, + default as cautionFilled, +} from './library/caution-filled'; export { default as chartBar } from './library/chart-bar'; export { default as check } from './library/check'; export { default as chevronDown } from './library/chevron-down'; @@ -84,6 +90,7 @@ export { default as download } from './library/download'; export { default as edit } from './library/edit'; export { default as envelope } from './library/envelope'; export { default as external } from './library/external'; +export { default as error } from './library/error'; export { default as file } from './library/file'; export { default as filter } from './library/filter'; export { default as flipHorizontal } from './library/flip-horizontal'; @@ -301,6 +308,5 @@ export { default as update } from './library/update'; export { default as upload } from './library/upload'; export { default as verse } from './library/verse'; export { default as video } from './library/video'; -export { default as warning } from './library/warning'; export { default as widget } from './library/widget'; export { default as wordpress } from './library/wordpress'; diff --git a/packages/icons/src/library/caution-filled.js b/packages/icons/src/library/caution-filled.js new file mode 100644 index 00000000000000..5e7779db85f862 --- /dev/null +++ b/packages/icons/src/library/caution-filled.js @@ -0,0 +1,12 @@ +/** + * WordPress dependencies + */ +import { SVG, Path } from '@wordpress/primitives'; + +const cautionFilled = ( + <SVG xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> + <Path d="M12 4C7.58172 4 4 7.58172 4 12C4 16.4183 7.58172 20 12 20C16.4183 20 20 16.4183 20 12C20 7.58172 16.4183 4 12 4ZM12.75 8V13H11.25V8H12.75ZM12.75 14.5V16H11.25V14.5H12.75Z" /> + </SVG> +); + +export default cautionFilled; diff --git a/packages/icons/src/library/caution.js b/packages/icons/src/library/caution.js new file mode 100644 index 00000000000000..f6d23fdfc7eddf --- /dev/null +++ b/packages/icons/src/library/caution.js @@ -0,0 +1,16 @@ +/** + * WordPress dependencies + */ +import { SVG, Path } from '@wordpress/primitives'; + +const caution = ( + <SVG viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"> + <Path + fillRule="evenodd" + clipRule="evenodd" + d="M5.5 12a6.5 6.5 0 1 0 13 0 6.5 6.5 0 0 0-13 0ZM12 4a8 8 0 1 0 0 16 8 8 0 0 0 0-16Zm-.75 12v-1.5h1.5V16h-1.5Zm0-8v5h1.5V8h-1.5Z" + /> + </SVG> +); + +export default caution; diff --git a/packages/icons/src/library/error.js b/packages/icons/src/library/error.js new file mode 100644 index 00000000000000..2dc2bccbf639ce --- /dev/null +++ b/packages/icons/src/library/error.js @@ -0,0 +1,16 @@ +/** + * WordPress dependencies + */ +import { SVG, Path } from '@wordpress/primitives'; + +const error = ( + <SVG viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"> + <Path + fillRule="evenodd" + clipRule="evenodd" + d="M12.218 5.377a.25.25 0 0 0-.436 0l-7.29 12.96a.25.25 0 0 0 .218.373h14.58a.25.25 0 0 0 .218-.372l-7.29-12.96Zm-1.743-.735c.669-1.19 2.381-1.19 3.05 0l7.29 12.96a1.75 1.75 0 0 1-1.525 2.608H4.71a1.75 1.75 0 0 1-1.525-2.608l7.29-12.96ZM12.75 17.46h-1.5v-1.5h1.5v1.5Zm-1.5-3h1.5v-5h-1.5v5Z" + /> + </SVG> +); + +export default error; diff --git a/packages/icons/src/library/info.js b/packages/icons/src/library/info.js index f3425d9e950415..24d41d798263f7 100644 --- a/packages/icons/src/library/info.js +++ b/packages/icons/src/library/info.js @@ -4,8 +4,12 @@ import { SVG, Path } from '@wordpress/primitives'; const info = ( - <SVG xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> - <Path d="M12 3.2c-4.8 0-8.8 3.9-8.8 8.8 0 4.8 3.9 8.8 8.8 8.8 4.8 0 8.8-3.9 8.8-8.8 0-4.8-4-8.8-8.8-8.8zm0 16c-4 0-7.2-3.3-7.2-7.2C4.8 8 8 4.8 12 4.8s7.2 3.3 7.2 7.2c0 4-3.2 7.2-7.2 7.2zM11 17h2v-6h-2v6zm0-8h2V7h-2v2z" /> + <SVG viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"> + <Path + fillRule="evenodd" + clipRule="evenodd" + d="M5.5 12a6.5 6.5 0 1 0 13 0 6.5 6.5 0 0 0-13 0ZM12 4a8 8 0 1 0 0 16 8 8 0 0 0 0-16Zm.75 4v1.5h-1.5V8h1.5Zm0 8v-5h-1.5v5h1.5Z" + /> </SVG> ); diff --git a/packages/icons/src/library/warning.js b/packages/icons/src/library/warning.js deleted file mode 100644 index 97086c5c9292bd..00000000000000 --- a/packages/icons/src/library/warning.js +++ /dev/null @@ -1,12 +0,0 @@ -/** - * WordPress dependencies - */ -import { SVG, Path } from '@wordpress/primitives'; - -const warning = ( - <SVG xmlns="http://www.w3.org/2000/svg" viewBox="-2 -2 24 24"> - <Path d="M10 2c4.42 0 8 3.58 8 8s-3.58 8-8 8-8-3.58-8-8 3.58-8 8-8zm1.13 9.38l.35-6.46H8.52l.35 6.46h2.26zm-.09 3.36c.24-.23.37-.55.37-.96 0-.42-.12-.74-.36-.97s-.59-.35-1.06-.35-.82.12-1.07.35-.37.55-.37.97c0 .41.13.73.38.96.26.23.61.34 1.06.34s.8-.11 1.05-.34z" /> - </SVG> -); - -export default warning; diff --git a/packages/icons/tsconfig.json b/packages/icons/tsconfig.json index 2877b1d31633bd..75638a3b50a790 100644 --- a/packages/icons/tsconfig.json +++ b/packages/icons/tsconfig.json @@ -1,10 +1,5 @@ { "$schema": "https://json.schemastore.org/tsconfig.json", "extends": "../../tsconfig.base.json", - "compilerOptions": { - "rootDir": "src", - "declarationDir": "build-types" - }, - "include": [ "src/**/*" ], "references": [ { "path": "../element" }, { "path": "../primitives" } ] } diff --git a/packages/interactivity-router/CHANGELOG.md b/packages/interactivity-router/CHANGELOG.md index fee7d90751ad5b..6cdc75206658a0 100644 --- a/packages/interactivity-router/CHANGELOG.md +++ b/packages/interactivity-router/CHANGELOG.md @@ -2,6 +2,16 @@ ## Unreleased +## 2.16.0 (2025-01-15) + +## 2.15.0 (2025-01-02) + +## 2.14.0 (2024-12-11) + +## 2.13.0 (2024-11-27) + +## 2.12.0 (2024-11-16) + ## 2.11.0 (2024-10-30) ## 2.10.0 (2024-10-16) diff --git a/packages/interactivity-router/package.json b/packages/interactivity-router/package.json index 378ddda56ee76b..ebe1a30209bd0a 100644 --- a/packages/interactivity-router/package.json +++ b/packages/interactivity-router/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/interactivity-router", - "version": "2.11.0", + "version": "2.16.0", "description": "Package that exposes state and actions from the `core/router` store, part of the Interactivity API.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -28,8 +28,8 @@ "wpScriptModuleExports": "./build-module/index.js", "types": "build-types", "dependencies": { - "@wordpress/a11y": "*", - "@wordpress/interactivity": "*" + "@wordpress/a11y": "file:../a11y", + "@wordpress/interactivity": "file:../interactivity" }, "publishConfig": { "access": "public" diff --git a/packages/interactivity-router/src/assets/styles.ts b/packages/interactivity-router/src/assets/styles.ts new file mode 100644 index 00000000000000..ddb41eabc7a758 --- /dev/null +++ b/packages/interactivity-router/src/assets/styles.ts @@ -0,0 +1,79 @@ +const cssUrlRegEx = + /url\(\s*(?:(["'])((?:\\.|[^\n\\"'])+)\1|((?:\\.|[^\s,"'()\\])+))\s*\)/g; + +const resolveUrl = ( relativeUrl: string, baseUrl: string ) => { + try { + return new URL( relativeUrl, baseUrl ).toString(); + } catch ( e ) { + return relativeUrl; + } +}; + +const withAbsoluteUrls = ( cssText: string, baseUrl: string ) => + cssText.replace( + cssUrlRegEx, + ( _match, quotes = '', relUrl1, relUrl2 ) => + `url(${ quotes }${ resolveUrl( + relUrl1 || relUrl2, + baseUrl + ) }${ quotes })` + ); + +const styleSheetCache = new Map< string, Promise< CSSStyleSheet > >(); + +const getCachedSheet = async ( + sheetId: string, + factory: () => Promise< CSSStyleSheet > +) => { + if ( ! styleSheetCache.has( sheetId ) ) { + styleSheetCache.set( sheetId, factory() ); + } + return styleSheetCache.get( sheetId ); +}; + +const sheetFromLink = async ( + { id, href, sheet: elementSheet }: HTMLLinkElement, + baseUrl: string +) => { + const sheetId = id || href; + const sheetUrl = resolveUrl( href, baseUrl ); + + if ( elementSheet ) { + return getCachedSheet( sheetId, () => { + const sheet = new CSSStyleSheet(); + for ( const { cssText } of elementSheet.cssRules ) { + sheet.insertRule( withAbsoluteUrls( cssText, sheetUrl ) ); + } + return Promise.resolve( sheet ); + } ); + } + return getCachedSheet( sheetId, async () => { + const response = await fetch( href ); + const text = await response.text(); + const sheet = new CSSStyleSheet(); + await sheet.replace( withAbsoluteUrls( text, sheetUrl ) ); + return sheet; + } ); +}; + +const sheetFromStyle = async ( { textContent }: HTMLStyleElement ) => { + const sheetId = textContent; + return getCachedSheet( sheetId, async () => { + const sheet = new CSSStyleSheet(); + await sheet.replace( textContent ); + return sheet; + } ); +}; + +export const generateCSSStyleSheets = ( + doc: Document, + baseUrl: string = ( doc.location || window.location ).href +): Promise< CSSStyleSheet >[] => + [ ...doc.querySelectorAll( 'style,link[rel=stylesheet]' ) ].map( + ( element ) => { + if ( 'LINK' === element.nodeName ) { + return sheetFromLink( element as HTMLLinkElement, baseUrl ); + } + return sheetFromStyle( element as HTMLStyleElement ); + } + ); diff --git a/packages/interactivity-router/src/head.ts b/packages/interactivity-router/src/head.ts deleted file mode 100644 index 69139348b582ff..00000000000000 --- a/packages/interactivity-router/src/head.ts +++ /dev/null @@ -1,126 +0,0 @@ -/** - * The cache of prefetched stylesheets and scripts. - */ -export const headElements = new Map< - string, - { tag: HTMLElement; text?: string } ->(); - -/** - * Helper to update only the necessary tags in the head. - * - * @async - * @param newHead The head elements of the new page. - */ -export const updateHead = async ( newHead: HTMLHeadElement[] ) => { - // Helper to get the tag id store in the cache. - const getTagId = ( tag: Element ) => tag.id || tag.outerHTML; - - // Map incoming head tags by their content. - const newHeadMap = new Map< string, Element >(); - for ( const child of newHead ) { - newHeadMap.set( getTagId( child ), child ); - } - - const toRemove: Element[] = []; - - // Detect nodes that should be added or removed. - for ( const child of document.head.children ) { - const id = getTagId( child ); - // Always remove styles and links as they might change. - if ( child.nodeName === 'LINK' || child.nodeName === 'STYLE' ) { - toRemove.push( child ); - } else if ( newHeadMap.has( id ) ) { - newHeadMap.delete( id ); - } else if ( child.nodeName !== 'SCRIPT' && child.nodeName !== 'META' ) { - toRemove.push( child ); - } - } - - await Promise.all( - [ ...headElements.entries() ] - .filter( ( [ , { tag } ] ) => tag.nodeName === 'SCRIPT' ) - .map( async ( [ url ] ) => { - await import( /* webpackIgnore: true */ url ); - } ) - ); - - // Prepare new assets. - const toAppend = [ ...newHeadMap.values() ]; - - // Apply the changes. - toRemove.forEach( ( n ) => n.remove() ); - document.head.append( ...toAppend ); -}; - -/** - * Fetches and processes head assets (stylesheets and scripts) from a specified document. - * - * @async - * @param doc The document from which to fetch head assets. It should support standard DOM querying methods. - * - * @return Returns an array of HTML elements representing the head assets. - */ -export const fetchHeadAssets = async ( - doc: Document -): Promise< HTMLElement[] > => { - const headTags = []; - - // We only want to fetch module scripts because regular scripts (without - // `async` or `defer` attributes) can depend on the execution of other scripts. - // Scripts found in the head are blocking and must be executed in order. - const scripts = doc.querySelectorAll< HTMLScriptElement >( - 'script[type="module"][src]' - ); - - scripts.forEach( ( script ) => { - const src = script.getAttribute( 'src' ); - if ( ! headElements.has( src ) ) { - // add the <link> elements to prefetch the module scripts - const link = doc.createElement( 'link' ); - link.rel = 'modulepreload'; - link.href = src; - document.head.append( link ); - headElements.set( src, { tag: script } ); - } - } ); - - const stylesheets = doc.querySelectorAll< HTMLLinkElement >( - 'link[rel=stylesheet]' - ); - - await Promise.all( - Array.from( stylesheets ).map( async ( tag ) => { - const href = tag.getAttribute( 'href' ); - if ( ! href ) { - return; - } - - if ( ! headElements.has( href ) ) { - try { - const response = await fetch( href ); - const text = await response.text(); - headElements.set( href, { - tag, - text, - } ); - } catch ( e ) { - // eslint-disable-next-line no-console - console.error( e ); - } - } - - const headElement = headElements.get( href ); - const styleElement = doc.createElement( 'style' ); - styleElement.textContent = headElement.text; - - headTags.push( styleElement ); - } ) - ); - - return [ - doc.querySelector( 'title' ), - ...doc.querySelectorAll( 'style' ), - ...headTags, - ]; -}; diff --git a/packages/interactivity-router/src/index.ts b/packages/interactivity-router/src/index.ts index 8a46d4ce350113..c128ac8a962de2 100644 --- a/packages/interactivity-router/src/index.ts +++ b/packages/interactivity-router/src/index.ts @@ -6,7 +6,7 @@ import { store, privateApis, getConfig } from '@wordpress/interactivity'; /** * Internal dependencies */ -import { fetchHeadAssets, updateHead, headElements } from './head'; +import { generateCSSStyleSheets } from './assets/styles'; const { directivePrefix, @@ -37,16 +37,18 @@ interface PrefetchOptions { interface VdomParams { vdom?: typeof initialVdom; + baseUrl?: string; } interface Page { regions: Record< string, any >; - head: HTMLHeadElement[]; + styles: Promise< CSSStyleSheet >[]; + scriptModules: string[]; title: string; initialData: any; } -type RegionsToVdom = ( dom: Document, params?: VdomParams ) => Promise< Page >; +type RegionsToVdom = ( dom: Document, params?: VdomParams ) => Page; // Check if the navigation mode is full page or region based. const navigationMode: 'regionBased' | 'fullPage' = @@ -73,7 +75,7 @@ const fetchPage = async ( url: string, { html }: { html: string } ) => { html = await res.text(); } const dom = new window.DOMParser().parseFromString( html, 'text/html' ); - return regionsToVdom( dom ); + return regionsToVdom( dom, { baseUrl: url } ); } catch ( e ) { return false; } @@ -81,12 +83,17 @@ const fetchPage = async ( url: string, { html }: { html: string } ) => { // Return an object with VDOM trees of those HTML regions marked with a // `router-region` directive. -const regionsToVdom: RegionsToVdom = async ( dom, { vdom } = {} ) => { +const regionsToVdom: RegionsToVdom = ( dom, { vdom, baseUrl } = {} ) => { const regions = { body: undefined }; - let head: HTMLElement[]; + const styles = generateCSSStyleSheets( dom, baseUrl ); + const scriptModules = [ + ...dom.querySelectorAll< HTMLScriptElement >( + 'script[type=module][src]' + ), + ].map( ( s ) => s.src ); + if ( globalThis.IS_GUTENBERG_PLUGIN ) { if ( navigationMode === 'fullPage' ) { - head = await fetchHeadAssets( dom ); regions.body = vdom ? vdom.get( document.body ) : toVdom( dom.body ); @@ -103,15 +110,28 @@ const regionsToVdom: RegionsToVdom = async ( dom, { vdom } = {} ) => { } const title = dom.querySelector( 'title' )?.innerText; const initialData = parseServerData( dom ); - return { regions, head, title, initialData }; + return { regions, styles, scriptModules, title, initialData }; }; // Render all interactive regions contained in the given page. const renderRegions = async ( page: Page ) => { + // Wait for styles and modules to be ready. + await Promise.all( [ + ...page.styles, + ...page.scriptModules.map( + ( src ) => import( /* webpackIgnore: true */ src ) + ), + ] ); + // Replace style sheets. + const sheets = await Promise.all( page.styles ); + window.document + .querySelectorAll( 'style,link[rel=stylesheet]' ) + .forEach( ( element ) => element.remove() ); + window.document.adoptedStyleSheets = sheets; + if ( globalThis.IS_GUTENBERG_PLUGIN ) { if ( navigationMode === 'fullPage' ) { - // Once this code is tested and more mature, the head should be updated for region based navigation as well. - await updateHead( page.head ); + // Update HTML. const fragment = getRegionRootFragment( document.body ); batch( () => { populateServerData( page.initialData ); @@ -169,23 +189,14 @@ window.addEventListener( 'popstate', async () => { // Initialize the router and cache the initial page using the initial vDOM. // Once this code is tested and more mature, the head should be updated for // region based navigation as well. -if ( globalThis.IS_GUTENBERG_PLUGIN ) { - if ( navigationMode === 'fullPage' ) { - // Cache the scripts. Has to be called before fetching the assets. - [].map.call( - document.querySelectorAll( 'script[type="module"][src]' ), - ( script ) => { - headElements.set( script.getAttribute( 'src' ), { - tag: script, - } ); - } - ); - await fetchHeadAssets( document ); - } -} pages.set( getPagePath( window.location.href ), - Promise.resolve( regionsToVdom( document, { vdom: initialVdom } ) ) + Promise.resolve( + regionsToVdom( document, { + vdom: initialVdom, + baseUrl: window.location.href, + } ) + ) ); // Check if the link is valid for client-side navigation. @@ -223,6 +234,7 @@ interface Store { state: { url: string; navigation: { + isLoading: boolean; hasStarted: boolean; hasFinished: boolean; }; @@ -237,6 +249,7 @@ export const { state, actions } = store< Store >( 'core/router', { state: { url: window.location.href, navigation: { + isLoading: false, hasStarted: false, hasFinished: false, }, @@ -245,7 +258,7 @@ export const { state, actions } = store< Store >( 'core/router', { /** * Navigates to the specified page. * - * This function normalizes the passed href, fetchs the page HTML if + * This function normalizes the passed href, fetches the page HTML if * needed, and updates any interactive regions whose contents have * changed. It also creates a new entry in the browser session history. * @@ -289,6 +302,7 @@ export const { state, actions } = store< Store >( 'core/router', { return; } + navigation.isLoading = true; if ( loadingAnimation ) { navigation.hasStarted = true; navigation.hasFinished = false; @@ -328,6 +342,7 @@ export const { state, actions } = store< Store >( 'core/router', { // Update the navigation status once the the new page rendering // has been completed. + navigation.isLoading = false; if ( loadingAnimation ) { navigation.hasStarted = false; navigation.hasFinished = true; @@ -348,7 +363,7 @@ export const { state, actions } = store< Store >( 'core/router', { }, /** - * Prefetchs the page with the passed URL. + * Prefetches the page with the passed URL. * * The function normalizes the URL and stores internally the fetch * promise, to avoid triggering a second fetch for an ongoing request. diff --git a/packages/interactivity-router/tsconfig.json b/packages/interactivity-router/tsconfig.json index f601a26a86f5f4..616718560d02cc 100644 --- a/packages/interactivity-router/tsconfig.json +++ b/packages/interactivity-router/tsconfig.json @@ -2,11 +2,8 @@ "$schema": "https://json.schemastore.org/tsconfig.json", "extends": "../../tsconfig.base.json", "compilerOptions": { - "rootDir": "src", - "declarationDir": "build-types", "checkJs": false, "strict": false }, - "references": [ { "path": "../a11y" }, { "path": "../interactivity" } ], - "include": [ "src/**/*" ] + "references": [ { "path": "../a11y" }, { "path": "../interactivity" } ] } diff --git a/packages/interactivity/CHANGELOG.md b/packages/interactivity/CHANGELOG.md index 6963ed57a48aed..c38870e9ba7403 100644 --- a/packages/interactivity/CHANGELOG.md +++ b/packages/interactivity/CHANGELOG.md @@ -2,6 +2,28 @@ ## Unreleased +## 6.16.0 (2025-01-15) + +### Bug Fixes + +- Fix the logic path that merges plain objects ([#68579](https://github.com/WordPress/gutenberg/pull/68579)). + +## 6.15.0 (2025-01-02) + +### Enhancements + +- Allow more iterables to be used in each directives ([#67798](https://github.com/WordPress/gutenberg/pull/67798)). + +### Bug Fixes + +- Fix an error when the value used in an each directive is not iterable ([#67798](https://github.com/WordPress/gutenberg/pull/67798)). + +## 6.14.0 (2024-12-11) + +## 6.13.0 (2024-11-27) + +## 6.12.0 (2024-11-16) + ### Bug Fixes - Fix property modification from inherited context two or more levels above ([#66872](https://github.com/WordPress/gutenberg/pull/66872)). diff --git a/packages/interactivity/package.json b/packages/interactivity/package.json index c9edd586bc9959..5e8c17b4512419 100644 --- a/packages/interactivity/package.json +++ b/packages/interactivity/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/interactivity", - "version": "6.11.0", + "version": "6.16.0", "description": "Package that provides a standard and simple way to handle the frontend interactivity of Gutenberg blocks.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/interactivity/src/directives.tsx b/packages/interactivity/src/directives.tsx index 31e07d095e0a4c..bddd017b1c99db 100644 --- a/packages/interactivity/src/directives.tsx +++ b/packages/interactivity/src/directives.tsx @@ -4,7 +4,7 @@ /** * External dependencies */ -import { h as createElement, type RefObject } from 'preact'; +import { h as createElement, type VNode, type RefObject } from 'preact'; import { useContext, useMemo, useRef } from 'preact/hooks'; /** @@ -567,11 +567,19 @@ export default () => { const [ entry ] = each; const { namespace } = entry; - const list = evaluate( entry ); + const iterable = evaluate( entry ); + + if ( typeof iterable?.[ Symbol.iterator ] !== 'function' ) { + return; + } + const itemProp = isNonDefaultDirectiveSuffix( entry ) ? kebabToCamelCase( entry.suffix ) : 'item'; - return list.map( ( item ) => { + + const result: VNode< any >[] = []; + + for ( const item of iterable ) { const itemContext = proxifyContext( proxifyState( namespace, {} ), inheritedValue.client[ namespace ] @@ -596,12 +604,15 @@ export default () => { ? getEvaluate( { scope } )( eachKey[ 0 ] ) : item; - return createElement( - Provider, - { value: mergedContext, key }, - element.props.content + result.push( + createElement( + Provider, + { value: mergedContext, key }, + element.props.content + ) ); - } ); + } + return result; }, { priority: 20 } ); diff --git a/packages/interactivity/src/hooks.tsx b/packages/interactivity/src/hooks.tsx index e9b9f48ba3518e..7899e3eafd2281 100644 --- a/packages/interactivity/src/hooks.tsx +++ b/packages/interactivity/src/hooks.tsx @@ -77,7 +77,7 @@ interface DirectiveArgs { } export interface DirectiveCallback { - ( args: DirectiveArgs ): VNode< any > | null | void; + ( args: DirectiveArgs ): VNode< any > | VNode< any >[] | null | void; } interface DirectiveOptions { diff --git a/packages/interactivity/src/proxies/state.ts b/packages/interactivity/src/proxies/state.ts index f9af257bada2ea..08977e8e18efae 100644 --- a/packages/interactivity/src/proxies/state.ts +++ b/packages/interactivity/src/proxies/state.ts @@ -36,7 +36,7 @@ const proxyToProps: WeakMap< > = new WeakMap(); /** - * Checks wether a {@link PropSignal | `PropSignal`} instance exists for the + * Checks whether a {@link PropSignal | `PropSignal`} instance exists for the * given property in the passed proxy. * * @param proxy Proxy of a state object or array. @@ -340,7 +340,9 @@ const deepMergeRecursive = ( // Handle nested objects } else if ( isPlainObject( source[ key ] ) ) { - if ( isNew || ( override && ! isPlainObject( target[ key ] ) ) ) { + const targetValue = Object.getOwnPropertyDescriptor( target, key ) + ?.value; + if ( isNew || ( override && ! isPlainObject( targetValue ) ) ) { // Create a new object if the property is new or needs to be overridden target[ key ] = {}; if ( propSignal ) { @@ -350,9 +352,10 @@ const deepMergeRecursive = ( proxifyState( ns, target[ key ] as Object ) ); } + deepMergeRecursive( target[ key ], source[ key ], override ); } // Both target and source are plain objects, merge them recursively - if ( isPlainObject( target[ key ] ) ) { + else if ( isPlainObject( targetValue ) ) { deepMergeRecursive( target[ key ], source[ key ], override ); } diff --git a/packages/interactivity/src/proxies/test/deep-merge.ts b/packages/interactivity/src/proxies/test/deep-merge.ts index aaa762cb979f3c..c1e32763a01ef5 100644 --- a/packages/interactivity/src/proxies/test/deep-merge.ts +++ b/packages/interactivity/src/proxies/test/deep-merge.ts @@ -455,6 +455,38 @@ describe( 'Interactivity API', () => { expect( target.message.fontStyle ).toBeUndefined(); } ); + it( 'should not overwrite getters that become objects if `override` is false', () => { + const target: any = proxifyState( 'test', { + get message() { + return 'hello'; + }, + } ); + + const getterSpy = jest.spyOn( target, 'message', 'get' ); + + let message: any; + const spy = jest.fn( () => ( message = target.message ) ); + effect( spy ); + + expect( spy ).toHaveBeenCalledTimes( 1 ); + expect( message ).toBe( 'hello' ); + + deepMerge( + target, + { message: { content: 'hello', fontStyle: 'italic' } }, + false + ); + + // The effect callback reads `target.message`, so the getter is executed once as well. + expect( spy ).toHaveBeenCalledTimes( 1 ); + expect( getterSpy ).toHaveBeenCalledTimes( 1 ); + + expect( message ).toBe( 'hello' ); + expect( target.message ).toBe( 'hello' ); + expect( target.message.content ).toBeUndefined(); + expect( target.message.fontStyle ).toBeUndefined(); + } ); + it( 'should keep reactivity of arrays that are initially undefined', () => { const target: any = proxifyState( 'test', {} ); diff --git a/packages/interactivity/src/scopes.ts b/packages/interactivity/src/scopes.ts index 722305f6bee112..96e23c86ade73f 100644 --- a/packages/interactivity/src/scopes.ts +++ b/packages/interactivity/src/scopes.ts @@ -77,7 +77,7 @@ export const getContext = < T extends object >( namespace?: string ): T => { /** * Retrieves a representation of the element where a function from the store - * is being evalutated. Such representation is read-only, and contains a + * is being evaluated. Such representation is read-only, and contains a * reference to the DOM element, its props and a local reactive state. * * @return Element representation. diff --git a/packages/interactivity/src/store.ts b/packages/interactivity/src/store.ts index b147e0f61163bf..0b37e043733bb7 100644 --- a/packages/interactivity/src/store.ts +++ b/packages/interactivity/src/store.ts @@ -28,7 +28,7 @@ export const getConfig = ( namespace?: string ) => * * The object returned is read-only, and includes the state defined in PHP with * `wp_interactivity_state()`. When using `actions.navigate()`, this object is - * updated to reflect the changes in its properites, without affecting the state + * updated to reflect the changes in its properties, without affecting the state * returned by `store()`. Directives can subscribe to those changes to update * the state if needed. * diff --git a/packages/interactivity/src/utils.ts b/packages/interactivity/src/utils.ts index ab6b0074727ee7..d894d37a7b84bc 100644 --- a/packages/interactivity/src/utils.ts +++ b/packages/interactivity/src/utils.ts @@ -119,7 +119,7 @@ export function useSignalEffect( callback: () => unknown ) { * accessible whenever the function runs. This is primarily to make the scope * available inside hook callbacks. * - * Asyncronous functions should use generators that yield promises instead of awaiting them. + * Asynchronous functions should use generators that yield promises instead of awaiting them. * See the documentation for details: https://developer.wordpress.org/block-editor/reference-guides/packages/packages-interactivity/packages-interactivity-api-reference/#the-store * * @param func The passed function. @@ -200,7 +200,7 @@ export function useWatch( callback: () => unknown ) { /** * Accepts a function that contains imperative code which runs only after the - * element's first render, mainly useful for intialization logic. + * element's first render, mainly useful for initialization logic. * * This hook makes the element's scope available so functions like * `getElement()` and `getContext()` can be used inside the passed callback. diff --git a/packages/interactivity/src/vdom.ts b/packages/interactivity/src/vdom.ts index 9a1ec7ec5d76f0..e61a5a9b52895e 100644 --- a/packages/interactivity/src/vdom.ts +++ b/packages/interactivity/src/vdom.ts @@ -24,7 +24,7 @@ const directiveParser = new RegExp( // segments. It excludes underscore intentionally to prevent confusion. // E.g., "custom-directive". '([a-z0-9]+(?:-[a-z0-9]+)*)' + - // (Optional) Match '--' followed by any alphanumeric charachters. It + // (Optional) Match '--' followed by any alphanumeric characters. It // excludes underscore intentionally to prevent confusion, but it can // contain multiple hyphens. E.g., "--custom-prefix--with-more-info". '(?:--([a-z0-9_-]+))?$', diff --git a/packages/interactivity/tsconfig.json b/packages/interactivity/tsconfig.json index 1d154e2089065d..a4d86e65fa1dd6 100644 --- a/packages/interactivity/tsconfig.json +++ b/packages/interactivity/tsconfig.json @@ -2,10 +2,6 @@ "$schema": "https://json.schemastore.org/tsconfig.json", "extends": "../../tsconfig.base.json", "compilerOptions": { - "rootDir": "src", - "declarationDir": "build-types", - "noImplicitAny": false - }, - "include": [ "src/**/*" ] + } } diff --git a/packages/interactivity/tsconfig.test.json b/packages/interactivity/tsconfig.test.json index 6a90abc2ba2210..ad6813d6fec0ff 100644 --- a/packages/interactivity/tsconfig.test.json +++ b/packages/interactivity/tsconfig.test.json @@ -2,12 +2,12 @@ "$schema": "https://json.schemastore.org/tsconfig.json", "extends": "../../tsconfig.base.json", "compilerOptions": { - "rootDir": "src", "noEmit": true, "emitDeclarationOnly": false, "types": [ "jest" ] }, "references": [ { "path": "./tsconfig.json" } ], "files": [ "src/test/store.ts" ], + "include": [], "exclude": [] } diff --git a/packages/interface/CHANGELOG.md b/packages/interface/CHANGELOG.md index 67cb692f877098..c5bff62d90e52e 100644 --- a/packages/interface/CHANGELOG.md +++ b/packages/interface/CHANGELOG.md @@ -2,6 +2,20 @@ ## Unreleased +## 9.1.0 (2025-01-15) + +## 9.0.0 (2025-01-02) + +### Breaking Changes + +- `ActionItem.Slot`: Render as `MenuGroup` by default ([#67985](https://github.com/WordPress/gutenberg/pull/67985)). + +## 8.3.0 (2024-12-11) + +## 8.2.0 (2024-11-27) + +## 8.1.0 (2024-11-16) + ## 8.0.0 (2024-10-30) ### Breaking Changes diff --git a/packages/interface/package.json b/packages/interface/package.json index f6a7952cb0d051..8e4e9485afc145 100644 --- a/packages/interface/package.json +++ b/packages/interface/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/interface", - "version": "8.0.0", + "version": "9.1.0", "description": "Interface module for WordPress. The package contains shared functionality across the modern JavaScript-based WordPress screens.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -34,17 +34,17 @@ ], "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/a11y": "*", - "@wordpress/components": "*", - "@wordpress/compose": "*", - "@wordpress/data": "*", - "@wordpress/deprecated": "*", - "@wordpress/element": "*", - "@wordpress/i18n": "*", - "@wordpress/icons": "*", - "@wordpress/plugins": "*", - "@wordpress/preferences": "*", - "@wordpress/viewport": "*", + "@wordpress/a11y": "file:../a11y", + "@wordpress/components": "file:../components", + "@wordpress/compose": "file:../compose", + "@wordpress/data": "file:../data", + "@wordpress/deprecated": "file:../deprecated", + "@wordpress/element": "file:../element", + "@wordpress/i18n": "file:../i18n", + "@wordpress/icons": "file:../icons", + "@wordpress/plugins": "file:../plugins", + "@wordpress/preferences": "file:../preferences", + "@wordpress/viewport": "file:../viewport", "clsx": "^2.1.1" }, "peerDependencies": { diff --git a/packages/interface/src/components/action-item/README.md b/packages/interface/src/components/action-item/README.md index 15c627adfd3296..5611e044c8a985 100644 --- a/packages/interface/src/components/action-item/README.md +++ b/packages/interface/src/components/action-item/README.md @@ -24,11 +24,11 @@ Property used to change the event bubbling behavior, passed to the `Slot` compon ### as -The component used as the container of the fills. Defaults to the `ButtonGroup` component. +The component used as the container of the fills. Defaults to the `MenuGroup` component. - Type: `Component` - Required: no -- Default: `ButtonGroup` +- Default: `MenuGroup` ## ActionItem diff --git a/packages/interface/src/components/action-item/index.js b/packages/interface/src/components/action-item/index.js index 4bd5a11e8d71f8..2f3fdd6d3ca301 100644 --- a/packages/interface/src/components/action-item/index.js +++ b/packages/interface/src/components/action-item/index.js @@ -1,14 +1,14 @@ /** * WordPress dependencies */ -import { ButtonGroup, Button, Slot, Fill } from '@wordpress/components'; +import { MenuGroup, Button, Slot, Fill } from '@wordpress/components'; import { Children } from '@wordpress/element'; const noop = () => {}; function ActionItemSlot( { name, - as: Component = ButtonGroup, + as: Component = MenuGroup, fillProps = {}, bubblesVirtually, ...props diff --git a/packages/interface/src/components/complementary-area-toggle/index.js b/packages/interface/src/components/complementary-area-toggle/index.js index 40b4dd99be7432..9c629a9bff2765 100644 --- a/packages/interface/src/components/complementary-area-toggle/index.js +++ b/packages/interface/src/components/complementary-area-toggle/index.js @@ -13,9 +13,9 @@ import { store as interfaceStore } from '../../store'; /** * Whether the role supports checked state. * + * @see https://www.w3.org/TR/wai-aria-1.1/#aria-checked * @param {import('react').AriaRole} role Role. * @return {boolean} Whether the role supports checked state. - * @see https://www.w3.org/TR/wai-aria-1.1/#aria-checked */ function roleSupportsCheckedState( role ) { return [ diff --git a/packages/is-shallow-equal/CHANGELOG.md b/packages/is-shallow-equal/CHANGELOG.md index 5dee7a281357a2..93829c3f9798ad 100644 --- a/packages/is-shallow-equal/CHANGELOG.md +++ b/packages/is-shallow-equal/CHANGELOG.md @@ -2,6 +2,16 @@ ## Unreleased +## 5.16.0 (2025-01-15) + +## 5.15.0 (2025-01-02) + +## 5.14.0 (2024-12-11) + +## 5.13.0 (2024-11-27) + +## 5.12.0 (2024-11-16) + ## 5.11.0 (2024-10-30) ## 5.10.0 (2024-10-16) diff --git a/packages/is-shallow-equal/package.json b/packages/is-shallow-equal/package.json index 7dafdea88e7a4f..fe686f46745617 100644 --- a/packages/is-shallow-equal/package.json +++ b/packages/is-shallow-equal/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/is-shallow-equal", - "version": "5.11.0", + "version": "5.16.0", "description": "Test for shallow equality between two objects or arrays.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/is-shallow-equal/tsconfig.json b/packages/is-shallow-equal/tsconfig.json index 6e33d8ff82d47e..7ff060ab6ce105 100644 --- a/packages/is-shallow-equal/tsconfig.json +++ b/packages/is-shallow-equal/tsconfig.json @@ -1,9 +1,4 @@ { "$schema": "https://json.schemastore.org/tsconfig.json", - "extends": "../../tsconfig.base.json", - "compilerOptions": { - "rootDir": "src", - "declarationDir": "build-types" - }, - "include": [ "src/**/*" ] + "extends": "../../tsconfig.base.json" } diff --git a/packages/jest-console/CHANGELOG.md b/packages/jest-console/CHANGELOG.md index 00cc44f548a858..941ba14bbed442 100644 --- a/packages/jest-console/CHANGELOG.md +++ b/packages/jest-console/CHANGELOG.md @@ -2,6 +2,16 @@ ## Unreleased +## 8.16.0 (2025-01-15) + +## 8.15.0 (2025-01-02) + +## 8.14.0 (2024-12-11) + +## 8.13.0 (2024-11-27) + +## 8.12.0 (2024-11-16) + ## 8.11.0 (2024-10-30) ## 8.10.0 (2024-10-16) diff --git a/packages/jest-console/package.json b/packages/jest-console/package.json index 78213b83e7ee54..aecf0cb90c04db 100644 --- a/packages/jest-console/package.json +++ b/packages/jest-console/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/jest-console", - "version": "8.11.0", + "version": "8.16.0", "description": "Custom Jest matchers for the Console object.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/jest-preset-default/CHANGELOG.md b/packages/jest-preset-default/CHANGELOG.md index 28e0f4e2bb420d..cdf892122d9dbe 100644 --- a/packages/jest-preset-default/CHANGELOG.md +++ b/packages/jest-preset-default/CHANGELOG.md @@ -2,6 +2,16 @@ ## Unreleased +## 12.16.0 (2025-01-15) + +## 12.15.0 (2025-01-02) + +## 12.14.0 (2024-12-11) + +## 12.13.0 (2024-11-27) + +## 12.12.0 (2024-11-16) + ## 12.11.0 (2024-10-30) ## 12.10.0 (2024-10-16) diff --git a/packages/jest-preset-default/package.json b/packages/jest-preset-default/package.json index 7a16a2bdd46d49..477dea75cdafd5 100644 --- a/packages/jest-preset-default/package.json +++ b/packages/jest-preset-default/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/jest-preset-default", - "version": "12.11.0", + "version": "12.16.0", "description": "Default Jest preset for WordPress development.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -31,7 +31,7 @@ ], "main": "index.js", "dependencies": { - "@wordpress/jest-console": "*", + "@wordpress/jest-console": "file:../jest-console", "babel-jest": "29.7.0" }, "peerDependencies": { diff --git a/packages/jest-puppeteer-axe/CHANGELOG.md b/packages/jest-puppeteer-axe/CHANGELOG.md index a6557def1f0f27..9ed1991d1fffdd 100644 --- a/packages/jest-puppeteer-axe/CHANGELOG.md +++ b/packages/jest-puppeteer-axe/CHANGELOG.md @@ -2,6 +2,16 @@ ## Unreleased +## 7.16.0 (2025-01-15) + +## 7.15.0 (2025-01-02) + +## 7.14.0 (2024-12-11) + +## 7.13.0 (2024-11-27) + +## 7.12.0 (2024-11-16) + ## 7.11.0 (2024-10-30) ## 7.10.0 (2024-10-16) diff --git a/packages/jest-puppeteer-axe/package.json b/packages/jest-puppeteer-axe/package.json index 4ea7c641be0e1d..db165c661246e5 100644 --- a/packages/jest-puppeteer-axe/package.json +++ b/packages/jest-puppeteer-axe/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/jest-puppeteer-axe", - "version": "7.11.0", + "version": "7.16.0", "description": "Axe API integration with Jest and Puppeteer.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/keyboard-shortcuts/CHANGELOG.md b/packages/keyboard-shortcuts/CHANGELOG.md index 386e6b95c1258a..01418579c072c0 100644 --- a/packages/keyboard-shortcuts/CHANGELOG.md +++ b/packages/keyboard-shortcuts/CHANGELOG.md @@ -2,6 +2,16 @@ ## Unreleased +## 5.16.0 (2025-01-15) + +## 5.15.0 (2025-01-02) + +## 5.14.0 (2024-12-11) + +## 5.13.0 (2024-11-27) + +## 5.12.0 (2024-11-16) + ## 5.11.0 (2024-10-30) ## 5.10.0 (2024-10-16) diff --git a/packages/keyboard-shortcuts/package.json b/packages/keyboard-shortcuts/package.json index ce029907c0dbf4..0af22fc709e56f 100644 --- a/packages/keyboard-shortcuts/package.json +++ b/packages/keyboard-shortcuts/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/keyboard-shortcuts", - "version": "5.11.0", + "version": "5.16.0", "description": "Handling keyboard shortcuts.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -28,9 +28,9 @@ "wpScript": true, "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/data": "*", - "@wordpress/element": "*", - "@wordpress/keycodes": "*" + "@wordpress/data": "file:../data", + "@wordpress/element": "file:../element", + "@wordpress/keycodes": "file:../keycodes" }, "peerDependencies": { "react": "^18.0.0" diff --git a/packages/keyboard-shortcuts/src/store/selectors.js b/packages/keyboard-shortcuts/src/store/selectors.js index 99309d3e24085c..1e4872b46a4a77 100644 --- a/packages/keyboard-shortcuts/src/store/selectors.js +++ b/packages/keyboard-shortcuts/src/store/selectors.js @@ -40,7 +40,7 @@ const FORMATTING_METHODS = { * @param {keyof FORMATTING_METHODS} representation Type of representation * (display, raw, ariaLabel). * - * @return {string?} Shortcut representation. + * @return {?string} Shortcut representation. */ function getKeyCombinationRepresentation( shortcut, representation ) { if ( ! shortcut ) { @@ -135,7 +135,7 @@ export function getShortcutKeyCombination( state, name ) { * }; *``` * - * @return {string?} Shortcut representation. + * @return {?string} Shortcut representation. */ export function getShortcutRepresentation( state, @@ -172,7 +172,7 @@ export function getShortcutRepresentation( * ); * }; *``` - * @return {string?} Shortcut description. + * @return {?string} Shortcut description. */ export function getShortcutDescription( state, name ) { return state[ name ] ? state[ name ].description : null; diff --git a/packages/keycodes/CHANGELOG.md b/packages/keycodes/CHANGELOG.md index 894501981ab1b5..5f742f523c2f88 100644 --- a/packages/keycodes/CHANGELOG.md +++ b/packages/keycodes/CHANGELOG.md @@ -2,6 +2,16 @@ ## Unreleased +## 4.16.0 (2025-01-15) + +## 4.15.0 (2025-01-02) + +## 4.14.0 (2024-12-11) + +## 4.13.0 (2024-11-27) + +## 4.12.0 (2024-11-16) + ## 4.11.0 (2024-10-30) ## 4.10.0 (2024-10-16) diff --git a/packages/keycodes/package.json b/packages/keycodes/package.json index 2038db1984760f..ba9374d19b33d6 100644 --- a/packages/keycodes/package.json +++ b/packages/keycodes/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/keycodes", - "version": "4.11.0", + "version": "4.16.0", "description": "Keycodes utilities for WordPress. Used to check for keyboard events across browsers/operating systems.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -30,7 +30,7 @@ "sideEffects": false, "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/i18n": "*" + "@wordpress/i18n": "file:../i18n" }, "publishConfig": { "access": "public" diff --git a/packages/keycodes/src/index.js b/packages/keycodes/src/index.js index 6b01109e2a40db..b6df5e3f4a2145 100644 --- a/packages/keycodes/src/index.js +++ b/packages/keycodes/src/index.js @@ -211,9 +211,9 @@ export const modifiers = { * @type {WPModifierHandler<WPKeyHandler<string>>} Keyed map of functions to raw * shortcuts. */ -export const rawShortcut = mapValues( - modifiers, - ( /** @type {WPModifier} */ modifier ) => { +export const rawShortcut = + /* @__PURE__ */ + mapValues( modifiers, ( /** @type {WPModifier} */ modifier ) => { return /** @type {WPKeyHandler<string>} */ ( character, _isApple = isAppleOS @@ -222,8 +222,7 @@ export const rawShortcut = mapValues( '+' ); }; - } -); + } ); /** * Return an array of the parts of a keyboard shortcut chord for display. @@ -238,9 +237,9 @@ export const rawShortcut = mapValues( * @type {WPModifierHandler<WPKeyHandler<string[]>>} Keyed map of functions to * shortcut sequences. */ -export const displayShortcutList = mapValues( - modifiers, - ( /** @type {WPModifier} */ modifier ) => { +export const displayShortcutList = + /* @__PURE__ */ + mapValues( modifiers, ( /** @type {WPModifier} */ modifier ) => { return /** @type {WPKeyHandler<string[]>} */ ( character, _isApple = isAppleOS @@ -268,8 +267,7 @@ export const displayShortcutList = mapValues( return [ ...modifierKeys, capitaliseFirstCharacter( character ) ]; }; - } -); + } ); /** * An object that contains functions to display shortcuts. @@ -284,15 +282,17 @@ export const displayShortcutList = mapValues( * @type {WPModifierHandler<WPKeyHandler<string>>} Keyed map of functions to * display shortcuts. */ -export const displayShortcut = mapValues( - displayShortcutList, - ( /** @type {WPKeyHandler<string[]>} */ shortcutList ) => { - return /** @type {WPKeyHandler<string>} */ ( - character, - _isApple = isAppleOS - ) => shortcutList( character, _isApple ).join( '' ); - } -); +export const displayShortcut = + /* @__PURE__ */ + mapValues( + displayShortcutList, + ( /** @type {WPKeyHandler<string[]>} */ shortcutList ) => { + return /** @type {WPKeyHandler<string>} */ ( + character, + _isApple = isAppleOS + ) => shortcutList( character, _isApple ).join( '' ); + } + ); /** * An object that contains functions to return an aria label for a keyboard @@ -308,9 +308,9 @@ export const displayShortcut = mapValues( * @type {WPModifierHandler<WPKeyHandler<string>>} Keyed map of functions to * shortcut ARIA labels. */ -export const shortcutAriaLabel = mapValues( - modifiers, - ( /** @type {WPModifier} */ modifier ) => { +export const shortcutAriaLabel = + /* @__PURE__ */ + mapValues( modifiers, ( /** @type {WPModifier} */ modifier ) => { return /** @type {WPKeyHandler<string>} */ ( character, _isApple = isAppleOS @@ -338,8 +338,7 @@ export const shortcutAriaLabel = mapValues( ) .join( isApple ? ' ' : ' + ' ); }; - } -); + } ); /** * From a given KeyboardEvent, returns an array of active modifier constants for @@ -379,9 +378,9 @@ function getEventModifiers( event ) { * @type {WPModifierHandler<WPEventKeyHandler>} Keyed map of functions * to match events. */ -export const isKeyboardEvent = mapValues( - modifiers, - ( /** @type {WPModifier} */ getModifiers ) => { +export const isKeyboardEvent = + /* @__PURE__ */ + mapValues( modifiers, ( /** @type {WPModifier} */ getModifiers ) => { return /** @type {WPEventKeyHandler} */ ( event, character, @@ -439,5 +438,4 @@ export const isKeyboardEvent = mapValues( return key === character.toLowerCase(); }; - } -); + } ); diff --git a/packages/keycodes/tsconfig.json b/packages/keycodes/tsconfig.json index be13213c265f05..9534c034fa89e7 100644 --- a/packages/keycodes/tsconfig.json +++ b/packages/keycodes/tsconfig.json @@ -1,10 +1,5 @@ { "$schema": "https://json.schemastore.org/tsconfig.json", "extends": "../../tsconfig.base.json", - "compilerOptions": { - "rootDir": "src", - "declarationDir": "build-types" - }, - "references": [ { "path": "../i18n" } ], - "include": [ "src/**/*" ] + "references": [ { "path": "../i18n" } ] } diff --git a/packages/lazy-import/CHANGELOG.md b/packages/lazy-import/CHANGELOG.md index 1f9d1b15304e14..42e06ea6547bb0 100644 --- a/packages/lazy-import/CHANGELOG.md +++ b/packages/lazy-import/CHANGELOG.md @@ -2,6 +2,16 @@ ## Unreleased +## 2.16.0 (2025-01-15) + +## 2.15.0 (2025-01-02) + +## 2.14.0 (2024-12-11) + +## 2.13.0 (2024-11-27) + +## 2.12.0 (2024-11-16) + ## 2.11.0 (2024-10-30) ## 2.10.0 (2024-10-16) diff --git a/packages/lazy-import/README.md b/packages/lazy-import/README.md index cd3dcb16eb6abb..f08f494c410ce6 100644 --- a/packages/lazy-import/README.md +++ b/packages/lazy-import/README.md @@ -58,7 +58,7 @@ lazyImport( 'fbjs@^1.0.0', { } ).then( /* ... */ ); ``` -Note that `lazyImport` can throw an error when offline and unable to install the dependency using NPM. You may want to anticipate this and provide remediation steps for a failed install, such as logging a warning messsage: +Note that `lazyImport` can throw an error when offline and unable to install the dependency using NPM. You may want to anticipate this and provide remediation steps for a failed install, such as logging a warning message: ```js try { diff --git a/packages/lazy-import/package.json b/packages/lazy-import/package.json index af18373147c986..2de26b9f095139 100644 --- a/packages/lazy-import/package.json +++ b/packages/lazy-import/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/lazy-import", - "version": "2.11.0", + "version": "2.16.0", "description": "Lazily import a module, installing it automatically if missing.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/lazy-import/tsconfig.json b/packages/lazy-import/tsconfig.json index 3bf8bde807404d..9647e449d35454 100644 --- a/packages/lazy-import/tsconfig.json +++ b/packages/lazy-import/tsconfig.json @@ -3,8 +3,7 @@ "extends": "../../tsconfig.base.json", "compilerOptions": { "rootDir": "lib", - "declarationDir": "build-types", "useUnknownInCatchVariables": false }, - "include": [ "lib/**/*" ] + "include": [ "lib" ] } diff --git a/packages/list-reusable-blocks/CHANGELOG.md b/packages/list-reusable-blocks/CHANGELOG.md index 958057ffb976de..66c738b571f53c 100644 --- a/packages/list-reusable-blocks/CHANGELOG.md +++ b/packages/list-reusable-blocks/CHANGELOG.md @@ -2,6 +2,16 @@ ## Unreleased +## 5.16.0 (2025-01-15) + +## 5.15.0 (2025-01-02) + +## 5.14.0 (2024-12-11) + +## 5.13.0 (2024-11-27) + +## 5.12.0 (2024-11-16) + ## 5.11.0 (2024-10-30) ## 5.10.0 (2024-10-16) diff --git a/packages/list-reusable-blocks/package.json b/packages/list-reusable-blocks/package.json index 328695f31925ee..1390430dd40aaf 100644 --- a/packages/list-reusable-blocks/package.json +++ b/packages/list-reusable-blocks/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/list-reusable-blocks", - "version": "5.11.0", + "version": "5.16.0", "description": "Adding Export/Import support to the reusable blocks listing.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -28,12 +28,12 @@ "wpScript": true, "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/api-fetch": "*", - "@wordpress/blob": "*", - "@wordpress/components": "*", - "@wordpress/compose": "*", - "@wordpress/element": "*", - "@wordpress/i18n": "*", + "@wordpress/api-fetch": "file:../api-fetch", + "@wordpress/blob": "file:../blob", + "@wordpress/components": "file:../components", + "@wordpress/compose": "file:../compose", + "@wordpress/element": "file:../element", + "@wordpress/i18n": "file:../i18n", "change-case": "^4.1.2" }, "peerDependencies": { diff --git a/packages/media-utils/CHANGELOG.md b/packages/media-utils/CHANGELOG.md index 047ff84855f29d..e46cad7f4a2f94 100644 --- a/packages/media-utils/CHANGELOG.md +++ b/packages/media-utils/CHANGELOG.md @@ -2,6 +2,16 @@ ## Unreleased +## 5.16.0 (2025-01-15) + +## 5.15.0 (2025-01-02) + +## 5.14.0 (2024-12-11) + +## 5.13.0 (2024-11-27) + +## 5.12.0 (2024-11-16) + ## 5.11.0 (2024-10-30) ## 5.10.0 (2024-10-16) diff --git a/packages/media-utils/package.json b/packages/media-utils/package.json index e57b0f51844504..ff5091bb0c6828 100644 --- a/packages/media-utils/package.json +++ b/packages/media-utils/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/media-utils", - "version": "5.11.0", + "version": "5.16.0", "description": "WordPress Media Upload Utils.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -29,11 +29,11 @@ "types": "build-types", "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/api-fetch": "*", - "@wordpress/blob": "*", - "@wordpress/element": "*", - "@wordpress/i18n": "*", - "@wordpress/private-apis": "*" + "@wordpress/api-fetch": "file:../api-fetch", + "@wordpress/blob": "file:../blob", + "@wordpress/element": "file:../element", + "@wordpress/i18n": "file:../i18n", + "@wordpress/private-apis": "file:../private-apis" }, "publishConfig": { "access": "public" diff --git a/packages/media-utils/src/utils/types.ts b/packages/media-utils/src/utils/types.ts index c91d4c67cfc466..c4c6882ea2532e 100644 --- a/packages/media-utils/src/utils/types.ts +++ b/packages/media-utils/src/utils/types.ts @@ -199,7 +199,6 @@ export type Attachment = BetterOmit< }; export type OnChangeHandler = ( attachments: Partial< Attachment >[] ) => void; -export type OnSuccessHandler = ( attachments: Partial< Attachment >[] ) => void; export type OnErrorHandler = ( error: Error ) => void; export type CreateRestAttachment = Partial< RestAttachment >; diff --git a/packages/media-utils/src/utils/upload-media.ts b/packages/media-utils/src/utils/upload-media.ts index 1bc861cfb3b607..ff3f718076512b 100644 --- a/packages/media-utils/src/utils/upload-media.ts +++ b/packages/media-utils/src/utils/upload-media.ts @@ -12,7 +12,6 @@ import type { Attachment, OnChangeHandler, OnErrorHandler, - OnSuccessHandler, } from './types'; import { uploadToServer } from './upload-to-server'; import { validateMimeType } from './validate-mime-type'; @@ -20,6 +19,12 @@ import { validateMimeTypeForUser } from './validate-mime-type-for-user'; import { validateFileSize } from './validate-file-size'; import { UploadError } from './upload-error'; +declare global { + interface Window { + __experimentalMediaProcessing?: boolean; + } +} + interface UploadMediaArgs { // Additional data to include in the request. additionalData?: AdditionalData; @@ -33,8 +38,6 @@ interface UploadMediaArgs { onError?: OnErrorHandler; // Function called each time a file or a temporary representation of the file is available. onFileChange?: OnChangeHandler; - // Function called once a file has completely finished uploading, including thumbnails. - onSuccess?: OnSuccessHandler; // List of allowed mime types and file extensions. wpAllowedMimeTypes?: Record< string, string > | null; // Abort signal. @@ -69,8 +72,11 @@ export function uploadMedia( { const filesSet: Array< Partial< Attachment > | null > = []; const setAndUpdateFiles = ( index: number, value: Attachment | null ) => { - if ( filesSet[ index ]?.url ) { - revokeBlobURL( filesSet[ index ].url ); + // For client-side media processing, this is handled by the upload-media package. + if ( ! window.__experimentalMediaProcessing ) { + if ( filesSet[ index ]?.url ) { + revokeBlobURL( filesSet[ index ].url ); + } } filesSet[ index ] = value; onFileChange?.( @@ -107,10 +113,13 @@ export function uploadMedia( { validFiles.push( mediaFile ); - // Set temporary URL to create placeholder media file, this is replaced - // with final file from media gallery when upload is `done` below. - filesSet.push( { url: createBlobURL( mediaFile ) } ); - onFileChange?.( filesSet as Array< Partial< Attachment > > ); + // For client-side media processing, this is handled by the upload-media package. + if ( ! window.__experimentalMediaProcessing ) { + // Set temporary URL to create placeholder media file, this is replaced + // with final file from media gallery when upload is `done` below. + filesSet.push( { url: createBlobURL( mediaFile ) } ); + onFileChange?.( filesSet as Array< Partial< Attachment > > ); + } } validFiles.map( async ( file, index ) => { diff --git a/packages/media-utils/tsconfig.json b/packages/media-utils/tsconfig.json index ca3e93c2dee668..380e55bc58ff09 100644 --- a/packages/media-utils/tsconfig.json +++ b/packages/media-utils/tsconfig.json @@ -2,12 +2,9 @@ "$schema": "https://json.schemastore.org/tsconfig.json", "extends": "../../tsconfig.base.json", "compilerOptions": { - "rootDir": "src", - "declarationDir": "build-types", "types": [ "gutenberg-env" ], "checkJs": false }, - "include": [ "src/**/*" ], "references": [ { "path": "../api-fetch" }, { "path": "../blob" }, diff --git a/packages/notices/CHANGELOG.md b/packages/notices/CHANGELOG.md index d70c37f4fe721b..4a7d7d0b75fc1e 100644 --- a/packages/notices/CHANGELOG.md +++ b/packages/notices/CHANGELOG.md @@ -2,6 +2,16 @@ ## Unreleased +## 5.16.0 (2025-01-15) + +## 5.15.0 (2025-01-02) + +## 5.14.0 (2024-12-11) + +## 5.13.0 (2024-11-27) + +## 5.12.0 (2024-11-16) + ## 5.11.0 (2024-10-30) ## 5.10.0 (2024-10-16) diff --git a/packages/notices/package.json b/packages/notices/package.json index f150616445b060..fd8a813c3741bb 100644 --- a/packages/notices/package.json +++ b/packages/notices/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/notices", - "version": "5.11.0", + "version": "5.16.0", "description": "State management for notices.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -29,8 +29,8 @@ "types": "build-types", "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/a11y": "*", - "@wordpress/data": "*" + "@wordpress/a11y": "file:../a11y", + "@wordpress/data": "file:../data" }, "peerDependencies": { "react": "^18.0.0" diff --git a/packages/notices/tsconfig.json b/packages/notices/tsconfig.json index e36a6fe9f4eb6b..9c48147297764e 100644 --- a/packages/notices/tsconfig.json +++ b/packages/notices/tsconfig.json @@ -2,11 +2,8 @@ "$schema": "https://json.schemastore.org/tsconfig.json", "extends": "../../tsconfig.base.json", "compilerOptions": { - "rootDir": "src", - "declarationDir": "build-types", "types": [ "gutenberg-env" ], "checkJs": false }, - "references": [ { "path": "../a11y" }, { "path": "../data" } ], - "include": [ "src/**/*" ] + "references": [ { "path": "../a11y" }, { "path": "../data" } ] } diff --git a/packages/npm-package-json-lint-config/CHANGELOG.md b/packages/npm-package-json-lint-config/CHANGELOG.md index 9dee828f63fea0..dc66fa29d362be 100644 --- a/packages/npm-package-json-lint-config/CHANGELOG.md +++ b/packages/npm-package-json-lint-config/CHANGELOG.md @@ -2,6 +2,16 @@ ## Unreleased +## 5.16.0 (2025-01-15) + +## 5.15.0 (2025-01-02) + +## 5.14.0 (2024-12-11) + +## 5.13.0 (2024-11-27) + +## 5.12.0 (2024-11-16) + ### Enhancement - Include `exports`, `wpScript`, `wpScriptModuleExports` and `sideEffects` in the `prefer-property-order` rule ([#66239](https://github.com/WordPress/gutenberg/pull/66239)). diff --git a/packages/npm-package-json-lint-config/package.json b/packages/npm-package-json-lint-config/package.json index c02560393b9a9f..f27c1c4326d038 100644 --- a/packages/npm-package-json-lint-config/package.json +++ b/packages/npm-package-json-lint-config/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/npm-package-json-lint-config", - "version": "5.11.0", + "version": "5.16.0", "description": "WordPress npm-package-json-lint shareable configuration.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/nux/CHANGELOG.md b/packages/nux/CHANGELOG.md index 7446f00f381ac4..68fb077917be77 100644 --- a/packages/nux/CHANGELOG.md +++ b/packages/nux/CHANGELOG.md @@ -2,6 +2,16 @@ ## Unreleased +## 9.16.0 (2025-01-15) + +## 9.15.0 (2025-01-02) + +## 9.14.0 (2024-12-11) + +## 9.13.0 (2024-11-27) + +## 9.12.0 (2024-11-16) + ## 9.11.0 (2024-10-30) ## 9.10.0 (2024-10-16) diff --git a/packages/nux/README.md b/packages/nux/README.md index c0941ddd0c5f2a..31508d38f5995f 100644 --- a/packages/nux/README.md +++ b/packages/nux/README.md @@ -59,7 +59,7 @@ console.log( isVisible ); // true or false ## Disabling and enabling tips -Tips can be programatically disabled or enabled using the `disableTips` and `enableTips` dispatch methods. You can query the current setting by using the `areTipsEnabled` select method. +Tips can be programmatically disabled or enabled using the `disableTips` and `enableTips` dispatch methods. You can query the current setting by using the `areTipsEnabled` select method. Calling `enableTips` will also un-dismiss all previously dismissed tips. diff --git a/packages/nux/package.json b/packages/nux/package.json index 50a4d5acf6d028..139ce12c3a61fd 100644 --- a/packages/nux/package.json +++ b/packages/nux/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/nux", - "version": "9.11.0", + "version": "9.16.0", "description": "NUX (New User eXperience) module for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -33,13 +33,13 @@ ], "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/components": "*", - "@wordpress/compose": "*", - "@wordpress/data": "*", - "@wordpress/deprecated": "*", - "@wordpress/element": "*", - "@wordpress/i18n": "*", - "@wordpress/icons": "*" + "@wordpress/components": "file:../components", + "@wordpress/compose": "file:../compose", + "@wordpress/data": "file:../data", + "@wordpress/deprecated": "file:../deprecated", + "@wordpress/element": "file:../element", + "@wordpress/i18n": "file:../i18n", + "@wordpress/icons": "file:../icons" }, "peerDependencies": { "react": "^18.0.0", diff --git a/packages/nux/src/components/dot-tip/style.scss b/packages/nux/src/components/dot-tip/style.scss index 3ab0c1a540fd5a..87b4546b64f62c 100644 --- a/packages/nux/src/components/dot-tip/style.scss +++ b/packages/nux/src/components/dot-tip/style.scss @@ -12,7 +12,6 @@ $dot-scale: 3; // How much the pulse animation should scale up by in size } &::before { - animation: nux-pulse 1.6s infinite cubic-bezier(0.17, 0.67, 0.92, 0.62); background: rgba(#00739c, 0.9); opacity: 0.9; height: $dot-size * $dot-scale; @@ -20,6 +19,10 @@ $dot-scale: 3; // How much the pulse animation should scale up by in size top: -($dot-size * $dot-scale) * 0.5; transform: scale(math.div(1, $dot-scale)); width: $dot-size * $dot-scale; + + @media not (prefers-reduced-motion) { + animation: nux-pulse 1.6s infinite cubic-bezier(0.17, 0.67, 0.92, 0.62); + } } &::after { diff --git a/packages/patterns/CHANGELOG.md b/packages/patterns/CHANGELOG.md index ac4c4e09056129..c56c8ed041700f 100644 --- a/packages/patterns/CHANGELOG.md +++ b/packages/patterns/CHANGELOG.md @@ -2,6 +2,16 @@ ## Unreleased +## 2.16.0 (2025-01-15) + +## 2.15.0 (2025-01-02) + +## 2.14.0 (2024-12-11) + +## 2.13.0 (2024-11-27) + +## 2.12.0 (2024-11-16) + ## 2.11.0 (2024-10-30) ## 2.10.0 (2024-10-16) diff --git a/packages/patterns/README.md b/packages/patterns/README.md index 1123805836f2ab..1fa11a1eefe463 100644 --- a/packages/patterns/README.md +++ b/packages/patterns/README.md @@ -15,7 +15,7 @@ _This package assumes that your code will run in an **ES2015+** environment. If ## Components -This package doesn't currently have any publically exported components. +This package doesn't currently have any publicly exported components. ## Contributing to this package diff --git a/packages/patterns/package.json b/packages/patterns/package.json index d29fc9bcf49bca..fecf698279aed2 100644 --- a/packages/patterns/package.json +++ b/packages/patterns/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/patterns", - "version": "2.11.0", + "version": "2.16.0", "description": "Management of user pattern editing.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -33,20 +33,20 @@ ], "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/a11y": "*", - "@wordpress/block-editor": "*", - "@wordpress/blocks": "*", - "@wordpress/components": "*", - "@wordpress/compose": "*", - "@wordpress/core-data": "*", - "@wordpress/data": "*", - "@wordpress/element": "*", - "@wordpress/html-entities": "*", - "@wordpress/i18n": "*", - "@wordpress/icons": "*", - "@wordpress/notices": "*", - "@wordpress/private-apis": "*", - "@wordpress/url": "*" + "@wordpress/a11y": "file:../a11y", + "@wordpress/block-editor": "file:../block-editor", + "@wordpress/blocks": "file:../blocks", + "@wordpress/components": "file:../components", + "@wordpress/compose": "file:../compose", + "@wordpress/core-data": "file:../core-data", + "@wordpress/data": "file:../data", + "@wordpress/element": "file:../element", + "@wordpress/html-entities": "file:../html-entities", + "@wordpress/i18n": "file:../i18n", + "@wordpress/icons": "file:../icons", + "@wordpress/notices": "file:../notices", + "@wordpress/private-apis": "file:../private-apis", + "@wordpress/url": "file:../url" }, "peerDependencies": { "react": "^18.0.0", diff --git a/packages/patterns/src/components/duplicate-pattern-modal.js b/packages/patterns/src/components/duplicate-pattern-modal.js index 70fb0830e0f1cc..d4fd5e744c4e18 100644 --- a/packages/patterns/src/components/duplicate-pattern-modal.js +++ b/packages/patterns/src/components/duplicate-pattern-modal.js @@ -24,7 +24,7 @@ function getTermLabels( pattern, categories ) { return categories.user ?.filter( ( category ) => - pattern.wp_pattern_category.includes( category.id ) + pattern.wp_pattern_category?.includes( category.id ) ) .map( ( category ) => category.label ); } diff --git a/packages/patterns/src/components/pattern-convert-button.js b/packages/patterns/src/components/pattern-convert-button.js index d670cd85946aa9..4573a6a5de4e82 100644 --- a/packages/patterns/src/components/pattern-convert-button.js +++ b/packages/patterns/src/components/pattern-convert-button.js @@ -6,6 +6,7 @@ import { isReusableBlock, createBlock, serialize, + getBlockType, } from '@wordpress/blocks'; import { store as blockEditorStore } from '@wordpress/block-editor'; import { useState, useCallback } from '@wordpress/element'; @@ -60,6 +61,15 @@ export default function PatternConvertButton( { const blocks = getBlocksByClientId( clientIds ) ?? []; + // Check if the block has reusable support defined. + const hasReusableBlockSupport = ( blockName ) => { + const blockType = getBlockType( blockName ); + const hasParent = blockType && 'parent' in blockType; + + // If the block has a parent, check with false as default, otherwise with true. + return hasBlockSupport( blockName, 'reusable', ! hasParent ); + }; + const isReusable = blocks.length === 1 && blocks[ 0 ] && @@ -82,7 +92,7 @@ export default function PatternConvertButton( { // Hide on invalid blocks. block.isValid && // Hide when block doesn't support being made into a pattern. - hasBlockSupport( block.name, 'reusable', true ) + hasReusableBlockSupport( block.name ) ) && // Hide when current doesn't have permission to do that. // Blocks refers to the wp_block post type, this checks the ability to create a post of that type. diff --git a/packages/patterns/src/components/reset-overrides-control.js b/packages/patterns/src/components/reset-overrides-control.js index 697a595dc42166..9d5d68d58fe236 100644 --- a/packages/patterns/src/components/reset-overrides-control.js +++ b/packages/patterns/src/components/reset-overrides-control.js @@ -14,7 +14,7 @@ const CONTENT = 'content'; export default function ResetOverridesControl( props ) { const name = props.attributes.metadata?.name; const registry = useRegistry(); - const isOverriden = useSelect( + const isOverridden = useSelect( ( select ) => { if ( ! name ) { return; @@ -81,7 +81,7 @@ export default function ResetOverridesControl( props ) { return ( <BlockToolbarLastItem> <ToolbarGroup> - <ToolbarButton onClick={ onClick } disabled={ ! isOverriden }> + <ToolbarButton onClick={ onClick } disabled={ ! isOverridden }> { __( 'Reset' ) } </ToolbarButton> </ToolbarGroup> diff --git a/packages/patterns/src/store/actions.js b/packages/patterns/src/store/actions.js index 5eef01b2bb8f89..80b3f9bff4588a 100644 --- a/packages/patterns/src/store/actions.js +++ b/packages/patterns/src/store/actions.js @@ -104,7 +104,7 @@ export const convertSyncedPatternToStatic = delete metadata.bindings; // Use overridden values of the pattern block if they exist. if ( existingOverrides?.[ metadata.name ] ) { - // Iterate over each overriden attribute. + // Iterate over each overridden attribute. for ( const [ attributeName, value ] of Object.entries( existingOverrides[ metadata.name ] ) ) { diff --git a/packages/plugins/CHANGELOG.md b/packages/plugins/CHANGELOG.md index 2696ce8b2bf0b4..d0e5cb5e4f2a06 100644 --- a/packages/plugins/CHANGELOG.md +++ b/packages/plugins/CHANGELOG.md @@ -2,6 +2,16 @@ ## Unreleased +## 7.16.0 (2025-01-15) + +## 7.15.0 (2025-01-02) + +## 7.14.0 (2024-12-11) + +## 7.13.0 (2024-11-27) + +## 7.12.0 (2024-11-16) + ## 7.11.0 (2024-10-30) ## 7.10.0 (2024-10-16) diff --git a/packages/plugins/package.json b/packages/plugins/package.json index a926c2fd9a4e17..b99718cfcc84bd 100644 --- a/packages/plugins/package.json +++ b/packages/plugins/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/plugins", - "version": "7.11.0", + "version": "7.16.0", "description": "Plugins module for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -29,13 +29,13 @@ "types": "build-types", "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/components": "*", - "@wordpress/compose": "*", - "@wordpress/deprecated": "*", - "@wordpress/element": "*", - "@wordpress/hooks": "*", - "@wordpress/icons": "*", - "@wordpress/is-shallow-equal": "*", + "@wordpress/components": "file:../components", + "@wordpress/compose": "file:../compose", + "@wordpress/deprecated": "file:../deprecated", + "@wordpress/element": "file:../element", + "@wordpress/hooks": "file:../hooks", + "@wordpress/icons": "file:../icons", + "@wordpress/is-shallow-equal": "file:../is-shallow-equal", "memize": "^2.0.1" }, "peerDependencies": { diff --git a/packages/plugins/tsconfig.json b/packages/plugins/tsconfig.json index 47c626f9ddedc8..66fb760f8896d0 100644 --- a/packages/plugins/tsconfig.json +++ b/packages/plugins/tsconfig.json @@ -2,8 +2,6 @@ "$schema": "https://json.schemastore.org/tsconfig.json", "extends": "../../tsconfig.base.json", "compilerOptions": { - "rootDir": "src", - "declarationDir": "build-types", "types": [ "gutenberg-env" ] }, "references": [ @@ -14,6 +12,5 @@ { "path": "../hooks" }, { "path": "../icons" }, { "path": "../is-shallow-equal" } - ], - "include": [ "src/**/*" ] + ] } diff --git a/packages/postcss-plugins-preset/CHANGELOG.md b/packages/postcss-plugins-preset/CHANGELOG.md index 939a997a5eb577..d7bb504db29744 100644 --- a/packages/postcss-plugins-preset/CHANGELOG.md +++ b/packages/postcss-plugins-preset/CHANGELOG.md @@ -2,6 +2,20 @@ ## Unreleased +## 5.16.0 (2025-01-15) + +## 5.15.0 (2025-01-02) + +### Enhancements + +- The bundled `autoprefixer` dependency has been updated from requiring `^10.2.5` to requiring `^10.4.20` (see [#68237](https://github.com/WordPress/gutenberg/pull/68237)). + +## 5.14.0 (2024-12-11) + +## 5.13.0 (2024-11-27) + +## 5.12.0 (2024-11-16) + ## 5.11.0 (2024-10-30) ## 5.10.0 (2024-10-16) diff --git a/packages/postcss-plugins-preset/package.json b/packages/postcss-plugins-preset/package.json index cda39c4a0bf0bf..13aea6c3a5832a 100644 --- a/packages/postcss-plugins-preset/package.json +++ b/packages/postcss-plugins-preset/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/postcss-plugins-preset", - "version": "5.11.0", + "version": "5.16.0", "description": "PostCSS sharable plugins preset for WordPress development.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -30,8 +30,8 @@ ], "main": "lib/index.js", "dependencies": { - "@wordpress/base-styles": "*", - "autoprefixer": "^10.2.5" + "@wordpress/base-styles": "file:../base-styles", + "autoprefixer": "^10.4.20" }, "peerDependencies": { "postcss": "^8.0.0" diff --git a/packages/postcss-themes/CHANGELOG.md b/packages/postcss-themes/CHANGELOG.md index 05cebfe533c6cf..2279d924c67d57 100644 --- a/packages/postcss-themes/CHANGELOG.md +++ b/packages/postcss-themes/CHANGELOG.md @@ -2,6 +2,16 @@ ## Unreleased +## 6.16.0 (2025-01-15) + +## 6.15.0 (2025-01-02) + +## 6.14.0 (2024-12-11) + +## 6.13.0 (2024-11-27) + +## 6.12.0 (2024-11-16) + ## 6.11.0 (2024-10-30) ## 6.10.0 (2024-10-16) diff --git a/packages/postcss-themes/package.json b/packages/postcss-themes/package.json index 613b00789e78eb..2086b420c6d7ea 100644 --- a/packages/postcss-themes/package.json +++ b/packages/postcss-themes/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/postcss-themes", - "version": "6.11.0", + "version": "6.16.0", "description": "PostCSS plugin to generate theme colors.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/preferences-persistence/CHANGELOG.md b/packages/preferences-persistence/CHANGELOG.md index 67bde4ee87c98d..f06253add01a1d 100644 --- a/packages/preferences-persistence/CHANGELOG.md +++ b/packages/preferences-persistence/CHANGELOG.md @@ -2,6 +2,16 @@ ## Unreleased +## 2.16.0 (2025-01-15) + +## 2.15.0 (2025-01-02) + +## 2.14.0 (2024-12-11) + +## 2.13.0 (2024-11-27) + +## 2.12.0 (2024-11-16) + ## 2.11.0 (2024-10-30) ## 2.10.0 (2024-10-16) diff --git a/packages/preferences-persistence/package.json b/packages/preferences-persistence/package.json index 05d2cf7031929d..ec41ffe8a6c1b4 100644 --- a/packages/preferences-persistence/package.json +++ b/packages/preferences-persistence/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/preferences-persistence", - "version": "2.11.0", + "version": "2.16.0", "description": "Persistence utilities for `wordpress/preferences`.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -30,7 +30,7 @@ "sideEffects": false, "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/api-fetch": "*" + "@wordpress/api-fetch": "file:../api-fetch" }, "publishConfig": { "access": "public" diff --git a/packages/preferences-persistence/src/create/test/index.js b/packages/preferences-persistence/src/create/test/index.js index acf28a9c51ff07..1d3c8ab7f09da7 100644 --- a/packages/preferences-persistence/src/create/test/index.js +++ b/packages/preferences-persistence/src/create/test/index.js @@ -33,8 +33,8 @@ describe( 'create', () => { // The second param of the call to `setItem` has been JSON.stringified. // Parse it to check it contains the data. - const setItemDataParm = spy.mock.calls[ 0 ][ 1 ]; - expect( JSON.parse( setItemDataParm ) ).toEqual( + const setItemDataParam = spy.mock.calls[ 0 ][ 1 ]; + expect( JSON.parse( setItemDataParam ) ).toEqual( expect.objectContaining( data ) ); } ); diff --git a/packages/preferences/CHANGELOG.md b/packages/preferences/CHANGELOG.md index 2850950d6df7f5..ac60bd2299ab54 100644 --- a/packages/preferences/CHANGELOG.md +++ b/packages/preferences/CHANGELOG.md @@ -2,6 +2,16 @@ ## Unreleased +## 4.16.0 (2025-01-15) + +## 4.15.0 (2025-01-02) + +## 4.14.0 (2024-12-11) + +## 4.13.0 (2024-11-27) + +## 4.12.0 (2024-11-16) + ## 4.11.0 (2024-10-30) ## 4.10.0 (2024-10-16) diff --git a/packages/preferences/package.json b/packages/preferences/package.json index 205f9b5d4966b1..c280e0810dbd94 100644 --- a/packages/preferences/package.json +++ b/packages/preferences/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/preferences", - "version": "4.11.0", + "version": "4.16.0", "description": "Utilities for managing WordPress preferences.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -31,15 +31,15 @@ "sideEffects": false, "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/a11y": "*", - "@wordpress/components": "*", - "@wordpress/compose": "*", - "@wordpress/data": "*", - "@wordpress/deprecated": "*", - "@wordpress/element": "*", - "@wordpress/i18n": "*", - "@wordpress/icons": "*", - "@wordpress/private-apis": "*", + "@wordpress/a11y": "file:../a11y", + "@wordpress/components": "file:../components", + "@wordpress/compose": "file:../compose", + "@wordpress/data": "file:../data", + "@wordpress/deprecated": "file:../deprecated", + "@wordpress/element": "file:../element", + "@wordpress/i18n": "file:../i18n", + "@wordpress/icons": "file:../icons", + "@wordpress/private-apis": "file:../private-apis", "clsx": "^2.1.1" }, "peerDependencies": { diff --git a/packages/prettier-config/CHANGELOG.md b/packages/prettier-config/CHANGELOG.md index a942a98605ab56..bcc63a0c4c0f84 100644 --- a/packages/prettier-config/CHANGELOG.md +++ b/packages/prettier-config/CHANGELOG.md @@ -2,6 +2,16 @@ ## Unreleased +## 4.16.0 (2025-01-15) + +## 4.15.0 (2025-01-02) + +## 4.14.0 (2024-12-11) + +## 4.13.0 (2024-11-27) + +## 4.12.0 (2024-11-16) + ## 4.11.0 (2024-10-30) ## 4.10.0 (2024-10-16) diff --git a/packages/prettier-config/package.json b/packages/prettier-config/package.json index 367a782acffc4d..6f434a5fe4c5eb 100644 --- a/packages/prettier-config/package.json +++ b/packages/prettier-config/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/prettier-config", - "version": "4.11.0", + "version": "4.16.0", "description": "WordPress Prettier shared configuration.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/prettier-config/tsconfig.json b/packages/prettier-config/tsconfig.json index 0636ff7d0081dd..7899aeee7dfbc9 100644 --- a/packages/prettier-config/tsconfig.json +++ b/packages/prettier-config/tsconfig.json @@ -3,8 +3,7 @@ "extends": "../../tsconfig.base.json", "compilerOptions": { "rootDir": "lib", - "declarationDir": "build-types", "types": [ "node" ] }, - "include": [ "lib/**/*" ] + "include": [ "lib" ] } diff --git a/packages/primitives/CHANGELOG.md b/packages/primitives/CHANGELOG.md index ac8c402ea9e757..b79a725eafeb54 100644 --- a/packages/primitives/CHANGELOG.md +++ b/packages/primitives/CHANGELOG.md @@ -2,6 +2,16 @@ ## Unreleased +## 4.16.0 (2025-01-15) + +## 4.15.0 (2025-01-02) + +## 4.14.0 (2024-12-11) + +## 4.13.0 (2024-11-27) + +## 4.12.0 (2024-11-16) + ## 4.11.0 (2024-10-30) ## 4.10.0 (2024-10-16) diff --git a/packages/primitives/package.json b/packages/primitives/package.json index d24bc1acf25e2d..c3fd277efd5055 100644 --- a/packages/primitives/package.json +++ b/packages/primitives/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/primitives", - "version": "4.11.0", + "version": "4.16.0", "description": "WordPress cross-platform primitives.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -33,7 +33,7 @@ ], "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/element": "*", + "@wordpress/element": "file:../element", "clsx": "^2.1.1" }, "peerDependencies": { diff --git a/packages/primitives/tsconfig.json b/packages/primitives/tsconfig.json index 59a95359b5ea65..5dea3e64597b43 100644 --- a/packages/primitives/tsconfig.json +++ b/packages/primitives/tsconfig.json @@ -1,10 +1,5 @@ { "$schema": "https://json.schemastore.org/tsconfig.json", "extends": "../../tsconfig.base.json", - "compilerOptions": { - "rootDir": "src", - "declarationDir": "build-types" - }, - "include": [ "src/**/*" ], "references": [ { "path": "../element" } ] } diff --git a/packages/priority-queue/CHANGELOG.md b/packages/priority-queue/CHANGELOG.md index 042e4ae3e97c12..3cf20de9612fc4 100644 --- a/packages/priority-queue/CHANGELOG.md +++ b/packages/priority-queue/CHANGELOG.md @@ -2,6 +2,16 @@ ## Unreleased +## 3.16.0 (2025-01-15) + +## 3.15.0 (2025-01-02) + +## 3.14.0 (2024-12-11) + +## 3.13.0 (2024-11-27) + +## 3.12.0 (2024-11-16) + ## 3.11.0 (2024-10-30) ## 3.10.0 (2024-10-16) diff --git a/packages/priority-queue/package.json b/packages/priority-queue/package.json index 8c98b0b3552198..610cc00de8c707 100644 --- a/packages/priority-queue/package.json +++ b/packages/priority-queue/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/priority-queue", - "version": "3.11.0", + "version": "3.16.0", "description": "Generic browser priority queue.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/priority-queue/tsconfig.json b/packages/priority-queue/tsconfig.json index 96d649eb7a6233..2a790d65e67612 100644 --- a/packages/priority-queue/tsconfig.json +++ b/packages/priority-queue/tsconfig.json @@ -2,9 +2,6 @@ "$schema": "https://json.schemastore.org/tsconfig.json", "extends": "../../tsconfig.base.json", "compilerOptions": { - "rootDir": "src", - "declarationDir": "build-types", "types": [ "requestidlecallback" ] - }, - "include": [ "src/**/*" ] + } } diff --git a/packages/private-apis/CHANGELOG.md b/packages/private-apis/CHANGELOG.md index db68d750432bc5..6dfb92821a3495 100644 --- a/packages/private-apis/CHANGELOG.md +++ b/packages/private-apis/CHANGELOG.md @@ -2,6 +2,16 @@ ## Unreleased +## 1.16.0 (2025-01-15) + +## 1.15.0 (2025-01-02) + +## 1.14.0 (2024-12-11) + +## 1.13.0 (2024-11-27) + +## 1.12.0 (2024-11-16) + ## 1.11.0 (2024-10-30) ## 1.10.0 (2024-10-16) diff --git a/packages/private-apis/package.json b/packages/private-apis/package.json index 84436470521332..20b0ce41e4650b 100644 --- a/packages/private-apis/package.json +++ b/packages/private-apis/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/private-apis", - "version": "1.11.0", + "version": "1.16.0", "description": "Internal experimental APIs for WordPress core.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/private-apis/src/implementation.ts b/packages/private-apis/src/implementation.ts index bae53bae8d158a..1ac08a71550ff1 100644 --- a/packages/private-apis/src/implementation.ts +++ b/packages/private-apis/src/implementation.ts @@ -32,6 +32,7 @@ const CORE_MODULES_USING_PRIVATE_APIS = [ '@wordpress/dataviews', '@wordpress/fields', '@wordpress/media-utils', + '@wordpress/upload-media', ]; /** @@ -137,14 +138,16 @@ export const __dangerousOptInToUnstableAPIsOnlyForCoreModules = ( * @param object The object to bind the private data to. * @param privateData The private data to bind to the object. */ -function lock( object: Record< symbol, WeakKey >, privateData: unknown ) { +function lock( object: unknown, privateData: unknown ) { if ( ! object ) { throw new Error( 'Cannot lock an undefined object.' ); } - if ( ! ( __private in object ) ) { - object[ __private ] = {}; + const _object = object as Record< symbol, WeakKey >; + + if ( ! ( __private in _object ) ) { + _object[ __private ] = {}; } - lockedData.set( object[ __private ], privateData ); + lockedData.set( _object[ __private ], privateData ); } /** @@ -170,17 +173,19 @@ function lock( object: Record< symbol, WeakKey >, privateData: unknown ) { * @param object The object to unlock the private data from. * @return The private data bound to the object. */ -function unlock( object: Record< symbol, WeakKey > ) { +function unlock< T = any >( object: unknown ): T { if ( ! object ) { throw new Error( 'Cannot unlock an undefined object.' ); } - if ( ! ( __private in object ) ) { + const _object = object as Record< symbol, WeakKey >; + + if ( ! ( __private in _object ) ) { throw new Error( 'Cannot unlock an object that was not locked before. ' ); } - return lockedData.get( object[ __private ] ); + return lockedData.get( _object[ __private ] ); } const lockedData = new WeakMap(); diff --git a/packages/private-apis/src/test/index.js b/packages/private-apis/src/test/index.js index 51d1b6a3afba00..36e57ee58165d0 100644 --- a/packages/private-apis/src/test/index.js +++ b/packages/private-apis/src/test/index.js @@ -236,7 +236,7 @@ describe( 'Specific use-cases of sharing private APIs', () => { * * ```js * import { logData } from 'package1'; - * const experimenalLogData = unlock( logData ); + * const experimentalLogData = unlock( logData ); * ``` */ expect( unlock( logData ) ).toBe( __privateLogData ); diff --git a/packages/private-apis/tsconfig.json b/packages/private-apis/tsconfig.json index 9e3edfe0ae443c..f197b56919708b 100644 --- a/packages/private-apis/tsconfig.json +++ b/packages/private-apis/tsconfig.json @@ -2,9 +2,6 @@ "$schema": "https://json.schemastore.org/tsconfig.json", "extends": "../../tsconfig.base.json", "compilerOptions": { - "rootDir": "src", - "declarationDir": "build-types", "types": [ "gutenberg-env" ] - }, - "include": [ "src/**/*" ] + } } diff --git a/packages/project-management-automation/CHANGELOG.md b/packages/project-management-automation/CHANGELOG.md index da01acd92c8f3b..cb6c8a493bd7c8 100644 --- a/packages/project-management-automation/CHANGELOG.md +++ b/packages/project-management-automation/CHANGELOG.md @@ -2,6 +2,16 @@ ## Unreleased +## 2.16.0 (2025-01-15) + +## 2.15.0 (2025-01-02) + +## 2.14.0 (2024-12-11) + +## 2.13.0 (2024-11-27) + +## 2.12.0 (2024-11-16) + ## 2.11.0 (2024-10-30) ## 2.10.0 (2024-10-16) diff --git a/packages/project-management-automation/package.json b/packages/project-management-automation/package.json index a3f6e3f0a44230..de027f146c439a 100644 --- a/packages/project-management-automation/package.json +++ b/packages/project-management-automation/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/project-management-automation", - "version": "2.11.0", + "version": "2.16.0", "description": "GitHub Action that implements various automation to assist with managing the Gutenberg GitHub repository.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/project-management-automation/tsconfig.json b/packages/project-management-automation/tsconfig.json index 0636ff7d0081dd..7899aeee7dfbc9 100644 --- a/packages/project-management-automation/tsconfig.json +++ b/packages/project-management-automation/tsconfig.json @@ -3,8 +3,7 @@ "extends": "../../tsconfig.base.json", "compilerOptions": { "rootDir": "lib", - "declarationDir": "build-types", "types": [ "node" ] }, - "include": [ "lib/**/*" ] + "include": [ "lib" ] } diff --git a/packages/react-i18n/CHANGELOG.md b/packages/react-i18n/CHANGELOG.md index 165c1f0bc95549..aab09809845511 100644 --- a/packages/react-i18n/CHANGELOG.md +++ b/packages/react-i18n/CHANGELOG.md @@ -2,6 +2,16 @@ ## Unreleased +## 4.16.0 (2025-01-15) + +## 4.15.0 (2025-01-02) + +## 4.14.0 (2024-12-11) + +## 4.13.0 (2024-11-27) + +## 4.12.0 (2024-11-16) + ## 4.11.0 (2024-10-30) ## 4.10.0 (2024-10-16) diff --git a/packages/react-i18n/package.json b/packages/react-i18n/package.json index 31c4d98fa475e0..77967e49936108 100644 --- a/packages/react-i18n/package.json +++ b/packages/react-i18n/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/react-i18n", - "version": "4.11.0", + "version": "4.16.0", "description": "React bindings for @wordpress/i18n.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -30,8 +30,8 @@ "sideEffects": false, "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/element": "*", - "@wordpress/i18n": "*", + "@wordpress/element": "file:../element", + "@wordpress/i18n": "file:../i18n", "utility-types": "^3.10.0" }, "publishConfig": { diff --git a/packages/react-i18n/tsconfig.json b/packages/react-i18n/tsconfig.json index e8e7f164f89a34..32b019421ed3d5 100644 --- a/packages/react-i18n/tsconfig.json +++ b/packages/react-i18n/tsconfig.json @@ -1,10 +1,5 @@ { "$schema": "https://json.schemastore.org/tsconfig.json", "extends": "../../tsconfig.base.json", - "compilerOptions": { - "rootDir": "src", - "declarationDir": "build-types" - }, - "references": [ { "path": "../element" }, { "path": "../i18n" } ], - "include": [ "src/**/*" ] + "references": [ { "path": "../element" }, { "path": "../i18n" } ] } diff --git a/packages/react-native-aztec/ios/RNTAztecView/RCTAztecView.swift b/packages/react-native-aztec/ios/RNTAztecView/RCTAztecView.swift index 03362c3a371fb2..bc3426a41680f0 100644 --- a/packages/react-native-aztec/ios/RNTAztecView/RCTAztecView.swift +++ b/packages/react-native-aztec/ios/RNTAztecView/RCTAztecView.swift @@ -72,18 +72,18 @@ class RCTAztecView: Aztec.TextView { return label }() - // RCTScrollViews are flipped horizontally on RTL. This messes up competelly horizontal layout contraints + // RCTScrollViews are flipped horizontally on RTL. This messes up competelly horizontal layout constraints // on views inserted after the transformation. - var placeholderPreferedHorizontalAnchor: NSLayoutXAxisAnchor { + var placeholderPreferredHorizontalAnchor: NSLayoutXAxisAnchor { return hasRTLLayout ? placeholderLabel.rightAnchor : placeholderLabel.leftAnchor } - // This constraint is created from the prefered horizontal anchor (analog to "leading") + // This constraint is created from the preferred horizontal anchor (analog to "leading") // but appending it always to left of its super view (Aztec). // This partially fixes the position issue originated from fliping the scroll view. // fixLabelPositionForRTLLayout() fixes the rest. private lazy var placeholderHorizontalConstraint: NSLayoutConstraint = { - return placeholderPreferedHorizontalAnchor.constraint( + return placeholderPreferredHorizontalAnchor.constraint( equalTo: leftAnchor, constant: leftTextInset ) @@ -169,7 +169,7 @@ class RCTAztecView: Aztec.TextView { /** This handles a bug introduced by iOS 13.0 (tested up to 13.2) where link interactions don't respect what the documentation says. - The documenatation for textView(_:shouldInteractWith:in:interaction:) says: + The documentation for textView(_:shouldInteractWith:in:interaction:) says: > Links in text views are interactive only if the text view is selectable but noneditable. @@ -413,7 +413,7 @@ class RCTAztecView: Aztec.TextView { return text.isStartOfParagraph(at: currentLocation) && !(text.endIndex == currentLocation) } override var keyCommands: [UIKeyCommand]? { - // Remove defautls Tab and Shift+Tab commands, leaving just Shift+Enter command. + // Remove defaults Tab and Shift+Tab commands, leaving just Shift+Enter command. return [carriageReturnKeyCommand] } @@ -673,7 +673,7 @@ class RCTAztecView: Aztec.TextView { } } - /// This method refreshes the font for the palceholder field and typing attributes. + /// This method refreshes the font for the placeholder field and typing attributes. /// This method should not be called directly. Call `refreshFont()` instead. /// private func refreshTypingAttributesAndPlaceholderFont() { diff --git a/packages/react-native-aztec/ios/RNTAztecView/RCTAztecViewManager.swift b/packages/react-native-aztec/ios/RNTAztecView/RCTAztecViewManager.swift index 5422d2feb864a0..23aeb04ea8e40f 100644 --- a/packages/react-native-aztec/ios/RNTAztecView/RCTAztecViewManager.swift +++ b/packages/react-native-aztec/ios/RNTAztecView/RCTAztecViewManager.swift @@ -18,7 +18,7 @@ public class RCTAztecViewManager: RCTViewManager { public override func view() -> UIView { let view = RCTAztecView( defaultFont: defaultFont, - defaultParagraphStyle: defaultParagrahStyle, + defaultParagraphStyle: defaultParagraphStyle, defaultMissingImage: UIImage()) view.isScrollEnabled = false @@ -71,7 +71,7 @@ public class RCTAztecViewManager: RCTViewManager { return defaultFont } - private var defaultParagrahStyle: ParagraphStyle { + private var defaultParagraphStyle: ParagraphStyle { let defaultStyle = ParagraphStyle.default defaultStyle.textListParagraphSpacing = 5 defaultStyle.textListParagraphSpacingBefore = 5 diff --git a/packages/react-native-aztec/package.json b/packages/react-native-aztec/package.json index b0327b531395e5..e2f5d5f425d869 100644 --- a/packages/react-native-aztec/package.json +++ b/packages/react-native-aztec/package.json @@ -23,8 +23,8 @@ "npm": ">=8.19.2" }, "dependencies": { - "@wordpress/element": "*", - "@wordpress/keycodes": "*" + "@wordpress/element": "file:../element", + "@wordpress/keycodes": "file:../keycodes" }, "peerDependencies": { "react": "*", diff --git a/packages/react-native-bridge/common/gutenberg-web-single-block/editor-behavior-overrides.js b/packages/react-native-bridge/common/gutenberg-web-single-block/editor-behavior-overrides.js index 1a4f4422a47ba2..e606b694e8efdd 100644 --- a/packages/react-native-bridge/common/gutenberg-web-single-block/editor-behavior-overrides.js +++ b/packages/react-native-bridge/common/gutenberg-web-single-block/editor-behavior-overrides.js @@ -13,10 +13,10 @@ function isAndroid() { * tapped. This is done via the 'hideTextSelectionContextMenu' method, which * is sent back to the Android app, where the dismissal is then handle. * - * @return {void} * @see https://github.com/WordPress/gutenberg/pull/34668 + * @return {void} */ -function manageTextSelectonContextMenu() { +function manageTextSelectionContextMenu() { // Listeners for native context menu visibility changes. let isContextMenuVisible = false; const hideContextMenuListeners = []; @@ -74,7 +74,7 @@ function manageTextSelectonContextMenu() { } if ( isAndroid() ) { - manageTextSelectonContextMenu(); + manageTextSelectionContextMenu(); } function _toggleBlockSelectedClass( isBlockSelected ) { diff --git a/packages/react-native-bridge/index.js b/packages/react-native-bridge/index.js index da16f75e161dac..12dbfc2bb6fead 100644 --- a/packages/react-native-bridge/index.js +++ b/packages/react-native-bridge/index.js @@ -494,7 +494,7 @@ export function logException( if ( ! wasSent ) { // eslint-disable-next-line no-console console.error( - 'An error ocurred when logging the exception', + 'An error occurred when logging the exception', parsedException ); } diff --git a/packages/react-native-bridge/ios/Gutenberg.swift b/packages/react-native-bridge/ios/Gutenberg.swift index adb24baa778511..09ea9f4d997723 100644 --- a/packages/react-native-bridge/ios/Gutenberg.swift +++ b/packages/react-native-bridge/ios/Gutenberg.swift @@ -85,7 +85,7 @@ public class Gutenberg: UIResponder { let editorSettings = dataSource.gutenbergEditorSettings() let settingsUpdates = properties(from: editorSettings) - initialProps.merge(settingsUpdates) { (intialProp, settingsUpdates) -> Any in + initialProps.merge(settingsUpdates) { (initialProp, settingsUpdates) -> Any in settingsUpdates } @@ -136,8 +136,8 @@ public class Gutenberg: UIResponder { } public func updateCapabilities() { - let capabilites = dataSource.gutenbergCapabilities() - sendEvent(.updateCapabilities, body: capabilites.toJSPayload()) + let capabilities = dataSource.gutenbergCapabilities() + sendEvent(.updateCapabilities, body: capabilities.toJSPayload()) } private func sendEvent(_ event: RNReactNativeGutenbergBridge.EventName, body: [String: Any]? = nil) { diff --git a/packages/react-native-bridge/ios/GutenbergBridgeDelegate.swift b/packages/react-native-bridge/ios/GutenbergBridgeDelegate.swift index 8ff78b8fa1415f..077ef8bc0fb6c0 100644 --- a/packages/react-native-bridge/ios/GutenbergBridgeDelegate.swift +++ b/packages/react-native-bridge/ios/GutenbergBridgeDelegate.swift @@ -89,7 +89,7 @@ public typealias MediaPickerDidPickMediaCallback = (_ media: [MediaInfo]?) -> Vo public typealias MediaImportCallback = (_ media: MediaInfo?) -> Void /// Declare internal Media Sources. -/// Label and Type are not relevant since they are delcared on the JS side. +/// Label and Type are not relevant since they are declared on the JS side. /// Hopefully soon, this will need to be declared on the client side. extension Gutenberg.MediaSource { public static let mediaLibrary = Gutenberg.MediaSource(id: "SITE_MEDIA_LIBRARY", label: "", types: [.image, .video]) diff --git a/packages/react-native-bridge/ios/GutenbergWebFallback/FallbackJavascriptInjection.swift b/packages/react-native-bridge/ios/GutenbergWebFallback/FallbackJavascriptInjection.swift index ee75dc0968a586..e3fe710eadcfea 100644 --- a/packages/react-native-bridge/ios/GutenbergWebFallback/FallbackJavascriptInjection.swift +++ b/packages/react-native-bridge/ios/GutenbergWebFallback/FallbackJavascriptInjection.swift @@ -22,7 +22,7 @@ public struct FallbackJavascriptInjection { public let editorBehaviorScript: WKUserScript /// Init an instance of GutenbergWebJavascriptInjection or throws if any of the required sources doesn't exist. - /// This helps to cach early any possible error due to missing source files. + /// This helps to cache early any possible error due to missing source files. /// - Parameter blockHTML: The block HTML code to be injected. /// - Parameter userId: The id of the logged user. /// - Throws: Throws an error if any required source doesn't exist. diff --git a/packages/react-native-bridge/lib/test/parseException.test.js b/packages/react-native-bridge/lib/test/parseException.test.js index 64d16356adba27..37738a1bbf2f6f 100644 --- a/packages/react-native-bridge/lib/test/parseException.test.js +++ b/packages/react-native-bridge/lib/test/parseException.test.js @@ -66,7 +66,7 @@ describe( 'Parse exception', () => { expect( exception.message ).toBe( 'No error message' ); } ); - it( 'sets unkown error type', () => { + it( 'sets unknown error type', () => { const exception = parseException( { message: { error: { message: '' } }, } ); diff --git a/packages/react-native-bridge/package.json b/packages/react-native-bridge/package.json index b0c0a2485520df..925b83103dca00 100644 --- a/packages/react-native-bridge/package.json +++ b/packages/react-native-bridge/package.json @@ -24,7 +24,7 @@ "main": "index.js", "react-native": "index", "dependencies": { - "@wordpress/react-native-aztec": "*" + "@wordpress/react-native-aztec": "file:../react-native-aztec" }, "peerDependencies": { "react-native": "*" diff --git a/packages/react-native-editor/CHANGELOG.md b/packages/react-native-editor/CHANGELOG.md index 6031e402100c98..304f305173eb45 100644 --- a/packages/react-native-editor/CHANGELOG.md +++ b/packages/react-native-editor/CHANGELOG.md @@ -270,7 +270,7 @@ For each user feature we should also add a importance categorization label to i ## 1.95.0 - [*] Fix crash when trying to convert to regular blocks an undefined/deleted reusable block [#50475] -- [**] Tapping on nested text blocks gets focus directly instead of having to tap multiple times depeding on the nesting levels. [#50108] +- [**] Tapping on nested text blocks gets focus directly instead of having to tap multiple times depending on the nesting levels. [#50108] - [*] Use host app namespace in reusable block message [#50478] - [**] Configuring a link to open in a new tab no longer results in a partial loss of edit history (undo and redo) [#50460] @@ -344,11 +344,11 @@ For each user feature we should also add a importance categorization label to i ## 1.86.0 - [**] Upgrade React Native to 0.69.4 [#43485] -- [**] Prevent error message from unneccesarily firing when uploading to Gallery block [#46175] +- [**] Prevent error message from unnecessarily firing when uploading to Gallery block [#46175] ## 1.85.1 -- [**] Prevent error message from unneccesarily firing when uploading to Gallery block [#46175] +- [**] Prevent error message from unnecessarily firing when uploading to Gallery block [#46175] ## 1.85.0 @@ -967,7 +967,7 @@ For each user feature we should also add a importance categorization label to i - New block: Latest Posts - Fix Quote block's left border not being visible in Dark Mode - Added Starter Page Templates: when you create a new page, we now show you a few templates to get started more quickly. -- Fix crash when pasting HTML content with embeded images on paragraphs +- Fix crash when pasting HTML content with embedded images on paragraphs ## 1.23.0 @@ -977,7 +977,7 @@ For each user feature we should also add a importance categorization label to i - New block: Button - Add scroll support inside block picker and block settings - [Android] Fix issue preventing correct placeholder image from displaying during image upload -- [iOS] Fix diplay of large numbers on ordered lists +- [iOS] Fix display of large numbers on ordered lists - Fix issue where adding emojis to the post title add strong HTML elements to the title of the post - [iOS] Fix issue where alignment of paragraph blocks was not always being respected when splitting the paragraph or reading the post's html content. - We’ve introduced a new toolbar that floats above the block you’re editing, which makes navigating your blocks easier — especially complex ones. diff --git a/packages/react-native-editor/README.md b/packages/react-native-editor/README.md index 5fe50c1972fbef..37f3a165138413 100644 --- a/packages/react-native-editor/README.md +++ b/packages/react-native-editor/README.md @@ -1,6 +1,6 @@ # React Native Editor -This package provides a demo application to simplify the environment setup required for the development of Gutenberg for native Android and iOS. The demo application allows running the mobile versions of Gutenberg blocks while avoiding the additional setup steps required by the [WordPress Android](https://github.com/wordpress-mobile/WordPress-Android) and [Wordpress iOS](https://github.com/wordpress-mobile/WordPress-iOS) apps. +This package provides a demo application to simplify the environment setup required for the development of Gutenberg for native Android and iOS. The demo application allows running the mobile versions of Gutenberg blocks while avoiding the additional setup steps required by the [WordPress Android](https://github.com/wordpress-mobile/WordPress-Android) and [WordPress iOS](https://github.com/wordpress-mobile/WordPress-iOS) apps. ## Getting Started diff --git a/packages/react-native-editor/__device-tests__/pages/editor-page.js b/packages/react-native-editor/__device-tests__/pages/editor-page.js index 77f4a700b11370..2333d2dc899d5e 100644 --- a/packages/react-native-editor/__device-tests__/pages/editor-page.js +++ b/packages/react-native-editor/__device-tests__/pages/editor-page.js @@ -1118,7 +1118,7 @@ const blockNames = { image: 'Image', latestPosts: 'Latest Posts', list: 'List', - listItem: 'List item', + listItem: 'List Item', more: 'More', paragraph: 'Paragraph', search: 'Search', diff --git a/packages/react-native-editor/ios/Gemfile.lock b/packages/react-native-editor/ios/Gemfile.lock index e8fbb72a6ca659..4884203a2a00d5 100644 --- a/packages/react-native-editor/ios/Gemfile.lock +++ b/packages/react-native-editor/ios/Gemfile.lock @@ -72,21 +72,19 @@ GEM nap (1.1.0) netrc (0.11.0) public_suffix (4.0.7) - rexml (3.2.8) - strscan (>= 3.0.9) + rexml (3.3.9) ruby-macho (2.5.1) - strscan (3.1.0) typhoeus (1.4.1) ethon (>= 0.9.0) tzinfo (2.0.6) concurrent-ruby (~> 1.0) - xcodeproj (1.24.0) + xcodeproj (1.25.1) CFPropertyList (>= 2.3.3, < 4.0) atomos (~> 0.1.3) claide (>= 1.0.2, < 2.0) colored2 (~> 3.1) nanaimo (~> 0.3.0) - rexml (~> 3.2.4) + rexml (>= 3.3.6, < 4.0) zeitwerk (2.6.11) PLATFORMS diff --git a/packages/react-native-editor/ios/GutenbergDemo/GutenbergViewController.swift b/packages/react-native-editor/ios/GutenbergDemo/GutenbergViewController.swift index c9c1bca191c22e..9575dac96d488c 100644 --- a/packages/react-native-editor/ios/GutenbergDemo/GutenbergViewController.swift +++ b/packages/react-native-editor/ios/GutenbergDemo/GutenbergViewController.swift @@ -219,7 +219,7 @@ extension GutenbergViewController: GutenbergBridgeDelegate { } alertController.addAction(dismissAction) - if progress.fractionCompleted < 1 && mediaUploadCoordinator.successfullUpload { + if progress.fractionCompleted < 1 && mediaUploadCoordinator.successfulUpload { let cancelUploadAction = UIAlertAction(title: "Cancel upload", style: .destructive) { (action) in self.mediaUploadCoordinator.cancelUpload(with: mediaID) } @@ -317,7 +317,7 @@ extension GutenbergViewController: GutenbergBridgeDelegate { } func gutenbergDidRequestSendEventToHost(_ eventName: String, properties: [AnyHashable: Any]) -> Void { - print("Gutenberg requested sending '\(eventName)' event to host with propreties: \(properties).") + print("Gutenberg requested sending '\(eventName)' event to host with properties: \(properties).") } func gutenbergDidRequestToggleUndoButton(_ isDisabled: Bool) -> Void { diff --git a/packages/react-native-editor/ios/GutenbergDemo/MediaUploadCoordinator.swift b/packages/react-native-editor/ios/GutenbergDemo/MediaUploadCoordinator.swift index e9183d33c70d02..a21df84b6eafae 100644 --- a/packages/react-native-editor/ios/GutenbergDemo/MediaUploadCoordinator.swift +++ b/packages/react-native-editor/ios/GutenbergDemo/MediaUploadCoordinator.swift @@ -10,7 +10,7 @@ class MediaUploadCoordinator: NSObject { private let gutenberg: Gutenberg private var activeUploads: [Int32: Progress] = [:] - private(set) var successfullUpload = true + private(set) var successfulUpload = true init(gutenberg: Gutenberg) { self.gutenberg = gutenberg @@ -20,7 +20,7 @@ class MediaUploadCoordinator: NSObject { func upload(url: URL) -> Int32? { //Make sure the media is not larger than a 32 bits to number to avoid problems when bridging to JS - successfullUpload = true + successfulUpload = true let mediaID = Int32(truncatingIfNeeded:UUID().uuidString.hash) let progress = Progress(parent: nil, userInfo: [ProgressUserInfoKey.mediaID: mediaID, ProgressUserInfoKey.mediaURL: url]) progress.totalUnitCount = 100 @@ -42,7 +42,7 @@ class MediaUploadCoordinator: NSObject { return } progress.completedUnitCount = 0 - successfullUpload = true + successfulUpload = true Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(timerFireMethod(_:)), userInfo: progress, repeats: true) } @@ -54,7 +54,7 @@ class MediaUploadCoordinator: NSObject { } @objc func failUpload() { - successfullUpload = false + successfulUpload = false } @objc func timerFireMethod(_ timer: Timer) { @@ -67,11 +67,11 @@ class MediaUploadCoordinator: NSObject { } progress.completedUnitCount += 1 - if !successfullUpload { + if !successfulUpload { timer.invalidate() progress.setUserInfoObject("Network upload failed", forKey: .mediaError) gutenberg.mediaUploadUpdate(id: mediaID, state: .failed, progress: 1, url: nil, serverID: nil, metadata: ["demoApp" : true, "failReason" : "Network upload failed"]) - successfullUpload = true + successfulUpload = true return } diff --git a/packages/react-native-editor/package.json b/packages/react-native-editor/package.json index e6e53af1190ad7..3a345a23e0a5d9 100644 --- a/packages/react-native-editor/package.json +++ b/packages/react-native-editor/package.json @@ -38,18 +38,18 @@ "@react-navigation/native": "6.0.14", "@react-navigation/routers": "5.4.9", "@react-navigation/stack": "6.3.5", - "@wordpress/api-fetch": "*", - "@wordpress/block-editor": "*", - "@wordpress/block-library": "*", - "@wordpress/blocks": "*", - "@wordpress/components": "*", - "@wordpress/data": "*", - "@wordpress/edit-post": "*", - "@wordpress/element": "*", - "@wordpress/hooks": "*", - "@wordpress/i18n": "*", - "@wordpress/react-native-aztec": "*", - "@wordpress/react-native-bridge": "*", + "@wordpress/api-fetch": "file:../api-fetch", + "@wordpress/block-editor": "file:../block-editor", + "@wordpress/block-library": "file:../block-library", + "@wordpress/blocks": "file:../blocks", + "@wordpress/components": "file:../components", + "@wordpress/data": "file:../data", + "@wordpress/edit-post": "file:../edit-post", + "@wordpress/element": "file:../element", + "@wordpress/hooks": "file:../hooks", + "@wordpress/i18n": "file:../i18n", + "@wordpress/react-native-aztec": "file:../react-native-aztec", + "@wordpress/react-native-bridge": "file:../react-native-bridge", "core-js": "^3.31.0", "fast-average-color": "^9.1.1", "gettext-parser": "^1.3.1", diff --git a/packages/react-native-editor/sass-transformer.js b/packages/react-native-editor/sass-transformer.js index 3b561eca6d88bd..8cf49ca293c646 100644 --- a/packages/react-native-editor/sass-transformer.js +++ b/packages/react-native-editor/sass-transformer.js @@ -70,14 +70,14 @@ function findVariant( name, extensions, includePaths, projectRoot ) { } // Try to find the file iterating through the extensions, in order. - const foundExtention = extensions.find( ( extension ) => { + const foundExtension = extensions.find( ( extension ) => { const fname = includePath + '/' + name + extension; const partialfname = includePath + '/_' + name + extension; return fs.existsSync( fname ) || fs.existsSync( partialfname ); } ); - if ( foundExtention ) { - return includePath + '/' + name + foundExtention; + if ( foundExtension ) { + return includePath + '/' + name + foundExtension; } } diff --git a/packages/react-native-editor/src/jsdom-patches.js b/packages/react-native-editor/src/jsdom-patches.js index 680bdcf1eb12e8..284bd9359931a5 100644 --- a/packages/react-native-editor/src/jsdom-patches.js +++ b/packages/react-native-editor/src/jsdom-patches.js @@ -54,7 +54,7 @@ Node.prototype.contains = function ( node ) { * Copy of insertBefore function from jsdom-jscore, WRONG_DOCUMENT_ERR exception * disabled. * - * @param {Object} newChild The node to be insterted. + * @param {Object} newChild The node to be inserted. * @param {Object} refChild The node before which newChild is inserted. * @return {Object} the newly inserted child node * diff --git a/packages/readable-js-assets-webpack-plugin/CHANGELOG.md b/packages/readable-js-assets-webpack-plugin/CHANGELOG.md index 63a3192e7aa95f..b6a6f5e78d3604 100644 --- a/packages/readable-js-assets-webpack-plugin/CHANGELOG.md +++ b/packages/readable-js-assets-webpack-plugin/CHANGELOG.md @@ -2,6 +2,16 @@ ## Unreleased +## 3.16.0 (2025-01-15) + +## 3.15.0 (2025-01-02) + +## 3.14.0 (2024-12-11) + +## 3.13.0 (2024-11-27) + +## 3.12.0 (2024-11-16) + ## 3.11.0 (2024-10-30) ## 3.10.0 (2024-10-16) diff --git a/packages/readable-js-assets-webpack-plugin/package.json b/packages/readable-js-assets-webpack-plugin/package.json index 3e7d14813968ee..0e9971881a6e87 100644 --- a/packages/readable-js-assets-webpack-plugin/package.json +++ b/packages/readable-js-assets-webpack-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/readable-js-assets-webpack-plugin", - "version": "3.11.0", + "version": "3.16.0", "description": "Generate a readable JS file for each JS asset.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/redux-routine/CHANGELOG.md b/packages/redux-routine/CHANGELOG.md index 54df85dfd5717f..532055587007d4 100644 --- a/packages/redux-routine/CHANGELOG.md +++ b/packages/redux-routine/CHANGELOG.md @@ -2,6 +2,16 @@ ## Unreleased +## 5.16.0 (2025-01-15) + +## 5.15.0 (2025-01-02) + +## 5.14.0 (2024-12-11) + +## 5.13.0 (2024-11-27) + +## 5.12.0 (2024-11-16) + ## 5.11.0 (2024-10-30) ## 5.10.0 (2024-10-16) diff --git a/packages/redux-routine/README.md b/packages/redux-routine/README.md index b455d52db8b819..a79196fb2dc906 100644 --- a/packages/redux-routine/README.md +++ b/packages/redux-routine/README.md @@ -53,7 +53,7 @@ store.dispatch( retrieveTemperature() ); ``` In this example, when we dispatch `retrieveTemperature`, it will trigger the control handler to take effect, issuing the network request and assigning the result into the `result` variable. Only once the -request has completed does the action creator procede to return the `SET_TEMPERATURE` action type. +request has completed does the action creator proceed to return the `SET_TEMPERATURE` action type. ## API diff --git a/packages/redux-routine/package.json b/packages/redux-routine/package.json index b3aa6fa79474a8..4f38cad4824e4b 100644 --- a/packages/redux-routine/package.json +++ b/packages/redux-routine/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/redux-routine", - "version": "5.11.0", + "version": "5.16.0", "description": "Redux middleware for generator coroutines.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/redux-routine/tsconfig.json b/packages/redux-routine/tsconfig.json index 6e33d8ff82d47e..7ff060ab6ce105 100644 --- a/packages/redux-routine/tsconfig.json +++ b/packages/redux-routine/tsconfig.json @@ -1,9 +1,4 @@ { "$schema": "https://json.schemastore.org/tsconfig.json", - "extends": "../../tsconfig.base.json", - "compilerOptions": { - "rootDir": "src", - "declarationDir": "build-types" - }, - "include": [ "src/**/*" ] + "extends": "../../tsconfig.base.json" } diff --git a/packages/report-flaky-tests/tsconfig.json b/packages/report-flaky-tests/tsconfig.json index 09fc242db010db..26fcd6f5e51c6f 100644 --- a/packages/report-flaky-tests/tsconfig.json +++ b/packages/report-flaky-tests/tsconfig.json @@ -3,10 +3,7 @@ "extends": "../../tsconfig.base.json", "compilerOptions": { "module": "CommonJS", - "declarationDir": "build-types", - "rootDir": "src", "types": [ "jest" ] }, - "include": [ "src/**/*" ], - "exclude": [ "src/__tests__/**/*", "src/__fixtures__/**/*" ] + "exclude": [ "src/__tests__", "src/__fixtures__" ] } diff --git a/packages/reusable-blocks/CHANGELOG.md b/packages/reusable-blocks/CHANGELOG.md index e4ba1b052ae218..87307053262faa 100644 --- a/packages/reusable-blocks/CHANGELOG.md +++ b/packages/reusable-blocks/CHANGELOG.md @@ -2,6 +2,16 @@ ## Unreleased +## 5.16.0 (2025-01-15) + +## 5.15.0 (2025-01-02) + +## 5.14.0 (2024-12-11) + +## 5.13.0 (2024-11-27) + +## 5.12.0 (2024-11-16) + ## 5.11.0 (2024-10-30) ## 5.10.0 (2024-10-16) diff --git a/packages/reusable-blocks/package.json b/packages/reusable-blocks/package.json index e2649609016642..27e3faf524d34b 100644 --- a/packages/reusable-blocks/package.json +++ b/packages/reusable-blocks/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/reusable-blocks", - "version": "5.11.0", + "version": "5.16.0", "description": "Reusable blocks utilities.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -31,17 +31,17 @@ ], "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/block-editor": "*", - "@wordpress/blocks": "*", - "@wordpress/components": "*", - "@wordpress/core-data": "*", - "@wordpress/data": "*", - "@wordpress/element": "*", - "@wordpress/i18n": "*", - "@wordpress/icons": "*", - "@wordpress/notices": "*", - "@wordpress/private-apis": "*", - "@wordpress/url": "*" + "@wordpress/block-editor": "file:../block-editor", + "@wordpress/blocks": "file:../blocks", + "@wordpress/components": "file:../components", + "@wordpress/core-data": "file:../core-data", + "@wordpress/data": "file:../data", + "@wordpress/element": "file:../element", + "@wordpress/i18n": "file:../i18n", + "@wordpress/icons": "file:../icons", + "@wordpress/notices": "file:../notices", + "@wordpress/private-apis": "file:../private-apis", + "@wordpress/url": "file:../url" }, "peerDependencies": { "react": "^18.0.0", diff --git a/packages/rich-text/CHANGELOG.md b/packages/rich-text/CHANGELOG.md index 138d963020deb5..c3174a29a081a7 100644 --- a/packages/rich-text/CHANGELOG.md +++ b/packages/rich-text/CHANGELOG.md @@ -2,6 +2,16 @@ ## Unreleased +## 7.16.0 (2025-01-15) + +## 7.15.0 (2025-01-02) + +## 7.14.0 (2024-12-11) + +## 7.13.0 (2024-11-27) + +## 7.12.0 (2024-11-16) + ## 7.11.0 (2024-10-30) ## 7.10.0 (2024-10-16) diff --git a/packages/rich-text/package.json b/packages/rich-text/package.json index f038e097668984..008f06b33f35d5 100644 --- a/packages/rich-text/package.json +++ b/packages/rich-text/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/rich-text", - "version": "7.11.0", + "version": "7.16.0", "description": "Rich text value and manipulation API.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -33,14 +33,14 @@ ], "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/a11y": "*", - "@wordpress/compose": "*", - "@wordpress/data": "*", - "@wordpress/deprecated": "*", - "@wordpress/element": "*", - "@wordpress/escape-html": "*", - "@wordpress/i18n": "*", - "@wordpress/keycodes": "*", + "@wordpress/a11y": "file:../a11y", + "@wordpress/compose": "file:../compose", + "@wordpress/data": "file:../data", + "@wordpress/deprecated": "file:../deprecated", + "@wordpress/element": "file:../element", + "@wordpress/escape-html": "file:../escape-html", + "@wordpress/i18n": "file:../i18n", + "@wordpress/keycodes": "file:../keycodes", "memize": "^2.1.0" }, "peerDependencies": { diff --git a/packages/rich-text/src/component/index.js b/packages/rich-text/src/component/index.js index 600fc0faff5209..e17a4704d8a370 100644 --- a/packages/rich-text/src/component/index.js +++ b/packages/rich-text/src/component/index.js @@ -157,7 +157,7 @@ export function useRichText( { const didMountRef = useRef( false ); - // Value updates must happen synchonously to avoid overwriting newer values. + // Value updates must happen synchronously to avoid overwriting newer values. useLayoutEffect( () => { if ( didMountRef.current && value !== _valueRef.current ) { applyFromProps(); @@ -165,7 +165,7 @@ export function useRichText( { } }, [ value ] ); - // Value updates must happen synchonously to avoid overwriting newer values. + // Value updates must happen synchronously to avoid overwriting newer values. useLayoutEffect( () => { if ( ! hadSelectionUpdateRef.current ) { return; diff --git a/packages/rich-text/src/component/use-anchor.js b/packages/rich-text/src/component/use-anchor.js index 412c31bf5b7072..320ef3dbdca2d7 100644 --- a/packages/rich-text/src/component/use-anchor.js +++ b/packages/rich-text/src/component/use-anchor.js @@ -21,7 +21,7 @@ import { useState, useLayoutEffect } from '@wordpress/element'; function getFormatElement( range, editableContentElement, tagName, className ) { let element = range.startContainer; - // Even if the active format is defined, the actualy DOM range's start + // Even if the active format is defined, the actually DOM range's start // container may be outside of the format's DOM element: // `a‸<strong>b</strong>` (DOM) while visually it's `a<strong>‸b</strong>`. // So at a given selection index, start with the deepest format DOM element. diff --git a/packages/rich-text/src/create.js b/packages/rich-text/src/create.js index 898bdfa73b330e..a8fc4b92c49600 100644 --- a/packages/rich-text/src/create.js +++ b/packages/rich-text/src/create.js @@ -125,6 +125,13 @@ export class RichTextData { static fromHTMLString( html ) { return new RichTextData( create( { html } ) ); } + /** + * Create a RichTextData instance from an HTML element. + * + * @param {HTMLElement} htmlElement The HTML element to create the instance from. + * @param {{preserveWhiteSpace?: boolean}} options Options. + * @return {RichTextData} The RichTextData instance. + */ static fromHTMLElement( htmlElement, options = {} ) { const { preserveWhiteSpace = false } = options; const element = preserveWhiteSpace @@ -144,6 +151,12 @@ export class RichTextData { } // We could expose `toHTMLElement` at some point as well, but we'd only use // it internally. + /** + * Convert the rich text value to an HTML string. + * + * @param {{preserveWhiteSpace?: boolean}} options Options. + * @return {string} The HTML string. + */ toHTMLString( { preserveWhiteSpace } = {} ) { return ( this.originalHTML || @@ -469,6 +482,34 @@ function createFromElement( { element, range, isEditableTree } ) { continue; } + if ( + node.nodeType === node.COMMENT_NODE || + ( node.nodeType === node.ELEMENT_NODE && + node.tagName === 'SPAN' && + node.hasAttribute( 'data-rich-text-comment' ) ) + ) { + const value = { + formats: [ , ], + replacements: [ + { + type: '#comment', + attributes: { + 'data-rich-text-comment': + node.nodeType === node.COMMENT_NODE + ? node.nodeValue + : node.getAttribute( + 'data-rich-text-comment' + ), + }, + }, + ], + text: OBJECT_REPLACEMENT_CHARACTER, + }; + accumulateSelection( accumulator, node, range, value ); + mergePair( accumulator, value ); + continue; + } + if ( node.nodeType !== node.ELEMENT_NODE ) { continue; } diff --git a/packages/rich-text/src/join.js b/packages/rich-text/src/join.js index 805d2528f0c688..6d91fbec1e7a3f 100644 --- a/packages/rich-text/src/join.js +++ b/packages/rich-text/src/join.js @@ -23,13 +23,13 @@ export function join( values, separator = '' ) { } return normaliseFormats( - values.reduce( ( accumlator, { formats, replacements, text } ) => ( { - formats: accumlator.formats.concat( separator.formats, formats ), - replacements: accumlator.replacements.concat( + values.reduce( ( accumulator, { formats, replacements, text } ) => ( { + formats: accumulator.formats.concat( separator.formats, formats ), + replacements: accumulator.replacements.concat( separator.replacements, replacements ), - text: accumlator.text + separator.text + text, + text: accumulator.text + separator.text + text, } ) ) ); } diff --git a/packages/rich-text/src/store/selectors.js b/packages/rich-text/src/store/selectors.js index df87c6a99211a2..16572e301c1dba 100644 --- a/packages/rich-text/src/store/selectors.js +++ b/packages/rich-text/src/store/selectors.js @@ -75,7 +75,7 @@ export const getFormatTypes = createSelector( * }; * ``` * - * @return {Object?} Format type. + * @return {?Object} Format type. */ export function getFormatType( state, name ) { return state.formatTypes[ name ]; diff --git a/packages/rich-text/src/test/__snapshots__/to-dom.js.snap b/packages/rich-text/src/test/__snapshots__/to-dom.js.snap index 0daf48aa9a1c36..0e2ac57bea3704 100644 --- a/packages/rich-text/src/test/__snapshots__/to-dom.js.snap +++ b/packages/rich-text/src/test/__snapshots__/to-dom.js.snap @@ -272,6 +272,21 @@ exports[`recordToDom should not error with overlapping formats (2) 1`] = ` </body> `; +exports[`recordToDom should preserve comments 1`] = ` +<body> + + <span + contenteditable="false" + data-rich-text-comment="comment" + > + <span> + comment + </span> + </span> + +</body> +`; + exports[`recordToDom should preserve emoji 1`] = ` <body> 🍒 @@ -289,6 +304,21 @@ exports[`recordToDom should preserve emoji in formatting 1`] = ` </body> `; +exports[`recordToDom should preserve funky comments 1`] = ` +<body> + + <span + contenteditable="false" + data-rich-text-comment="/funky" + > + <span> + /funky + </span> + </span> + +</body> +`; + exports[`recordToDom should preserve non breaking space 1`] = ` <body> test  test diff --git a/packages/rich-text/src/test/helpers/index.js b/packages/rich-text/src/test/helpers/index.js index f246ab956db3a7..7658ede7e37737 100644 --- a/packages/rich-text/src/test/helpers/index.js +++ b/packages/rich-text/src/test/helpers/index.js @@ -551,6 +551,58 @@ export const spec = [ text: '\ufffc', }, }, + { + description: 'should preserve comments', + html: '<!--comment-->', + createRange: ( element ) => ( { + startOffset: 0, + startContainer: element, + endOffset: 1, + endContainer: element, + } ), + startPath: [ 0, 0 ], + endPath: [ 2, 0 ], + record: { + start: 0, + end: 1, + formats: [ , ], + replacements: [ + { + attributes: { + 'data-rich-text-comment': 'comment', + }, + type: '#comment', + }, + ], + text: '\ufffc', + }, + }, + { + description: 'should preserve funky comments', + html: '<//funky>', + createRange: ( element ) => ( { + startOffset: 0, + startContainer: element, + endOffset: 1, + endContainer: element, + } ), + startPath: [ 0, 0 ], + endPath: [ 2, 0 ], + record: { + start: 0, + end: 1, + formats: [ , ], + replacements: [ + { + attributes: { + 'data-rich-text-comment': '/funky', + }, + type: '#comment', + }, + ], + text: '\ufffc', + }, + }, ]; export const specWithRegistration = [ diff --git a/packages/rich-text/src/test/remove-format.js b/packages/rich-text/src/test/remove-format.js index bf3dd19179f703..5bc3061b413494 100644 --- a/packages/rich-text/src/test/remove-format.js +++ b/packages/rich-text/src/test/remove-format.js @@ -45,7 +45,7 @@ describe( 'removeFormat', () => { expect( getSparseArrayLength( result.formats ) ).toBe( 3 ); } ); - it( 'should remove format for collased selection', () => { + it( 'should remove format for collapsed selection', () => { const record = { formats: [ , diff --git a/packages/rich-text/src/to-dom.js b/packages/rich-text/src/to-dom.js index e7288e4ba16332..ac8308c7274b58 100644 --- a/packages/rich-text/src/to-dom.js +++ b/packages/rich-text/src/to-dom.js @@ -68,10 +68,16 @@ function append( element, child ) { const { type, attributes } = child; if ( type ) { - child = element.ownerDocument.createElement( type ); + if ( type === '#comment' ) { + child = element.ownerDocument.createComment( + attributes[ 'data-rich-text-comment' ] + ); + } else { + child = element.ownerDocument.createElement( type ); - for ( const key in attributes ) { - child.setAttribute( key, attributes[ key ] ); + for ( const key in attributes ) { + child.setAttribute( key, attributes[ key ] ); + } } } diff --git a/packages/rich-text/src/to-html-string.js b/packages/rich-text/src/to-html-string.js index f770dfdefc128a..a4c12b4c47f00d 100644 --- a/packages/rich-text/src/to-html-string.js +++ b/packages/rich-text/src/to-html-string.js @@ -88,6 +88,15 @@ function remove( object ) { } function createElementHTML( { type, attributes, object, children } ) { + if ( type === '#comment' ) { + // We can't restore the original comment delimiters, because once parsed + // into DOM nodes, we don't have the information. But in the future we + // could allow comment handlers to specify custom delimiters, for + // example `</{comment-content}>` for Bits, where `comment-content` + // would be `/{bit-name}` or `__{translatable-string}` (TBD). + return `<!--${ attributes[ 'data-rich-text-comment' ] }-->`; + } + let attributeString = ''; for ( const key in attributes ) { diff --git a/packages/rich-text/src/to-tree.js b/packages/rich-text/src/to-tree.js index 46671c951bc09d..0e3caad4f70c83 100644 --- a/packages/rich-text/src/to-tree.js +++ b/packages/rich-text/src/to-tree.js @@ -229,7 +229,20 @@ export function toTree( { const { type, attributes, innerHTML } = replacement; const formatType = getFormatType( type ); - if ( ! isEditableTree && type === 'script' ) { + if ( isEditableTree && type === '#comment' ) { + pointer = append( getParent( pointer ), { + type: 'span', + attributes: { + contenteditable: 'false', + 'data-rich-text-comment': + attributes[ 'data-rich-text-comment' ], + }, + } ); + append( + append( pointer, { type: 'span' } ), + attributes[ 'data-rich-text-comment' ].trim() + ); + } else if ( ! isEditableTree && type === 'script' ) { pointer = append( getParent( pointer ), fromFormat( { diff --git a/packages/rich-text/tsconfig.json b/packages/rich-text/tsconfig.json index 57fe0ae604215f..5dadcb0ed0045c 100644 --- a/packages/rich-text/tsconfig.json +++ b/packages/rich-text/tsconfig.json @@ -2,8 +2,6 @@ "$schema": "https://json.schemastore.org/tsconfig.json", "extends": "../../tsconfig.base.json", "compilerOptions": { - "rootDir": "src", - "declarationDir": "build-types", "types": [ "gutenberg-env" ], "checkJs": false }, @@ -16,6 +14,5 @@ { "path": "../escape-html" }, { "path": "../i18n" }, { "path": "../keycodes" } - ], - "include": [ "src/**/*" ] + ] } diff --git a/packages/router/CHANGELOG.md b/packages/router/CHANGELOG.md index cd0a91605c6f58..c94f6d2f77d92c 100644 --- a/packages/router/CHANGELOG.md +++ b/packages/router/CHANGELOG.md @@ -2,6 +2,16 @@ ## Unreleased +## 1.16.0 (2025-01-15) + +## 1.15.0 (2025-01-02) + +## 1.14.0 (2024-12-11) + +## 1.13.0 (2024-11-27) + +## 1.12.0 (2024-11-16) + ## 1.11.0 (2024-10-30) ## 1.10.0 (2024-10-16) diff --git a/packages/router/package.json b/packages/router/package.json index 3d80481c9b6baa..20fa44db3fd0ab 100644 --- a/packages/router/package.json +++ b/packages/router/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/router", - "version": "1.11.0", + "version": "1.16.0", "description": "Router API for WordPress pages.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -29,10 +29,12 @@ "types": "build-types", "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/element": "*", - "@wordpress/private-apis": "*", - "@wordpress/url": "*", - "history": "^5.3.0" + "@wordpress/compose": "file:../compose", + "@wordpress/element": "file:../element", + "@wordpress/private-apis": "file:../private-apis", + "@wordpress/url": "file:../url", + "history": "^5.3.0", + "route-recognizer": "^0.3.4" }, "peerDependencies": { "react": "^18.0.0" diff --git a/packages/router/src/history.ts b/packages/router/src/history.ts deleted file mode 100644 index 6cbef108eec206..00000000000000 --- a/packages/router/src/history.ts +++ /dev/null @@ -1,99 +0,0 @@ -/** - * External dependencies - */ -import { createBrowserHistory, type BrowserHistory } from 'history'; - -/** - * WordPress dependencies - */ -import { buildQueryString } from '@wordpress/url'; - -export interface EnhancedHistory extends BrowserHistory { - getLocationWithParams: () => Location; -} - -interface PushOptions { - transition?: string; -} - -const history = createBrowserHistory(); - -const originalHistoryPush = history.push; -const originalHistoryReplace = history.replace; - -// Preserve the `wp_theme_preview` query parameter when navigating -// around the Site Editor. -// TODO: move this hack out of the router into Site Editor code. -function preserveThemePreview( params: Record< string, any > ) { - if ( params.hasOwnProperty( 'wp_theme_preview' ) ) { - return params; - } - const currentSearch = new URLSearchParams( history.location.search ); - const currentThemePreview = currentSearch.get( 'wp_theme_preview' ); - if ( currentThemePreview === null ) { - return params; - } - return { ...params, wp_theme_preview: currentThemePreview }; -} - -function push( - params: Record< string, any >, - state: Record< string, any >, - options: PushOptions = {} -) { - const performPush = () => { - const search = buildQueryString( preserveThemePreview( params ) ); - return originalHistoryPush.call( history, { search }, state ); - }; - - /* - * Skip transition in mobile, otherwise it crashes the browser. - * See: https://github.com/WordPress/gutenberg/pull/63002. - */ - const isMediumOrBigger = window.matchMedia( '(min-width: 782px)' ).matches; - if ( - ! isMediumOrBigger || - // @ts-expect-error - ! document.startViewTransition || - ! options.transition - ) { - return performPush(); - } - document.documentElement.classList.add( options.transition ); - // @ts-expect-error - const transition = document.startViewTransition( () => performPush() ); - transition.finished.finally( () => { - document.documentElement.classList.remove( options.transition ?? '' ); - } ); -} - -function replace( - params: Record< string, any >, - state: Record< string, any > -) { - const search = buildQueryString( preserveThemePreview( params ) ); - return originalHistoryReplace.call( history, { search }, state ); -} - -const locationMemo = new WeakMap(); -function getLocationWithParams() { - const location = history.location; - let locationWithParams = locationMemo.get( location ); - if ( ! locationWithParams ) { - locationWithParams = { - ...location, - params: Object.fromEntries( - new URLSearchParams( location.search ) - ), - }; - locationMemo.set( location, locationWithParams ); - } - return locationWithParams; -} - -export default { - ...history, - push, - replace, - getLocationWithParams, -}; diff --git a/packages/router/src/link.tsx b/packages/router/src/link.tsx new file mode 100644 index 00000000000000..d312a9da144601 --- /dev/null +++ b/packages/router/src/link.tsx @@ -0,0 +1,55 @@ +/** + * WordPress dependencies + */ +import { useContext, useMemo } from '@wordpress/element'; +import { getQueryArgs, getPath, buildQueryString } from '@wordpress/url'; + +/** + * Internal dependencies + */ +import { ConfigContext, type NavigationOptions, useHistory } from './router'; + +export function useLink( to: string, options: NavigationOptions = {} ) { + const history = useHistory(); + const { pathArg, beforeNavigate } = useContext( ConfigContext ); + function onClick( event: React.SyntheticEvent< HTMLAnchorElement > ) { + event?.preventDefault(); + history.navigate( to, options ); + } + const query = getQueryArgs( to ); + const path = getPath( 'http://domain.com/' + to ) ?? ''; + const link = useMemo( () => { + return beforeNavigate + ? beforeNavigate( { path, query } ) + : { path, query }; + }, [ path, query, beforeNavigate ] ); + + const [ before ] = window.location.href.split( '?' ); + + return { + href: `${ before }?${ buildQueryString( { + [ pathArg ]: link.path, + ...link.query, + } ) }`, + onClick, + }; +} + +export function Link( { + to, + options, + children, + ...props +}: { + to: string; + options?: NavigationOptions; + children: React.ReactNode; +} ) { + const { href, onClick } = useLink( to, options ); + + return ( + <a href={ href } onClick={ onClick } { ...props }> + { children } + </a> + ); +} diff --git a/packages/router/src/private-apis.ts b/packages/router/src/private-apis.ts index 7b2945a24ab1a1..9ef316ed716cf4 100644 --- a/packages/router/src/private-apis.ts +++ b/packages/router/src/private-apis.ts @@ -2,6 +2,7 @@ * Internal dependencies */ import { useHistory, useLocation, RouterProvider } from './router'; +import { useLink, Link } from './link'; import { lock } from './lock-unlock'; export const privateApis = {}; @@ -9,4 +10,6 @@ lock( privateApis, { useHistory, useLocation, RouterProvider, + useLink, + Link, } ); diff --git a/packages/router/src/router.tsx b/packages/router/src/router.tsx index 9a1d01aa5f8d88..8ad54e9264678a 100644 --- a/packages/router/src/router.tsx +++ b/packages/router/src/router.tsx @@ -1,3 +1,9 @@ +/** + * External dependencies + */ +import RouteRecognizer from 'route-recognizer'; +import { createBrowserHistory } from 'history'; + /** * WordPress dependencies */ @@ -5,37 +11,227 @@ import { createContext, useContext, useSyncExternalStore, + useMemo, } from '@wordpress/element'; +import { + addQueryArgs, + getQueryArgs, + getPath, + buildQueryString, +} from '@wordpress/url'; +import { useEvent } from '@wordpress/compose'; /** * Internal dependencies */ -import history from './history'; -import type { EnhancedHistory } from './history'; +import type { ReactNode } from 'react'; + +const history = createBrowserHistory(); +interface Route { + name: string; + path: string; + areas: Record< string, ReactNode >; + widths: Record< string, number >; +} + +type LocationWithQuery = Location & { + query?: Record< string, any >; +}; + +interface Match { + name: string; + path: string; + areas: Record< string, ReactNode >; + widths: Record< string, number >; + query?: Record< string, any >; + params?: Record< string, any >; +} + +export type BeforeNavigate = ( arg: { + path: string; + query: Record< string, any >; +} ) => { + path: string; + query: Record< string, any >; +}; + +interface Config { + pathArg: string; + beforeNavigate?: BeforeNavigate; +} + +export interface NavigationOptions { + transition?: string; + state?: Record< string, any >; +} -const RoutesContext = createContext< Location | null >( null ); -const HistoryContext = createContext< EnhancedHistory >( history ); +const RoutesContext = createContext< Match | null >( null ); +export const ConfigContext = createContext< Config >( { pathArg: 'p' } ); + +const locationMemo = new WeakMap(); +function getLocationWithQuery() { + const location = history.location; + let locationWithQuery = locationMemo.get( location ); + if ( ! locationWithQuery ) { + locationWithQuery = { + ...location, + query: Object.fromEntries( new URLSearchParams( location.search ) ), + }; + locationMemo.set( location, locationWithQuery ); + } + return locationWithQuery; +} export function useLocation() { - return useContext( RoutesContext ); + const context = useContext( RoutesContext ); + if ( ! context ) { + throw new Error( 'useLocation must be used within a RouterProvider' ); + } + return context; } export function useHistory() { - return useContext( HistoryContext ); + const { pathArg, beforeNavigate } = useContext( ConfigContext ); + + const navigate = useEvent( + async ( rawPath: string, options: NavigationOptions = {} ) => { + const query = getQueryArgs( rawPath ); + const path = getPath( 'http://domain.com/' + rawPath ) ?? ''; + const performPush = () => { + const result = beforeNavigate + ? beforeNavigate( { path, query } ) + : { path, query }; + return history.push( + { + search: buildQueryString( { + [ pathArg ]: result.path, + ...result.query, + } ), + }, + options.state + ); + }; + + /* + * Skip transition in mobile, otherwise it crashes the browser. + * See: https://github.com/WordPress/gutenberg/pull/63002. + */ + const isMediumOrBigger = + window.matchMedia( '(min-width: 782px)' ).matches; + if ( + ! isMediumOrBigger || + ! document.startViewTransition || + ! options.transition + ) { + performPush(); + return; + } + + await new Promise< void >( ( resolve ) => { + const classname = options.transition ?? ''; + document.documentElement.classList.add( classname ); + const transition = document.startViewTransition( () => + performPush() + ); + transition.finished.finally( () => { + document.documentElement.classList.remove( classname ); + resolve(); + } ); + } ); + } + ); + + return useMemo( + () => ( { + navigate, + back: history.back, + } ), + [ navigate ] + ); +} + +export default function useMatch( + location: LocationWithQuery, + matcher: RouteRecognizer, + pathArg: string +): Match { + const { query: rawQuery = {} } = location; + + return useMemo( () => { + const { [ pathArg ]: path = '/', ...query } = rawQuery; + const result = matcher.recognize( path )?.[ 0 ]; + if ( ! result ) { + return { + name: '404', + path: addQueryArgs( path, query ), + areas: {}, + widths: {}, + query, + params: {}, + }; + } + + const matchedRoute = result.handler as Route; + const resolveFunctions = ( record: Record< string, any > = {} ) => { + return Object.fromEntries( + Object.entries( record ).map( ( [ key, value ] ) => { + if ( typeof value === 'function' ) { + return [ + key, + value( { query, params: result.params } ), + ]; + } + return [ key, value ]; + } ) + ); + }; + return { + name: matchedRoute.name, + areas: resolveFunctions( matchedRoute.areas ), + widths: resolveFunctions( matchedRoute.widths ), + params: result.params, + query, + path: addQueryArgs( path, query ), + }; + }, [ matcher, rawQuery, pathArg ] ); } -export function RouterProvider( { children }: { children: React.ReactNode } ) { +export function RouterProvider( { + routes, + pathArg, + beforeNavigate, + children, +}: { + routes: Route[]; + pathArg: string; + beforeNavigate?: BeforeNavigate; + children: React.ReactNode; +} ) { const location = useSyncExternalStore( history.listen, - history.getLocationWithParams, - history.getLocationWithParams + getLocationWithQuery, + getLocationWithQuery + ); + const matcher = useMemo( () => { + const ret = new RouteRecognizer(); + routes.forEach( ( route ) => { + ret.add( [ { path: route.path, handler: route } ], { + as: route.name, + } ); + } ); + return ret; + }, [ routes ] ); + const match = useMatch( location, matcher, pathArg ); + const config = useMemo( + () => ( { beforeNavigate, pathArg } ), + [ beforeNavigate, pathArg ] ); return ( - <HistoryContext.Provider value={ history }> - <RoutesContext.Provider value={ location }> + <ConfigContext.Provider value={ config }> + <RoutesContext.Provider value={ match }> { children } </RoutesContext.Provider> - </HistoryContext.Provider> + </ConfigContext.Provider> ); } diff --git a/packages/router/tsconfig.json b/packages/router/tsconfig.json index e4945eef8bac0c..7d9ba227795ad6 100644 --- a/packages/router/tsconfig.json +++ b/packages/router/tsconfig.json @@ -2,16 +2,12 @@ "$schema": "https://json.schemastore.org/tsconfig.json", "extends": "../../tsconfig.base.json", "compilerOptions": { - "rootDir": "src", - "declarationDir": "build-types", - "types": [ "gutenberg-env" ], - "allowJs": false, - "checkJs": false + "types": [ "gutenberg-env" ] }, "references": [ + { "path": "../compose" }, { "path": "../element" }, { "path": "../private-apis" }, { "path": "../url" } - ], - "include": [ "src/**/*" ] + ] } diff --git a/packages/scripts/CHANGELOG.md b/packages/scripts/CHANGELOG.md index c09cfd1ac9aaaa..06f2e35e831903 100644 --- a/packages/scripts/CHANGELOG.md +++ b/packages/scripts/CHANGELOG.md @@ -2,6 +2,46 @@ ## Unreleased +## 30.9.0 (2025-01-15) + +## 30.8.0 (2025-01-02) + +### Enhancements + +- Recommend listing JavaScript entry points as paths passed to the `start` and `build` commands ([#68251](https://github.com/WordPress/gutenberg/pull/68251)). +- Introduce a new option `--source-path` to customize the source directory used with the `start` and `build` commands ([#68251](https://github.com/WordPress/gutenberg/pull/68251)). + +### Internal + +- The bundled `rtlcss-webpack-plugin` dependency has been replaced with a modified fork of the plugin to fix issues with the original package ([#68201](https://github.com/WordPress/gutenberg/pull/68201)). +- The bundled `sass` dependency has been updated from `^1.50.0` to `^1.54.0` ([#68380](https://github.com/WordPress/gutenberg/pull/68380)). + +## 30.7.0 (2024-12-11) + +### Internal + +- The bundled `sass` dependency has been updated from `^1.35.2` to `^1.50.1` ([#67572](https://github.com/WordPress/gutenberg/pull/67572)). +- The bundled `sass-loader` dependency has been updated from `^12.1.0` to `^16.0.3` ([#67572](https://github.com/WordPress/gutenberg/pull/67572)). +- The bundled `mini-css-extract-plugin` dependency has been updated from `^2.5.1` to `^2.9.2` ([#67572](https://github.com/WordPress/gutenberg/pull/67572)). +- The bundled `webpack` dependency has been updated from `^5.95.0` to `^5.97.0` ([#67572](https://github.com/WordPress/gutenberg/pull/67572)). +- The bundled `cross-spawn` dependency has been updated from `^5.1.0` to `^7.0.6` ([#67708](https://github.com/WordPress/gutenberg/pull/67708)). +- The bundled `jest-dev-server` dependency has been updated from `^9.0.1` to `^10.1.4` ([#67708](https://github.com/WordPress/gutenberg/pull/67708)). +- The bundled `puppeteer-core` dependency has been updated from `^23.1.0` to `^23.10.1` ([#67708](https://github.com/WordPress/gutenberg/pull/67708)). + +### Bug Fix + +- Make React Fast Refresh in the `start` command work with multiple blocks ([64924](https://github.com/WordPress/gutenberg/pull/64924)). + +## 30.6.0 (2024-11-27) + +## 30.5.1 (2024-11-18) + +### Bug Fix + +- Revert changes from [#61121](https://github.com/WordPress/gutenberg/pull/61121) that inlined CSS files imported from other CSS files before optimization in the `build` command. + +## 30.5.0 (2024-11-16) + ### Bug Fix - Make `start` script more resilient for developer errors ([#66752](https://github.com/WordPress/gutenberg/pull/66752)). @@ -317,7 +357,7 @@ ### Breaking Changes -- Remove `lint-md-js` script that was broken for some time and it's extemely hard to make it work correctly with the recommended ESLint config in Markdown files ([#40511](https://github.com/WordPress/gutenberg/pull/40511)). +- Remove `lint-md-js` script that was broken for some time and it's extremely hard to make it work correctly with the recommended ESLint config in Markdown files ([#40511](https://github.com/WordPress/gutenberg/pull/40511)). - Remove the previously deprecated and undocumented `format-js` command ([#40512](https://github.com/WordPress/gutenberg/pull/40512)). You should use the `format` command instead. ### New Features diff --git a/packages/scripts/README.md b/packages/scripts/README.md index f86a4c6091c408..aaf4e03d8a0605 100644 --- a/packages/scripts/README.md +++ b/packages/scripts/README.md @@ -46,10 +46,6 @@ _Example:_ It might also be a good idea to get familiar with the [JavaScript Build Setup tutorial](https://github.com/WordPress/gutenberg/tree/HEAD/docs/how-to-guides/javascript/js-build-setup.md) for setting up a development environment to use ESNext syntax. It gives a very in-depth explanation of how to use the [build](#build) and [start](#start) scripts. -## Automatic block.json detection and the source code directory - -When using the `start` or `build` commands, the source code directory ( the default is `./src`) and its subdirectories are scanned for the existence of `block.json` files. If one or more are found, they are treated a entry points and will be output into corresponding folders in the `build` directory. This allows for the creation of multiple blocks that use a single build process. The source directory can be customized using the `--webpack-src-dir` flag and the output directory with the `--output-path` flag. - ## Updating to New Release To update an existing project to a new version of `@wordpress/scripts`, open the [changelog](https://github.com/WordPress/gutenberg/blob/HEAD/packages/scripts/CHANGELOG.md), find the version you’re currently on (check `package.json` in the top-level directory of your project), and apply the migration instructions for the newer versions. @@ -66,19 +62,7 @@ Transforms your code according the configuration provided so it’s ready for pr _This script exits after producing a single build. For incremental builds, better suited for development, see the [start](#start) script._ -The entry points for your project get detected by scanning all script fields in `block.json` files located in the `src` directory. The script fields in `block.json` should pass relative paths to `block.json` in the same folder. - -_Example:_ - -```json -{ - "editorScript": "file:index.js", - "script": "file:script.js", - "viewScript": "file:view.js" -} -``` - -The fallback entry point is `src/index.js` (other supported extensions: `.jsx`, `.ts`, and `.tsx`) in case there is no `block.json` file found. In that scenario, the output generated will be written to `build/index.js`. +#### Usage _Example:_ @@ -88,7 +72,7 @@ _Example:_ "build": "wp-scripts build", "build:custom": "wp-scripts build entry-one.js entry-two.js --output-path=custom", "build:copy-php": "wp-scripts build --webpack-copy-php", - "build:custom-directory": "wp-scripts build --webpack-src-dir=custom-directory" + "build:custom-directory": "wp-scripts build --source-path=custom-directory" } } ``` @@ -104,20 +88,21 @@ This script automatically use the optimized config but sometimes you may want to - `--webpack-bundle-analyzer` – enables visualization for the size of webpack output files with an interactive zoomable treemap. - `--webpack-copy-php` – enables copying all PHP files from the source directory ( default is `src` ) and its subfolders to the output directory. -- `--webpack-no-externals` – disables scripts' assets generation, and omits the list of default externals. -- `--webpack-src-dir` – Allows customization of the source code directory. Default is `src`. -- `--output-path` – Allows customization of the output directory. Default is `build`. +- `--webpack-no-externals` – disables scripts’ assets generation, and omits the list of default externals. +- `--source-path` – allows customization of the source directory. The default is the project root `.` when [entry points are listed](#listing-entry-points) in the command, or `src` otherwise. +- `--output-path` – allows customization of the output directory. The default is the `build` folder. Experimental support for the block.json `viewScriptModule` field is available via the `--experimental-modules` option. With this option enabled, script and module fields will all be compiled. The `viewScriptModule` field is analogous to the `viewScript` field, but will compile a module and should be registered in WordPress using the Modules API. +Learn more about [using build scripts](#using-build-scripts) to optimize the development experience based on your specific needs. + #### Advanced information This script uses [webpack](https://webpack.js.org/) behind the scenes. It’ll look for a webpack config in the top-level directory of your package and will use it if it finds one. If none is found, it’ll use the default config provided by `@wordpress/scripts` packages. Learn more in the [Advanced Usage](#advanced-usage) section. - ### `build-blocks-manifest` This script generates a PHP file containing block metadata from all @@ -128,10 +113,12 @@ when registering multiple block types, as it allows you to use Usage: `wp-scripts build-blocks-manifest [options]` Options: -- `--input`: Specify the input directory (default: 'build') -- `--output`: Specify the output file path (default: 'build/blocks-manifest.php') + +- `--input`: Specify the input directory (default: 'build') +- `--output`: Specify the output file path (default: 'build/blocks-manifest.php') Example: + ```bash wp-scripts build-blocks-manifest --input=src --output=dist/blocks-manifest.php ``` @@ -382,8 +369,8 @@ This is how you create a custom root folder inside the zip file. - When updating a plugin, WordPress expects a folder in the root of the zip file which matches the plugin name. So be aware that this may affect the plugin update process. - `--root-folder` - Add a custom root folder to the zip file. -- `npm run plugin-zip` - By default, unzipping your plugin's zip file will result in a folder with the same name as your plugin. -- `npm run plugin-zip --root-folder='custom-directory'` - Your plugin's zip file will be unzipped into a folder named `custom-directory`. +- `npm run plugin-zip` - By default, unzipping your plugin’s zip file will result in a folder with the same name as your plugin. +- `npm run plugin-zip --root-folder='custom-directory'` - Your plugin’s zip file will be unzipped into a folder named `custom-directory`. - `npm run plugin-zip --no-root-folder` - This will create a zip file that has no folder inside, your plugin files will be unzipped directly into the target directory. ### `start` @@ -392,19 +379,7 @@ Transforms your code according the configuration provided so it’s ready for de _For single builds, better suited for production, see the [build](#build) script._ -The entry points for your project get detected by scanning all script fields in `block.json` files located in the `src` directory. The script fields in `block.json` should pass relative paths to `block.json` in the same folder. - -_Example:_ - -```json -{ - "editorScript": "file:index.js", - "script": "file:script.js", - "viewScript": "file:view.js" -} -``` - -The fallback entry point is `src/index.js` (other supported extensions: `.jsx`, `.ts`, and `.tsx`) in case there is no `block.json` file found. In that scenario, the output generated will be written to `build/index.js`. +#### Usage _Example:_ @@ -415,7 +390,7 @@ _Example:_ "start:hot": "wp-scripts start --hot", "start:custom": "wp-scripts start entry-one.js entry-two.js --output-path=custom", "start:copy-php": "wp-scripts start --webpack-copy-php", - "start:custom-directory": "wp-scripts start --webpack-src-dir=custom-directory" + "start:custom-directory": "wp-scripts start --source-path=custom-directory" } } ``` @@ -435,15 +410,17 @@ This script automatically use the optimized config but sometimes you may want to - `--webpack-bundle-analyzer` – enables visualization for the size of webpack output files with an interactive zoomable treemap. - `--webpack-copy-php` – enables copying all PHP files from the source directory ( default is `src` ) and its subfolders to the output directory. - `--webpack-devtool` – controls how source maps are generated. See options at https://webpack.js.org/configuration/devtool/#devtool. -- `--webpack-no-externals` – disables scripts' assets generation, and omits the list of default externals. -- `--webpack-src-dir` – Allows customization of the source code directory. Default is `src`. -- `--output-path` – Allows customization of the output directory. Default is `build`. +- `--webpack-no-externals` – disables scripts’ assets generation, and omits the list of default externals. +- `--source-path` – allows customization of the source directory. The default is the project root `.` when [entry points are listed](#listing-entry-points) in the command, or `src` otherwise. +- `--output-path` – allows customization of the output directory. The default is the `build` folder. Experimental support for the block.json `viewScriptModule` field is available via the `--experimental-modules` option. With this option enabled, script and module fields will all be compiled. The `viewScriptModule` field is analogous to the `viewScript` field, but will compile a module and should be registered in WordPress using the Modules API. +Learn more about [using build scripts](#using-build-scripts) to optimize the development experience based on your specific needs. + #### Advanced information This script uses [webpack](https://webpack.js.org/) behind the scenes. It’ll look for a webpack config in the top-level directory of your package and will use it if it finds one. If none is found, it’ll use the default config provided by `@wordpress/scripts` packages. Learn more in the [Advanced Usage](#advanced-usage) section. @@ -492,7 +469,7 @@ We enforce that all tests run serially in the current process using [--runInBand When tests fail, both a screenshot and an HTML snapshot will be taken of the page and stored in the `artifacts/` directory at the root of your project. These snapshots may help debug failed tests during development or when running tests in a CI environment. -The `artifacts/` directory can be customized by setting the `WP_ARTIFACTS_PATH` environment variable to the relative path of the desired directory within your project's root. For example: to change the default directory from `artifacts/` to `my/custom/artifacts`, you could use `WP_ARTIFACTS_PATH=my/custom/artifacts npm run test:e2e`. +The `artifacts/` directory can be customized by setting the `WP_ARTIFACTS_PATH` environment variable to the relative path of the desired directory within your project’s root. For example: to change the default directory from `artifacts/` to `my/custom/artifacts`, you could use `WP_ARTIFACTS_PATH=my/custom/artifacts npm run test:e2e`. #### Advanced information @@ -581,11 +558,11 @@ To do so, you can add a file called `playwright.config.ts` or `playwright.config When tests fail, snapshots will be taken of the page and stored in the `artifacts/` directory at the root of your project. These snapshots may help debug failed tests during development or when running tests in a CI environment. -The `artifacts/` directory can be customized by setting the `WP_ARTIFACTS_PATH` environment variable to the relative path of the desired directory within your project's root. For example: to change the default directory from `artifacts/` to `my/custom/artifacts`, you could use `WP_ARTIFACTS_PATH=my/custom/artifacts npm run test:playwright`. +The `artifacts/` directory can be customized by setting the `WP_ARTIFACTS_PATH` environment variable to the relative path of the desired directory within your project’s root. For example: to change the default directory from `artifacts/` to `my/custom/artifacts`, you could use `WP_ARTIFACTS_PATH=my/custom/artifacts npm run test:playwright`. #### Advanced information -You are able to use all of Playwright's [CLI options](https://playwright.dev/docs/test-cli#reference). You can also run `./node_modules/.bin/wp-scripts test-playwright --help` or `npm run test:playwright:help` (as mentioned above) to view all the available options. Learn more in the [Advanced Usage](#advanced-usage) section. +You are able to use all of Playwright’s [CLI options](https://playwright.dev/docs/test-cli#reference). You can also run `./node_modules/.bin/wp-scripts test-playwright --help` or `npm run test:playwright:help` (as mentioned above) to view all the available options. Learn more in the [Advanced Usage](#advanced-usage) section. ## Passing Node.js options @@ -639,30 +616,49 @@ To also debug the browser context, run `wp-scripts --inspect-brk test-e2e --pupp For more e2e debugging tips check out the [Puppeteer debugging docs](https://developers.google.com/web/tools/puppeteer/debugging). -## Advanced Usage +## Using build scripts -In general, this package should be used with the set of recommended config files. While it’s possible to override every single config file provided, if you have to do it, it means that your use case is far more complicated than anticipated. If that happens, it would be better to avoid using the whole abstraction layer and set up your project with full control over tooling used. +The `build` and `start` commands use [webpack](https://webpack.js.org/) behind the scenes. webpack is used to bundle and optimize code for web applications, enabling developers to manage dependencies efficiently, enhance performance, and simplify the development workflow. -### Working with build scripts +### Listing entry points -The `build` and `start` commands use [webpack](https://webpack.js.org/) behind the scenes. webpack is a tool that helps you transform your code into something else. For example: it can take code written in ESNext and output ES5 compatible code that is minified for production. +The simplest way to list JavaScript entry points is to pass them as arguments for the command. -#### Default webpack config +_Example:_ -`@wordpress/scripts` bundles the default webpack config used as a base by the WordPress editor. These are the defaults: +```bash +wp-scripts build entry-one.js entry-two.js +``` -- [Entry](https://webpack.js.org/configuration/entry-context/#entry): the entry points for your project get detected by scanning all script fields in `block.json` files located in the `src` directory. The fallback entry point is `src/index.js` (other supported extensions: `.jsx`, `.ts`, and `.tsx`) in case there is no `block.json` file found. -- [Output](https://webpack.js.org/configuration/output): `build/[name].js`, for example: `build/index.js`, or `build/my-block/index.js`. -- [Loaders](https://webpack.js.org/loaders/): - - [`babel-loader`](https://webpack.js.org/loaders/babel-loader/) allows transpiling JavaScript and TypeScript files using Babel and webpack. - - [`@svgr/webpack`](https://www.npmjs.com/package/@svgr/webpack) and [`url-loader`](https://webpack.js.org/loaders/url-loader/) makes it possible to handle SVG files in JavaScript code. - - [`css-loader`](https://webpack.js.org/loaders/css-loader/) chained with [`postcss-loader`](https://webpack.js.org/loaders/postcss-loader/) and [sass-loader](https://webpack.js.org/loaders/sass-loader/) let webpack process CSS, SASS or SCSS files referenced in JavaScript files. -- [Plugins](https://webpack.js.org/configuration/plugins) (among others): - - [`CopyWebpackPlugin`](https://webpack.js.org/plugins/copy-webpack-plugin/) copies all `block.json` files discovered in the `src` directory to the build directory. - - [`MiniCssExtractPlugin`](https://webpack.js.org/plugins/mini-css-extract-plugin/) extracts CSS into separate files. It creates a CSS file per JavaScript entry point which contains CSS. - - [`@wordpress/dependency-extraction-webpack-plugin`](https://github.com/WordPress/gutenberg/tree/HEAD/packages/dependency-extraction-webpack-plugin/README.md) is used with the default configuration to ensure that WordPress provided scripts are not included in the built bundle. +The default location for the source files is the project’s root. In effect, the command above will look for `entry-one.js` and `entry-two.js` in the project’s root and output the generated files into the `build` directory. + +### Automatic block.json detection and the source code directory -#### Using CSS +A convenient alternative for blocks is using automatic entry point detection. In that case, the source code directory (the default is `./src`) and its subdirectories are scanned for the existence of `block.json` files. If one or more are found, the JavaScript files listed in metadata are treated as entry points and will be output into corresponding folders in the `build` directory. The script fields in `block.json` should pass relative paths to `block.json` in the same folder. + +_Example:_ + +```json +{ + "editorScript": "file:index.js", + "script": "file:script.js", + "viewScript": "file:view.js" +} +``` + +This allows for the creation of multiple blocks that use a single build process triggered with a simple command: + +```bash +wp-scripts build +``` + +The source directory can be customized using the `--source-path` flag and the output directory with the `--output-path` flag. + +### Fallback entry point + +The fallback entry point is `src/index.js` (other supported extensions: `.jsx`, `.ts`, and `.tsx`) in case there is no `block.json` file found. In that scenario, the output generated will be written to `build/index.js`. + +### Importing styles in JavaScript _Example:_ @@ -694,19 +690,19 @@ When you run the build using the default command `wp-scripts build` (also applie 1. `index.css` – all imported CSS files are bundled into one chunk named after the entry point, which defaults to `index.js`, and thus the file created becomes `index.css`. This is for styles used only in the editor. 2. `style-index.css` – imported `style.css` file(s) (applies to PCSS, SASS and SCSS extensions) get bundled into one `style-index.css` file that is meant to be used both on the front-end and in the editor. -You can also have multiple entry points as described in the docs for the script: +For example, when the project has two entry points: ```bash -wp-scripts start entry-one.js entry-two.js --output-path=custom +wp-scripts build entry-one.js entry-two.js ``` -If you do so, then CSS files generated will follow the names of the entry points: `entry-one.css` and `entry-two.css`. +In that case, the CSS generated based on import statements in the JavaScript code will follow the names of the entry points: `entry-one.css` and `entry-two.css`. -Avoid using `style` keyword in an entry point name, this might break your build process. +_Important:_ Avoid using `style` keyword in an entry point name, this might break your build process. You can also bundle CSS modules by prefixing `.module` to the extension, e.g. `style.module.scss`. Otherwise, these files are handled like all other `style.scss`. They will also be extracted into `style-index.css`. -#### Using fonts and images +### Using fonts and images It is possible to reference font (`woff`, `woff2`, `eot`, `ttf` and `otf`) and image (`bmp`, `png`, `jpg`, `jpeg`, `gif` and `wepb`) files from CSS that is controlled by webpack as explained in the previous section. @@ -724,7 +720,7 @@ _Example:_ } ``` -#### Using SVG +### Using SVG _Example:_ @@ -739,18 +735,37 @@ const App = () => ( ); ``` -#### Provide your own webpack config +## Advanced Usage + +This package should generally be used with the set of recommended config files. While it’s possible to override every config file provided, if you have to do it, your use case is far more complicated than anticipated. If that happens, it would be better to avoid using the whole abstraction layer and set up your project with full control over the tooling used. + +### Default webpack config + +`@wordpress/scripts` bundles the default webpack config used as a base by the WordPress editor. These are the defaults: + +- [Entry](https://webpack.js.org/configuration/entry-context/#entry): the entry points for your project get detected by scanning all script fields in `block.json` files located in the `src` directory. The fallback entry point is `src/index.js` (other supported extensions: `.jsx`, `.ts`, and `.tsx`) in case there is no `block.json` file found. +- [Output](https://webpack.js.org/configuration/output): `build/[name].js`, for example: `build/index.js`, or `build/my-block/index.js`. +- [Loaders](https://webpack.js.org/loaders/): + - [`babel-loader`](https://webpack.js.org/loaders/babel-loader/) allows transpiling JavaScript and TypeScript files using Babel and webpack. + - [`@svgr/webpack`](https://www.npmjs.com/package/@svgr/webpack) and [`url-loader`](https://webpack.js.org/loaders/url-loader/) makes it possible to handle SVG files in JavaScript code. + - [`css-loader`](https://webpack.js.org/loaders/css-loader/) chained with [`postcss-loader`](https://webpack.js.org/loaders/postcss-loader/) and [sass-loader](https://webpack.js.org/loaders/sass-loader/) let webpack process CSS, SASS or SCSS files referenced in JavaScript files. +- [Plugins](https://webpack.js.org/configuration/plugins) (among others): + - [`CopyWebpackPlugin`](https://webpack.js.org/plugins/copy-webpack-plugin/) copies all `block.json` files discovered in the `src` directory to the build directory. + - [`MiniCssExtractPlugin`](https://webpack.js.org/plugins/mini-css-extract-plugin/) extracts CSS into separate files. It creates a CSS file per JavaScript entry point which contains CSS. + - [`@wordpress/dependency-extraction-webpack-plugin`](https://github.com/WordPress/gutenberg/tree/HEAD/packages/dependency-extraction-webpack-plugin/README.md) is used with the default configuration to ensure that WordPress provided scripts are not included in the built bundle. + +### Provide your own webpack config Should there be any situation where you want to provide your own webpack config, you can do so. The `build` and `start` commands will use your provided file when: - the command receives a `--config` argument. Example: `wp-scripts build --config my-own-webpack-config.js`. - there is a file called `webpack.config.js` or `webpack.config.babel.js` in the top-level directory of your project (at the same level as `package.json`). -##### Extending the webpack config +#### Extending the webpack config To extend the provided webpack config, or replace subsections within the provided webpack config, you can provide your own `webpack.config.js` file, `require` the provided `webpack.config.js` file, and use the [`spread` operator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax) to import all of or part of the provided configuration. -In the example below, a `webpack.config.js` file is added to the root folder extending the provided webpack config to include custom logic to parse module's source and convert it to a JavaScript object using [`toml`](https://www.npmjs.com/package/toml). It may be useful to import toml or other non-JSON files as JSON, without specific loaders: +In the example below, a `webpack.config.js` file is added to the root folder extending the provided webpack config to include custom logic to parse module’s source and convert it to a JavaScript object using [`toml`](https://www.npmjs.com/package/toml). It may be useful to import toml or other non-JSON files as JSON, without specific loaders: ```javascript const toml = require( 'toml' ); @@ -781,8 +796,8 @@ If you follow this approach, please, be aware that: ## Contributing to this package -This is an individual package that's part of the Gutenberg project. The project is organized as a monorepo. It's made up of multiple self-contained software packages, each with a specific purpose. The packages in this monorepo are published to [npm](https://www.npmjs.com/) and used by [WordPress](https://make.wordpress.org/core/) as well as other software projects. +This is an individual package that’s part of the Gutenberg project. The project is organized as a monorepo. It’s made up of multiple self-contained software packages, each with a specific purpose. The packages in this monorepo are published to [npm](https://www.npmjs.com/) and used by [WordPress](https://make.wordpress.org/core/) as well as other software projects. -To find out more about contributing to this package or Gutenberg as a whole, please read the project's main [contributor guide](https://github.com/WordPress/gutenberg/tree/HEAD/CONTRIBUTING.md). +To find out more about contributing to this package or Gutenberg as a whole, please read the project’s main [contributor guide](https://github.com/WordPress/gutenberg/tree/HEAD/CONTRIBUTING.md). <br /><br /><p align="center"><img src="https://s.w.org/style/images/codeispoetry.png?1" alt="Code is Poetry." /></p> diff --git a/packages/scripts/config/webpack.config.js b/packages/scripts/config/webpack.config.js index 91ef19fc27ed6b..f0e425a8d5998f 100644 --- a/packages/scripts/config/webpack.config.js +++ b/packages/scripts/config/webpack.config.js @@ -7,9 +7,8 @@ const CopyWebpackPlugin = require( 'copy-webpack-plugin' ); const webpack = require( 'webpack' ); const browserslist = require( 'browserslist' ); const MiniCSSExtractPlugin = require( 'mini-css-extract-plugin' ); -const { basename, dirname, resolve } = require( 'path' ); +const { basename, dirname, relative, resolve, sep } = require( 'path' ); const ReactRefreshWebpackPlugin = require( '@pmmmwh/react-refresh-webpack-plugin' ); -const RtlCssPlugin = require( 'rtlcss-webpack-plugin' ); const TerserPlugin = require( 'terser-webpack-plugin' ); const { realpathSync } = require( 'fs' ); const { sync: glob } = require( 'fast-glob' ); @@ -23,19 +22,20 @@ const postcssPlugins = require( '@wordpress/postcss-plugins-preset' ); /** * Internal dependencies */ +const PhpFilePathsPlugin = require( '../plugins/php-file-paths-plugin' ); +const RtlCssPlugin = require( '../plugins/rtlcss-webpack-plugin' ); const { fromConfigRoot, hasBabelConfig, hasArgInCLI, hasCssnanoConfig, hasPostCSSConfig, - getWordPressSrcDirectory, + getProjectSourcePath, getWebpackEntryPoints, getAsBooleanFromENV, getBlockJsonModuleFields, getBlockJsonScriptFields, fromProjectRoot, - PhpFilePathsPlugin, } = require( '../utils' ); const isProduction = process.env.NODE_ENV === 'production'; @@ -75,7 +75,6 @@ const cssLoaders = [ plugins: isProduction ? [ ...postcssPlugins, - require( 'postcss-import' ), require( 'cssnano' )( { // Provide a fallback configuration if there's not // one explicitly available in the project. @@ -104,7 +103,7 @@ const baseConfig = { target, output: { filename: '[name].js', - chunkFilename: '[name].js?v=[chunkhash]', + chunkFilename: '[name].js?ver=[chunkhash]', path: resolve( process.cwd(), 'build' ), }, resolve: { @@ -116,6 +115,7 @@ const baseConfig = { optimization: { // Only concatenate modules in production, when not analyzing bundles. concatenateModules: isProduction && ! process.env.WP_BUNDLE_ANALYZER, + runtimeChunk: hasReactFastRefresh && 'single', splitChunks: { cacheGroups: { style: { @@ -302,14 +302,14 @@ const scriptConfig = { } ), new PhpFilePathsPlugin( { - context: getWordPressSrcDirectory(), + context: getProjectSourcePath(), props: [ 'render', 'variations' ], } ), new CopyWebpackPlugin( { patterns: [ { from: '**/block.json', - context: getWordPressSrcDirectory(), + context: getProjectSourcePath(), noErrorOnMissing: true, transform( content, absoluteFrom ) { const convertExtension = ( path ) => { @@ -341,6 +341,32 @@ const scriptConfig = { } } ); + if ( hasReactFastRefresh ) { + // Prepends the file reference to the shared runtime chunk to every script type defined for the block. + const runtimePath = relative( + dirname( absoluteFrom ), + fromProjectRoot( + getProjectSourcePath() + + sep + + 'runtime.js' + ) + ); + const fields = + getBlockJsonScriptFields( blockJson ); + for ( const [ fieldName ] of Object.entries( + fields + ) ) { + blockJson[ fieldName ] = [ + `file:${ runtimePath }`, + ...( Array.isArray( + blockJson[ fieldName ] + ) + ? blockJson[ fieldName ] + : [ blockJson[ fieldName ] ] ), + ]; + } + } + return JSON.stringify( blockJson, null, 2 ); } @@ -349,7 +375,7 @@ const scriptConfig = { }, { from: '**/*.php', - context: getWordPressSrcDirectory(), + context: getProjectSourcePath(), noErrorOnMissing: true, filter: ( filepath ) => { return ( @@ -366,11 +392,11 @@ const scriptConfig = { // bundle content as a convenient interactive zoomable treemap. process.env.WP_BUNDLE_ANALYZER && new BundleAnalyzerPlugin(), // MiniCSSExtractPlugin to extract the CSS thats gets imported into JavaScript. - new MiniCSSExtractPlugin( { filename: '[name].css' } ), - // RtlCssPlugin to generate RTL CSS files. - new RtlCssPlugin( { - filename: `[name]-rtl.css`, + new MiniCSSExtractPlugin( { + filename: '[name].css', } ), + // RtlCssPlugin to generate RTL CSS files. + new RtlCssPlugin(), // React Fast Refresh. hasReactFastRefresh && new ReactRefreshWebpackPlugin(), // WP_NO_EXTERNALS global variable controls whether scripts' assets get @@ -389,7 +415,7 @@ if ( hasExperimentalModulesFlag ) { /** @type {ReadonlyArray<string>} */ this.blockJsonFiles = glob( '**/block.json', { absolute: true, - cwd: fromProjectRoot( getWordPressSrcDirectory() ), + cwd: fromProjectRoot( getProjectSourcePath() ), } ); } diff --git a/packages/scripts/package.json b/packages/scripts/package.json index 3a50d921145e19..4d62ec66a3f7d7 100644 --- a/packages/scripts/package.json +++ b/packages/scripts/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/scripts", - "version": "30.4.0", + "version": "30.9.0", "description": "Collection of reusable scripts for WordPress development.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -25,6 +25,7 @@ "files": [ "bin", "config", + "plugins", "scripts", "utils" ], @@ -35,16 +36,16 @@ "@babel/core": "7.25.7", "@pmmmwh/react-refresh-webpack-plugin": "^0.5.11", "@svgr/webpack": "^8.0.1", - "@wordpress/babel-preset-default": "*", - "@wordpress/browserslist-config": "*", - "@wordpress/dependency-extraction-webpack-plugin": "*", - "@wordpress/e2e-test-utils-playwright": "*", - "@wordpress/eslint-plugin": "*", - "@wordpress/jest-preset-default": "*", - "@wordpress/npm-package-json-lint-config": "*", - "@wordpress/postcss-plugins-preset": "*", - "@wordpress/prettier-config": "*", - "@wordpress/stylelint-config": "*", + "@wordpress/babel-preset-default": "file:../babel-preset-default", + "@wordpress/browserslist-config": "file:../browserslist-config", + "@wordpress/dependency-extraction-webpack-plugin": "file:../dependency-extraction-webpack-plugin", + "@wordpress/e2e-test-utils-playwright": "file:../e2e-test-utils-playwright", + "@wordpress/eslint-plugin": "file:../eslint-plugin", + "@wordpress/jest-preset-default": "file:../jest-preset-default", + "@wordpress/npm-package-json-lint-config": "file:../npm-package-json-lint-config", + "@wordpress/postcss-plugins-preset": "file:../postcss-plugins-preset", + "@wordpress/prettier-config": "file:../prettier-config", + "@wordpress/stylelint-config": "file:../stylelint-config", "adm-zip": "^0.5.9", "babel-jest": "29.7.0", "babel-loader": "9.2.1", @@ -53,7 +54,7 @@ "check-node-version": "^4.1.0", "clean-webpack-plugin": "^3.0.0", "copy-webpack-plugin": "^10.2.0", - "cross-spawn": "^5.1.0", + "cross-spawn": "^7.0.6", "css-loader": "^6.2.0", "cssnano": "^6.0.1", "cwd": "^0.10.0", @@ -63,39 +64,38 @@ "fast-glob": "^3.2.7", "filenamify": "^4.2.0", "jest": "^29.6.2", - "jest-dev-server": "^9.0.1", + "jest-dev-server": "^10.1.4", "jest-environment-jsdom": "^29.6.2", "jest-environment-node": "^29.6.2", "json2php": "^0.0.9", "markdownlint-cli": "^0.31.1", "merge-deep": "^3.0.3", - "mini-css-extract-plugin": "^2.5.1", + "mini-css-extract-plugin": "^2.9.2", "minimist": "^1.2.0", "npm-package-json-lint": "^6.4.0", "npm-packlist": "^3.0.0", "postcss": "^8.4.5", - "postcss-import": "^16.1.0", "postcss-loader": "^6.2.1", "prettier": "npm:wp-prettier@3.0.3", - "puppeteer-core": "^23.1.0", + "puppeteer-core": "^23.10.1", "react-refresh": "^0.14.0", "read-pkg-up": "^7.0.1", "resolve-bin": "^0.4.0", - "rtlcss-webpack-plugin": "^4.0.7", - "sass": "^1.35.2", - "sass-loader": "^12.1.0", + "rtlcss": "^4.3.0", + "sass": "^1.54.0", + "sass-loader": "^16.0.3", "schema-utils": "^4.2.0", "source-map-loader": "^3.0.0", "stylelint": "^16.8.2", "terser-webpack-plugin": "^5.3.10", "url-loader": "^4.1.1", - "webpack": "^5.95.0", + "webpack": "^5.97.0", "webpack-bundle-analyzer": "^4.9.1", "webpack-cli": "^5.1.4", "webpack-dev-server": "^4.15.1" }, "peerDependencies": { - "@playwright/test": "^1.48.1", + "@playwright/test": "^1.49.1", "react": "^18.0.0", "react-dom": "^18.0.0" }, diff --git a/packages/scripts/utils/php-file-paths-plugin.js b/packages/scripts/plugins/php-file-paths-plugin/index.js similarity index 92% rename from packages/scripts/utils/php-file-paths-plugin.js rename to packages/scripts/plugins/php-file-paths-plugin/index.js index 6f95dae6505a80..df39e1626a8766 100644 --- a/packages/scripts/utils/php-file-paths-plugin.js +++ b/packages/scripts/plugins/php-file-paths-plugin/index.js @@ -6,7 +6,7 @@ const { validate } = require( 'schema-utils' ); /** * Internal dependencies */ -const { getPhpFilePaths } = require( './config' ); +const { getPhpFilePaths } = require( '../../utils' ); const phpFilePathsPluginSchema = { type: 'object', @@ -57,4 +57,4 @@ class PhpFilePathsPlugin { } } -module.exports = { PhpFilePathsPlugin }; +module.exports = PhpFilePathsPlugin; diff --git a/packages/scripts/plugins/rtlcss-webpack-plugin/index.js b/packages/scripts/plugins/rtlcss-webpack-plugin/index.js new file mode 100644 index 00000000000000..c46c01320c763e --- /dev/null +++ b/packages/scripts/plugins/rtlcss-webpack-plugin/index.js @@ -0,0 +1,66 @@ +/** + * Parts of this source were derived and modified from the package + * rtlcss-webpack-plugin, released under the MIT license. + * + * https://github.com/wix-incubator/rtlcss-webpack-plugin + * + * Copyright (c) 2018 Wix.com + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * External dependencies + */ +const path = require( 'node:path' ); +const rtlcss = require( 'rtlcss' ); +const webpack = require( 'webpack' ); + +const cssOnly = ( filename ) => path.extname( filename ) === '.css'; + +class RtlCssPlugin { + processAssets = ( compilation, callback ) => { + const chunks = Array.from( compilation.chunks ); + + // Explore each chunk (build output): + chunks.forEach( ( chunk ) => { + // Explore each asset filename generated by the chunk: + const files = Array.from( chunk.files ); + + files.filter( cssOnly ).forEach( ( filename ) => { + // Get the asset source for each file generated by the chunk: + const src = compilation.assets[ filename ].source(); + const dst = rtlcss.process( src ); + const dstFileName = compilation.getPath( '[name]-rtl.css', { + chunk, + cssFileName: filename, + } ); + + compilation.assets[ dstFileName ] = + new webpack.sources.RawSource( dst ); + chunk.files.add( dstFileName ); + } ); + } ); + + callback(); + }; + + apply( compiler ) { + compiler.hooks.compilation.tap( 'RtlCssPlugin', ( compilation ) => { + compilation.hooks.processAssets.tapAsync( + { + name: 'TPAStylePlugin.pluginName', + stage: compilation.PROCESS_ASSETS_STAGE_OPTIMIZE, + }, + ( chunks, callback ) => + this.processAssets( compilation, callback ) + ); + } ); + } +} + +module.exports = RtlCssPlugin; diff --git a/packages/scripts/scripts/build.js b/packages/scripts/scripts/build.js index 0eef2afb451bfc..8c7b768ba3e694 100644 --- a/packages/scripts/scripts/build.js +++ b/packages/scripts/scripts/build.js @@ -7,31 +7,11 @@ const { sync: resolveBin } = require( 'resolve-bin' ); /** * Internal dependencies */ -const { getWebpackArgs, hasArgInCLI, getArgFromCLI } = require( '../utils' ); +const { getWebpackArgs } = require( '../utils' ); const EXIT_ERROR_CODE = 1; process.env.NODE_ENV = process.env.NODE_ENV || 'production'; -if ( hasArgInCLI( '--experimental-modules' ) ) { - process.env.WP_EXPERIMENTAL_MODULES = true; -} - -if ( hasArgInCLI( '--webpack-no-externals' ) ) { - process.env.WP_NO_EXTERNALS = true; -} - -if ( hasArgInCLI( '--webpack-bundle-analyzer' ) ) { - process.env.WP_BUNDLE_ANALYZER = true; -} - -if ( hasArgInCLI( '--webpack-copy-php' ) ) { - process.env.WP_COPY_PHP_FILES_TO_DIST = true; -} - -process.env.WP_SRC_DIRECTORY = hasArgInCLI( '--webpack-src-dir' ) - ? getArgFromCLI( '--webpack-src-dir' ) - : 'src'; - const { status } = spawn( resolveBin( 'webpack' ), getWebpackArgs(), { stdio: 'inherit', } ); diff --git a/packages/scripts/scripts/lint-style.js b/packages/scripts/scripts/lint-style.js index 103ec6d5f9d2b4..ef8b0ad3bf0e78 100644 --- a/packages/scripts/scripts/lint-style.js +++ b/packages/scripts/scripts/lint-style.js @@ -20,7 +20,7 @@ const args = getArgsFromCLI(); const defaultFilesArgs = hasFileArgInCLI() ? [] : [ '**/*.{css,pcss,scss}' ]; -// See: https://stylelint.io/user-guide/configuration +// See: https://stylelint.io/user-guide/configure/ const hasLintConfig = hasArgInCLI( '--config' ) || hasProjectFile( '.stylelintrc.js' ) || diff --git a/packages/scripts/scripts/start.js b/packages/scripts/scripts/start.js index 6296192ef302b1..fd0a191f168e9e 100644 --- a/packages/scripts/scripts/start.js +++ b/packages/scripts/scripts/start.js @@ -7,33 +7,9 @@ const { sync: resolveBin } = require( 'resolve-bin' ); /** * Internal dependencies */ -const { getArgFromCLI, getWebpackArgs, hasArgInCLI } = require( '../utils' ); +const { getWebpackArgs, hasArgInCLI } = require( '../utils' ); const EXIT_ERROR_CODE = 1; -if ( hasArgInCLI( '--experimental-modules' ) ) { - process.env.WP_EXPERIMENTAL_MODULES = true; -} - -if ( hasArgInCLI( '--webpack-no-externals' ) ) { - process.env.WP_NO_EXTERNALS = true; -} - -if ( hasArgInCLI( '--webpack-bundle-analyzer' ) ) { - process.env.WP_BUNDLE_ANALYZER = true; -} - -if ( hasArgInCLI( '--webpack--devtool' ) ) { - process.env.WP_DEVTOOL = getArgFromCLI( '--webpack--devtool' ); -} - -if ( hasArgInCLI( '--webpack-copy-php' ) ) { - process.env.WP_COPY_PHP_FILES_TO_DIST = true; -} - -process.env.WP_SRC_DIRECTORY = hasArgInCLI( '--webpack-src-dir' ) - ? getArgFromCLI( '--webpack-src-dir' ) - : 'src'; - const webpackArgs = getWebpackArgs(); if ( hasArgInCLI( '--hot' ) ) { webpackArgs.unshift( 'serve' ); diff --git a/packages/scripts/scripts/test/build-blocks-manifest.js b/packages/scripts/scripts/test/build-blocks-manifest.js index 70009bd6087b56..5b630b73d9f958 100644 --- a/packages/scripts/scripts/test/build-blocks-manifest.js +++ b/packages/scripts/scripts/test/build-blocks-manifest.js @@ -4,7 +4,7 @@ const fs = require( 'fs' ); const path = require( 'path' ); const { execSync } = require( 'child_process' ); -const rimraf = require( 'rimraf' ); +const rimraf = require( 'rimraf' ).sync; const fixturesPath = path.join( __dirname, @@ -18,11 +18,11 @@ describe( 'build-blocks-manifest script', () => { if ( ! fs.existsSync( outputPath ) ) { fs.mkdirSync( outputPath, { recursive: true } ); } - rimraf.sync( outputPath ); + rimraf( outputPath ); } ); afterAll( () => { - rimraf.sync( outputPath ); + rimraf( outputPath ); } ); it( 'should generate expected blocks manifest', () => { diff --git a/packages/scripts/utils/config.js b/packages/scripts/utils/config.js index 3d99f3784859df..be6f1831378911 100644 --- a/packages/scripts/utils/config.js +++ b/packages/scripts/utils/config.js @@ -9,6 +9,7 @@ const { sync: glob } = require( 'fast-glob' ); * Internal dependencies */ const { + getArgFromCLI, getArgsFromCLI, getFileArgsFromCLI, hasArgInCLI, @@ -114,9 +115,37 @@ const getWebpackArgs = () => { // Gets all args from CLI without those prefixed with `--webpack`. let webpackArgs = getArgsFromCLI( [ '--experimental-modules', + '--source-path', '--webpack', ] ); + if ( hasArgInCLI( '--experimental-modules' ) ) { + process.env.WP_EXPERIMENTAL_MODULES = true; + } + + if ( hasArgInCLI( '--source-path' ) ) { + process.env.WP_SOURCE_PATH = getArgFromCLI( '--source-path' ); + } else if ( hasArgInCLI( '--webpack-src-dir' ) ) { + // Backwards compatibility. + process.env.WP_SOURCE_PATH = getArgFromCLI( '--webpack-src-dir' ); + } + + if ( hasArgInCLI( '--webpack-bundle-analyzer' ) ) { + process.env.WP_BUNDLE_ANALYZER = true; + } + + if ( hasArgInCLI( '--webpack-copy-php' ) ) { + process.env.WP_COPY_PHP_FILES_TO_DIST = true; + } + + if ( hasArgInCLI( '--webpack--devtool' ) ) { + process.env.WP_DEVTOOL = getArgFromCLI( '--webpack--devtool' ); + } + + if ( hasArgInCLI( '--webpack-no-externals' ) ) { + process.env.WP_NO_EXTERNALS = true; + } + const hasWebpackOutputOption = hasArgInCLI( '-o' ) || hasArgInCLI( '--output' ); if ( @@ -136,10 +165,6 @@ const getWebpackArgs = () => { const pathToEntry = ( path ) => { const entryName = basename( path, '.js' ); - if ( ! path.startsWith( './' ) ) { - path = './' + path; - } - return [ entryName, path ]; }; @@ -162,7 +187,11 @@ const getWebpackArgs = () => { const [ entryName, path ] = fileArg.includes( '=' ) ? fileArg.split( '=' ) : pathToEntry( fileArg ); - entry[ entryName ] = path; + entry[ entryName ] = fromProjectRoot( + process.env.WP_SOURCE_PATH + ? join( process.env.WP_SOURCE_PATH, path ) + : path + ); } ); process.env.WP_ENTRY = JSON.stringify( entry ); } @@ -176,20 +205,20 @@ const getWebpackArgs = () => { }; /** - * Returns the WordPress source directory. It defaults to 'src' if the - * `process.env.WP_SRC_DIRECTORY` variable is not set. + * Returns the project source path. It defaults to 'src' if the + * `process.env.WP_SOURCE_PATH` variable is not set. * * @return {string} The WordPress source directory. */ -function getWordPressSrcDirectory() { - return process.env.WP_SRC_DIRECTORY || 'src'; +function getProjectSourcePath() { + return process.env.WP_SOURCE_PATH || 'src'; } /** - * Detects the list of entry points to use with webpack. There are three ways to do this: - * 1. Use the legacy webpack 4 format passed as CLI arguments. - * 2. Scan `block.json` files for scripts. - * 3. Fallback to `src/index.*` file. + * Detects the list of entry points to use with webpack. There are three alternative ways to do this: + * 1. Use the recommended command format that lists the paths to JavaScript files. + * 2. Scan `block.json` files to detect referenced JavaScript and PHP files automatically. + * 3. Fallback to the `src/index.*` file. * * @see https://webpack.js.org/concepts/entry-points/ * @@ -200,31 +229,32 @@ function getWebpackEntryPoints( buildType ) { * @return {Object<string,string>} The list of entry points. */ return () => { - // 1. Handles the legacy format for entry points when explicitly provided with the `process.env.WP_ENTRY`. + // 1. Uses the recommended command format that lists entry points as paths to JavaScript files. + // Example: `wp-scripts build one.js two.js`. if ( process.env.WP_ENTRY ) { return buildType === 'script' ? JSON.parse( process.env.WP_ENTRY ) : {}; } - // Continue only if the source directory exists. - if ( ! hasProjectFile( getWordPressSrcDirectory() ) ) { + // Continues only if the source directory exists. Defaults to "src" if not explicitly set in the command. + if ( ! hasProjectFile( getProjectSourcePath() ) ) { warn( - `Source directory "${ getWordPressSrcDirectory() }" was not found. Please confirm there is a "src" directory in the root or the value passed to --webpack-src-dir is correct.` + `Source directory "${ getProjectSourcePath() }" was not found. Please confirm there is a "src" directory in the root or the value passed with "--output-path" is correct.` ); return {}; } // 2. Checks whether any block metadata files can be detected in the defined source directory. - // It scans all discovered files looking for JavaScript assets and converts them to entry points. + // It scans all discovered files, looks for JavaScript assets, and converts them to entry points. const blockMetadataFiles = glob( '**/block.json', { absolute: true, - cwd: fromProjectRoot( getWordPressSrcDirectory() ), + cwd: fromProjectRoot( getProjectSourcePath() ), } ); if ( blockMetadataFiles.length > 0 ) { const srcDirectory = fromProjectRoot( - getWordPressSrcDirectory() + sep + getProjectSourcePath() + sep ); const entryPoints = {}; @@ -276,7 +306,7 @@ function getWebpackEntryPoints( buildType ) { ) }" listed in "${ blockMetadataFile.replace( fromProjectRoot( sep ), '' - ) }". File is located outside of the "${ getWordPressSrcDirectory() }" directory.` + ) }". File is located outside of the "${ getProjectSourcePath() }" directory.` ); continue; } @@ -290,7 +320,7 @@ function getWebpackEntryPoints( buildType ) { `${ entryName }.?(m)[jt]s?(x)`, { absolute: true, - cwd: fromProjectRoot( getWordPressSrcDirectory() ), + cwd: fromProjectRoot( getProjectSourcePath() ), } ); @@ -302,7 +332,7 @@ function getWebpackEntryPoints( buildType ) { ) }" listed in "${ blockMetadataFile.replace( fromProjectRoot( sep ), '' - ) }". File does not exist in the "${ getWordPressSrcDirectory() }" directory.` + ) }". File does not exist in the "${ getProjectSourcePath() }" directory.` ); continue; } @@ -322,15 +352,15 @@ function getWebpackEntryPoints( buildType ) { } // 3. Checks whether a standard file name can be detected in the defined source directory, - // and converts the discovered file to entry point. + // and converts the discovered file to entry point. const [ entryFile ] = glob( 'index.[jt]s?(x)', { absolute: true, - cwd: fromProjectRoot( getWordPressSrcDirectory() ), + cwd: fromProjectRoot( getProjectSourcePath() ), } ); if ( ! entryFile ) { warn( - `No entry file discovered in the "${ getWordPressSrcDirectory() }" directory.` + `No entry file discovered in the "${ getProjectSourcePath() }" directory.` ); return {}; } @@ -412,10 +442,10 @@ function getPhpFilePaths( context, props ) { module.exports = { getJestOverrideConfigFile, + getPhpFilePaths, + getProjectSourcePath, getWebpackArgs, - getWordPressSrcDirectory, getWebpackEntryPoints, - getPhpFilePaths, hasBabelConfig, hasCssnanoConfig, hasJestConfig, diff --git a/packages/scripts/utils/index.js b/packages/scripts/utils/index.js index cb7e592f83d554..b26df4bd479d9a 100644 --- a/packages/scripts/utils/index.js +++ b/packages/scripts/utils/index.js @@ -13,10 +13,10 @@ const { } = require( './cli' ); const { getJestOverrideConfigFile, + getPhpFilePaths, + getProjectSourcePath, getWebpackArgs, - getWordPressSrcDirectory, getWebpackEntryPoints, - getPhpFilePaths, hasBabelConfig, hasCssnanoConfig, hasJestConfig, @@ -29,7 +29,6 @@ const { getBlockJsonModuleFields, getBlockJsonScriptFields, } = require( './block-json' ); -const { PhpFilePathsPlugin } = require( './php-file-paths-plugin' ); module.exports = { fromProjectRoot, @@ -41,10 +40,10 @@ module.exports = { getJestOverrideConfigFile, getNodeArgsFromCLI, getPackageProp, + getPhpFilePaths, + getProjectSourcePath, getWebpackArgs, - getWordPressSrcDirectory, getWebpackEntryPoints, - getPhpFilePaths, getBlockJsonModuleFields, getBlockJsonScriptFields, hasArgInCLI, @@ -56,6 +55,5 @@ module.exports = { hasPostCSSConfig, hasPrettierConfig, hasProjectFile, - PhpFilePathsPlugin, spawnScript, }; diff --git a/packages/server-side-render/CHANGELOG.md b/packages/server-side-render/CHANGELOG.md index 479fbf835e74d5..6c02f3d295b4a0 100644 --- a/packages/server-side-render/CHANGELOG.md +++ b/packages/server-side-render/CHANGELOG.md @@ -2,6 +2,16 @@ ## Unreleased +## 5.16.0 (2025-01-15) + +## 5.15.0 (2025-01-02) + +## 5.14.0 (2024-12-11) + +## 5.13.0 (2024-11-27) + +## 5.12.0 (2024-11-16) + ## 5.11.0 (2024-10-30) ## 5.10.0 (2024-10-16) diff --git a/packages/server-side-render/package.json b/packages/server-side-render/package.json index cdfe9679c5049d..ad9db197126961 100644 --- a/packages/server-side-render/package.json +++ b/packages/server-side-render/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/server-side-render", - "version": "5.11.0", + "version": "5.16.0", "description": "The component used with WordPress to server-side render a preview of dynamic blocks to display in the editor.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -29,15 +29,15 @@ "wpScript": true, "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/api-fetch": "*", - "@wordpress/blocks": "*", - "@wordpress/components": "*", - "@wordpress/compose": "*", - "@wordpress/data": "*", - "@wordpress/deprecated": "*", - "@wordpress/element": "*", - "@wordpress/i18n": "*", - "@wordpress/url": "*", + "@wordpress/api-fetch": "file:../api-fetch", + "@wordpress/blocks": "file:../blocks", + "@wordpress/components": "file:../components", + "@wordpress/compose": "file:../compose", + "@wordpress/data": "file:../data", + "@wordpress/deprecated": "file:../deprecated", + "@wordpress/element": "file:../element", + "@wordpress/i18n": "file:../i18n", + "@wordpress/url": "file:../url", "fast-deep-equal": "^3.1.3" }, "peerDependencies": { diff --git a/packages/shortcode/CHANGELOG.md b/packages/shortcode/CHANGELOG.md index f869446602cb16..7cc68c61d9fb20 100644 --- a/packages/shortcode/CHANGELOG.md +++ b/packages/shortcode/CHANGELOG.md @@ -2,6 +2,20 @@ ## Unreleased +## 4.16.0 (2025-01-15) + +## 4.15.0 (2025-01-02) + +## 4.14.0 (2024-12-11) + +## Enhancements + +- The package now has built-in TypeScript definitions 🎉 ([#67416](https://github.com/WordPress/gutenberg/pull/67416)) + +## 4.13.0 (2024-11-27) + +## 4.12.0 (2024-11-16) + ## 4.11.0 (2024-10-30) ## 4.10.0 (2024-10-16) diff --git a/packages/shortcode/README.md b/packages/shortcode/README.md index a1a016e75e755c..b6042ab284fe42 100644 --- a/packages/shortcode/README.md +++ b/packages/shortcode/README.md @@ -32,7 +32,7 @@ _Parameters_ _Returns_ -- `WPShortcodeAttrs`: Parsed shortcode attributes. +- `import('./types').ShortcodeAttrs`: Parsed shortcode attributes. ### default @@ -40,13 +40,9 @@ Creates a shortcode instance. To access a raw representation of a shortcode, pass an `options` object, containing a `tag` string, a string or object of `attrs`, a string indicating the `type` of the shortcode ('single', 'self-closing', or 'closed'), and a `content` string. -_Parameters_ - -- _options_ `Object`: Options as described. - -_Returns_ +_Type_ -- `WPShortcode`: Shortcode instance. +- `import('./types').shortcode`Shortcode instance. ### fromMatch @@ -56,11 +52,11 @@ Accepts a `match` object from calling `regexp.exec()` on a `RegExp` generated by _Parameters_ -- _match_ `Array`: Match array. +- _match_ `import('./types').Match`: Match array. _Returns_ -- `WPShortcode`: Shortcode instance. +- `InstanceType<import('./types').shortcode>`: Shortcode instance. ### next @@ -74,7 +70,7 @@ _Parameters_ _Returns_ -- `WPShortcodeMatch | undefined`: Matched information. +- `import('./types').ShortcodeMatch | undefined`: Matched information. ### regexp @@ -108,7 +104,7 @@ _Parameters_ - _tag_ `string`: Shortcode tag. - _text_ `string`: Text to search. -- _callback_ `Function`: Function to process the match and return replacement string. +- _callback_ `import('./types').ReplaceCallback`: Function to process the match and return replacement string. _Returns_ diff --git a/packages/shortcode/package.json b/packages/shortcode/package.json index c476788e3cfaf8..90276719432ea8 100644 --- a/packages/shortcode/package.json +++ b/packages/shortcode/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/shortcode", - "version": "4.11.0", + "version": "4.16.0", "description": "Shortcode module for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -26,6 +26,7 @@ "module": "build-module/index.js", "react-native": "src/index", "wpScript": true, + "types": "build-types", "dependencies": { "@babel/runtime": "7.25.7", "memize": "^2.0.1" diff --git a/packages/shortcode/src/index.js b/packages/shortcode/src/index.js index 04e69c272378b3..4d99086033e957 100644 --- a/packages/shortcode/src/index.js +++ b/packages/shortcode/src/index.js @@ -3,34 +3,7 @@ */ import memize from 'memize'; -/** - * Shortcode attributes object. - * - * @typedef {Object} WPShortcodeAttrs - * - * @property {Object} named Object with named attributes. - * @property {Array} numeric Array with numeric attributes. - */ - -/** - * Shortcode object. - * - * @typedef {Object} WPShortcode - * - * @property {string} tag Shortcode tag. - * @property {WPShortcodeAttrs} attrs Shortcode attributes. - * @property {string} content Shortcode content. - * @property {string} type Shortcode type: `self-closing`, - * `closed`, or `single`. - */ - -/** - * @typedef {Object} WPShortcodeMatch - * - * @property {number} index Index the shortcode is found at. - * @property {string} content Matched content. - * @property {WPShortcode} shortcode Shortcode instance of the match. - */ +export * from './types'; /** * Find the next matching shortcode. @@ -39,7 +12,7 @@ import memize from 'memize'; * @param {string} text Text to search. * @param {number} index Index to start search from. * - * @return {WPShortcodeMatch | undefined} Matched information. + * @return {import('./types').ShortcodeMatch | undefined} Matched information. */ export function next( tag, text, index = 0 ) { const re = regexp( tag ); @@ -81,10 +54,10 @@ export function next( tag, text, index = 0 ) { /** * Replace matching shortcodes in a block of text. * - * @param {string} tag Shortcode tag. - * @param {string} text Text to search. - * @param {Function} callback Function to process the match and return - * replacement string. + * @param {string} tag Shortcode tag. + * @param {string} text Text to search. + * @param {import('./types').ReplaceCallback} callback Function to process the match and return + * replacement string. * * @return {string} Text with shortcodes replaced. */ @@ -169,7 +142,7 @@ export function regexp( tag ) { * * @param {string} text Serialised shortcode attributes. * - * @return {WPShortcodeAttrs} Parsed shortcode attributes. + * @return {import('./types').ShortcodeAttrs} Parsed shortcode attributes. */ export const attrs = memize( ( text ) => { const named = {}; @@ -224,9 +197,9 @@ export const attrs = memize( ( text ) => { * by `regexp()`. `match` can also be set to the `arguments` from a callback * passed to `regexp.replace()`. * - * @param {Array} match Match array. + * @param {import('./types').Match} match Match array. * - * @return {WPShortcode} Shortcode instance. + * @return {InstanceType<import('./types').shortcode>} Shortcode instance. */ export function fromMatch( match ) { let type; @@ -255,9 +228,7 @@ export function fromMatch( match ) { * the `type` of the shortcode ('single', 'self-closing', or 'closed'), and a * `content` string. * - * @param {Object} options Options as described. - * - * @return {WPShortcode} Shortcode instance. + * @type {import('./types').shortcode} Shortcode instance. */ const shortcode = Object.assign( function ( options ) { @@ -328,7 +299,7 @@ Object.assign( shortcode.prototype, { * @param {(number|string)} attr Attribute key. * @param {string} value Attribute value. * - * @return {WPShortcode} Shortcode instance. + * @return {InstanceType< import('./types').shortcode >} Shortcode instance. */ set( attr, value ) { this.attrs[ typeof attr === 'number' ? 'numeric' : 'named' ][ attr ] = diff --git a/packages/shortcode/src/types.ts b/packages/shortcode/src/types.ts new file mode 100644 index 00000000000000..2b9ae084cc31a6 --- /dev/null +++ b/packages/shortcode/src/types.ts @@ -0,0 +1,210 @@ +/** + * Shortcode attributes object. + */ +export type ShortcodeAttrs = { + /** + * Object with named attributes. + */ + named: Record< string, string | undefined >; + + /** + * Array with numeric attributes. + */ + numeric: string[]; +}; + +export type ShortcodeMatch = { + /** + * Index the shortcode is found at. + */ + index: number; + + /** + * Matched content. + */ + content: string; + + /** + * Shortcode instance of the match. + */ + shortcode: Shortcode; +}; + +/** + * Shortcode options. + */ +export interface ShortcodeOptions { + /** + * Shortcode tag. + */ + tag: string; + + /** + * Shortcode attributes. + */ + attrs?: Partial< ShortcodeAttrs > | string; + + /** + * Shortcode content. + */ + content?: string; + + /** + * Shortcode type: `self-closing`, `closed`, or `single`. + */ + type?: 'self-closing' | 'closed' | 'single'; +} + +/** + * Shortcode object. + */ +export interface Shortcode extends ShortcodeOptions { + /** + * Shortcode attributes. + */ + attrs: ShortcodeAttrs; +} + +export type Match = + | NonNullable< ReturnType< RegExp[ 'exec' ] > > + | Array< string >; + +export type ReplaceCallback = ( shortcode: Shortcode ) => string; + +/** + * WordPress Shortcode instance. + */ +export interface shortcode { + new ( options: Partial< ShortcodeOptions > ): Shortcode & { + /** + * Transform the shortcode into a string. + * + * @return {string} String representation of the shortcode. + */ + string: () => string; + + /** + * Get a shortcode attribute. + * + * Automatically detects whether `attr` is named or numeric and routes it + * accordingly. + * + * @param {(number|string)} attr Attribute key. + * + * @return {string} Attribute value. + */ + get: ( attr: string | number ) => string | undefined; + + /** + * Set a shortcode attribute. + * + * Automatically detects whether `attr` is named or numeric and routes it + * accordingly. + * + * @param {(number|string)} attr Attribute key. + * @param {string} value Attribute value. + * + * @return {InstanceType< shortcode >} Shortcode instance. + */ + set: ( + attr: string | number, + value: string + ) => InstanceType< shortcode >; + }; + + /** + * Parse shortcode attributes. + * + * Shortcodes accept many types of attributes. These can chiefly be divided into + * named and numeric attributes: + * + * Named attributes are assigned on a key/value basis, while numeric attributes + * are treated as an array. + * + * Named attributes can be formatted as either `name="value"`, `name='value'`, + * or `name=value`. Numeric attributes can be formatted as `"value"` or just + * `value`. + * + * @param text Serialised shortcode attributes. + * + * @return Parsed shortcode attributes. + */ + attrs: ( text: string ) => ShortcodeAttrs; + + /** + * Generate a Shortcode Object from a RegExp match. + * + * Accepts a `match` object from calling `regexp.exec()` on a `RegExp` generated + * by `regexp()`. `match` can also be set to the `arguments` from a callback + * passed to `regexp.replace()`. + * + * @param match Match array. + * + * @return Shortcode instance. + */ + fromMatch: ( match: Match ) => InstanceType< shortcode >; + + /** + * Find the next matching shortcode. + * + * @param tag Shortcode tag. + * @param text Text to search. + * @param index Index to start search from. + * + * @return Matched information. + */ + next: ( + tag: string, + text: string, + index?: number + ) => ShortcodeMatch | undefined; + + /** + * Generate a RegExp to identify a shortcode. + * + * The base regex is functionally equivalent to the one found in + * `get_shortcode_regex()` in `wp-includes/shortcodes.php`. + * + * Capture groups: + * + * 1. An extra `[` to allow for escaping shortcodes with double `[[]]` + * 2. The shortcode name + * 3. The shortcode argument list + * 4. The self closing `/` + * 5. The content of a shortcode when it wraps some content. + * 6. The closing tag. + * 7. An extra `]` to allow for escaping shortcodes with double `[[]]` + * + * @param tag Shortcode tag. + * + * @return Shortcode RegExp. + */ + regexp: ( tag: string ) => RegExp; + + /** + * Replace matching shortcodes in a block of text. + * + * @param tag Shortcode tag. + * @param text Text to search. + * @param callback Function to process the match and return + * replacement string. + * + * @return Text with shortcodes replaced. + */ + replace: ( tag: string, text: string, callback: ReplaceCallback ) => string; + + /** + * Generate a string from shortcode parameters. + * + * Creates a shortcode instance and returns a string. + * + * Accepts the same `options` as the `shortcode()` constructor, containing a + * `tag` string, a string or object of `attrs`, a boolean indicating whether to + * format the shortcode using a `single` tag, and a `content` string. + * + * @param options + * + * @return String representation of the shortcode. + */ + string: ( options: ShortcodeOptions ) => string; +} diff --git a/packages/shortcode/tsconfig.json b/packages/shortcode/tsconfig.json new file mode 100644 index 00000000000000..2ab16a25d51788 --- /dev/null +++ b/packages/shortcode/tsconfig.json @@ -0,0 +1,7 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig.json", + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "checkJs": false + } +} diff --git a/packages/style-engine/CHANGELOG.md b/packages/style-engine/CHANGELOG.md index f9e50558d83664..69065f20ee6310 100644 --- a/packages/style-engine/CHANGELOG.md +++ b/packages/style-engine/CHANGELOG.md @@ -2,6 +2,16 @@ ## Unreleased +## 2.16.0 (2025-01-15) + +## 2.15.0 (2025-01-02) + +## 2.14.0 (2024-12-11) + +## 2.13.0 (2024-11-27) + +## 2.12.0 (2024-11-16) + ## 2.11.0 (2024-10-30) ## 2.10.0 (2024-10-16) diff --git a/packages/style-engine/package.json b/packages/style-engine/package.json index 378f4dce91dad9..38ac5a8b90b2ac 100644 --- a/packages/style-engine/package.json +++ b/packages/style-engine/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/style-engine", - "version": "2.11.0", + "version": "2.16.0", "description": "A suite of parsers and compilers for WordPress styles.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/style-engine/tsconfig.json b/packages/style-engine/tsconfig.json index 6e33d8ff82d47e..7ff060ab6ce105 100644 --- a/packages/style-engine/tsconfig.json +++ b/packages/style-engine/tsconfig.json @@ -1,9 +1,4 @@ { "$schema": "https://json.schemastore.org/tsconfig.json", - "extends": "../../tsconfig.base.json", - "compilerOptions": { - "rootDir": "src", - "declarationDir": "build-types" - }, - "include": [ "src/**/*" ] + "extends": "../../tsconfig.base.json" } diff --git a/packages/stylelint-config/CHANGELOG.md b/packages/stylelint-config/CHANGELOG.md index ac778801a0160d..8af849b3496924 100644 --- a/packages/stylelint-config/CHANGELOG.md +++ b/packages/stylelint-config/CHANGELOG.md @@ -2,6 +2,16 @@ ## Unreleased +## 23.8.0 (2025-01-15) + +## 23.7.0 (2025-01-02) + +## 23.6.0 (2024-12-11) + +## 23.5.0 (2024-11-27) + +## 23.4.0 (2024-11-16) + ## 23.3.0 (2024-10-30) ## 23.2.0 (2024-10-16) @@ -305,7 +315,7 @@ - Added: `no-extra-semicolons` rule. - Added: `selector-attribute-operator-space-after` rule. - Added: `selector-attribute-operator-space-before` rule. -- Added: `selector-max-empty-liness` rule. +- Added: `selector-max-empty-lines` rule. ## 5.0.0 (2016-04-24) diff --git a/packages/stylelint-config/package.json b/packages/stylelint-config/package.json index 0043e0a62be412..8570b0b9d9b33b 100644 --- a/packages/stylelint-config/package.json +++ b/packages/stylelint-config/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/stylelint-config", - "version": "23.3.0", + "version": "23.8.0", "description": "stylelint config for WordPress development.", "author": "The WordPress Contributors", "license": "MIT", diff --git a/packages/sync/CHANGELOG.md b/packages/sync/CHANGELOG.md index d9e04f700b8d78..761bef22b81895 100644 --- a/packages/sync/CHANGELOG.md +++ b/packages/sync/CHANGELOG.md @@ -2,6 +2,16 @@ ## Unreleased +## 1.16.0 (2025-01-15) + +## 1.15.0 (2025-01-02) + +## 1.14.0 (2024-12-11) + +## 1.13.0 (2024-11-27) + +## 1.12.0 (2024-11-16) + ## 1.11.0 (2024-10-30) ## 1.10.0 (2024-10-16) diff --git a/packages/sync/package.json b/packages/sync/package.json index d7181479327d3a..5c42b6ed998f37 100644 --- a/packages/sync/package.json +++ b/packages/sync/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/sync", - "version": "1.11.0", + "version": "1.16.0", "description": "Sync Data.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -31,7 +31,7 @@ "dependencies": { "@babel/runtime": "7.25.7", "@types/simple-peer": "^9.11.5", - "@wordpress/url": "*", + "@wordpress/url": "file:../url", "import-locals": "^2.0.0", "lib0": "^0.2.42", "simple-peer": "^9.11.0", diff --git a/packages/sync/src/provider.js b/packages/sync/src/provider.js index 15d972dbcd4f09..0be1dedab5d308 100644 --- a/packages/sync/src/provider.js +++ b/packages/sync/src/provider.js @@ -35,7 +35,7 @@ export const createSyncProvider = ( connectLocal, connectRemote ) => { const docs = {}; /** - * Registeres an object type. + * Registers an object type. * * @param {ObjectType} objectType Object type to register. * @param {ObjectConfig} objectConfig Object config. diff --git a/packages/sync/tsconfig.json b/packages/sync/tsconfig.json index 40b3ecb72f9aba..f0a5cb0530d297 100644 --- a/packages/sync/tsconfig.json +++ b/packages/sync/tsconfig.json @@ -2,10 +2,7 @@ "$schema": "https://json.schemastore.org/tsconfig.json", "extends": "../../tsconfig.base.json", "compilerOptions": { - "rootDir": "src", - "declarationDir": "build-types", "types": [ "node" ] }, - "include": [ "src/**/*" ], "references": [ { "path": "../url" } ] } diff --git a/packages/token-list/CHANGELOG.md b/packages/token-list/CHANGELOG.md index 304d67cefdd026..6ccb9e49021b49 100644 --- a/packages/token-list/CHANGELOG.md +++ b/packages/token-list/CHANGELOG.md @@ -2,6 +2,16 @@ ## Unreleased +## 3.16.0 (2025-01-15) + +## 3.15.0 (2025-01-02) + +## 3.14.0 (2024-12-11) + +## 3.13.0 (2024-11-27) + +## 3.12.0 (2024-11-16) + ## 3.11.0 (2024-10-30) ## 3.10.0 (2024-10-16) diff --git a/packages/token-list/package.json b/packages/token-list/package.json index 370d50e5b4700a..1d0c8b85c1e4b4 100644 --- a/packages/token-list/package.json +++ b/packages/token-list/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/token-list", - "version": "3.11.0", + "version": "3.16.0", "description": "Constructable, plain JavaScript DOMTokenList implementation, supporting non-browser runtimes.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/token-list/src/test/index.ts b/packages/token-list/src/test/index.ts index fda0de0c53e489..7897a3a62de31f 100644 --- a/packages/token-list/src/test/index.ts +++ b/packages/token-list/src/test/index.ts @@ -26,7 +26,7 @@ describe( 'token-list', () => { expect( list ).toHaveLength( 1 ); } ); - describe( 'array method inheritence', () => { + describe( 'array method inheritance', () => { it( 'entries', () => { const list = new TokenList( 'abc ' ); diff --git a/packages/token-list/tsconfig.json b/packages/token-list/tsconfig.json index d1947d4c52ffdf..7ff060ab6ce105 100644 --- a/packages/token-list/tsconfig.json +++ b/packages/token-list/tsconfig.json @@ -1,9 +1,4 @@ { "$schema": "https://json.schemastore.org/tsconfig.json", - "extends": "../../tsconfig.base.json", - "compilerOptions": { - "rootDir": "src", - "outDir": "build-types" - }, - "include": [ "src/**/*" ] + "extends": "../../tsconfig.base.json" } diff --git a/packages/undo-manager/CHANGELOG.md b/packages/undo-manager/CHANGELOG.md index 0f779e36f51687..36bce4f7524d7d 100644 --- a/packages/undo-manager/CHANGELOG.md +++ b/packages/undo-manager/CHANGELOG.md @@ -2,6 +2,16 @@ ## Unreleased +## 1.16.0 (2025-01-15) + +## 1.15.0 (2025-01-02) + +## 1.14.0 (2024-12-11) + +## 1.13.0 (2024-11-27) + +## 1.12.0 (2024-11-16) + ## 1.11.0 (2024-10-30) ## 1.10.0 (2024-10-16) diff --git a/packages/undo-manager/package.json b/packages/undo-manager/package.json index 66f895f8da887d..518e37ad2b061c 100644 --- a/packages/undo-manager/package.json +++ b/packages/undo-manager/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/undo-manager", - "version": "1.11.0", + "version": "1.16.0", "description": "A small package to manage undo/redo.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -31,7 +31,7 @@ "sideEffects": false, "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/is-shallow-equal": "*" + "@wordpress/is-shallow-equal": "file:../is-shallow-equal" }, "publishConfig": { "access": "public" diff --git a/packages/undo-manager/tsconfig.json b/packages/undo-manager/tsconfig.json index 055c19d5bf513d..a3c336bec45609 100644 --- a/packages/undo-manager/tsconfig.json +++ b/packages/undo-manager/tsconfig.json @@ -2,10 +2,7 @@ "$schema": "https://json.schemastore.org/tsconfig.json", "extends": "../../tsconfig.base.json", "compilerOptions": { - "rootDir": "src", - "declarationDir": "build-types", "types": [ "node" ] }, - "references": [ { "path": "../is-shallow-equal" } ], - "include": [ "src/**/*" ] + "references": [ { "path": "../is-shallow-equal" } ] } diff --git a/packages/upload-media/CHANGELOG.md b/packages/upload-media/CHANGELOG.md new file mode 100644 index 00000000000000..f6f9e4317b9a26 --- /dev/null +++ b/packages/upload-media/CHANGELOG.md @@ -0,0 +1,7 @@ +<!-- Learn how to maintain this file at https://github.com/WordPress/gutenberg/tree/HEAD/packages#maintaining-changelogs. --> + +## Unreleased + +## 0.1.0 (2025-01-15) + +Initial release. diff --git a/packages/upload-media/README.md b/packages/upload-media/README.md new file mode 100644 index 00000000000000..982e59148fe87c --- /dev/null +++ b/packages/upload-media/README.md @@ -0,0 +1,136 @@ +# (Experimental) Upload Media + +This module is a media upload handler with a queue-like system that is implemented using a custom `@wordpress/data` store. + +Such a system is useful for additional client-side processing of media files (e.g. image compression) before uploading them to a server. + +It is typically used by `@wordpress/block-editor` but can also be leveraged outside of it. + +## Installation + +Install the module + +```bash +npm install @wordpress/upload-media --save +``` + +## Usage + +This is a basic example of how one can interact with the upload data store: + +```js +import { store as uploadStore } from '@wordpress/upload-media'; +import { dispatch } from '@wordpress/data'; + +dispatch( uploadStore ).updateSettings( /* ... */ ); +dispatch( uploadStore ).addItems( [ + /* ... */ +] ); +``` + +Refer to the API reference below or the TypeScript types for further help. + +## API Reference + +### Actions + +The following set of dispatching action creators are available on the object returned by `wp.data.dispatch( 'core/upload-media' )`: + +<!-- START TOKEN(Autogenerated actions|src/store/actions.ts) --> + +#### addItems + +Adds a new item to the upload queue. + +_Parameters_ + +- _$0_ `AddItemsArgs`: +- _$0.files_ `AddItemsArgs[ 'files' ]`: Files +- _$0.onChange_ `[AddItemsArgs[ 'onChange' ]]`: Function called each time a file or a temporary representation of the file is available. +- _$0.onSuccess_ `[AddItemsArgs[ 'onSuccess' ]]`: Function called after the file is uploaded. +- _$0.onBatchSuccess_ `[AddItemsArgs[ 'onBatchSuccess' ]]`: Function called after a batch of files is uploaded. +- _$0.onError_ `[AddItemsArgs[ 'onError' ]]`: Function called when an error happens. +- _$0.additionalData_ `[AddItemsArgs[ 'additionalData' ]]`: Additional data to include in the request. +- _$0.allowedTypes_ `[AddItemsArgs[ 'allowedTypes' ]]`: Array with the types of media that can be uploaded, if unset all types are allowed. + +#### cancelItem + +Cancels an item in the queue based on an error. + +_Parameters_ + +- _id_ `QueueItemId`: Item ID. +- _error_ `Error`: Error instance. +- _silent_ Whether to cancel the item silently, without invoking its `onError` callback. + +<!-- END TOKEN(Autogenerated actions|src/store/actions.ts) --> + +### Selectors + +The following selectors are available on the object returned by `wp.data.select( 'core/upload-media' )`: + +<!-- START TOKEN(Autogenerated selectors|src/store/selectors.ts) --> + +#### getItems + +Returns all items currently being uploaded. + +_Parameters_ + +- _state_ `State`: Upload state. + +_Returns_ + +- `QueueItem[]`: Queue items. + +#### getSettings + +Returns the media upload settings. + +_Parameters_ + +- _state_ `State`: Upload state. + +_Returns_ + +- `Settings`: Settings + +#### isUploading + +Determines whether any upload is currently in progress. + +_Parameters_ + +- _state_ `State`: Upload state. + +_Returns_ + +- `boolean`: Whether any upload is currently in progress. + +#### isUploadingById + +Determines whether an upload is currently in progress given an attachment ID. + +_Parameters_ + +- _state_ `State`: Upload state. +- _attachmentId_ `number`: Attachment ID. + +_Returns_ + +- `boolean`: Whether upload is currently in progress for the given attachment. + +#### isUploadingByUrl + +Determines whether an upload is currently in progress given an attachment URL. + +_Parameters_ + +- _state_ `State`: Upload state. +- _url_ `string`: Attachment URL. + +_Returns_ + +- `boolean`: Whether upload is currently in progress for the given attachment. + +<!-- END TOKEN(Autogenerated selectors|src/store/selectors.ts) --> diff --git a/packages/upload-media/package.json b/packages/upload-media/package.json new file mode 100644 index 00000000000000..828ed745acdaf6 --- /dev/null +++ b/packages/upload-media/package.json @@ -0,0 +1,50 @@ +{ + "name": "@wordpress/upload-media", + "version": "0.1.0", + "description": "Core media upload logic.", + "author": "The WordPress Contributors", + "license": "GPL-2.0-or-later", + "keywords": [ + "wordpress", + "media" + ], + "homepage": "https://github.com/WordPress/gutenberg/tree/HEAD/packages/upload-media/README.md", + "repository": { + "type": "git", + "url": "https://github.com/WordPress/gutenberg.git", + "directory": "packages/upload-media" + }, + "bugs": { + "url": "https://github.com/WordPress/gutenberg/issues" + }, + "engines": { + "node": ">=18.12.0", + "npm": ">=8.19.2" + }, + "main": "build/index.js", + "module": "build-module/index.js", + "wpScript": true, + "types": "build-types", + "sideEffects": false, + "dependencies": { + "@babel/runtime": "7.25.7", + "@shopify/web-worker": "^6.4.0", + "@wordpress/api-fetch": "file:../api-fetch", + "@wordpress/blob": "file:../blob", + "@wordpress/compose": "file:../compose", + "@wordpress/data": "file:../data", + "@wordpress/element": "file:../element", + "@wordpress/i18n": "file:../i18n", + "@wordpress/preferences": "file:../preferences", + "@wordpress/private-apis": "file:../private-apis", + "@wordpress/url": "file:../url", + "uuid": "^9.0.1" + }, + "peerDependencies": { + "react": "^18.0.0", + "react-dom": "^18.0.0" + }, + "publishConfig": { + "access": "public" + } +} diff --git a/packages/upload-media/src/components/provider/index.tsx b/packages/upload-media/src/components/provider/index.tsx new file mode 100644 index 00000000000000..0bc187e6a1d861 --- /dev/null +++ b/packages/upload-media/src/components/provider/index.tsx @@ -0,0 +1,25 @@ +/** + * WordPress dependencies + */ +import { useEffect } from '@wordpress/element'; +import { useDispatch } from '@wordpress/data'; + +/** + * Internal dependencies + */ +import withRegistryProvider from './with-registry-provider'; +import { unlock } from '../../lock-unlock'; +import { store as uploadStore } from '../../store'; + +const MediaUploadProvider = withRegistryProvider( ( props: any ) => { + const { children, settings } = props; + const { updateSettings } = unlock( useDispatch( uploadStore ) ); + + useEffect( () => { + updateSettings( settings ); + }, [ settings, updateSettings ] ); + + return <>{ children }</>; +} ); + +export default MediaUploadProvider; diff --git a/packages/upload-media/src/components/provider/with-registry-provider.tsx b/packages/upload-media/src/components/provider/with-registry-provider.tsx new file mode 100644 index 00000000000000..9a47a5601d33ed --- /dev/null +++ b/packages/upload-media/src/components/provider/with-registry-provider.tsx @@ -0,0 +1,59 @@ +/** + * WordPress dependencies + */ +import { useState } from '@wordpress/element'; +import { useRegistry, createRegistry, RegistryProvider } from '@wordpress/data'; +import { createHigherOrderComponent } from '@wordpress/compose'; + +/** + * Internal dependencies + */ +import { storeConfig } from '../../store'; +import { STORE_NAME as mediaUploadStoreName } from '../../store/constants'; + +type WPDataRegistry = ReturnType< typeof createRegistry >; + +function getSubRegistry( + subRegistries: WeakMap< WPDataRegistry, WPDataRegistry >, + registry: WPDataRegistry, + useSubRegistry: boolean +) { + if ( ! useSubRegistry ) { + return registry; + } + let subRegistry = subRegistries.get( registry ); + if ( ! subRegistry ) { + subRegistry = createRegistry( {}, registry ); + subRegistry.registerStore( mediaUploadStoreName, storeConfig ); + subRegistries.set( registry, subRegistry ); + } + return subRegistry; +} + +const withRegistryProvider = createHigherOrderComponent( + ( WrappedComponent ) => + ( { useSubRegistry = true, ...props } ) => { + const registry = useRegistry() as unknown as WPDataRegistry; + const [ subRegistries ] = useState< + WeakMap< WPDataRegistry, WPDataRegistry > + >( () => new WeakMap() ); + const subRegistry = getSubRegistry( + subRegistries, + registry, + useSubRegistry + ); + + if ( subRegistry === registry ) { + return <WrappedComponent registry={ registry } { ...props } />; + } + + return ( + <RegistryProvider value={ subRegistry }> + <WrappedComponent registry={ subRegistry } { ...props } /> + </RegistryProvider> + ); + }, + 'withRegistryProvider' +); + +export default withRegistryProvider; diff --git a/packages/upload-media/src/get-mime-types-array.ts b/packages/upload-media/src/get-mime-types-array.ts new file mode 100644 index 00000000000000..d4940d36cd6ae5 --- /dev/null +++ b/packages/upload-media/src/get-mime-types-array.ts @@ -0,0 +1,29 @@ +/** + * Browsers may use unexpected mime types, and they differ from browser to browser. + * This function computes a flexible array of mime types from the mime type structured provided by the server. + * Converts { jpg|jpeg|jpe: "image/jpeg" } into [ "image/jpeg", "image/jpg", "image/jpeg", "image/jpe" ] + * + * @param {?Object} wpMimeTypesObject Mime type object received from the server. + * Extensions are keys separated by '|' and values are mime types associated with an extension. + * + * @return An array of mime types or null + */ +export function getMimeTypesArray( + wpMimeTypesObject?: Record< string, string > | null +) { + if ( ! wpMimeTypesObject ) { + return null; + } + return Object.entries( wpMimeTypesObject ).flatMap( + ( [ extensionsString, mime ] ) => { + const [ type ] = mime.split( '/' ); + const extensions = extensionsString.split( '|' ); + return [ + mime, + ...extensions.map( + ( extension ) => `${ type }/${ extension }` + ), + ]; + } + ); +} diff --git a/packages/upload-media/src/image-file.ts b/packages/upload-media/src/image-file.ts new file mode 100644 index 00000000000000..2c1a43ee1ab67e --- /dev/null +++ b/packages/upload-media/src/image-file.ts @@ -0,0 +1,38 @@ +/** + * ImageFile class. + * + * Small wrapper around the `File` class to hold + * information about current dimensions and original + * dimensions, in case the image was resized. + */ +export class ImageFile extends File { + width = 0; + height = 0; + originalWidth? = 0; + originalHeight? = 0; + + get wasResized() { + return ( + ( this.originalWidth || 0 ) > this.width || + ( this.originalHeight || 0 ) > this.height + ); + } + + constructor( + file: File, + width: number, + height: number, + originalWidth?: number, + originalHeight?: number + ) { + super( [ file ], file.name, { + type: file.type, + lastModified: file.lastModified, + } ); + + this.width = width; + this.height = height; + this.originalWidth = originalWidth; + this.originalHeight = originalHeight; + } +} diff --git a/packages/upload-media/src/index.ts b/packages/upload-media/src/index.ts new file mode 100644 index 00000000000000..d105c2dba90392 --- /dev/null +++ b/packages/upload-media/src/index.ts @@ -0,0 +1,11 @@ +/** + * Internal dependencies + */ +import { store as uploadStore } from './store'; + +export { uploadStore as store }; + +export { default as MediaUploadProvider } from './components/provider'; +export { UploadError } from './upload-error'; + +export type { ImageFormat } from './store/types'; diff --git a/packages/upload-media/src/lock-unlock.ts b/packages/upload-media/src/lock-unlock.ts new file mode 100644 index 00000000000000..5089cb80e4bb46 --- /dev/null +++ b/packages/upload-media/src/lock-unlock.ts @@ -0,0 +1,10 @@ +/** + * WordPress dependencies + */ +import { __dangerousOptInToUnstableAPIsOnlyForCoreModules } from '@wordpress/private-apis'; + +export const { lock, unlock } = + __dangerousOptInToUnstableAPIsOnlyForCoreModules( + 'I acknowledge private features are not for use in themes or plugins and doing so will break in the next version of WordPress.', + '@wordpress/upload-media' + ); diff --git a/packages/upload-media/src/store/actions.ts b/packages/upload-media/src/store/actions.ts new file mode 100644 index 00000000000000..4cc3c3e31ae0e2 --- /dev/null +++ b/packages/upload-media/src/store/actions.ts @@ -0,0 +1,183 @@ +/** + * External dependencies + */ +import { v4 as uuidv4 } from 'uuid'; + +/** + * WordPress dependencies + */ +import type { createRegistry } from '@wordpress/data'; + +type WPDataRegistry = ReturnType< typeof createRegistry >; + +/** + * Internal dependencies + */ +import type { + AdditionalData, + CancelAction, + OnBatchSuccessHandler, + OnChangeHandler, + OnErrorHandler, + OnSuccessHandler, + QueueItemId, + State, +} from './types'; +import { Type } from './types'; +import type { + addItem, + processItem, + removeItem, + revokeBlobUrls, +} from './private-actions'; +import { validateMimeType } from '../validate-mime-type'; +import { validateMimeTypeForUser } from '../validate-mime-type-for-user'; +import { validateFileSize } from '../validate-file-size'; + +type ActionCreators = { + addItem: typeof addItem; + addItems: typeof addItems; + removeItem: typeof removeItem; + processItem: typeof processItem; + cancelItem: typeof cancelItem; + revokeBlobUrls: typeof revokeBlobUrls; + < T = Record< string, unknown > >( args: T ): void; +}; + +type AllSelectors = typeof import('./selectors') & + typeof import('./private-selectors'); +type CurriedState< F > = F extends ( state: State, ...args: infer P ) => infer R + ? ( ...args: P ) => R + : F; +type Selectors = { + [ key in keyof AllSelectors ]: CurriedState< AllSelectors[ key ] >; +}; + +type ThunkArgs = { + select: Selectors; + dispatch: ActionCreators; + registry: WPDataRegistry; +}; + +interface AddItemsArgs { + files: File[]; + onChange?: OnChangeHandler; + onSuccess?: OnSuccessHandler; + onBatchSuccess?: OnBatchSuccessHandler; + onError?: OnErrorHandler; + additionalData?: AdditionalData; + allowedTypes?: string[]; +} + +/** + * Adds a new item to the upload queue. + * + * @param $0 + * @param $0.files Files + * @param [$0.onChange] Function called each time a file or a temporary representation of the file is available. + * @param [$0.onSuccess] Function called after the file is uploaded. + * @param [$0.onBatchSuccess] Function called after a batch of files is uploaded. + * @param [$0.onError] Function called when an error happens. + * @param [$0.additionalData] Additional data to include in the request. + * @param [$0.allowedTypes] Array with the types of media that can be uploaded, if unset all types are allowed. + */ +export function addItems( { + files, + onChange, + onSuccess, + onError, + onBatchSuccess, + additionalData, + allowedTypes, +}: AddItemsArgs ) { + return async ( { select, dispatch }: ThunkArgs ) => { + const batchId = uuidv4(); + for ( const file of files ) { + /* + Check if the caller (e.g. a block) supports this mime type. + Special case for file types such as HEIC which will be converted before upload anyway. + Another check will be done before upload. + */ + try { + validateMimeType( file, allowedTypes ); + validateMimeTypeForUser( + file, + select.getSettings().allowedMimeTypes + ); + } catch ( error: unknown ) { + onError?.( error as Error ); + continue; + } + + try { + validateFileSize( + file, + select.getSettings().maxUploadFileSize + ); + } catch ( error: unknown ) { + onError?.( error as Error ); + continue; + } + + dispatch.addItem( { + file, + batchId, + onChange, + onSuccess, + onBatchSuccess, + onError, + additionalData, + } ); + } + }; +} + +/** + * Cancels an item in the queue based on an error. + * + * @param id Item ID. + * @param error Error instance. + * @param silent Whether to cancel the item silently, + * without invoking its `onError` callback. + */ +export function cancelItem( id: QueueItemId, error: Error, silent = false ) { + return async ( { select, dispatch }: ThunkArgs ) => { + const item = select.getItem( id ); + + if ( ! item ) { + /* + * Do nothing if item has already been removed. + * This can happen if an upload is cancelled manually + * while transcoding with vips is still in progress. + * Then, cancelItem() is once invoked manually and once + * by the error handler in optimizeImageItem(). + */ + return; + } + + item.abortController?.abort(); + + if ( ! silent ) { + const { onError } = item; + onError?.( error ?? new Error( 'Upload cancelled' ) ); + if ( ! onError && error ) { + // TODO: Find better way to surface errors with sideloads etc. + // eslint-disable-next-line no-console -- Deliberately log errors here. + console.error( 'Upload cancelled', error ); + } + } + + dispatch< CancelAction >( { + type: Type.Cancel, + id, + error, + } ); + dispatch.removeItem( id ); + dispatch.revokeBlobUrls( id ); + + // All items of this batch were cancelled or finished. + if ( item.batchId && select.isBatchUploaded( item.batchId ) ) { + item.onBatchSuccess?.(); + } + }; +} diff --git a/packages/upload-media/src/store/constants.ts b/packages/upload-media/src/store/constants.ts new file mode 100644 index 00000000000000..ad0960cf62f46d --- /dev/null +++ b/packages/upload-media/src/store/constants.ts @@ -0,0 +1 @@ +export const STORE_NAME = 'core/upload-media'; diff --git a/packages/upload-media/src/store/index.ts b/packages/upload-media/src/store/index.ts new file mode 100644 index 00000000000000..c74f59ea7a7cf3 --- /dev/null +++ b/packages/upload-media/src/store/index.ts @@ -0,0 +1,43 @@ +/** + * WordPress dependencies + */ +import { createReduxStore, register } from '@wordpress/data'; + +/** + * Internal dependencies + */ +import reducer from './reducer'; +import * as selectors from './selectors'; +import * as privateSelectors from './private-selectors'; +import * as actions from './actions'; +import * as privateActions from './private-actions'; +import { unlock } from '../lock-unlock'; +import { STORE_NAME } from './constants'; + +/** + * Media upload data store configuration. + * + * @see https://github.com/WordPress/gutenberg/blob/HEAD/packages/data/README.md#registerStore + */ +export const storeConfig = { + reducer, + selectors, + actions, +}; + +/** + * Store definition for the media upload namespace. + * + * @see https://github.com/WordPress/gutenberg/blob/HEAD/packages/data/README.md#createReduxStore + */ +export const store = createReduxStore( STORE_NAME, { + reducer, + selectors, + actions, +} ); + +register( store ); +// @ts-ignore +unlock( store ).registerPrivateActions( privateActions ); +// @ts-ignore +unlock( store ).registerPrivateSelectors( privateSelectors ); diff --git a/packages/upload-media/src/store/private-actions.ts b/packages/upload-media/src/store/private-actions.ts new file mode 100644 index 00000000000000..a4d4ee7b99c781 --- /dev/null +++ b/packages/upload-media/src/store/private-actions.ts @@ -0,0 +1,407 @@ +/** + * External dependencies + */ +import { v4 as uuidv4 } from 'uuid'; + +/** + * WordPress dependencies + */ +import { createBlobURL, isBlobURL, revokeBlobURL } from '@wordpress/blob'; +import type { createRegistry } from '@wordpress/data'; + +type WPDataRegistry = ReturnType< typeof createRegistry >; + +/** + * Internal dependencies + */ +import { cloneFile, convertBlobToFile } from '../utils'; +import { StubFile } from '../stub-file'; +import type { + AddAction, + AdditionalData, + AddOperationsAction, + BatchId, + CacheBlobUrlAction, + OnBatchSuccessHandler, + OnChangeHandler, + OnErrorHandler, + OnSuccessHandler, + Operation, + OperationFinishAction, + OperationStartAction, + PauseQueueAction, + QueueItem, + QueueItemId, + ResumeQueueAction, + RevokeBlobUrlsAction, + Settings, + State, + UpdateSettingsAction, +} from './types'; +import { ItemStatus, OperationType, Type } from './types'; +import type { cancelItem } from './actions'; + +type ActionCreators = { + cancelItem: typeof cancelItem; + addItem: typeof addItem; + removeItem: typeof removeItem; + prepareItem: typeof prepareItem; + processItem: typeof processItem; + finishOperation: typeof finishOperation; + uploadItem: typeof uploadItem; + revokeBlobUrls: typeof revokeBlobUrls; + < T = Record< string, unknown > >( args: T ): void; +}; + +type AllSelectors = typeof import('./selectors') & + typeof import('./private-selectors'); +type CurriedState< F > = F extends ( state: State, ...args: infer P ) => infer R + ? ( ...args: P ) => R + : F; +type Selectors = { + [ key in keyof AllSelectors ]: CurriedState< AllSelectors[ key ] >; +}; + +type ThunkArgs = { + select: Selectors; + dispatch: ActionCreators; + registry: WPDataRegistry; +}; + +interface AddItemArgs { + // It should always be a File, but some consumers might still pass Blobs only. + file: File | Blob; + batchId?: BatchId; + onChange?: OnChangeHandler; + onSuccess?: OnSuccessHandler; + onError?: OnErrorHandler; + onBatchSuccess?: OnBatchSuccessHandler; + additionalData?: AdditionalData; + sourceUrl?: string; + sourceAttachmentId?: number; + abortController?: AbortController; + operations?: Operation[]; +} + +/** + * Adds a new item to the upload queue. + * + * @param $0 + * @param $0.file File + * @param [$0.batchId] Batch ID. + * @param [$0.onChange] Function called each time a file or a temporary representation of the file is available. + * @param [$0.onSuccess] Function called after the file is uploaded. + * @param [$0.onBatchSuccess] Function called after a batch of files is uploaded. + * @param [$0.onError] Function called when an error happens. + * @param [$0.additionalData] Additional data to include in the request. + * @param [$0.sourceUrl] Source URL. Used when importing a file from a URL or optimizing an existing file. + * @param [$0.sourceAttachmentId] Source attachment ID. Used when optimizing an existing file for example. + * @param [$0.abortController] Abort controller for upload cancellation. + * @param [$0.operations] List of operations to perform. Defaults to automatically determined list, based on the file. + */ +export function addItem( { + file: fileOrBlob, + batchId, + onChange, + onSuccess, + onBatchSuccess, + onError, + additionalData = {} as AdditionalData, + sourceUrl, + sourceAttachmentId, + abortController, + operations, +}: AddItemArgs ) { + return async ( { dispatch }: ThunkArgs ) => { + const itemId = uuidv4(); + + // Hardening in case a Blob is passed instead of a File. + // See https://github.com/WordPress/gutenberg/pull/65693 for an example. + const file = convertBlobToFile( fileOrBlob ); + + let blobUrl; + + // StubFile could be coming from addItemFromUrl(). + if ( ! ( file instanceof StubFile ) ) { + blobUrl = createBlobURL( file ); + dispatch< CacheBlobUrlAction >( { + type: Type.CacheBlobUrl, + id: itemId, + blobUrl, + } ); + } + + dispatch< AddAction >( { + type: Type.Add, + item: { + id: itemId, + batchId, + status: ItemStatus.Processing, + sourceFile: cloneFile( file ), + file, + attachment: { + url: blobUrl, + }, + additionalData: { + convert_format: false, + ...additionalData, + }, + onChange, + onSuccess, + onBatchSuccess, + onError, + sourceUrl, + sourceAttachmentId, + abortController: abortController || new AbortController(), + operations: Array.isArray( operations ) + ? operations + : [ OperationType.Prepare ], + }, + } ); + + dispatch.processItem( itemId ); + }; +} + +/** + * Processes a single item in the queue. + * + * Runs the next operation in line and invokes any callbacks. + * + * @param id Item ID. + */ +export function processItem( id: QueueItemId ) { + return async ( { select, dispatch }: ThunkArgs ) => { + if ( select.isPaused() ) { + return; + } + + const item = select.getItem( id ) as QueueItem; + + const { attachment, onChange, onSuccess, onBatchSuccess, batchId } = + item; + + const operation = Array.isArray( item.operations?.[ 0 ] ) + ? item.operations[ 0 ][ 0 ] + : item.operations?.[ 0 ]; + + if ( attachment ) { + onChange?.( [ attachment ] ); + } + + /* + If there are no more operations, the item can be removed from the queue, + but only if there are no thumbnails still being side-loaded, + or if itself is a side-loaded item. + */ + + if ( ! operation ) { + if ( attachment ) { + onSuccess?.( [ attachment ] ); + } + + // dispatch.removeItem( id ); + dispatch.revokeBlobUrls( id ); + + if ( batchId && select.isBatchUploaded( batchId ) ) { + onBatchSuccess?.(); + } + + /* + At this point we are dealing with a parent whose children haven't fully uploaded yet. + Do nothing and let the removal happen once the last side-loaded item finishes. + */ + + return; + } + + if ( ! operation ) { + // This shouldn't really happen. + return; + } + + dispatch< OperationStartAction >( { + type: Type.OperationStart, + id, + operation, + } ); + + switch ( operation ) { + case OperationType.Prepare: + dispatch.prepareItem( item.id ); + break; + + case OperationType.Upload: + dispatch.uploadItem( id ); + break; + } + }; +} + +/** + * Returns an action object that pauses all processing in the queue. + * + * Useful for testing purposes. + * + * @return Action object. + */ +export function pauseQueue(): PauseQueueAction { + return { + type: Type.PauseQueue, + }; +} + +/** + * Resumes all processing in the queue. + * + * Dispatches an action object for resuming the queue itself, + * and triggers processing for each remaining item in the queue individually. + */ +export function resumeQueue() { + return async ( { select, dispatch }: ThunkArgs ) => { + dispatch< ResumeQueueAction >( { + type: Type.ResumeQueue, + } ); + + for ( const item of select.getAllItems() ) { + dispatch.processItem( item.id ); + } + }; +} + +/** + * Removes a specific item from the queue. + * + * @param id Item ID. + */ +export function removeItem( id: QueueItemId ) { + return async ( { select, dispatch }: ThunkArgs ) => { + const item = select.getItem( id ); + if ( ! item ) { + return; + } + + dispatch( { + type: Type.Remove, + id, + } ); + }; +} + +/** + * Finishes an operation for a given item ID and immediately triggers processing the next one. + * + * @param id Item ID. + * @param updates Updated item data. + */ +export function finishOperation( + id: QueueItemId, + updates: Partial< QueueItem > +) { + return async ( { dispatch }: ThunkArgs ) => { + dispatch< OperationFinishAction >( { + type: Type.OperationFinish, + id, + item: updates, + } ); + + dispatch.processItem( id ); + }; +} + +/** + * Prepares an item for initial processing. + * + * Determines the list of operations to perform for a given image, + * depending on its media type. + * + * For example, HEIF images first need to be converted, resized, + * compressed, and then uploaded. + * + * Or videos need to be compressed, and then need poster generation + * before upload. + * + * @param id Item ID. + */ +export function prepareItem( id: QueueItemId ) { + return async ( { dispatch }: ThunkArgs ) => { + const operations: Operation[] = [ OperationType.Upload ]; + + dispatch< AddOperationsAction >( { + type: Type.AddOperations, + id, + operations, + } ); + + dispatch.finishOperation( id, {} ); + }; +} + +/** + * Uploads an item to the server. + * + * @param id Item ID. + */ +export function uploadItem( id: QueueItemId ) { + return async ( { select, dispatch }: ThunkArgs ) => { + const item = select.getItem( id ) as QueueItem; + + select.getSettings().mediaUpload( { + filesList: [ item.file ], + additionalData: item.additionalData, + signal: item.abortController?.signal, + onFileChange: ( [ attachment ] ) => { + if ( ! isBlobURL( attachment.url ) ) { + dispatch.finishOperation( id, { + attachment, + } ); + } + }, + onSuccess: ( [ attachment ] ) => { + dispatch.finishOperation( id, { + attachment, + } ); + }, + onError: ( error ) => { + dispatch.cancelItem( id, error ); + }, + } ); + }; +} + +/** + * Revokes all blob URLs for a given item, freeing up memory. + * + * @param id Item ID. + */ +export function revokeBlobUrls( id: QueueItemId ) { + return async ( { select, dispatch }: ThunkArgs ) => { + const blobUrls = select.getBlobUrls( id ); + + for ( const blobUrl of blobUrls ) { + revokeBlobURL( blobUrl ); + } + + dispatch< RevokeBlobUrlsAction >( { + type: Type.RevokeBlobUrls, + id, + } ); + }; +} + +/** + * Returns an action object that pauses all processing in the queue. + * + * Useful for testing purposes. + * + * @param settings + * @return Action object. + */ +export function updateSettings( + settings: Partial< Settings > +): UpdateSettingsAction { + return { + type: Type.UpdateSettings, + settings, + }; +} diff --git a/packages/upload-media/src/store/private-selectors.ts b/packages/upload-media/src/store/private-selectors.ts new file mode 100644 index 00000000000000..f2cfdbef76df86 --- /dev/null +++ b/packages/upload-media/src/store/private-selectors.ts @@ -0,0 +1,113 @@ +/** + * Internal dependencies + */ +import { + type BatchId, + ItemStatus, + OperationType, + type QueueItem, + type QueueItemId, + type State, +} from './types'; + +/** + * Returns all items currently being uploaded. + * + * @param state Upload state. + * + * @return Queue items. + */ +export function getAllItems( state: State ): QueueItem[] { + return state.queue; +} + +/** + * Returns a specific item given its unique ID. + * + * @param state Upload state. + * @param id Item ID. + * + * @return Queue item. + */ +export function getItem( + state: State, + id: QueueItemId +): QueueItem | undefined { + return state.queue.find( ( item ) => item.id === id ); +} + +/** + * Determines whether a batch has been successfully uploaded, given its unique ID. + * + * @param state Upload state. + * @param batchId Batch ID. + * + * @return Whether a batch has been uploaded. + */ +export function isBatchUploaded( state: State, batchId: BatchId ): boolean { + const batchItems = state.queue.filter( + ( item ) => batchId === item.batchId + ); + return batchItems.length === 0; +} + +/** + * Determines whether an upload is currently in progress given a post or attachment ID. + * + * @param state Upload state. + * @param postOrAttachmentId Post ID or attachment ID. + * + * @return Whether upload is currently in progress for the given post or attachment. + */ +export function isUploadingToPost( + state: State, + postOrAttachmentId: number +): boolean { + return state.queue.some( + ( item ) => + item.currentOperation === OperationType.Upload && + item.additionalData.post === postOrAttachmentId + ); +} + +/** + * Returns the next paused upload for a given post or attachment ID. + * + * @param state Upload state. + * @param postOrAttachmentId Post ID or attachment ID. + * + * @return Paused item. + */ +export function getPausedUploadForPost( + state: State, + postOrAttachmentId: number +): QueueItem | undefined { + return state.queue.find( + ( item ) => + item.status === ItemStatus.Paused && + item.additionalData.post === postOrAttachmentId + ); +} + +/** + * Determines whether uploading is currently paused. + * + * @param state Upload state. + * + * @return Whether uploading is currently paused. + */ +export function isPaused( state: State ): boolean { + return state.queueStatus === 'paused'; +} + +/** + * Returns all cached blob URLs for a given item ID. + * + * @param state Upload state. + * @param id Item ID + * + * @return List of blob URLs. + */ +export function getBlobUrls( state: State, id: QueueItemId ): string[] { + return state.blobUrls[ id ] || []; +} diff --git a/packages/upload-media/src/store/reducer.ts b/packages/upload-media/src/store/reducer.ts new file mode 100644 index 00000000000000..290a319fcbc1da --- /dev/null +++ b/packages/upload-media/src/store/reducer.ts @@ -0,0 +1,195 @@ +/** + * Internal dependencies + */ +import { + type AddAction, + type AddOperationsAction, + type CacheBlobUrlAction, + type CancelAction, + type OperationFinishAction, + type OperationStartAction, + type PauseQueueAction, + type QueueItem, + type RemoveAction, + type ResumeQueueAction, + type RevokeBlobUrlsAction, + type State, + Type, + type UnknownAction, + type UpdateSettingsAction, +} from './types'; + +const noop = () => {}; + +const DEFAULT_STATE: State = { + queue: [], + queueStatus: 'active', + blobUrls: {}, + settings: { + mediaUpload: noop, + }, +}; + +type Action = + | AddAction + | RemoveAction + | CancelAction + | PauseQueueAction + | ResumeQueueAction + | AddOperationsAction + | OperationFinishAction + | OperationStartAction + | CacheBlobUrlAction + | RevokeBlobUrlsAction + | UpdateSettingsAction + | UnknownAction; + +function reducer( + state = DEFAULT_STATE, + action: Action = { type: Type.Unknown } +) { + switch ( action.type ) { + case Type.PauseQueue: { + return { + ...state, + queueStatus: 'paused', + }; + } + + case Type.ResumeQueue: { + return { + ...state, + queueStatus: 'active', + }; + } + + case Type.Add: + return { + ...state, + queue: [ ...state.queue, action.item ], + }; + + case Type.Cancel: + return { + ...state, + queue: state.queue.map( + ( item ): QueueItem => + item.id === action.id + ? { + ...item, + error: action.error, + } + : item + ), + }; + + case Type.Remove: + return { + ...state, + queue: state.queue.filter( ( item ) => item.id !== action.id ), + }; + + case Type.OperationStart: { + return { + ...state, + queue: state.queue.map( + ( item ): QueueItem => + item.id === action.id + ? { + ...item, + currentOperation: action.operation, + } + : item + ), + }; + } + + case Type.AddOperations: + return { + ...state, + queue: state.queue.map( ( item ): QueueItem => { + if ( item.id !== action.id ) { + return item; + } + + return { + ...item, + operations: [ + ...( item.operations || [] ), + ...action.operations, + ], + }; + } ), + }; + + case Type.OperationFinish: + return { + ...state, + queue: state.queue.map( ( item ): QueueItem => { + if ( item.id !== action.id ) { + return item; + } + + const operations = item.operations + ? item.operations.slice( 1 ) + : []; + + // Prevent an empty object if there's no attachment data. + const attachment = + item.attachment || action.item.attachment + ? { + ...item.attachment, + ...action.item.attachment, + } + : undefined; + + return { + ...item, + currentOperation: undefined, + operations, + ...action.item, + attachment, + additionalData: { + ...item.additionalData, + ...action.item.additionalData, + }, + }; + } ), + }; + + case Type.CacheBlobUrl: { + const blobUrls = state.blobUrls[ action.id ] || []; + return { + ...state, + blobUrls: { + ...state.blobUrls, + [ action.id ]: [ ...blobUrls, action.blobUrl ], + }, + }; + } + + case Type.RevokeBlobUrls: { + const newBlobUrls = { ...state.blobUrls }; + delete newBlobUrls[ action.id ]; + + return { + ...state, + blobUrls: newBlobUrls, + }; + } + + case Type.UpdateSettings: { + return { + ...state, + settings: { + ...state.settings, + ...action.settings, + }, + }; + } + } + + return state; +} + +export default reducer; diff --git a/packages/upload-media/src/store/selectors.ts b/packages/upload-media/src/store/selectors.ts new file mode 100644 index 00000000000000..8bcb8c5d63b6a7 --- /dev/null +++ b/packages/upload-media/src/store/selectors.ts @@ -0,0 +1,67 @@ +/** + * Internal dependencies + */ +import type { QueueItem, Settings, State } from './types'; + +/** + * Returns all items currently being uploaded. + * + * @param state Upload state. + * + * @return Queue items. + */ +export function getItems( state: State ): QueueItem[] { + return state.queue; +} + +/** + * Determines whether any upload is currently in progress. + * + * @param state Upload state. + * + * @return Whether any upload is currently in progress. + */ +export function isUploading( state: State ): boolean { + return state.queue.length >= 1; +} + +/** + * Determines whether an upload is currently in progress given an attachment URL. + * + * @param state Upload state. + * @param url Attachment URL. + * + * @return Whether upload is currently in progress for the given attachment. + */ +export function isUploadingByUrl( state: State, url: string ): boolean { + return state.queue.some( + ( item ) => item.attachment?.url === url || item.sourceUrl === url + ); +} + +/** + * Determines whether an upload is currently in progress given an attachment ID. + * + * @param state Upload state. + * @param attachmentId Attachment ID. + * + * @return Whether upload is currently in progress for the given attachment. + */ +export function isUploadingById( state: State, attachmentId: number ): boolean { + return state.queue.some( + ( item ) => + item.attachment?.id === attachmentId || + item.sourceAttachmentId === attachmentId + ); +} + +/** + * Returns the media upload settings. + * + * @param state Upload state. + * + * @return Settings + */ +export function getSettings( state: State ): Settings { + return state.settings; +} diff --git a/packages/upload-media/src/store/test/actions.ts b/packages/upload-media/src/store/test/actions.ts new file mode 100644 index 00000000000000..adb38ab27128e3 --- /dev/null +++ b/packages/upload-media/src/store/test/actions.ts @@ -0,0 +1,112 @@ +/** + * WordPress dependencies + */ +import { createRegistry } from '@wordpress/data'; + +type WPDataRegistry = ReturnType< typeof createRegistry >; + +/** + * Internal dependencies + */ +import { store as uploadStore } from '..'; +import { ItemStatus } from '../types'; +import { unlock } from '../../lock-unlock'; + +jest.mock( '@wordpress/blob', () => ( { + __esModule: true, + createBlobURL: jest.fn( () => 'blob:foo' ), + isBlobURL: jest.fn( ( str: string ) => str.startsWith( 'blob:' ) ), + revokeBlobURL: jest.fn(), +} ) ); + +function createRegistryWithStores() { + // Create a registry and register used stores. + const registry = createRegistry(); + // @ts-ignore + [ uploadStore ].forEach( registry.register ); + return registry; +} + +const jpegFile = new File( [ 'foo' ], 'example.jpg', { + lastModified: 1234567891, + type: 'image/jpeg', +} ); + +const mp4File = new File( [ 'foo' ], 'amazing-video.mp4', { + lastModified: 1234567891, + type: 'video/mp4', +} ); + +describe( 'actions', () => { + let registry: WPDataRegistry; + beforeEach( () => { + registry = createRegistryWithStores(); + unlock( registry.dispatch( uploadStore ) ).pauseQueue(); + } ); + + describe( 'addItem', () => { + it( 'adds an item to the queue', () => { + unlock( registry.dispatch( uploadStore ) ).addItem( { + file: jpegFile, + } ); + + expect( registry.select( uploadStore ).getItems() ).toHaveLength( + 1 + ); + expect( + registry.select( uploadStore ).getItems()[ 0 ] + ).toStrictEqual( + expect.objectContaining( { + id: expect.any( String ), + file: jpegFile, + sourceFile: jpegFile, + status: ItemStatus.Processing, + attachment: { + url: expect.stringMatching( /^blob:/ ), + }, + } ) + ); + } ); + } ); + + describe( 'addItems', () => { + it( 'adds multiple items to the queue', () => { + const onError = jest.fn(); + registry.dispatch( uploadStore ).addItems( { + files: [ jpegFile, mp4File ], + onError, + } ); + + expect( onError ).not.toHaveBeenCalled(); + expect( registry.select( uploadStore ).getItems() ).toHaveLength( + 2 + ); + expect( + registry.select( uploadStore ).getItems()[ 0 ] + ).toStrictEqual( + expect.objectContaining( { + id: expect.any( String ), + file: jpegFile, + sourceFile: jpegFile, + status: ItemStatus.Processing, + attachment: { + url: expect.stringMatching( /^blob:/ ), + }, + } ) + ); + expect( + registry.select( uploadStore ).getItems()[ 1 ] + ).toStrictEqual( + expect.objectContaining( { + id: expect.any( String ), + file: mp4File, + sourceFile: mp4File, + status: ItemStatus.Processing, + attachment: { + url: expect.stringMatching( /^blob:/ ), + }, + } ) + ); + } ); + } ); +} ); diff --git a/packages/upload-media/src/store/test/reducer.ts b/packages/upload-media/src/store/test/reducer.ts new file mode 100644 index 00000000000000..80b92e4b14c3d1 --- /dev/null +++ b/packages/upload-media/src/store/test/reducer.ts @@ -0,0 +1,279 @@ +/** + * Internal dependencies + */ +import reducer from '../reducer'; +import { + ItemStatus, + OperationType, + type QueueItem, + type State, + Type, +} from '../types'; + +describe( 'reducer', () => { + describe( `${ Type.Add }`, () => { + it( 'adds an item to the queue', () => { + const initialState: State = { + queueStatus: 'active', + blobUrls: {}, + settings: { + mediaUpload: jest.fn(), + }, + queue: [ + { + id: '1', + status: ItemStatus.Processing, + } as QueueItem, + ], + }; + const state = reducer( initialState, { + type: Type.Add, + item: { + id: '2', + status: ItemStatus.Processing, + } as QueueItem, + } ); + + expect( state ).toEqual( { + queueStatus: 'active', + blobUrls: {}, + settings: { + mediaUpload: expect.any( Function ), + }, + queue: [ + { + id: '1', + status: ItemStatus.Processing, + } as QueueItem, + { + id: '2', + status: ItemStatus.Processing, + }, + ], + } ); + } ); + } ); + + describe( `${ Type.Cancel }`, () => { + it( 'removes an item from the queue', () => { + const initialState: State = { + queueStatus: 'active', + blobUrls: {}, + settings: { + mediaUpload: jest.fn(), + }, + queue: [ + { + id: '1', + status: ItemStatus.Processing, + } as QueueItem, + { + id: '2', + status: ItemStatus.Processing, + } as QueueItem, + ], + }; + const state = reducer( initialState, { + type: Type.Cancel, + id: '2', + error: new Error(), + } ); + + expect( state ).toEqual( { + queueStatus: 'active', + blobUrls: {}, + settings: { + mediaUpload: expect.any( Function ), + }, + queue: [ + { + id: '1', + status: ItemStatus.Processing, + }, + { + id: '2', + status: ItemStatus.Processing, + error: expect.any( Error ), + }, + ], + } ); + } ); + } ); + + describe( `${ Type.Remove }`, () => { + it( 'removes an item from the queue', () => { + const initialState: State = { + queueStatus: 'active', + blobUrls: {}, + settings: { + mediaUpload: jest.fn(), + }, + queue: [ + { + id: '1', + status: ItemStatus.Processing, + } as QueueItem, + { + id: '2', + status: ItemStatus.Processing, + } as QueueItem, + ], + }; + const state = reducer( initialState, { + type: Type.Remove, + id: '1', + } ); + + expect( state ).toEqual( { + queueStatus: 'active', + blobUrls: {}, + settings: { + mediaUpload: expect.any( Function ), + }, + queue: [ + { + id: '2', + status: ItemStatus.Processing, + }, + ], + } ); + } ); + } ); + + describe( `${ Type.AddOperations }`, () => { + it( 'appends operations to the list', () => { + const initialState: State = { + queueStatus: 'active', + blobUrls: {}, + settings: { + mediaUpload: jest.fn(), + }, + queue: [ + { + id: '1', + status: ItemStatus.Processing, + operations: [ OperationType.Upload ], + } as QueueItem, + ], + }; + const state = reducer( initialState, { + type: Type.AddOperations, + id: '1', + operations: [ OperationType.Upload ], + } ); + + expect( state ).toEqual( { + queueStatus: 'active', + blobUrls: {}, + settings: { + mediaUpload: expect.any( Function ), + }, + queue: [ + { + id: '1', + status: ItemStatus.Processing, + operations: [ + OperationType.Upload, + OperationType.Upload, + ], + }, + ], + } ); + } ); + } ); + + describe( `${ Type.OperationStart }`, () => { + it( 'marks an item as processing', () => { + const initialState: State = { + queueStatus: 'active', + blobUrls: {}, + settings: { + mediaUpload: jest.fn(), + }, + queue: [ + { + id: '1', + status: ItemStatus.Processing, + operations: [ OperationType.Upload ], + } as QueueItem, + { + id: '2', + status: ItemStatus.Processing, + operations: [ OperationType.Upload ], + } as QueueItem, + ], + }; + const state = reducer( initialState, { + type: Type.OperationStart, + id: '2', + operation: OperationType.Upload, + } ); + + expect( state ).toEqual( { + queueStatus: 'active', + blobUrls: {}, + settings: { + mediaUpload: expect.any( Function ), + }, + queue: [ + { + id: '1', + status: ItemStatus.Processing, + operations: [ OperationType.Upload ], + }, + { + id: '2', + status: ItemStatus.Processing, + operations: [ OperationType.Upload ], + currentOperation: OperationType.Upload, + }, + ], + } ); + } ); + } ); + + describe( `${ Type.OperationFinish }`, () => { + it( 'marks an item as processing', () => { + const initialState: State = { + queueStatus: 'active', + blobUrls: {}, + settings: { + mediaUpload: jest.fn(), + }, + queue: [ + { + id: '1', + additionalData: {}, + attachment: {}, + status: ItemStatus.Processing, + operations: [ OperationType.Upload ], + currentOperation: OperationType.Upload, + } as QueueItem, + ], + }; + const state = reducer( initialState, { + type: Type.OperationFinish, + id: '1', + item: {}, + } ); + + expect( state ).toEqual( { + queueStatus: 'active', + blobUrls: {}, + settings: { + mediaUpload: expect.any( Function ), + }, + queue: [ + { + id: '1', + additionalData: {}, + attachment: {}, + status: ItemStatus.Processing, + currentOperation: undefined, + operations: [], + }, + ], + } ); + } ); + } ); +} ); diff --git a/packages/upload-media/src/store/test/selectors.ts b/packages/upload-media/src/store/test/selectors.ts new file mode 100644 index 00000000000000..716b7792ef77a4 --- /dev/null +++ b/packages/upload-media/src/store/test/selectors.ts @@ -0,0 +1,105 @@ +/** + * Internal dependencies + */ +import { + getItems, + isUploading, + isUploadingById, + isUploadingByUrl, +} from '../selectors'; +import { ItemStatus, type QueueItem, type State } from '../types'; + +describe( 'selectors', () => { + describe( 'getItems', () => { + it( 'should return empty array by default', () => { + const state: State = { + queue: [], + queueStatus: 'paused', + blobUrls: {}, + settings: { + mediaUpload: jest.fn(), + }, + }; + + expect( getItems( state ) ).toHaveLength( 0 ); + } ); + } ); + + describe( 'isUploading', () => { + it( 'should return true if there are items in the pipeline', () => { + const state: State = { + queue: [ + { + status: ItemStatus.Processing, + }, + { + status: ItemStatus.Processing, + }, + { + status: ItemStatus.Paused, + }, + ] as QueueItem[], + queueStatus: 'paused', + blobUrls: {}, + settings: { + mediaUpload: jest.fn(), + }, + }; + + expect( isUploading( state ) ).toBe( true ); + } ); + } ); + + describe( 'isUploadingByUrl', () => { + it( 'should return true if there are items in the pipeline', () => { + const state: State = { + queue: [ + { + status: ItemStatus.Processing, + attachment: { + url: 'https://example.com/one.jpeg', + }, + }, + { + status: ItemStatus.Processing, + }, + ] as QueueItem[], + queueStatus: 'paused', + blobUrls: {}, + settings: { + mediaUpload: jest.fn(), + }, + }; + + expect( + isUploadingByUrl( state, 'https://example.com/one.jpeg' ) + ).toBe( true ); + expect( + isUploadingByUrl( state, 'https://example.com/three.jpeg' ) + ).toBe( false ); + } ); + } ); + + describe( 'isUploadingById', () => { + it( 'should return true if there are items in the pipeline', () => { + const state: State = { + queue: [ + { + status: ItemStatus.Processing, + attachment: { + id: 123, + }, + }, + ] as QueueItem[], + queueStatus: 'paused', + blobUrls: {}, + settings: { + mediaUpload: jest.fn(), + }, + }; + + expect( isUploadingById( state, 123 ) ).toBe( true ); + expect( isUploadingById( state, 789 ) ).toBe( false ); + } ); + } ); +} ); diff --git a/packages/upload-media/src/store/types.ts b/packages/upload-media/src/store/types.ts new file mode 100644 index 00000000000000..5084e006a2cfa9 --- /dev/null +++ b/packages/upload-media/src/store/types.ts @@ -0,0 +1,172 @@ +export type QueueItemId = string; + +export type QueueStatus = 'active' | 'paused'; + +export type BatchId = string; + +export interface QueueItem { + id: QueueItemId; + sourceFile: File; + file: File; + poster?: File; + attachment?: Partial< Attachment >; + status: ItemStatus; + additionalData: AdditionalData; + onChange?: OnChangeHandler; + onSuccess?: OnSuccessHandler; + onError?: OnErrorHandler; + onBatchSuccess?: OnBatchSuccessHandler; + currentOperation?: OperationType; + operations?: Operation[]; + error?: Error; + batchId?: string; + sourceUrl?: string; + sourceAttachmentId?: number; + abortController?: AbortController; +} + +export interface State { + queue: QueueItem[]; + queueStatus: QueueStatus; + blobUrls: Record< QueueItemId, string[] >; + settings: Settings; +} + +export enum Type { + Unknown = 'REDUX_UNKNOWN', + Add = 'ADD_ITEM', + Prepare = 'PREPARE_ITEM', + Cancel = 'CANCEL_ITEM', + Remove = 'REMOVE_ITEM', + PauseItem = 'PAUSE_ITEM', + ResumeItem = 'RESUME_ITEM', + PauseQueue = 'PAUSE_QUEUE', + ResumeQueue = 'RESUME_QUEUE', + OperationStart = 'OPERATION_START', + OperationFinish = 'OPERATION_FINISH', + AddOperations = 'ADD_OPERATIONS', + CacheBlobUrl = 'CACHE_BLOB_URL', + RevokeBlobUrls = 'REVOKE_BLOB_URLS', + UpdateSettings = 'UPDATE_SETTINGS', +} + +type Action< T = Type, Payload = Record< string, unknown > > = { + type: T; +} & Payload; + +export type UnknownAction = Action< Type.Unknown >; +export type AddAction = Action< + Type.Add, + { + item: Omit< QueueItem, 'operations' > & + Partial< Pick< QueueItem, 'operations' > >; + } +>; +export type OperationStartAction = Action< + Type.OperationStart, + { id: QueueItemId; operation: OperationType } +>; +export type OperationFinishAction = Action< + Type.OperationFinish, + { + id: QueueItemId; + item: Partial< QueueItem >; + } +>; +export type AddOperationsAction = Action< + Type.AddOperations, + { id: QueueItemId; operations: Operation[] } +>; +export type CancelAction = Action< + Type.Cancel, + { id: QueueItemId; error: Error } +>; +export type PauseItemAction = Action< Type.PauseItem, { id: QueueItemId } >; +export type ResumeItemAction = Action< Type.ResumeItem, { id: QueueItemId } >; +export type PauseQueueAction = Action< Type.PauseQueue >; +export type ResumeQueueAction = Action< Type.ResumeQueue >; +export type RemoveAction = Action< Type.Remove, { id: QueueItemId } >; +export type CacheBlobUrlAction = Action< + Type.CacheBlobUrl, + { id: QueueItemId; blobUrl: string } +>; +export type RevokeBlobUrlsAction = Action< + Type.RevokeBlobUrls, + { id: QueueItemId } +>; +export type UpdateSettingsAction = Action< + Type.UpdateSettings, + { settings: Partial< Settings > } +>; + +interface UploadMediaArgs { + // Additional data to include in the request. + additionalData?: AdditionalData; + // Array with the types of media that can be uploaded, if unset all types are allowed. + allowedTypes?: string[]; + // List of files. + filesList: File[]; + // Maximum upload size in bytes allowed for the site. + maxUploadFileSize?: number; + // Function called when an error happens. + onError?: OnErrorHandler; + // Function called each time a file or a temporary representation of the file is available. + onFileChange?: OnChangeHandler; + // Function called once a file has completely finished uploading, including thumbnails. + onSuccess?: OnSuccessHandler; + // List of allowed mime types and file extensions. + wpAllowedMimeTypes?: Record< string, string > | null; + // Abort signal. + signal?: AbortSignal; +} + +export interface Settings { + // Function for uploading files to the server. + mediaUpload: ( args: UploadMediaArgs ) => void; + // List of allowed mime types and file extensions. + allowedMimeTypes?: Record< string, string > | null; + // Maximum upload file size + maxUploadFileSize?: number; +} + +// Must match the Attachment type from the media-utils package. +export interface Attachment { + id: number; + alt: string; + caption: string; + title: string; + url: string; + filename: string | null; + filesize: number | null; + media_type: 'image' | 'file'; + mime_type: string; + featured_media?: number; + missing_image_sizes?: string[]; + poster?: string; +} + +export type OnChangeHandler = ( attachments: Partial< Attachment >[] ) => void; +export type OnSuccessHandler = ( attachments: Partial< Attachment >[] ) => void; +export type OnErrorHandler = ( error: Error ) => void; +export type OnBatchSuccessHandler = () => void; + +export enum ItemStatus { + Processing = 'PROCESSING', + Paused = 'PAUSED', +} + +export enum OperationType { + Prepare = 'PREPARE', + Upload = 'UPLOAD', +} + +export interface OperationArgs {} + +type OperationWithArgs< T extends keyof OperationArgs = keyof OperationArgs > = + [ T, OperationArgs[ T ] ]; + +export type Operation = OperationType | OperationWithArgs; + +export type AdditionalData = Record< string, unknown >; + +export type ImageFormat = 'jpeg' | 'webp' | 'avif' | 'png' | 'gif'; diff --git a/packages/upload-media/src/stub-file.ts b/packages/upload-media/src/stub-file.ts new file mode 100644 index 00000000000000..f308c0d48b6f49 --- /dev/null +++ b/packages/upload-media/src/stub-file.ts @@ -0,0 +1,5 @@ +export class StubFile extends File { + constructor( fileName = 'stub-file' ) { + super( [], fileName ); + } +} diff --git a/packages/upload-media/src/test/get-file-basename.ts b/packages/upload-media/src/test/get-file-basename.ts new file mode 100644 index 00000000000000..6bf968a7643468 --- /dev/null +++ b/packages/upload-media/src/test/get-file-basename.ts @@ -0,0 +1,15 @@ +/** + * Internal dependencies + */ +import { getFileBasename } from '../utils'; + +describe( 'getFileBasename', () => { + it.each( [ + [ 'my-video.mp4', 'my-video' ], + [ 'my.video.mp4', 'my.video' ], + [ 'my-video', 'my-video' ], + [ '', '' ], + ] )( 'for file name %s returns basename %s', ( fileName, baseName ) => { + expect( getFileBasename( fileName ) ).toStrictEqual( baseName ); + } ); +} ); diff --git a/packages/upload-media/src/test/get-file-extension.ts b/packages/upload-media/src/test/get-file-extension.ts new file mode 100644 index 00000000000000..b26c4571be73fc --- /dev/null +++ b/packages/upload-media/src/test/get-file-extension.ts @@ -0,0 +1,15 @@ +/** + * Internal dependencies + */ +import { getFileExtension } from '../utils'; + +describe( 'getFileExtension', () => { + it.each( [ + [ 'my-video.mp4', 'mp4' ], + [ 'my.video.mp4', 'mp4' ], + [ 'my-video', null ], + [ '', null ], + ] )( 'for file name %s returns extension %s', ( fileName, baseName ) => { + expect( getFileExtension( fileName ) ).toStrictEqual( baseName ); + } ); +} ); diff --git a/packages/upload-media/src/test/get-file-name-from-url.ts b/packages/upload-media/src/test/get-file-name-from-url.ts new file mode 100644 index 00000000000000..6e2d497472e762 --- /dev/null +++ b/packages/upload-media/src/test/get-file-name-from-url.ts @@ -0,0 +1,14 @@ +/** + * Internal dependencies + */ +import { getFileNameFromUrl } from '../utils'; + +describe( 'getFileNameFromUrl', () => { + it.each( [ + [ 'https://example.com/', 'unnamed' ], + [ 'https://example.com/photo.jpeg', 'photo.jpeg' ], + [ 'https://example.com/path/to/video.mp4', 'video.mp4' ], + ] )( 'for %s returns %s', ( url, fileName ) => { + expect( getFileNameFromUrl( url ) ).toBe( fileName ); + } ); +} ); diff --git a/packages/upload-media/src/test/get-mime-types-array.ts b/packages/upload-media/src/test/get-mime-types-array.ts new file mode 100644 index 00000000000000..156955373bd0da --- /dev/null +++ b/packages/upload-media/src/test/get-mime-types-array.ts @@ -0,0 +1,47 @@ +/** + * Internal dependencies + */ +import { getMimeTypesArray } from '../get-mime-types-array'; + +describe( 'getMimeTypesArray', () => { + it( 'should return null if it is "falsy" e.g: undefined or null', () => { + expect( getMimeTypesArray( null ) ).toEqual( null ); + expect( getMimeTypesArray( undefined ) ).toEqual( null ); + } ); + + it( 'should return an empty array if an empty object is passed', () => { + expect( getMimeTypesArray( {} ) ).toEqual( [] ); + } ); + + it( 'should return the type plus a new mime type with type and subtype with the extension if a type is passed', () => { + expect( getMimeTypesArray( { ext: 'chicken' } ) ).toEqual( [ + 'chicken', + 'chicken/ext', + ] ); + } ); + + it( 'should return the mime type passed and a new mime type with type and the extension as subtype', () => { + expect( getMimeTypesArray( { ext: 'chicken/ribs' } ) ).toEqual( [ + 'chicken/ribs', + 'chicken/ext', + ] ); + } ); + + it( 'should return the mime type passed and an additional mime type per extension supported', () => { + expect( getMimeTypesArray( { 'jpg|jpeg|jpe': 'image/jpeg' } ) ).toEqual( + [ 'image/jpeg', 'image/jpg', 'image/jpeg', 'image/jpe' ] + ); + } ); + + it( 'should handle multiple mime types', () => { + expect( + getMimeTypesArray( { 'ext|aaa': 'chicken/ribs', aaa: 'bbb' } ) + ).toEqual( [ + 'chicken/ribs', + 'chicken/ext', + 'chicken/aaa', + 'bbb', + 'bbb/aaa', + ] ); + } ); +} ); diff --git a/packages/upload-media/src/test/image-file.ts b/packages/upload-media/src/test/image-file.ts new file mode 100644 index 00000000000000..e48ae2df6ebcef --- /dev/null +++ b/packages/upload-media/src/test/image-file.ts @@ -0,0 +1,15 @@ +/** + * Internal dependencies + */ +import { ImageFile } from '../image-file'; + +describe( 'ImageFile', () => { + it( 'returns whether the file was resizes', () => { + const file = new window.File( [ 'fake_file' ], 'test.jpeg', { + type: 'image/jpeg', + } ); + + const image = new ImageFile( file, 1000, 1000, 2000, 200 ); + expect( image.wasResized ).toBe( true ); + } ); +} ); diff --git a/packages/upload-media/src/test/upload-error.ts b/packages/upload-media/src/test/upload-error.ts new file mode 100644 index 00000000000000..4d5f025ed8cf39 --- /dev/null +++ b/packages/upload-media/src/test/upload-error.ts @@ -0,0 +1,24 @@ +/** + * Internal dependencies + */ +import { UploadError } from '../upload-error'; + +describe( 'UploadError', () => { + it( 'holds error code and file name', () => { + const file = new File( [], 'example.jpg', { + lastModified: 1234567891, + type: 'image/jpeg', + } ); + + const error = new UploadError( { + code: 'some_error', + message: 'An error occurred', + file, + } ); + + expect( error ).toStrictEqual( expect.any( Error ) ); + expect( error.code ).toBe( 'some_error' ); + expect( error.message ).toBe( 'An error occurred' ); + expect( error.file ).toBe( file ); + } ); +} ); diff --git a/packages/upload-media/src/test/validate-file-size.ts b/packages/upload-media/src/test/validate-file-size.ts new file mode 100644 index 00000000000000..31d6af0e7e4a55 --- /dev/null +++ b/packages/upload-media/src/test/validate-file-size.ts @@ -0,0 +1,70 @@ +/** + * Internal dependencies + */ +import { validateFileSize } from '../validate-file-size'; +import { UploadError } from '../upload-error'; + +const imageFile = new window.File( [ 'fake_file' ], 'test.jpeg', { + type: 'image/jpeg', +} ); + +const emptyFile = new window.File( [], 'test.jpeg', { + type: 'image/jpeg', +} ); + +describe( 'validateFileSize', () => { + afterEach( () => { + jest.clearAllMocks(); + } ); + + it( 'should error if the file is empty', () => { + expect( () => { + validateFileSize( emptyFile ); + } ).toThrow( + new UploadError( { + code: 'EMPTY_FILE', + message: 'test.jpeg: This file is empty.', + file: imageFile, + } ) + ); + } ); + + it( 'should error if the file is is greater than the maximum', () => { + expect( () => { + validateFileSize( imageFile, 2 ); + } ).toThrow( + new UploadError( { + code: 'SIZE_ABOVE_LIMIT', + message: + 'test.jpeg: This file exceeds the maximum upload size for this site.', + file: imageFile, + } ) + ); + } ); + + it( 'should not error if the file is below the limit', () => { + expect( () => { + validateFileSize( imageFile, 100 ); + } ).not.toThrow( + new UploadError( { + code: 'SIZE_ABOVE_LIMIT', + message: + 'test.jpeg: This file exceeds the maximum upload size for this site.', + file: imageFile, + } ) + ); + } ); + + it( 'should not error if there is no limit', () => { + expect( () => { + validateFileSize( imageFile ); + } ).not.toThrow( + new UploadError( { + code: 'SIZE_ABOVE_LIMIT', + message: + 'test.jpeg: This file exceeds the maximum upload size for this site.', + file: imageFile, + } ) + ); + } ); +} ); diff --git a/packages/upload-media/src/test/validate-mime-type-for-user.ts b/packages/upload-media/src/test/validate-mime-type-for-user.ts new file mode 100644 index 00000000000000..d2566566862142 --- /dev/null +++ b/packages/upload-media/src/test/validate-mime-type-for-user.ts @@ -0,0 +1,37 @@ +/** + * Internal dependencies + */ +import { validateMimeTypeForUser } from '../validate-mime-type-for-user'; +import { UploadError } from '../upload-error'; + +const imageFile = new window.File( [ 'fake_file' ], 'test.jpeg', { + type: 'image/jpeg', +} ); + +describe( 'validateMimeTypeForUser', () => { + afterEach( () => { + jest.clearAllMocks(); + } ); + + it( 'should not error if wpAllowedMimeTypes is null or missing', async () => { + expect( () => { + validateMimeTypeForUser( imageFile ); + } ).not.toThrow(); + expect( () => { + validateMimeTypeForUser( imageFile, null ); + } ).not.toThrow(); + } ); + + it( 'should error if file type is not allowed for user', async () => { + expect( () => { + validateMimeTypeForUser( imageFile, { aac: 'audio/aac' } ); + } ).toThrow( + new UploadError( { + code: 'MIME_TYPE_NOT_ALLOWED_FOR_USER', + message: + 'test.jpeg: Sorry, you are not allowed to upload this file type.', + file: imageFile, + } ) + ); + } ); +} ); diff --git a/packages/upload-media/src/test/validate-mime-type.ts b/packages/upload-media/src/test/validate-mime-type.ts new file mode 100644 index 00000000000000..a83cdcefe5f99a --- /dev/null +++ b/packages/upload-media/src/test/validate-mime-type.ts @@ -0,0 +1,57 @@ +/** + * Internal dependencies + */ +import { validateMimeType } from '../validate-mime-type'; +import { UploadError } from '../upload-error'; + +const xmlFile = new window.File( [ 'fake_file' ], 'test.xml', { + type: 'text/xml', +} ); +const imageFile = new window.File( [ 'fake_file' ], 'test.jpeg', { + type: 'image/jpeg', +} ); + +describe( 'validateMimeType', () => { + afterEach( () => { + jest.clearAllMocks(); + } ); + + it( 'should error if allowedTypes contains a partial mime type and the validation fails', async () => { + expect( () => { + validateMimeType( xmlFile, [ 'image' ] ); + } ).toThrow( + new UploadError( { + code: 'MIME_TYPE_NOT_SUPPORTED', + message: + 'test.xml: Sorry, this file type is not supported here.', + file: xmlFile, + } ) + ); + } ); + + it( 'should error if allowedTypes contains a complete mime type and the validation fails', async () => { + expect( () => { + validateMimeType( imageFile, [ 'image/gif' ] ); + } ).toThrow( + new UploadError( { + code: 'MIME_TYPE_NOT_SUPPORTED', + message: + 'test.jpeg: Sorry, this file type is not supported here.', + file: xmlFile, + } ) + ); + } ); + + it( 'should error if allowedTypes contains multiple types and the validation fails', async () => { + expect( () => { + validateMimeType( xmlFile, [ 'video', 'image' ] ); + } ).toThrow( + new UploadError( { + code: 'MIME_TYPE_NOT_SUPPORTED', + message: + 'test.xml: Sorry, this file type is not supported here.', + file: xmlFile, + } ) + ); + } ); +} ); diff --git a/packages/upload-media/src/upload-error.ts b/packages/upload-media/src/upload-error.ts new file mode 100644 index 00000000000000..d712e9dcdb6966 --- /dev/null +++ b/packages/upload-media/src/upload-error.ts @@ -0,0 +1,26 @@ +interface UploadErrorArgs { + code: string; + message: string; + file: File; + cause?: Error; +} + +/** + * MediaError class. + * + * Small wrapper around the `Error` class + * to hold an error code and a reference to a file object. + */ +export class UploadError extends Error { + code: string; + file: File; + + constructor( { code, message, file, cause }: UploadErrorArgs ) { + super( message, { cause } ); + + Object.setPrototypeOf( this, new.target.prototype ); + + this.code = code; + this.file = file; + } +} diff --git a/packages/upload-media/src/utils.ts b/packages/upload-media/src/utils.ts new file mode 100644 index 00000000000000..3950ec03887928 --- /dev/null +++ b/packages/upload-media/src/utils.ts @@ -0,0 +1,90 @@ +/** + * WordPress dependencies + */ +import { getFilename } from '@wordpress/url'; +import { _x } from '@wordpress/i18n'; + +/** + * Converts a Blob to a File with a default name like "image.png". + * + * If it is already a File object, it is returned unchanged. + * + * @param fileOrBlob Blob object. + * @return File object. + */ +export function convertBlobToFile( fileOrBlob: Blob | File ): File { + if ( fileOrBlob instanceof File ) { + return fileOrBlob; + } + + // Extension is only an approximation. + // The server will override it if incorrect. + const ext = fileOrBlob.type.split( '/' )[ 1 ]; + const mediaType = + 'application/pdf' === fileOrBlob.type + ? 'document' + : fileOrBlob.type.split( '/' )[ 0 ]; + return new File( [ fileOrBlob ], `${ mediaType }.${ ext }`, { + type: fileOrBlob.type, + } ); +} + +/** + * Renames a given file and returns a new file. + * + * Copies over the last modified time. + * + * @param file File object. + * @param name File name. + * @return Renamed file object. + */ +export function renameFile( file: File, name: string ): File { + return new File( [ file ], name, { + type: file.type, + lastModified: file.lastModified, + } ); +} + +/** + * Clones a given file object. + * + * @param file File object. + * @return New file object. + */ +export function cloneFile( file: File ): File { + return renameFile( file, file.name ); +} + +/** + * Returns the file extension from a given file name or URL. + * + * @param file File URL. + * @return File extension or null if it does not have one. + */ +export function getFileExtension( file: string ): string | null { + return file.includes( '.' ) ? file.split( '.' ).pop() || null : null; +} + +/** + * Returns file basename without extension. + * + * For example, turns "my-awesome-file.jpeg" into "my-awesome-file". + * + * @param name File name. + * @return File basename. + */ +export function getFileBasename( name: string ): string { + return name.includes( '.' ) + ? name.split( '.' ).slice( 0, -1 ).join( '.' ) + : name; +} + +/** + * Returns the file name including extension from a URL. + * + * @param url File URL. + * @return File name. + */ +export function getFileNameFromUrl( url: string ) { + return getFilename( url ) || _x( 'unnamed', 'file name' ); +} diff --git a/packages/upload-media/src/validate-file-size.ts b/packages/upload-media/src/validate-file-size.ts new file mode 100644 index 00000000000000..cc34462b268dda --- /dev/null +++ b/packages/upload-media/src/validate-file-size.ts @@ -0,0 +1,44 @@ +/** + * WordPress dependencies + */ +import { __, sprintf } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import { UploadError } from './upload-error'; + +/** + * Verifies whether the file is within the file upload size limits for the site. + * + * @param file File object. + * @param maxUploadFileSize Maximum upload size in bytes allowed for the site. + */ +export function validateFileSize( file: File, maxUploadFileSize?: number ) { + // Don't allow empty files to be uploaded. + if ( file.size <= 0 ) { + throw new UploadError( { + code: 'EMPTY_FILE', + message: sprintf( + // translators: %s: file name. + __( '%s: This file is empty.' ), + file.name + ), + file, + } ); + } + + if ( maxUploadFileSize && file.size > maxUploadFileSize ) { + throw new UploadError( { + code: 'SIZE_ABOVE_LIMIT', + message: sprintf( + // translators: %s: file name. + __( + '%s: This file exceeds the maximum upload size for this site.' + ), + file.name + ), + file, + } ); + } +} diff --git a/packages/upload-media/src/validate-mime-type-for-user.ts b/packages/upload-media/src/validate-mime-type-for-user.ts new file mode 100644 index 00000000000000..858c583561978e --- /dev/null +++ b/packages/upload-media/src/validate-mime-type-for-user.ts @@ -0,0 +1,46 @@ +/** + * WordPress dependencies + */ +import { __, sprintf } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import { UploadError } from './upload-error'; +import { getMimeTypesArray } from './get-mime-types-array'; + +/** + * Verifies if the user is allowed to upload this mime type. + * + * @param file File object. + * @param wpAllowedMimeTypes List of allowed mime types and file extensions. + */ +export function validateMimeTypeForUser( + file: File, + wpAllowedMimeTypes?: Record< string, string > | null +) { + // Allowed types for the current WP_User. + const allowedMimeTypesForUser = getMimeTypesArray( wpAllowedMimeTypes ); + + if ( ! allowedMimeTypesForUser ) { + return; + } + + const isAllowedMimeTypeForUser = allowedMimeTypesForUser.includes( + file.type + ); + + if ( file.type && ! isAllowedMimeTypeForUser ) { + throw new UploadError( { + code: 'MIME_TYPE_NOT_ALLOWED_FOR_USER', + message: sprintf( + // translators: %s: file name. + __( + '%s: Sorry, you are not allowed to upload this file type.' + ), + file.name + ), + file, + } ); + } +} diff --git a/packages/upload-media/src/validate-mime-type.ts b/packages/upload-media/src/validate-mime-type.ts new file mode 100644 index 00000000000000..2d99455d7b60f1 --- /dev/null +++ b/packages/upload-media/src/validate-mime-type.ts @@ -0,0 +1,43 @@ +/** + * WordPress dependencies + */ +import { __, sprintf } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import { UploadError } from './upload-error'; + +/** + * Verifies if the caller (e.g. a block) supports this mime type. + * + * @param file File object. + * @param allowedTypes List of allowed mime types. + */ +export function validateMimeType( file: File, allowedTypes?: string[] ) { + if ( ! allowedTypes ) { + return; + } + + // Allowed type specified by consumer. + const isAllowedType = allowedTypes.some( ( allowedType ) => { + // If a complete mimetype is specified verify if it matches exactly the mime type of the file. + if ( allowedType.includes( '/' ) ) { + return allowedType === file.type; + } + // Otherwise a general mime type is used, and we should verify if the file mimetype starts with it. + return file.type.startsWith( `${ allowedType }/` ); + } ); + + if ( file.type && ! isAllowedType ) { + throw new UploadError( { + code: 'MIME_TYPE_NOT_SUPPORTED', + message: sprintf( + // translators: %s: file name. + __( '%s: Sorry, this file type is not supported here.' ), + file.name + ), + file, + } ); + } +} diff --git a/packages/upload-media/tsconfig.json b/packages/upload-media/tsconfig.json new file mode 100644 index 00000000000000..df9f913b1e11b7 --- /dev/null +++ b/packages/upload-media/tsconfig.json @@ -0,0 +1,17 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig.json", + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "types": [ "gutenberg-env" ] + }, + "references": [ + { "path": "../api-fetch" }, + { "path": "../blob" }, + { "path": "../compose" }, + { "path": "../data" }, + { "path": "../element" }, + { "path": "../i18n" }, + { "path": "../private-apis" }, + { "path": "../url" } + ] +} diff --git a/packages/url/CHANGELOG.md b/packages/url/CHANGELOG.md index 0e0ac497785e46..6544673cacd109 100644 --- a/packages/url/CHANGELOG.md +++ b/packages/url/CHANGELOG.md @@ -2,6 +2,16 @@ ## Unreleased +## 4.16.0 (2025-01-15) + +## 4.15.0 (2025-01-02) + +## 4.14.0 (2024-12-11) + +## 4.13.0 (2024-11-27) + +## 4.12.0 (2024-11-16) + ## 4.11.0 (2024-10-30) ## 4.10.0 (2024-10-16) @@ -216,7 +226,7 @@ ### Bug Fixes -- The `isValidProtocol` function now correctly considers the protocol of the URL as only incoporating characters up to and including the colon (':'). +- The `isValidProtocol` function now correctly considers the protocol of the URL as only incorporating characters up to and including the colon (':'). - `getFragment` is now greedier and matches fragments from the first occurrence of the '#' symbol instead of the last. ## 2.3.0 (2018-11-12) diff --git a/packages/url/package.json b/packages/url/package.json index 2ecdb8eae18113..36221de0b8e72e 100644 --- a/packages/url/package.json +++ b/packages/url/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/url", - "version": "4.11.0", + "version": "4.16.0", "description": "WordPress URL utilities.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/url/src/is-phone-number.js b/packages/url/src/is-phone-number.js index 857b468bc52398..ed7aad1a3540ea 100644 --- a/packages/url/src/is-phone-number.js +++ b/packages/url/src/is-phone-number.js @@ -13,7 +13,7 @@ const PHONE_REGEXP = /^(tel:)?(\+)?\d{6,15}$/; * @return {boolean} Whether or not it looks like a phone number. */ export function isPhoneNumber( phoneNumber ) { - // Remove any seperator from phone number. + // Remove any separator from phone number. phoneNumber = phoneNumber.replace( /[-.() ]/g, '' ); return PHONE_REGEXP.test( phoneNumber ); } diff --git a/packages/url/src/normalize-path.js b/packages/url/src/normalize-path.js index 57c9d1a5ab6795..eb1cafed083657 100644 --- a/packages/url/src/normalize-path.js +++ b/packages/url/src/normalize-path.js @@ -8,9 +8,9 @@ * @return {string} Normalized path. */ export function normalizePath( path ) { - const splitted = path.split( '?' ); - const query = splitted[ 1 ]; - const base = splitted[ 0 ]; + const split = path.split( '?' ); + const query = split[ 1 ]; + const base = split[ 0 ]; if ( ! query ) { return base; } diff --git a/packages/url/src/test/index.js b/packages/url/src/test/index.js index 4fc3d5e2970d67..3d622ad2d8db7c 100644 --- a/packages/url/src/test/index.js +++ b/packages/url/src/test/index.js @@ -796,7 +796,7 @@ describe( 'getQueryArg', () => { expect( getQueryArg( url, 'baz' ) ).toBeUndefined(); } ); - it( 'should get the value of an arry query arg', () => { + it( 'should get the value of an array query arg', () => { const url = 'https://andalouses.example/beach?foo[]=bar&foo[]=baz'; expect( getQueryArg( url, 'foo' ) ).toEqual( [ 'bar', 'baz' ] ); @@ -823,7 +823,7 @@ describe( 'hasQueryArg', () => { expect( hasQueryArg( url, 'baz' ) ).toBeFalsy(); } ); - it( 'should return true for an arry query arg', () => { + it( 'should return true for an array query arg', () => { const url = 'https://andalouses.example/beach?foo[]=bar&foo[]=baz'; expect( hasQueryArg( url, 'foo' ) ).toBeTruthy(); diff --git a/packages/url/tsconfig.json b/packages/url/tsconfig.json index 6e33d8ff82d47e..7ff060ab6ce105 100644 --- a/packages/url/tsconfig.json +++ b/packages/url/tsconfig.json @@ -1,9 +1,4 @@ { "$schema": "https://json.schemastore.org/tsconfig.json", - "extends": "../../tsconfig.base.json", - "compilerOptions": { - "rootDir": "src", - "declarationDir": "build-types" - }, - "include": [ "src/**/*" ] + "extends": "../../tsconfig.base.json" } diff --git a/packages/viewport/CHANGELOG.md b/packages/viewport/CHANGELOG.md index ce3f20a6d8e2b7..1e9831981822a3 100644 --- a/packages/viewport/CHANGELOG.md +++ b/packages/viewport/CHANGELOG.md @@ -2,6 +2,16 @@ ## Unreleased +## 6.16.0 (2025-01-15) + +## 6.15.0 (2025-01-02) + +## 6.14.0 (2024-12-11) + +## 6.13.0 (2024-11-27) + +## 6.12.0 (2024-11-16) + ## 6.11.0 (2024-10-30) ## 6.10.0 (2024-10-16) diff --git a/packages/viewport/package.json b/packages/viewport/package.json index 224da8a871addf..33b7c2c290db6d 100644 --- a/packages/viewport/package.json +++ b/packages/viewport/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/viewport", - "version": "6.11.0", + "version": "6.16.0", "description": "Viewport module for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -28,9 +28,9 @@ "wpScript": true, "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/compose": "*", - "@wordpress/data": "*", - "@wordpress/element": "*" + "@wordpress/compose": "file:../compose", + "@wordpress/data": "file:../data", + "@wordpress/element": "file:../element" }, "peerDependencies": { "react": "^18.0.0" diff --git a/packages/vips/README.md b/packages/vips/README.md index d7bf6001b51027..fe367e3bb6490c 100644 --- a/packages/vips/README.md +++ b/packages/vips/README.md @@ -42,7 +42,7 @@ _Parameters_ _Returns_ -- `Promise< ArrayBuffer >`: Compressed file data. +- `Promise< ArrayBuffer | ArrayBufferLike >`: Compressed file data. ### convertImageFormat @@ -83,7 +83,7 @@ _Parameters_ _Returns_ -- `Promise< { buffer: ArrayBuffer; width: number; height: number; originalWidth: number; originalHeight: number; } >`: Processed file data plus the old and new dimensions. +- `Promise< { buffer: ArrayBuffer | ArrayBufferLike; width: number; height: number; originalWidth: number; originalHeight: number; } >`: Processed file data plus the old and new dimensions. ### setLocation diff --git a/packages/vips/src/index.ts b/packages/vips/src/index.ts index 5dffe1f0808f28..dcd1858f0e57fb 100644 --- a/packages/vips/src/index.ts +++ b/packages/vips/src/index.ts @@ -122,7 +122,7 @@ export async function convertImageFormat( outputType: string, quality = 0.82, interlaced = false -): Promise< ArrayBuffer > { +): Promise< ArrayBuffer | ArrayBufferLike > { const ext = outputType.split( '/' )[ 1 ]; inProgressOperations.add( id ); @@ -186,7 +186,7 @@ export async function compressImage( type: string, quality = 0.82, interlaced = false -): Promise< ArrayBuffer > { +): Promise< ArrayBuffer | ArrayBufferLike > { return convertImageFormat( id, buffer, type, type, quality, interlaced ); } @@ -207,7 +207,7 @@ export async function resizeImage( resize: ImageSizeCrop, smartCrop = false ): Promise< { - buffer: ArrayBuffer; + buffer: ArrayBuffer | ArrayBufferLike; width: number; height: number; originalWidth: number; diff --git a/packages/vips/tsconfig.json b/packages/vips/tsconfig.json index 6e33d8ff82d47e..7ff060ab6ce105 100644 --- a/packages/vips/tsconfig.json +++ b/packages/vips/tsconfig.json @@ -1,9 +1,4 @@ { "$schema": "https://json.schemastore.org/tsconfig.json", - "extends": "../../tsconfig.base.json", - "compilerOptions": { - "rootDir": "src", - "declarationDir": "build-types" - }, - "include": [ "src/**/*" ] + "extends": "../../tsconfig.base.json" } diff --git a/packages/warning/CHANGELOG.md b/packages/warning/CHANGELOG.md index e6ae7b4a229850..5c629bc33745da 100644 --- a/packages/warning/CHANGELOG.md +++ b/packages/warning/CHANGELOG.md @@ -2,6 +2,16 @@ ## Unreleased +## 3.16.0 (2025-01-15) + +## 3.15.0 (2025-01-02) + +## 3.14.0 (2024-12-11) + +## 3.13.0 (2024-11-27) + +## 3.12.0 (2024-11-16) + ## 3.11.0 (2024-10-30) ## 3.10.0 (2024-10-16) diff --git a/packages/warning/package.json b/packages/warning/package.json index d9c5a4dd83dc10..cfc9e7127b3952 100644 --- a/packages/warning/package.json +++ b/packages/warning/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/warning", - "version": "3.11.0", + "version": "3.16.0", "description": "Warning utility for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/warning/tsconfig.json b/packages/warning/tsconfig.json index 9e3edfe0ae443c..f197b56919708b 100644 --- a/packages/warning/tsconfig.json +++ b/packages/warning/tsconfig.json @@ -2,9 +2,6 @@ "$schema": "https://json.schemastore.org/tsconfig.json", "extends": "../../tsconfig.base.json", "compilerOptions": { - "rootDir": "src", - "declarationDir": "build-types", "types": [ "gutenberg-env" ] - }, - "include": [ "src/**/*" ] + } } diff --git a/packages/widgets/CHANGELOG.md b/packages/widgets/CHANGELOG.md index 367add006f8895..403d8ec209218a 100644 --- a/packages/widgets/CHANGELOG.md +++ b/packages/widgets/CHANGELOG.md @@ -2,6 +2,16 @@ ## Unreleased +## 4.16.0 (2025-01-15) + +## 4.15.0 (2025-01-02) + +## 4.14.0 (2024-12-11) + +## 4.13.0 (2024-11-27) + +## 4.12.0 (2024-11-16) + ## 4.11.0 (2024-10-30) ## 4.10.0 (2024-10-16) diff --git a/packages/widgets/package.json b/packages/widgets/package.json index a755890c48ed92..3020b68c6f429d 100644 --- a/packages/widgets/package.json +++ b/packages/widgets/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/widgets", - "version": "4.11.0", + "version": "4.16.0", "description": "Functionality used by the widgets block editor in the Widgets screen and the Customizer.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -26,17 +26,17 @@ "wpScript": true, "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/api-fetch": "*", - "@wordpress/block-editor": "*", - "@wordpress/blocks": "*", - "@wordpress/components": "*", - "@wordpress/compose": "*", - "@wordpress/core-data": "*", - "@wordpress/data": "*", - "@wordpress/element": "*", - "@wordpress/i18n": "*", - "@wordpress/icons": "*", - "@wordpress/notices": "*", + "@wordpress/api-fetch": "file:../api-fetch", + "@wordpress/block-editor": "file:../block-editor", + "@wordpress/blocks": "file:../blocks", + "@wordpress/components": "file:../components", + "@wordpress/compose": "file:../compose", + "@wordpress/core-data": "file:../core-data", + "@wordpress/data": "file:../data", + "@wordpress/element": "file:../element", + "@wordpress/i18n": "file:../i18n", + "@wordpress/icons": "file:../icons", + "@wordpress/notices": "file:../notices", "clsx": "^2.1.1" }, "peerDependencies": { diff --git a/packages/widgets/src/blocks/legacy-widget/edit/control.js b/packages/widgets/src/blocks/legacy-widget/edit/control.js index 250fb7f2078f03..e1f512aa07c30f 100644 --- a/packages/widgets/src/blocks/legacy-widget/edit/control.js +++ b/packages/widgets/src/blocks/legacy-widget/edit/control.js @@ -8,7 +8,7 @@ import { __ } from '@wordpress/i18n'; /** * An API for creating and loading a widget control (a <div class="widget"> * element) that is compatible with most third party widget scripts. By not - * using React for this, we ensure that we have complete contorl over the DOM + * using React for this, we ensure that we have complete control over the DOM * and do not accidentally remove any elements that a third party widget script * has attached an event listener to. * @@ -60,7 +60,7 @@ export default class Control { } /** - * Clean up the control so that it can be garabge collected. + * Clean up the control so that it can be garbage collected. * * @access public */ diff --git a/packages/wordcount/CHANGELOG.md b/packages/wordcount/CHANGELOG.md index 62b4b2efe1506e..0edbe6b8b21c10 100644 --- a/packages/wordcount/CHANGELOG.md +++ b/packages/wordcount/CHANGELOG.md @@ -2,6 +2,16 @@ ## Unreleased +## 4.16.0 (2025-01-15) + +## 4.15.0 (2025-01-02) + +## 4.14.0 (2024-12-11) + +## 4.13.0 (2024-11-27) + +## 4.12.0 (2024-11-16) + ## 4.11.0 (2024-10-30) ## 4.10.0 (2024-10-16) diff --git a/packages/wordcount/package.json b/packages/wordcount/package.json index 117e2227a926c8..c05f8ed9aee13b 100644 --- a/packages/wordcount/package.json +++ b/packages/wordcount/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/wordcount", - "version": "4.11.0", + "version": "4.16.0", "description": "WordPress word count utility.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/wordcount/tsconfig.json b/packages/wordcount/tsconfig.json index 6e33d8ff82d47e..7ff060ab6ce105 100644 --- a/packages/wordcount/tsconfig.json +++ b/packages/wordcount/tsconfig.json @@ -1,9 +1,4 @@ { "$schema": "https://json.schemastore.org/tsconfig.json", - "extends": "../../tsconfig.base.json", - "compilerOptions": { - "rootDir": "src", - "declarationDir": "build-types" - }, - "include": [ "src/**/*" ] + "extends": "../../tsconfig.base.json" } diff --git a/patches/README.md b/patches/README.md index 8149c96950057a..b811c824532c37 100644 --- a/patches/README.md +++ b/patches/README.md @@ -24,8 +24,16 @@ See https://github.com/facebook/react/pull/17883. ### `patches/react-devtools-core+4.28.5.patch` -No notes. +The mobile editor relies upon `jsdom-jscore-rn` to create a partial DOM +environment, which causes `react-devtools-core` to believe it's running in a +browser environment. We added a custom conditional to disable browser-specific +features when running in a `jsdom-jscore-rn` environment. + +See https://github.com/WordPress/gutenberg/pull/47616. ### `patches/react-native+0.73.3.patch` -No notes. +Accessibility changes in React Native 0.73.0 resulted in a broken braille screen +input on iOS. This patch is a workaround to restore the previous behavior. + +See https://github.com/WordPress/gutenberg/pull/53895. diff --git a/patches/lighthouse+10.4.0.patch b/patches/lighthouse+12.2.2.patch similarity index 52% rename from patches/lighthouse+10.4.0.patch rename to patches/lighthouse+12.2.2.patch index 377647186d4b6b..579f3475bb8885 100644 --- a/patches/lighthouse+10.4.0.patch +++ b/patches/lighthouse+12.2.2.patch @@ -1,11 +1,12 @@ diff --git a/node_modules/lighthouse/core/index.d.cts b/node_modules/lighthouse/core/index.d.cts -index 1c399e1..23c3d1f 100644 +index a3645a1..b105ca3 100644 --- a/node_modules/lighthouse/core/index.d.cts +++ b/node_modules/lighthouse/core/index.d.cts -@@ -1,4 +1,6 @@ - export = lighthouse; - /** @type {import('./index.js')['default']} */ +@@ -7,5 +7,7 @@ export = lighthouse; + * @property {import('./index.js')['snapshot']} snapshot + */ + /** @type {import('./index.js')['default'] & ExportType} */ +// Otherwise TS is confused when using ES types in CJS. +// @ts-ignore - declare const lighthouse: typeof import("./index.js")['default']; + declare const lighthouse: typeof import("./index.js")["default"] & ExportType; //# sourceMappingURL=index.d.cts.map diff --git a/patches/storybook-source-link+2.0.9.patch b/patches/storybook-source-link+2.0.9.patch new file mode 100644 index 00000000000000..c1db218d953a90 --- /dev/null +++ b/patches/storybook-source-link+2.0.9.patch @@ -0,0 +1,55 @@ +diff --git a/node_modules/storybook-source-link/dist/esm/Tool.js b/node_modules/storybook-source-link/dist/esm/Tool.js +index 100099e..53d37c4 100644 +--- a/node_modules/storybook-source-link/dist/esm/Tool.js ++++ b/node_modules/storybook-source-link/dist/esm/Tool.js +@@ -1,7 +1,8 @@ + import React from "react"; +-import { Icons, IconButton, TooltipMessage, WithTooltip } from "@storybook/components"; +-import { PARAM_KEY, PREFIX_PARAM_KEY, ICON_PARAM_KEY, INFO_LINK, TOOL_ID } from "./constants"; +-import { useParameter } from '@storybook/api'; ++import { IconButton, TooltipMessage, WithTooltip } from "@storybook/components"; ++import { RepoIcon } from '@storybook/icons'; ++import { PARAM_KEY, PREFIX_PARAM_KEY, INFO_LINK, TOOL_ID } from "./constants"; ++import { useParameter } from '@storybook/manager-api'; + + var Tooltip = function Tooltip() { + return /*#__PURE__*/React.createElement(TooltipMessage, { +@@ -24,7 +25,6 @@ export var getLink = function getLink(prefix, link) { + export var Tool = function Tool() { + var param_link = useParameter(PARAM_KEY, null); + var param_prefix = useParameter(PREFIX_PARAM_KEY, null); +- var param_icon = useParameter(ICON_PARAM_KEY, "repository"); + var link = getLink(param_prefix, param_link); + return link ? /*#__PURE__*/React.createElement(IconButton, { + key: TOOL_ID, +@@ -35,9 +35,7 @@ export var Tool = function Tool() { + window.open(link); + } + } +- }, /*#__PURE__*/React.createElement(Icons, { +- icon: param_icon +- })) : /*#__PURE__*/React.createElement(WithTooltip, { ++ }, /*#__PURE__*/React.createElement(RepoIcon)) : /*#__PURE__*/React.createElement(WithTooltip, { + placement: "top", + trigger: "click", + tooltip: /*#__PURE__*/React.createElement(Tooltip, null) +@@ -45,7 +43,5 @@ export var Tool = function Tool() { + key: TOOL_ID, + title: "View Source Repository", + active: false +- }, /*#__PURE__*/React.createElement(Icons, { +- icon: param_icon +- }))); ++ }, /*#__PURE__*/React.createElement(RepoIcon))); + }; +\ No newline at end of file +diff --git a/node_modules/storybook-source-link/dist/esm/preset/manager.js b/node_modules/storybook-source-link/dist/esm/preset/manager.js +index 845f81d..ca1d066 100644 +--- a/node_modules/storybook-source-link/dist/esm/preset/manager.js ++++ b/node_modules/storybook-source-link/dist/esm/preset/manager.js +@@ -1,4 +1,4 @@ +-import { addons, types } from "@storybook/addons"; ++import { addons, types } from "@storybook/manager-api"; + import { ADDON_ID, TOOL_ID } from "../constants"; + import { Tool } from "../Tool"; // Register the addon + diff --git a/phpunit/block-supports/typography-test.php b/phpunit/block-supports/typography-test.php index 1804659c11af3c..c7701b673e7a7d 100644 --- a/phpunit/block-supports/typography-test.php +++ b/phpunit/block-supports/typography-test.php @@ -283,111 +283,6 @@ public function test_should_generate_classname_for_font_family() { $this->assertSame( $expected, $actual ); } - /** - * Tests that stabilized typography supports will also apply to blocks using - * the experimental syntax, for backwards compatibility with existing blocks. - * - * @covers ::gutenberg_apply_typography_support - */ - public function test_should_apply_experimental_typography_supports() { - $this->test_block_name = 'test/experimental-typography-supports'; - register_block_type( - $this->test_block_name, - array( - 'api_version' => 3, - 'attributes' => array( - 'style' => array( - 'type' => 'object', - ), - ), - 'supports' => array( - 'typography' => array( - '__experimentalFontFamily' => true, - '__experimentalFontStyle' => true, - '__experimentalFontWeight' => true, - '__experimentalLetterSpacing' => true, - '__experimentalTextDecoration' => true, - '__experimentalTextTransform' => true, - ), - ), - ) - ); - $registry = WP_Block_Type_Registry::get_instance(); - $block_type = $registry->get_registered( $this->test_block_name ); - $block_atts = array( - 'fontFamily' => 'serif', - 'style' => array( - 'typography' => array( - 'fontStyle' => 'italic', - 'fontWeight' => 'bold', - 'letterSpacing' => '1px', - 'textDecoration' => 'underline', - 'textTransform' => 'uppercase', - ), - ), - ); - - $actual = gutenberg_apply_typography_support( $block_type, $block_atts ); - $expected = array( - 'class' => 'has-serif-font-family', - 'style' => 'font-style:italic;font-weight:bold;text-decoration:underline;text-transform:uppercase;letter-spacing:1px;', - ); - - $this->assertSame( $expected, $actual ); - } - - /** - * Tests that stabilized typography supports are applied correctly. - * - * @covers ::gutenberg_apply_typography_support - */ - public function test_should_apply_stabilized_typography_supports() { - $this->test_block_name = 'test/experimental-typography-supports'; - register_block_type( - $this->test_block_name, - array( - 'api_version' => 3, - 'attributes' => array( - 'style' => array( - 'type' => 'object', - ), - ), - 'supports' => array( - 'typography' => array( - 'fontFamily' => true, - 'fontStyle' => true, - 'fontWeight' => true, - 'letterSpacing' => true, - 'textDecoration' => true, - 'textTransform' => true, - ), - ), - ) - ); - $registry = WP_Block_Type_Registry::get_instance(); - $block_type = $registry->get_registered( $this->test_block_name ); - $block_atts = array( - 'fontFamily' => 'serif', - 'style' => array( - 'typography' => array( - 'fontStyle' => 'italic', - 'fontWeight' => 'bold', - 'letterSpacing' => '1px', - 'textDecoration' => 'underline', - 'textTransform' => 'uppercase', - ), - ), - ); - - $actual = gutenberg_apply_typography_support( $block_type, $block_atts ); - $expected = array( - 'class' => 'has-serif-font-family', - 'style' => 'font-style:italic;font-weight:bold;text-decoration:underline;text-transform:uppercase;letter-spacing:1px;', - ); - - $this->assertSame( $expected, $actual ); - } - /** * Tests generating font size values, including fluid formulae, from fontSizes preset. * @@ -1064,7 +959,7 @@ public function data_generate_should_override_theme_settings_fixtures() { * @param string $theme_slug A theme slug corresponding to an available test theme. * @param string $expected_output Expected value of style property from gutenberg_apply_typography_support(). */ - public function test_should_covert_font_sizes_to_fluid_values( $font_size_value, $theme_slug, $expected_output ) { + public function test_should_convert_font_sizes_to_fluid_values( $font_size_value, $theme_slug, $expected_output ) { switch_theme( $theme_slug ); $this->test_block_name = 'test/font-size-fluid-value'; @@ -1101,7 +996,7 @@ public function test_should_covert_font_sizes_to_fluid_values( $font_size_value, } /** - * Data provider for test_should_covert_font_sizes_to_fluid_values. + * Data provider for test_should_convert_font_sizes_to_fluid_values. * * @return array */ diff --git a/phpunit/blocks/block-navigation-block-hooks-test.php b/phpunit/blocks/block-navigation-block-hooks-test.php deleted file mode 100644 index 1d3c86bf4bca4b..00000000000000 --- a/phpunit/blocks/block-navigation-block-hooks-test.php +++ /dev/null @@ -1,148 +0,0 @@ -<?php -/** - * Navigation block block hooks tests. - * - * @package WordPress - * @subpackage Blocks - */ - -/** - * Tests for the Navigation block. - * - * @group blocks - */ -class Block_Navigation_Block_Hooks_Test extends WP_UnitTestCase { - /** - * Original markup. - * - * @var string - */ - protected static $original_markup; - - /** - * Post object. - * - * @var object - */ - protected static $navigation_post; - - /** - * Setup method. - */ - public static function wpSetUpBeforeClass() { - //self::$original_markup = '<!-- wp:navigation-link {"label":"News & About","type":"page","id":2,"url":"http://localhost:8888/?page_id=2","kind":"post-type"} /-->'; - - self::$navigation_post = self::factory()->post->create_and_get( - array( - 'post_type' => 'wp_navigation', - 'post_title' => 'Navigation Menu', - 'post_content' => 'Original content', - ) - ); - } - - /** - * Tear down each test method. - */ - public function tear_down() { - $registry = WP_Block_Type_Registry::get_instance(); - - if ( $registry->is_registered( 'tests/my-block' ) ) { - $registry->unregister( 'tests/my-block' ); - } - - parent::tear_down(); - } - - /** - * @covers ::gutenberg_block_core_navigation_update_ignore_hooked_blocks_meta - */ - public function test_block_core_navigation_update_ignore_hooked_blocks_meta_preserves_entities() { - register_block_type( - 'tests/my-block', - array( - 'block_hooks' => array( - 'core/navigation' => 'last_child', - ), - ) - ); - - $original_markup = '<!-- wp:navigation-link {"label":"News & About","type":"page","id":2,"url":"http://localhost:8888/?page_id=2","kind":"post-type"} /-->'; - $post = new stdClass(); - $post->ID = self::$navigation_post->ID; - $post->post_content = $original_markup; - - $post = gutenberg_block_core_navigation_update_ignore_hooked_blocks_meta( $post ); - - // We expect the '&' character to be replaced with its unicode representation. - $expected_markup = str_replace( '&', '\u0026', $original_markup ); - - $this->assertSame( - $expected_markup, - $post->post_content, - 'Post content did not match expected markup with entities escaped.' - ); - $this->assertSame( - array( 'tests/my-block' ), - json_decode( get_post_meta( self::$navigation_post->ID, '_wp_ignored_hooked_blocks', true ), true ), - 'Block was not added to ignored hooked blocks metadata.' - ); - } - - /** - * @covers ::gutenberg_block_core_navigation_update_ignore_hooked_blocks_meta - */ - public function test_block_core_navigation_dont_modify_no_post_id() { - register_block_type( - 'tests/my-block', - array( - 'block_hooks' => array( - 'core/navigation' => 'last_child', - ), - ) - ); - - $original_markup = '<!-- wp:navigation-link {"label":"News","type":"page","id":2,"url":"http://localhost:8888/?page_id=2","kind":"post-type"} /-->'; - $post = new stdClass(); - $post->post_content = $original_markup; - - $post = gutenberg_block_core_navigation_update_ignore_hooked_blocks_meta( $post ); - - $this->assertSame( - $original_markup, - $post->post_content, - 'Post content did not match the original markup.' - ); - } - - /** - * @covers ::gutenberg_block_core_navigation_update_ignore_hooked_blocks_meta - */ - public function test_block_core_navigation_retains_content_if_not_set() { - register_block_type( - 'tests/my-block', - array( - 'block_hooks' => array( - 'core/navigation' => 'last_child', - ), - ) - ); - - $post = new stdClass(); - $post->ID = self::$navigation_post->ID; - $post->post_title = 'Navigation Menu with changes'; - - $post = gutenberg_block_core_navigation_update_ignore_hooked_blocks_meta( $post ); - - $this->assertSame( - 'Navigation Menu with changes', - $post->post_title, - 'Post title was changed.' - ); - - $this->assertFalse( - isset( $post->post_content ), - 'Post content should not be set.' - ); - } -} diff --git a/phpunit/blocks/get-block-templates-test.php b/phpunit/blocks/get-block-templates-test.php deleted file mode 100644 index 7c8045a1f25f74..00000000000000 --- a/phpunit/blocks/get-block-templates-test.php +++ /dev/null @@ -1,109 +0,0 @@ -<?php -/** - * @group blocks - * @group block-templates - * - * @covers ::get_block_templates - */ -class Tests_Blocks_GetBlockTemplates extends WP_UnitTestCase { - - const TEST_THEME = 'block-theme'; - - /** - * Theme root directory. - * - * @var string - */ - private $theme_root; - - /** - * Original theme directory. - * - * @var string - */ - private $orig_theme_dir; - - public function set_up() { - parent::set_up(); - $this->theme_root = realpath( __DIR__ . '/../data/themedir1' ); - $this->orig_theme_dir = $GLOBALS['wp_theme_directories']; - - // /themes is necessary as theme.php functions assume /themes is the root if there is only one root. - $GLOBALS['wp_theme_directories'] = array( WP_CONTENT_DIR . '/themes', $this->theme_root ); - - add_filter( 'theme_root', array( $this, 'filter_set_theme_root' ) ); - add_filter( 'stylesheet_root', array( $this, 'filter_set_theme_root' ) ); - add_filter( 'template_root', array( $this, 'filter_set_theme_root' ) ); - // Clear caches. - wp_clean_themes_cache(); - unset( $GLOBALS['wp_themes'] ); - switch_theme( self::TEST_THEME ); - } - - public function tear_down() { - $GLOBALS['wp_theme_directories'] = $this->orig_theme_dir; - wp_clean_themes_cache(); - unset( $GLOBALS['wp_themes'] ); - parent::tear_down(); - } - - public function filter_set_theme_root() { - return $this->theme_root; - } - - /** - * Gets the template IDs from the given array. - * - * @param object[] $templates Array of template objects to parse. - * @return string[] The template IDs. - */ - private function get_template_ids( $templates ) { - return array_map( - static function ( $template ) { - return $template->id; - }, - $templates - ); - } - - /** - * @dataProvider data_get_block_templates_should_respect_posttypes_property - * @ticket 55881 - * - * @param string $post_type Post type for query. - * @param array $expected Expected template IDs. - */ - public function test_get_block_templates_should_respect_posttypes_property( $post_type, $expected ) { - $templates = gutenberg_get_block_templates( array( 'post_type' => $post_type ) ); - - $this->assertSameSets( - $expected, - $this->get_template_ids( $templates ) - ); - } - - /** - * Data provider. - * - * @return array - */ - public function data_get_block_templates_should_respect_posttypes_property() { - // @code-merge: This code will go into Core's tests/phpunit/tests/blocks/getBlockTemplates.php. - return array( - 'post' => array( - 'post_type' => 'post', - 'expected' => array( - 'block-theme//custom-hero-template', - 'block-theme//custom-single-post-template', - ), - ), - 'page' => array( - 'post_type' => 'page', - 'expected' => array( - 'block-theme//custom-hero-template', - 'block-theme//page-home', - ), - ), - ); - } -} diff --git a/phpunit/blocks/renderBlockCorePostExcerpt.php b/phpunit/blocks/renderBlockCorePostExcerpt.php index 38c27bfde9b30a..c37599b150590c 100644 --- a/phpunit/blocks/renderBlockCorePostExcerpt.php +++ b/phpunit/blocks/renderBlockCorePostExcerpt.php @@ -79,7 +79,7 @@ public function test_should_render_empty_string_when_excerpt_is_empty() { /** * Test gutenberg_render_block_core_post_excerpt() method. */ - public function test_should_render_correct_exceprt() { + public function test_should_render_correct_excerpt() { $block = new stdClass(); $GLOBALS['post'] = self::$post; diff --git a/phpunit/class-gutenberg-hierarchical-sort-test.php b/phpunit/class-gutenberg-hierarchical-sort-test.php new file mode 100644 index 00000000000000..31b78b272a29a2 --- /dev/null +++ b/phpunit/class-gutenberg-hierarchical-sort-test.php @@ -0,0 +1,207 @@ +<?php + +/** + * Test the build_post_ids_to_display function. + * + * @package Gutenberg + */ +class GutenbergHierarchicalSortTest extends WP_UnitTestCase { + + public function test_return_all_post_ids() { + /* + * Keep this updated as the input array changes. + * The sorted hierarchy would be as follows: + * + * 12 + * - 3 + * -- 6 + * -- 5 + * - 4 + * -- 7 + * 8 + * - 9 + * -- 11 + * - 10 + * + */ + $input = array( + (object) array( + 'ID' => 11, + 'post_parent' => 9, + ), + (object) array( + 'ID' => 12, + 'post_parent' => 0, + ), + (object) array( + 'ID' => 8, + 'post_parent' => 0, + ), + (object) array( + 'ID' => 3, + 'post_parent' => 12, + ), + (object) array( + 'ID' => 6, + 'post_parent' => 3, + ), + (object) array( + 'ID' => 7, + 'post_parent' => 4, + ), + (object) array( + 'ID' => 9, + 'post_parent' => 8, + ), + (object) array( + 'ID' => 4, + 'post_parent' => 12, + ), + (object) array( + 'ID' => 5, + 'post_parent' => 3, + ), + (object) array( + 'ID' => 10, + 'post_parent' => 8, + ), + ); + + $hs = Gutenberg_Hierarchical_Sort::get_instance(); + $result = $hs->sort( $input ); + $this->assertEquals( array( 12, 3, 6, 5, 4, 7, 8, 9, 11, 10 ), $result['post_ids'] ); + $this->assertEquals( + array( + 12 => 0, + 3 => 1, + 6 => 2, + 5 => 2, + 4 => 1, + 7 => 2, + 8 => 0, + 9 => 1, + 11 => 2, + 10 => 1, + ), + $result['levels'] + ); + } + + public function test_return_orphans() { + /* + * Keep this updated as the input array changes. + * The sorted hierarchy would be as follows: + * + * - 11 (orphan) + * - 4 (orphan) + * -- 7 + * + */ + $input = array( + (object) array( + 'ID' => 11, + 'post_parent' => 2, + ), + (object) array( + 'ID' => 7, + 'post_parent' => 4, + ), + (object) array( + 'ID' => 4, + 'post_parent' => 2, + ), + ); + + $hs = Gutenberg_Hierarchical_Sort::get_instance(); + $result = $hs->sort( $input ); + $this->assertEquals( array( 11, 4, 7 ), $result['post_ids'] ); + $this->assertEquals( + array( + 11 => 1, + 4 => 1, + 7 => 2, + ), + $result['levels'] + ); + } + + public function test_post_with_empty_post_parent_are_considered_top_level() { + /* + * Keep this updated as the input array changes. + * The sorted hierarchy would be as follows: + * + * 2 + * - 3 + * -- 5 + * -- 6 + * - 4 + * -- 7 + * 8 + * - 9 + * -- 11 + * - 10 + * + */ + $input = array( + (object) array( + 'ID' => 11, + 'post_parent' => 9, + ), + (object) array( + 'ID' => 2, + 'post_parent' => 0, + ), + (object) array( + 'ID' => 8, + 'post_parent' => '', // Empty post parent, should be considered top-level. + ), + (object) array( + 'ID' => 3, + 'post_parent' => 2, + ), + (object) array( + 'ID' => 5, + 'post_parent' => 3, + ), + (object) array( + 'ID' => 7, + 'post_parent' => 4, + ), + (object) array( + 'ID' => 9, + 'post_parent' => 8, + ), + (object) array( + 'ID' => 4, + 'post_parent' => 2, + ), + (object) array( + 'ID' => 6, + 'post_parent' => 3, + ), + (object) array( + 'ID' => 10, + 'post_parent' => 8, + ), + ); + + $hs = Gutenberg_Hierarchical_Sort::get_instance(); + $result = $hs->sort( $input ); + $this->assertEquals( array( 2, 3, 5, 6, 4, 7, 8, 9, 11, 10 ), $result['post_ids'] ); + $this->assertEquals( + array( + 2 => 0, + 3 => 1, + 5 => 2, + 6 => 2, + 4 => 1, + 7 => 2, + 8 => 0, + 9 => 1, + 11 => 2, + 10 => 1, + ), + $result['levels'] + ); + } +} diff --git a/phpunit/class-wp-theme-json-test.php b/phpunit/class-wp-theme-json-test.php index 1aacb92759e591..9f9ae7a26f2998 100644 --- a/phpunit/class-wp-theme-json-test.php +++ b/phpunit/class-wp-theme-json-test.php @@ -3205,7 +3205,7 @@ public function test_get_editor_settings_blank() { public function test_get_editor_settings_custom_units_can_be_disabled() { add_theme_support( 'custom-units', array() ); - $actual = WP_Theme_JSON_Gutenberg::get_from_editor_settings( gutenberg_get_classic_theme_supports_block_editor_settings() ); + $actual = WP_Theme_JSON_Gutenberg::get_from_editor_settings( get_classic_theme_supports_block_editor_settings() ); remove_theme_support( 'custom-units' ); $expected = array( @@ -3218,7 +3218,7 @@ public function test_get_editor_settings_custom_units_can_be_disabled() { public function test_get_editor_settings_custom_units_can_be_enabled() { add_theme_support( 'custom-units' ); - $actual = WP_Theme_JSON_Gutenberg::get_from_editor_settings( gutenberg_get_classic_theme_supports_block_editor_settings() ); + $actual = WP_Theme_JSON_Gutenberg::get_from_editor_settings( get_classic_theme_supports_block_editor_settings() ); remove_theme_support( 'custom-units' ); $expected = array( @@ -3231,7 +3231,7 @@ public function test_get_editor_settings_custom_units_can_be_enabled() { public function test_get_editor_settings_custom_units_can_be_filtered() { add_theme_support( 'custom-units', 'rem', 'em' ); - $actual = WP_Theme_JSON_Gutenberg::get_from_editor_settings( gutenberg_get_classic_theme_supports_block_editor_settings() ); + $actual = WP_Theme_JSON_Gutenberg::get_from_editor_settings( get_classic_theme_supports_block_editor_settings() ); remove_theme_support( 'custom-units' ); $expected = array( @@ -4308,6 +4308,74 @@ public function data_get_styles_for_block_with_style_variations() { ); } + public function test_get_styles_for_block_with_style_variations_and_custom_selectors() { + register_block_type( + 'test/milk', + array( + 'api_version' => 3, + 'selectors' => array( + 'root' => '.milk', + 'color' => '.wp-block-test-milk .liquid, .wp-block-test-milk:not(.spoiled), .wp-block-test-milk.in-bottle', + ), + ) + ); + + register_block_style( + 'test/milk', + array( + 'name' => 'chocolate', + 'label' => 'Chocolate', + ) + ); + + $theme_json = new WP_Theme_JSON_Gutenberg( + array( + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, + 'styles' => array( + 'blocks' => array( + 'test/milk' => array( + 'color' => array( + 'background' => 'white', + ), + 'variations' => array( + 'chocolate' => array( + 'color' => array( + 'background' => '#35281E', + ), + ), + ), + ), + ), + ), + ) + ); + + $metadata = array( + 'name' => 'test/milk', + 'path' => array( 'styles', 'blocks', 'test/milk' ), + 'selector' => '.wp-block-test-milk', + 'selectors' => array( + 'color' => '.wp-block-test-milk .liquid, .wp-block-test-milk:not(.spoiled), .wp-block-test-milk.in-bottle', + ), + 'variations' => array( + 'chocolate' => array( + 'path' => array( 'styles', 'blocks', 'test/milk', 'variations', 'chocolate' ), + 'selector' => '.is-style-chocolate.wp-block-test-milk', + ), + ), + ); + + $actual_styles = $theme_json->get_styles_for_block( $metadata ); + $default_styles = ':root :where(.wp-block-test-milk .liquid, .wp-block-test-milk:not(.spoiled), .wp-block-test-milk.in-bottle){background-color: white;}'; + $variation_styles = ':root :where(.is-style-chocolate.wp-block-test-milk .liquid,.is-style-chocolate.wp-block-test-milk:not(.spoiled),.is-style-chocolate.wp-block-test-milk.in-bottle){background-color: #35281E;}'; + $expected = $default_styles . $variation_styles; + + unregister_block_style( 'test/milk', 'chocolate' ); + unregister_block_type( 'test/milk' ); + + $this->assertSame( $expected, $actual_styles ); + } + public function test_block_style_variations() { wp_set_current_user( static::$administrator_id ); @@ -4927,7 +4995,7 @@ public function data_set_spacing_sizes_when_invalid() { } /** - * Tests the core separator block outbut based on various provided settings. + * Tests the core separator block output based on various provided settings. * * @dataProvider data_update_separator_declarations * diff --git a/platform-docs/docs/create-block/transforms.md b/platform-docs/docs/create-block/transforms.md index fd235b669cd720..4a4118d6850b9e 100644 --- a/platform-docs/docs/create-block/transforms.md +++ b/platform-docs/docs/create-block/transforms.md @@ -37,7 +37,7 @@ A transformation of type `block` is an object that takes the following parameter - **transform** _(function)_: a callback that receives the attributes and inner blocks of the block being processed. It should return a block object or an array of block objects. - **isMatch** _(function, optional)_: a callback that receives the block attributes as the first argument and the block object as the second argument and should return a boolean. Returning `false` from this function will prevent the transform from being available and displayed as an option to the user. - **isMultiBlock** _(boolean, optional)_: whether the transformation can be applied when multiple blocks are selected. If `true`, the `transform` function's first parameter will be an array containing each selected block's attributes, and the second an array of each selected block's inner blocks. Returns `false` by default. -- **priority** _(number, optional)_: controls the priority with which a transformation is applied, where a lower value will take precedence over higher values. This behaves much like a [WordPress hook](https://codex.wordpress.org/Plugin_API#Hook_to_WordPress). Like hooks, the default priority is `10` when not otherwise set. +- **priority** _(number, optional)_: controls the priority with which a transformation is applied, where a lower value will take precedence over higher values. This behaves much like a [WordPress hook](https://developer.wordpress.org/reference/#Hook_to_WordPress). Like hooks, the default priority is `10` when not otherwise set. **Example: Let's declare a transform from our Gutenpride block to Heading block** diff --git a/platform-docs/docs/intro.md b/platform-docs/docs/intro.md index 05cfa103492f33..48f1410871ccef 100644 --- a/platform-docs/docs/intro.md +++ b/platform-docs/docs/intro.md @@ -4,17 +4,16 @@ sidebar_position: 1 # Getting Started -Let's discover how to use the **Gutenberg Block Editor** to build your own block editor in less than 10 minutes**. - +Let's discover how to use the **Gutenberg Block Editor** to build your own block editor in less than 10 minutes. ## What you'll need - [Node.js](https://nodejs.org/en/download/) version 20.10 or above. -- We're going to be using "vite" to setup our single page application (SPA) that contains a block editor. You can use your own setup, and your own application for this. +- We're going to be using "Vite" to setup our single page application (SPA) that contains a block editor. You can use your own setup, and your own application for this. ## Preparing the SPA powered by Vite. -First bootstrap a vite project using `npm create vite@latest` and pick `Vanilla` variant and `JavaScript` as a language. +First bootstrap a Vite project using `npm create vite@latest` and pick `React` variant and `JavaScript` as a language. Once done, you can navigate to your application folder and run it locally using `npm run dev`. Open the displayed local URL in a browser. @@ -28,58 +27,72 @@ To build a block editor, you need to install the following dependencies: ## JSX -We're going to be using JSX to write our UI and components. So one of the first steps we need to do is to configure our build tooling, By default vite supports JSX and and outputs the result as a React pragma. The Block editor uses React so there's no need to configure anything here but if you're using a different bundler/build tool, make sure the JSX transpilation is setup properly. +We're going to be using JSX to write our UI and components as the block editor is built with React. Using the Vite bootstrap described above there’s no need to configure anything as it outputs the result as a React pragma. If you're using a different bundler/build tool, you may need to configure the JSX transpilation to do the same. ## Bootstrap your block editor -It's time to render our first block editor. +It's time to render our first block editor. We’ll do this with changes to three files – `index.html`, `src/main.jsx`, and `src/App.jsx`. + +First, we’ll add the base styles are for the editor UI. In `index.html` add these styles in the `<head>`: +```html +<link href="node_modules/@wordpress/components/build-style/style.css" rel="stylesheet" vite-ignore/> +<link href="node_modules/@wordpress/block-editor/build-style/style.css" rel="stylesheet" vite-ignore/> +``` +:::note + +There are more styles needed but can’t be added here because they are for the editor’s content which is in a separate document within an `<iframe>`. We’ll add those styles via the `BlockCanvas` component in a later step. + +::: + +Next, we’ll add blocks for the editor to work with. In `src/main.jsx` import and call `registerCoreBlocks`: +```js +import { registerCoreBlocks } from '@wordpress/block-library' +registerCoreBlocks(); +``` + +Finally, we’ll put our editor together. In `src/App.jsx` replace the contents with the following code: - - Update your `index.jsx` file with the following code: ```jsx -import { createElement, useState } from "react"; -import { createRoot } from 'react-dom/client'; +import { useState } from "react"; import { BlockEditorProvider, BlockCanvas, } from "@wordpress/block-editor"; -import { registerCoreBlocks } from "@wordpress/block-library"; - -// Default styles that are needed for the editor. -import "@wordpress/components/build-style/style.css"; -import "@wordpress/block-editor/build-style/style.css"; - -// Default styles that are needed for the core blocks. -import "@wordpress/block-library/build-style/common.css"; -import "@wordpress/block-library/build-style/style.css"; -import "@wordpress/block-library/build-style/editor.css"; - -// Register the default core block types. -registerCoreBlocks(); -function Editor() { - const [blocks, setBlocks] = useState([]); +// Base styles for the content within the block canvas iframe. +import componentsStyles from "@wordpress/components/build-style/style.css?raw"; +import blockEditorContentStyles from "@wordpress/block-editor/build-style/content.css?raw"; +import blocksStyles from "@wordpress/block-library/build-style/style.css?raw"; +import blocksEditorStyles from "@wordpress/block-library/build-style/editor.css?raw"; + +const contentStyles = [ + { css: componentsStyles }, + { css: blockEditorContentStyles }, + { css: blocksStyles }, + { css: blocksEditorStyles }, +]; + +export default function Editor() { + const [ blocks, setBlocks ] = useState( [] ); return ( /* The BlockEditorProvider is the wrapper of the block editor's state. All the UI elements of the block editor need to be rendered within this provider. */ <BlockEditorProvider - value={blocks} - onChange={setBlocks} - onInput={setBlocks} + value={ blocks } + onChange={ setBlocks } + onInput={ setBlocks } > - {/* - The BlockCanvas component renders the block list within an iframe - and wire up all the necessary events to make the block editor work. - */} - <BlockCanvas height="500px" /> + { /* + The BlockCanvas component renders the block list within an iframe + and wires up all the necessary events to make the block editor work. + */ } + <BlockCanvas height="500px" styles={ contentStyles } /> </BlockEditorProvider> ); } -// Render your React component instead -const root = createRoot(document.getElementById("app")); -root.render(<Editor />); ``` That's it! You now have a very basic block editor with several block types included by default: paragraphs, headings, lists, quotes, images... diff --git a/react-scanner.config.js b/react-scanner.config.js index 7501e7e8fc3ab0..853bbde69327c6 100644 --- a/react-scanner.config.js +++ b/react-scanner.config.js @@ -19,6 +19,7 @@ module.exports = { 'storybook', 'test', 'tools', + 'ts-traces', 'typings', 'vendor', ], diff --git a/readme.txt b/readme.txt index 4bd7ddc36d6e9b..066de4a678a552 100644 --- a/readme.txt +++ b/readme.txt @@ -1,6 +1,6 @@ === Gutenberg === Contributors: matveb, joen, karmatosed -Tested up to: 6.6 +Tested up to: 6.7 Stable tag: V.V.V License: GPLv2 or later License URI: http://www.gnu.org/licenses/gpl-2.0.html diff --git a/schemas/json/theme.json b/schemas/json/theme.json index a1f51ace920259..4eec377e3a94b9 100644 --- a/schemas/json/theme.json +++ b/schemas/json/theme.json @@ -922,6 +922,9 @@ "core/file": { "$ref": "#/definitions/settingsPropertiesComplete" }, + "core/footnotes": { + "$ref": "#/definitions/settingsPropertiesComplete" + }, "core/freeform": { "$ref": "#/definitions/settingsPropertiesComplete" }, @@ -1030,9 +1033,6 @@ "core/post-terms": { "$ref": "#/definitions/settingsPropertiesComplete" }, - "core/post-time-to-read": { - "$ref": "#/definitions/settingsPropertiesComplete" - }, "core/post-title": { "$ref": "#/definitions/settingsPropertiesComplete" }, @@ -1063,6 +1063,9 @@ "core/query-title": { "$ref": "#/definitions/settingsPropertiesComplete" }, + "core/query-total": { + "$ref": "#/definitions/settingsPropertiesComplete" + }, "core/quote": { "$ref": "#/definitions/settingsPropertiesComplete" }, @@ -1102,9 +1105,6 @@ "core/table": { "$ref": "#/definitions/settingsPropertiesComplete" }, - "core/table-of-contents": { - "$ref": "#/definitions/settingsPropertiesComplete" - }, "core/tag-cloud": { "$ref": "#/definitions/settingsPropertiesComplete" }, @@ -1902,6 +1902,9 @@ "core/file": { "$ref": "#/definitions/stylesPropertiesAndElementsComplete" }, + "core/footnotes": { + "$ref": "#/definitions/stylesPropertiesAndElementsComplete" + }, "core/freeform": { "$ref": "#/definitions/stylesPropertiesAndElementsComplete" }, @@ -2010,9 +2013,6 @@ "core/post-terms": { "$ref": "#/definitions/stylesPropertiesAndElementsComplete" }, - "core/post-time-to-read": { - "$ref": "#/definitions/stylesPropertiesAndElementsComplete" - }, "core/post-title": { "$ref": "#/definitions/stylesPropertiesAndElementsComplete" }, @@ -2043,6 +2043,9 @@ "core/query-title": { "$ref": "#/definitions/stylesPropertiesAndElementsComplete" }, + "core/query-total": { + "$ref": "#/definitions/stylesPropertiesAndElementsComplete" + }, "core/quote": { "$ref": "#/definitions/stylesPropertiesAndElementsComplete" }, @@ -2082,9 +2085,6 @@ "core/table": { "$ref": "#/definitions/stylesPropertiesAndElementsComplete" }, - "core/table-of-contents": { - "$ref": "#/definitions/stylesPropertiesAndElementsComplete" - }, "core/tag-cloud": { "$ref": "#/definitions/stylesPropertiesAndElementsComplete" }, @@ -2316,6 +2316,9 @@ "core/file": { "$ref": "#/definitions/stylesVariationBlockPropertiesComplete" }, + "core/footnotes": { + "$ref": "#/definitions/stylesVariationBlockPropertiesComplete" + }, "core/freeform": { "$ref": "#/definitions/stylesVariationBlockPropertiesComplete" }, @@ -2424,9 +2427,6 @@ "core/post-terms": { "$ref": "#/definitions/stylesVariationBlockPropertiesComplete" }, - "core/post-time-to-read": { - "$ref": "#/definitions/stylesVariationBlockPropertiesComplete" - }, "core/post-title": { "$ref": "#/definitions/stylesVariationBlockPropertiesComplete" }, @@ -2457,6 +2457,9 @@ "core/query-title": { "$ref": "#/definitions/stylesVariationBlockPropertiesComplete" }, + "core/query-total": { + "$ref": "#/definitions/stylesPropertiesAndElementsComplete" + }, "core/quote": { "$ref": "#/definitions/stylesVariationBlockPropertiesComplete" }, @@ -2496,9 +2499,6 @@ "core/table": { "$ref": "#/definitions/stylesVariationBlockPropertiesComplete" }, - "core/table-of-contents": { - "$ref": "#/definitions/stylesVariationBlockPropertiesComplete" - }, "core/tag-cloud": { "$ref": "#/definitions/stylesVariationBlockPropertiesComplete" }, diff --git a/schemas/json/wp-env.json b/schemas/json/wp-env.json index 491d1f8cf73017..5761fb3d877116 100644 --- a/schemas/json/wp-env.json +++ b/schemas/json/wp-env.json @@ -49,7 +49,7 @@ "default": [] }, "port": { - "description": "The primary port number to use for the installation. You'll access the instance through the port: 'http://localhost:8888'.", + "description": "The primary port number to use for the installation. You'll access the instance through the port: http://localhost:8888", "type": "integer", "default": 8888 }, @@ -62,6 +62,14 @@ "description": "Mapping of WordPress directories to local directories to be mounted in the WordPress instance.", "type": "object", "default": {} + }, + "phpmyadminPort": { + "description": "The port number to access phpMyAdmin.", + "type": "integer" + }, + "multisite": { + "description": "Whether to set up a multisite installation.", + "type": "boolean" } } }, @@ -73,7 +81,9 @@ "themes", "port", "config", - "mappings" + "mappings", + "phpmyadminPort", + "multisite" ] } }, @@ -104,6 +114,11 @@ } }, "default": {} + }, + "testsPort": { + "description": "The port number for the test site. You'll access the instance through the port: http://localhost:8889", + "type": "integer", + "default": 8889 } } }, @@ -115,7 +130,7 @@ "$ref": "#/definitions/wpEnvPropertyNames" }, { - "enum": [ "$schema", "env" ] + "enum": [ "$schema", "env", "testsPort" ] } ] } diff --git a/storybook/components/figma-embed/index.js b/storybook/components/figma-embed/index.js new file mode 100644 index 00000000000000..8d8ac6488a9334 --- /dev/null +++ b/storybook/components/figma-embed/index.js @@ -0,0 +1,47 @@ +/** + * Internal dependencies + */ +import './style.scss'; + +// See https://www.figma.com/developers/embed#embed-a-figma-file +const CONFIG = { + 'embed-host': 'wordpress-storybook', + footer: false, + 'page-selector': false, + 'viewport-controls': true, +}; + +/** + * Embed Figma links in the Storybook. + * + * @param {Object} props + * @param {string} props.url - Figma URL to embed. + * @param {string} props.title - Accessible title for the iframe. + */ +function FigmaEmbed( { url, title, ...props } ) { + const urlObj = new URL( url ); + + const queryParams = new URLSearchParams( urlObj.search ); + Object.entries( CONFIG ).forEach( ( [ key, value ] ) => { + queryParams.set( key, value ); + } ); + urlObj.search = queryParams.toString(); + + urlObj.hostname = urlObj.hostname.replace( + 'www.figma.com', + 'embed.figma.com' + ); + + const normalizedUrl = urlObj.toString(); + + return ( + <iframe + title={ title } + src={ normalizedUrl } + className="wp-storybook-figma-embed" + { ...props } + /> + ); +} + +export default FigmaEmbed; diff --git a/storybook/components/figma-embed/style.scss b/storybook/components/figma-embed/style.scss new file mode 100644 index 00000000000000..8311642b9b0974 --- /dev/null +++ b/storybook/components/figma-embed/style.scss @@ -0,0 +1,4 @@ +.wp-storybook-figma-embed { + width: 100%; + border: none; +} diff --git a/storybook/decorators/with-max-width-wrapper.js b/storybook/decorators/with-max-width-wrapper.js index ff979b93f213bf..84fb73f20b68f7 100644 --- a/storybook/decorators/with-max-width-wrapper.js +++ b/storybook/decorators/with-max-width-wrapper.js @@ -3,15 +3,12 @@ */ import styled from '@emotion/styled'; -/** - * A Storybook decorator to wrap a story in a div applying a max width and - * padding. This can be used to simulate real world constraints on components - * such as being located within the WordPress editor sidebars. - */ - -const Wrapper = styled.div` - max-width: 248px; -`; +const maxWidthWrapperMap = { + none: 0, + 'wordpress-sidebar': 248, + 'small-container': 600, + 'large-container': 960, +}; const Indicator = styled.div` display: flex; @@ -27,14 +24,19 @@ const Indicator = styled.div` `; export const WithMaxWidthWrapper = ( Story, context ) => { - if ( context.globals.maxWidthWrapper === 'none' ) { + /** + * A Storybook decorator to wrap a story in a div applying a max width. + * This can be used to simulate real world constraints on components + * such as being located within the WordPress editor sidebars. + */ + const maxWidth = maxWidthWrapperMap[ context.globals.maxWidthWrapper ]; + if ( ! maxWidth ) { return <Story { ...context } />; } - return ( - <Wrapper> + <div style={ { maxWidth } }> <Story { ...context } /> - <Indicator>Max-Width Wrapper - 248px</Indicator> - </Wrapper> + <Indicator>{ `Max-Width Wrapper - ${ maxWidth }px` }</Indicator> + </div> ); }; diff --git a/storybook/main.js b/storybook/main.js index e482ee23c2e5fc..29f24c223ccdfe 100644 --- a/storybook/main.js +++ b/storybook/main.js @@ -31,7 +31,8 @@ const stories = [ process.env.NODE_ENV !== 'test' && './stories/**/*.story.@(js|tsx)', process.env.NODE_ENV !== 'test' && './stories/**/*.mdx', '../packages/block-editor/src/**/stories/*.story.@(js|tsx|mdx)', - '../packages/components/src/**/stories/*.story.@(js|tsx|mdx)', + '../packages/components/src/**/stories/*.story.@(js|tsx)', + '../packages/components/src/**/stories/*.mdx', '../packages/icons/src/**/stories/*.story.@(js|tsx|mdx)', '../packages/edit-site/src/**/stories/*.story.@(js|tsx|mdx)', '../packages/dataviews/src/**/stories/*.story.@(js|tsx|mdx)', @@ -53,6 +54,7 @@ module.exports = { '@storybook/addon-a11y', '@storybook/addon-toolbars', '@storybook/addon-actions', + '@storybook/addon-webpack5-compiler-babel', 'storybook-source-link', '@geometricpanda/storybook-addon-badges', ], @@ -60,13 +62,9 @@ module.exports = { name: '@storybook/react-webpack5', options: {}, }, - features: { - babelModeV7: true, - emotionAlias: false, - storyStoreV7: true, - }, - docs: { - autodocs: true, + docs: {}, + typescript: { + reactDocgen: 'react-docgen-typescript', }, webpackFinal: async ( config ) => { return { diff --git a/storybook/manager-head.html b/storybook/manager-head.html index d3f156a6eb788b..a4f6941e981114 100644 --- a/storybook/manager-head.html +++ b/storybook/manager-head.html @@ -7,6 +7,7 @@ 'boxcontrol', 'customselectcontrol-v2', 'dimensioncontrol', + 'menu', 'navigation', 'navigator', 'progressbar', diff --git a/storybook/manager.js b/storybook/manager.js index c63f15efe11d02..d519de312737a9 100644 --- a/storybook/manager.js +++ b/storybook/manager.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { addons } from '@storybook/addons'; +import { addons } from '@storybook/manager-api'; /** * Internal dependencies diff --git a/storybook/package-styles/block-editor-ltr.lazy.scss b/storybook/package-styles/block-editor-ltr.lazy.scss index 4a84559461387f..6e2cce4438268d 100644 --- a/storybook/package-styles/block-editor-ltr.lazy.scss +++ b/storybook/package-styles/block-editor-ltr.lazy.scss @@ -1 +1 @@ -@import "../../packages/block-editor/build-style/style"; +@import "../../packages/block-editor/build-style/style.css"; diff --git a/storybook/package-styles/block-editor-rtl.lazy.scss b/storybook/package-styles/block-editor-rtl.lazy.scss index d8eacb3d25b585..8657d37518abac 100644 --- a/storybook/package-styles/block-editor-rtl.lazy.scss +++ b/storybook/package-styles/block-editor-rtl.lazy.scss @@ -1 +1 @@ -@import "../../packages/block-editor/build-style/style-rtl"; +@import "../../packages/block-editor/build-style/style-rtl.css"; diff --git a/storybook/package-styles/block-library-ltr.lazy.scss b/storybook/package-styles/block-library-ltr.lazy.scss index a5e373b1c061cd..af63958aa4f2fd 100644 --- a/storybook/package-styles/block-library-ltr.lazy.scss +++ b/storybook/package-styles/block-library-ltr.lazy.scss @@ -1,3 +1,3 @@ -@import "../../packages/block-library/build-style/style"; -@import "../../packages/block-library/build-style/theme"; -@import "../../packages/block-library/build-style/editor"; +@import "../../packages/block-library/build-style/style.css"; +@import "../../packages/block-library/build-style/theme.css"; +@import "../../packages/block-library/build-style/editor.css"; diff --git a/storybook/package-styles/block-library-rtl.lazy.scss b/storybook/package-styles/block-library-rtl.lazy.scss index de83f997385539..f472b81d720507 100644 --- a/storybook/package-styles/block-library-rtl.lazy.scss +++ b/storybook/package-styles/block-library-rtl.lazy.scss @@ -1,3 +1,3 @@ -@import "../../packages/block-library/build-style/style-rtl"; -@import "../../packages/block-library/build-style/theme-rtl"; -@import "../../packages/block-library/build-style/editor-rtl"; +@import "../../packages/block-library/build-style/style-rtl.css"; +@import "../../packages/block-library/build-style/theme-rtl.css"; +@import "../../packages/block-library/build-style/editor-rtl.css"; diff --git a/storybook/package-styles/components-ltr.lazy.scss b/storybook/package-styles/components-ltr.lazy.scss index e7891f25026c4c..c9d2002e2aff74 100644 --- a/storybook/package-styles/components-ltr.lazy.scss +++ b/storybook/package-styles/components-ltr.lazy.scss @@ -1 +1 @@ -@import "../../packages/components/build-style/style"; +@import "../../packages/components/build-style/style.css"; diff --git a/storybook/package-styles/components-rtl.lazy.scss b/storybook/package-styles/components-rtl.lazy.scss index 6cba6edf353da5..18eccbb8bd7c11 100644 --- a/storybook/package-styles/components-rtl.lazy.scss +++ b/storybook/package-styles/components-rtl.lazy.scss @@ -1 +1 @@ -@import "../../packages/components/build-style/style-rtl"; +@import "../../packages/components/build-style/style-rtl.css"; diff --git a/storybook/package-styles/dataviews-ltr.lazy.scss b/storybook/package-styles/dataviews-ltr.lazy.scss index b5e1aa042803aa..02ed0c9779f805 100644 --- a/storybook/package-styles/dataviews-ltr.lazy.scss +++ b/storybook/package-styles/dataviews-ltr.lazy.scss @@ -1 +1 @@ -@import "../../packages/dataviews/build-style/style"; +@import "../../packages/dataviews/build-style/style.css"; diff --git a/storybook/package-styles/dataviews-rtl.lazy.scss b/storybook/package-styles/dataviews-rtl.lazy.scss index d97479a1a5658e..f84964f93850d2 100644 --- a/storybook/package-styles/dataviews-rtl.lazy.scss +++ b/storybook/package-styles/dataviews-rtl.lazy.scss @@ -1 +1 @@ -@import "../../packages/dataviews/build-style/style-rtl"; +@import "../../packages/dataviews/build-style/style-rtl.css"; diff --git a/storybook/package-styles/edit-site-ltr.lazy.scss b/storybook/package-styles/edit-site-ltr.lazy.scss index c704fe9d9530ae..c650798e554856 100644 --- a/storybook/package-styles/edit-site-ltr.lazy.scss +++ b/storybook/package-styles/edit-site-ltr.lazy.scss @@ -1 +1 @@ -@import "../../packages/edit-site/build-style/style"; +@import "../../packages/edit-site/build-style/style.css"; diff --git a/storybook/package-styles/edit-site-rtl.lazy.scss b/storybook/package-styles/edit-site-rtl.lazy.scss index ae9ac27d6b51ef..a1f44ec2f7c51d 100644 --- a/storybook/package-styles/edit-site-rtl.lazy.scss +++ b/storybook/package-styles/edit-site-rtl.lazy.scss @@ -1 +1 @@ -@import "../../packages/edit-site/build-style/style-rtl"; +@import "../../packages/edit-site/build-style/style-rtl.css"; diff --git a/storybook/package-styles/format-library-ltr.lazy.scss b/storybook/package-styles/format-library-ltr.lazy.scss index 0f7db59c5d5dfb..7a7468769865db 100644 --- a/storybook/package-styles/format-library-ltr.lazy.scss +++ b/storybook/package-styles/format-library-ltr.lazy.scss @@ -1 +1 @@ -@import "../../packages/format-library/build-style/style"; +@import "../../packages/format-library/build-style/style.css"; diff --git a/storybook/package-styles/format-library-rtl.lazy.scss b/storybook/package-styles/format-library-rtl.lazy.scss index de2553bb5efa20..7d62ab7000b68b 100644 --- a/storybook/package-styles/format-library-rtl.lazy.scss +++ b/storybook/package-styles/format-library-rtl.lazy.scss @@ -1 +1 @@ -@import "../../packages/format-library/build-style/style-rtl"; +@import "../../packages/format-library/build-style/style-rtl.css"; diff --git a/storybook/preview.js b/storybook/preview.js index a7c9aa0c085fc1..b74640d9bcfbcf 100644 --- a/storybook/preview.js +++ b/storybook/preview.js @@ -2,7 +2,7 @@ * External dependencies */ import { - ArgsTable, + Controls, Description, Primary, Stories, @@ -86,6 +86,8 @@ export const globalTypes = { items: [ { value: 'none', title: 'None' }, { value: 'wordpress-sidebar', title: 'WP Sidebar' }, + { value: 'small-container', title: 'Small container' }, + { value: 'large-container', title: 'Large container' }, ], }, }, @@ -106,6 +108,9 @@ export const parameters = { sort: 'requiredFirst', }, docs: { + controls: { + sort: 'requiredFirst', + }, // Flips the order of the description and the primary component story // so the component is always visible before the fold. page: () => ( @@ -114,8 +119,7 @@ export const parameters = { <Subtitle /> <Primary /> <Description /> - { /* `story="^"` enables Controls for the primary props table */ } - <ArgsTable story="^" /> + <Controls /> <Stories includePrimary={ false } /> </> ), @@ -156,3 +160,5 @@ export const parameters = { }, sourceLinkPrefix: 'https://github.com/WordPress/gutenberg/blob/trunk/', }; + +export const tags = [ 'autodocs' ]; diff --git a/storybook/sidebar.js b/storybook/sidebar.js index d8ff2ba777dd7d..ab438440e3b834 100644 --- a/storybook/sidebar.js +++ b/storybook/sidebar.js @@ -28,7 +28,9 @@ const Title = styled.span( { const Icons = styled.span( {} ); -const Icon = styled.span( {} ); +const Icon = styled.span( { + lineHeight: 1, +} ); /** * Fetches tags from the Storybook API, and returns Icon @@ -41,7 +43,7 @@ function useIcons( item ) { return useMemo( () => { let data = {}; - if ( item.isComponent && item.children?.length ) { + if ( item.type === 'component' && item.children?.length ) { data = api.getData( item.children[ 0 ] ) ?? {}; } diff --git a/storybook/stories/docs/inline-icon.js b/storybook/stories/docs/inline-icon.js index d7d1fafc28723d..b947dbd534a00d 100644 --- a/storybook/stories/docs/inline-icon.js +++ b/storybook/stories/docs/inline-icon.js @@ -2,11 +2,13 @@ * External dependencies */ import styled from '@emotion/styled'; -import { Icons } from '@storybook/components'; -const StyledIcons = styled( Icons )` +const IconWrapper = ( { icon, ...props } ) => { + const IconComponent = icon; + return <IconComponent aria-hidden { ...props } />; +}; + +export const InlineIcon = styled( IconWrapper )` display: inline-block !important; width: 14px; `; - -export const InlineIcon = ( props ) => <StyledIcons aria-hidden { ...props } />; diff --git a/storybook/stories/docs/introduction.mdx b/storybook/stories/docs/introduction.mdx index 731c570942f6b8..cff649d189bd42 100644 --- a/storybook/stories/docs/introduction.mdx +++ b/storybook/stories/docs/introduction.mdx @@ -1,4 +1,5 @@ import { Meta } from '@storybook/blocks'; +import { RepoIcon } from '@storybook/icons'; import { InlineIcon } from './inline-icon'; <Meta title="Docs/Introduction" name="page" /> @@ -28,7 +29,7 @@ The site shows the individual components in the sidebar and the Canvas on the ri To view the documentation for each component use the **Docs** menu item in the top toolbar. -To view the source code for the component and its stories on GitHub, click the <InlineIcon icon="repository" /> View Source Repository button in the top right corner. +To view the source code for the component and its stories on GitHub, click the <InlineIcon icon={ RepoIcon } /> View Source Repository button in the top right corner. To use it in your local development environment run the following command in the top level Gutenberg directory: diff --git a/storybook/stories/foundations/design-language/elevation.mdx b/storybook/stories/foundations/design-language/elevation.mdx index 11591568b3600c..fcacfd2ee5a8d6 100644 --- a/storybook/stories/foundations/design-language/elevation.mdx +++ b/storybook/stories/foundations/design-language/elevation.mdx @@ -1,4 +1,4 @@ -import { Meta, Typeset } from '@storybook/addon-docs/blocks'; +import { Meta, Typeset } from '@storybook/blocks'; import elevation from './static/elevation.svg'; import elevationExamples from './static/elevation-examples.svg'; diff --git a/storybook/stories/foundations/design-language/radius.mdx b/storybook/stories/foundations/design-language/radius.mdx index 9117bf940d45a2..2cf5b05a92efee 100644 --- a/storybook/stories/foundations/design-language/radius.mdx +++ b/storybook/stories/foundations/design-language/radius.mdx @@ -1,4 +1,4 @@ -import { Meta, Typeset } from '@storybook/addon-docs/blocks'; +import { Meta, Typeset } from '@storybook/blocks'; import radius from './static/radius.svg'; import radiusDo from './static/radius-do.svg'; import radiusDont from './static/radius-dont.svg'; @@ -31,25 +31,29 @@ These steps are defined as tokens. To view the values and understand how to use - Accessibility: Larger radius values can create a more approachable and friendly appearance, especially for larger components like cards and modals that demand more attention from users. <table> - <tr> - <th width="50%">✅ Do</th> - <th width="50%">🚫 Don't</th> - </tr> - <tr> - <td width="50%" valign="top"> - <img src={ radiusDo } alt="Radius do" width="100%" /> - - - Scale application of radius with element or container size. - - - Use `radius-round` for circles and `radius-full` for pills. - </td> - <td width="50%" valign="top"> - <img src={ radiusDont } alt="Radius don't" width="100%" /> - - - Don't nest larger radii inside smaller radii. - - - Don't apply the same - radius value to container and immediate descendent. - </td> - </tr> + <thead> + <tr> + <th width="50%">✅ Do</th> + <th width="50%">🚫 Don't</th> + </tr> + </thead> + <tbody> + <tr> + <td width="50%" valign="top"> + <img src={ radiusDo } alt="Radius do" width="100%" /> + + - Scale application of radius with element or container size. + + - Use `radius-round` for circles and `radius-full` for pills. + </td> + <td width="50%" valign="top"> + <img src={ radiusDont } alt="Radius don't" width="100%" /> + + - Don't nest larger radii inside smaller radii. + + - Don't apply the same + radius value to container and immediate descendent. + </td> + </tr> + </tbody> </table> diff --git a/storybook/stories/foundations/layout.mdx b/storybook/stories/foundations/layout.mdx new file mode 100644 index 00000000000000..578f4e8b66e428 --- /dev/null +++ b/storybook/stories/foundations/layout.mdx @@ -0,0 +1,60 @@ +import { Meta } from '@storybook/blocks'; +import areas from './static/areas.svg'; +import pageLayoutExample1 from './static/page-layout-example-1.svg'; +import pageLayoutExample2 from './static/page-layout-example-2.svg'; +import pageLayoutExample3 from './static/page-layout-example-3.svg'; +import pageLayoutExample4 from './static/page-layout-example-4.svg'; + + +<Meta title="Foundations/Layout" name="page" /> + +# Layout + +Layout defines the arrangement and structure of admin pages, content sections, and other visual elements. + +## Page areas + +At the highest level admin pages are comprised of _areas_, that can be arranged in different ways. + +<img src={ areas } alt="Area definition diagram" width="100%" /> + +1. **Sidebar** – Primary admin navigation UI including notifications and access to Command Palette. Always positioned on the left of the page (assuming left-to-right language). +2. **Content Frame** – A container for admin page content e.g. data views, settings forms, etc. +3. **Preview Frame** – Displays a preview of the site homepage, a post or page in focus, or something more abstract like a template or Style Book. In many cases it can be clicked to open the editor. + +### Area compositions + +Areas can be combined in different ways depending on the use case. Here are some examples. + +<table> + <tbody> + <tr> + <td style={{verticalAlign: 'top', width: '50%'}}> + #### Sidebar, Content Frame and Preview Frame + <img src={ pageLayoutExample1 } alt="Diagram illustrating an example of the 'Sidebar, Content Frame and Preview Frame' arrangement" width="100%" /> + + A demonstration of this arrangement can be found in the Styles section of the Site Editor, and in the Pages and Templates sections when List layout is selected. + </td> + <td style={{verticalAlign: 'top', width: '50%'}}> + #### Sidebar and Preview Frame + <img src={ pageLayoutExample2 } alt="Diagram illustrating an example of the 'Sidebar and Preview Frame' arrangement" width="100%" /> + + A demonstration of this arrangement can be found in the Design section. + </td> + </tr> + <tr> + <td style={{verticalAlign: 'top', width: '50%'}}> + #### Sidebar and Content Frame + <img src={ pageLayoutExample3 } alt="Diagram illustrating an example of the 'Sidebar and Content Frame' arrangement" width="100%" /> + + A demonstration of this arrangement can be found in the Patterns and Templates sections of the Site Editor, or in the Pages section when Table or Grid layout are selected. + </td> + <td style={{verticalAlign: 'top', width: '50%'}}> + #### Sidebar and multiple Content Frames + <img src={ pageLayoutExample4 } alt="Diagram illustrating an example of the 'Sidebar and multiple Content Frames' arrangement" width="100%" /> + + Multiple content frames can be utilised as required. + </td> + </tr> + </tbody> +</table> diff --git a/storybook/stories/foundations/static/areas.svg b/storybook/stories/foundations/static/areas.svg new file mode 100644 index 00000000000000..9bf535e182411b --- /dev/null +++ b/storybook/stories/foundations/static/areas.svg @@ -0,0 +1,78 @@ +<svg width="1728" height="1117" fill="none" xmlns="http://www.w3.org/2000/svg"> + <path fill="#E0E0E0" d="M0 0h1728v1117H0z"/> + <rect x="112" y="513.5" width="40" height="40" rx="20" fill="#1E1E1E"/> + <path d="M133.923 527.864V539.5h-2.108v-9.585h-.068l-2.721 1.738v-1.931l2.892-1.858h2.005Z" fill="#fff"/> + <path d="M99.705 576.418v2.002a7.764 7.764 0 0 0-1.807-.869 5.806 5.806 0 0 0-1.816-.293c-.931 0-1.667.218-2.207.654-.54.43-.81 1.012-.81 1.748 0 .645.175 1.136.527 1.475.358.338 1.022.622 1.992.849l1.035.235c1.367.319 2.363.82 2.988 1.504.625.683.938 1.614.938 2.793 0 1.386-.43 2.444-1.29 3.173-.858.73-2.108 1.094-3.75 1.094-.683 0-1.37-.075-2.06-.224a11.333 11.333 0 0 1-2.08-.655v-2.099c.749.475 1.455.823 2.12 1.045.67.221 1.344.332 2.02.332.997 0 1.772-.222 2.325-.664.553-.45.83-1.075.83-1.875 0-.73-.192-1.286-.576-1.67-.378-.384-1.038-.681-1.982-.889l-1.055-.244c-1.354-.306-2.337-.768-2.95-1.387-.611-.618-.917-1.448-.917-2.49 0-1.302.436-2.344 1.308-3.125.88-.788 2.045-1.182 3.496-1.182.56 0 1.15.065 1.768.196.618.123 1.27.312 1.953.566Zm5.254 3.184h4.6v9.502h3.564v1.396h-8.926v-1.396h3.565v-8.106h-2.803v-1.396Zm2.803-4.248h1.797v2.265h-1.797v-2.265Zm14.541 5.605v-5.654h1.797V590.5h-1.797v-1.377c-.3.54-.7.954-1.201 1.24-.495.28-1.068.42-1.719.42-1.322 0-2.363-.511-3.125-1.533-.755-1.029-1.133-2.445-1.133-4.248 0-1.777.381-3.171 1.143-4.18.761-1.015 1.8-1.523 3.115-1.523.657 0 1.237.143 1.738.43.501.279.895.69 1.182 1.23Zm-5.283 4.082c0 1.393.221 2.445.664 3.154.442.71 1.097 1.065 1.962 1.065.866 0 1.524-.358 1.973-1.074.456-.717.684-1.765.684-3.145 0-1.387-.228-2.435-.684-3.145-.449-.716-1.107-1.074-1.973-1.074-.865 0-1.52.355-1.962 1.065-.443.709-.664 1.761-.664 3.154Zm19.892-.908v.879h-7.783v.058c-.052 1.491.231 2.562.85 3.213.625.651 1.503.977 2.636.977a6.39 6.39 0 0 0 1.797-.274 11.02 11.02 0 0 0 2.002-.83v1.787c-.684.28-1.344.489-1.982.625a8.253 8.253 0 0 1-1.836.215c-1.7 0-3.028-.508-3.985-1.523-.957-1.022-1.435-2.429-1.435-4.219 0-1.745.469-3.138 1.406-4.18.938-1.041 2.188-1.562 3.75-1.562 1.393 0 2.49.472 3.291 1.416.807.944 1.237 2.083 1.289 3.418Zm-1.797-.528a3.443 3.443 0 0 0-.83-1.953c-.469-.553-1.146-.83-2.031-.83-.866 0-1.579.287-2.139.86-.56.573-.866 1.217-.918 1.933l5.918-.01Zm11.875 1.436c0-1.393-.221-2.445-.664-3.154-.443-.71-1.097-1.065-1.963-1.065-.872 0-1.533.358-1.982 1.074-.449.71-.674 1.758-.674 3.145 0 1.38.225 2.428.674 3.145.449.716 1.11 1.074 1.982 1.074.866 0 1.52-.355 1.963-1.065.443-.709.664-1.761.664-3.154Zm-5.283-4.082c.286-.534.68-.944 1.182-1.23.507-.287 1.093-.43 1.757-.43 1.316 0 2.351.508 3.106 1.523.755 1.009 1.133 2.403 1.133 4.18 0 1.803-.381 3.219-1.143 4.248-.755 1.022-1.793 1.533-3.115 1.533-.651 0-1.227-.14-1.729-.42a3.177 3.177 0 0 1-1.191-1.24v1.377h-1.797v-15.195h1.797v5.654Zm15.225 4.043h-.596c-1.048 0-1.839.186-2.373.557-.527.364-.791.911-.791 1.64 0 .658.198 1.169.596 1.533.397.365.947.547 1.65.547.99 0 1.768-.341 2.334-1.025.566-.69.853-1.641.859-2.852v-.4h-1.679Zm3.486-.742v6.24h-1.807v-1.621c-.384.651-.869 1.133-1.455 1.445-.579.306-1.286.459-2.119.459-1.113 0-2.002-.312-2.666-.937-.664-.632-.996-1.475-.996-2.53 0-1.217.407-2.142 1.221-2.773.82-.632 2.021-.947 3.603-.947h2.412v-.284c-.006-.872-.228-1.503-.664-1.894-.436-.397-1.133-.596-2.09-.596-.612 0-1.23.088-1.855.264a8.109 8.109 0 0 0-1.826.771v-1.796a12.214 12.214 0 0 1 1.904-.567 8.55 8.55 0 0 1 1.777-.195c.905 0 1.677.133 2.315.4a3.648 3.648 0 0 1 1.562 1.201c.248.326.424.73.528 1.211.104.476.156 1.192.156 2.149Zm12.988-2.442a4.071 4.071 0 0 0-1.172-.654 3.987 3.987 0 0 0-1.308-.205c-1.12 0-1.976.352-2.569 1.055-.592.703-.888 1.718-.888 3.047v5.439h-1.807v-10.938h1.807v2.139c.299-.775.758-1.367 1.377-1.777.625-.417 1.364-.625 2.216-.625.443 0 .857.055 1.241.166.384.111.752.283 1.103.517v1.836Z" fill="#1E1E1E"/> + <g filter="url(#a)"> + <path d="M264 20a8 8 0 0 1 8-8h344a8 8 0 0 1 8 8v1077a8 8 0 0 1-8 8H272a8 8 0 0 1-8-8V20Z" fill="#F0F0F0"/> + <path d="M264.5 20a7.5 7.5 0 0 1 7.5-7.5h344a7.5 7.5 0 0 1 7.5 7.5v1077c0 4.14-3.358 7.5-7.5 7.5H272c-4.142 0-7.5-3.36-7.5-7.5V20Z" stroke="#949494"/> + <rect x="424" y="513.5" width="40" height="40" rx="20" fill="#1E1E1E"/> + <path d="M440.065 539.5v-1.523l4.04-3.96c.386-.39.708-.737.966-1.04.258-.303.451-.596.58-.88.128-.285.193-.588.193-.909 0-.368-.084-.682-.25-.944a1.664 1.664 0 0 0-.688-.613 2.214 2.214 0 0 0-.994-.216c-.383 0-.718.079-1.006.238a1.644 1.644 0 0 0-.67.665c-.156.288-.233.631-.233 1.029h-2.006c0-.739.169-1.381.506-1.927a3.42 3.42 0 0 1 1.392-1.267c.595-.299 1.276-.448 2.045-.448.781 0 1.466.145 2.057.437.591.292 1.049.691 1.375 1.199.33.507.494 1.087.494 1.739 0 .435-.083.863-.25 1.284-.166.42-.46.886-.88 1.397-.417.512-1.002 1.131-1.756 1.858l-2.006 2.04v.08h5.069v1.761h-7.978Z" fill="#fff"/> + <path d="M376.148 589.982a6.657 6.657 0 0 1-1.543.596 6.653 6.653 0 0 1-1.679.205c-1.869 0-3.321-.661-4.356-1.982-1.028-1.322-1.543-3.181-1.543-5.576 0-2.383.518-4.239 1.553-5.567 1.042-1.334 2.49-2.002 4.346-2.002.592 0 1.152.069 1.679.205a6.702 6.702 0 0 1 1.543.596v2.022a5.442 5.442 0 0 0-1.552-.909 4.718 4.718 0 0 0-1.67-.312c-1.283 0-2.243.495-2.881 1.484-.638.99-.957 2.484-.957 4.483 0 1.992.319 3.483.957 4.472.638.99 1.598 1.485 2.881 1.485.573 0 1.133-.105 1.679-.313a5.302 5.302 0 0 0 1.543-.908v2.021Zm7.588-9.16c-.911 0-1.601.355-2.07 1.065-.469.709-.703 1.761-.703 3.154 0 1.387.234 2.438.703 3.154.469.71 1.159 1.065 2.07 1.065.918 0 1.612-.355 2.08-1.065.469-.716.704-1.767.704-3.154 0-1.393-.235-2.445-.704-3.154-.468-.71-1.162-1.065-2.08-1.065Zm0-1.523c1.517 0 2.676.491 3.477 1.474.807.984 1.211 2.406 1.211 4.268 0 1.869-.401 3.294-1.201 4.277-.801.977-1.963 1.465-3.487 1.465-1.517 0-2.675-.488-3.476-1.465-.801-.983-1.201-2.408-1.201-4.277 0-1.862.4-3.284 1.201-4.268.801-.983 1.959-1.474 3.476-1.474Zm16.299 4.424v6.777h-1.806v-6.777c0-.983-.173-1.706-.518-2.168-.345-.463-.885-.694-1.621-.694-.84 0-1.488.3-1.944.899-.449.592-.673 1.445-.673 2.558v6.182h-1.797v-10.938h1.797v1.641c.319-.625.752-1.097 1.298-1.416.547-.325 1.195-.488 1.944-.488 1.113 0 1.943.368 2.49 1.103.553.73.83 1.836.83 3.321Zm7.783-7.266v3.105h4.082v1.397h-4.082v5.937c0 .808.153 1.371.459 1.69.306.319.84.478 1.602.478h2.021v1.436h-2.197c-1.348 0-2.298-.27-2.851-.811-.554-.54-.831-1.471-.831-2.793v-5.937h-2.919v-1.397h2.919v-3.105h1.797Zm16.993 7.676v.879h-7.784v.058c-.052 1.491.231 2.562.85 3.213.625.651 1.504.977 2.637.977a6.39 6.39 0 0 0 1.797-.274 10.983 10.983 0 0 0 2.001-.83v1.787a11.64 11.64 0 0 1-1.982.625 8.24 8.24 0 0 1-1.836.215c-1.699 0-3.027-.508-3.984-1.523-.957-1.022-1.436-2.429-1.436-4.219 0-1.745.469-3.138 1.406-4.18.938-1.041 2.188-1.562 3.75-1.562 1.394 0 2.491.472 3.291 1.416.808.944 1.237 2.083 1.29 3.418Zm-1.797-.528a3.443 3.443 0 0 0-.83-1.953c-.469-.553-1.146-.83-2.032-.83-.866 0-1.578.287-2.138.86-.56.573-.866 1.217-.918 1.933l5.918-.01Zm13.174.118v6.777h-1.807v-6.777c0-.983-.173-1.706-.518-2.168-.345-.463-.885-.694-1.621-.694-.84 0-1.487.3-1.943.899-.449.592-.674 1.445-.674 2.558v6.182h-1.797v-10.938h1.797v1.641c.319-.625.752-1.097 1.299-1.416.547-.325 1.194-.488 1.943-.488 1.113 0 1.944.368 2.49 1.103.554.73.831 1.836.831 3.321Zm7.783-7.266v3.105h4.082v1.397h-4.082v5.937c0 .808.153 1.371.459 1.69.306.319.84.478 1.601.478h2.022v1.436h-2.198c-1.347 0-2.298-.27-2.851-.811-.553-.54-.83-1.471-.83-2.793v-5.937h-2.92v-1.397h2.92v-3.105h1.797Zm20.381-.537h8.584v1.66h-6.602v3.906h5.986v1.66h-5.986v7.354h-1.982v-14.58Zm21.054 5.898a4.071 4.071 0 0 0-1.172-.654 3.987 3.987 0 0 0-1.308-.205c-1.12 0-1.976.352-2.569 1.055-.592.703-.888 1.718-.888 3.047v5.439h-1.807v-10.938h1.807v2.139c.299-.775.758-1.367 1.377-1.777.625-.417 1.364-.625 2.216-.625.443 0 .857.055 1.241.166.384.111.752.283 1.103.517v1.836Zm7.627 3.184h-.595c-1.049 0-1.84.186-2.374.557-.527.364-.791.911-.791 1.64 0 .658.199 1.169.596 1.533.397.365.947.547 1.651.547.989 0 1.767-.341 2.334-1.025.566-.69.852-1.641.859-2.852v-.4h-1.68Zm3.487-.742v6.24h-1.807v-1.621c-.384.651-.869 1.133-1.455 1.445-.58.306-1.286.459-2.119.459-1.114 0-2.002-.312-2.666-.937-.664-.632-.996-1.475-.996-2.53 0-1.217.406-2.142 1.22-2.773.821-.632 2.022-.947 3.604-.947h2.412v-.284c-.007-.872-.228-1.503-.664-1.894-.436-.397-1.133-.596-2.09-.596-.612 0-1.23.088-1.855.264a8.136 8.136 0 0 0-1.827.771v-1.796a12.192 12.192 0 0 1 1.905-.567 8.543 8.543 0 0 1 1.777-.195c.905 0 1.676.133 2.314.4a3.645 3.645 0 0 1 1.563 1.201c.247.326.423.73.527 1.211.104.476.157 1.192.157 2.149Zm8.31-3.584c.221-.469.501-.814.84-1.035.345-.228.758-.342 1.24-.342.879 0 1.498.342 1.856 1.025.364.677.546 1.957.546 3.838v6.338h-1.64v-6.26c0-1.543-.088-2.5-.264-2.871-.169-.377-.482-.566-.937-.566-.521 0-.879.202-1.075.605-.188.397-.283 1.341-.283 2.832v6.26h-1.64v-6.26c0-1.562-.095-2.526-.284-2.89-.182-.365-.514-.547-.996-.547-.475 0-.807.202-.996.605-.182.397-.273 1.341-.273 2.832v6.26h-1.631v-10.938h1.631v.938c.215-.391.482-.687.801-.889a2.003 2.003 0 0 1 1.103-.312c.495 0 .905.114 1.231.342.332.227.589.573.771 1.035Zm16.387 3.457v.879h-7.783v.058c-.052 1.491.231 2.562.849 3.213.625.651 1.504.977 2.637.977a6.39 6.39 0 0 0 1.797-.274 11.02 11.02 0 0 0 2.002-.83v1.787c-.684.28-1.345.489-1.983.625a8.24 8.24 0 0 1-1.836.215c-1.699 0-3.027-.508-3.984-1.523-.957-1.022-1.436-2.429-1.436-4.219 0-1.745.469-3.138 1.407-4.18.937-1.041 2.187-1.562 3.75-1.562 1.393 0 2.49.472 3.291 1.416.807.944 1.237 2.083 1.289 3.418Zm-1.797-.528a3.443 3.443 0 0 0-.83-1.953c-.469-.553-1.146-.83-2.031-.83-.866 0-1.579.287-2.139.86-.56.573-.866 1.217-.918 1.933l5.918-.01Z" fill="#1E1E1E"/> + </g> + <g filter="url(#b)"> + <path d="M636 20a8 8 0 0 1 8-8h1064c4.42 0 8 3.582 8 8v1077c0 4.42-3.58 8-8 8H644a8 8 0 0 1-8-8V20Z" fill="#fff"/> + <path d="M636.5 20a7.5 7.5 0 0 1 7.5-7.5h1064c4.14 0 7.5 3.358 7.5 7.5v1077c0 4.14-3.36 7.5-7.5 7.5H644c-4.142 0-7.5-3.36-7.5-7.5V20Z" stroke="#949494"/> + <rect x="1156" y="513.5" width="40" height="40" rx="20" fill="#1E1E1E"/> + <path d="M1176 539.659c-.82 0-1.55-.14-2.18-.42-.64-.281-1.14-.671-1.5-1.171-.37-.5-.57-1.077-.59-1.733h2.14c.02.315.12.589.31.824.19.231.44.411.76.54.31.129.66.193 1.05.193.42 0 .79-.072 1.11-.216.32-.148.58-.352.76-.614.18-.261.27-.562.26-.903.01-.352-.08-.663-.27-.932-.18-.269-.45-.479-.81-.63-.34-.152-.76-.228-1.26-.228h-1.03v-1.625h1.03c.41 0 .76-.07 1.07-.21.3-.14.54-.337.72-.591.17-.257.26-.555.25-.892a1.514 1.514 0 0 0-.85-1.432 2.067 2.067 0 0 0-.95-.204c-.35 0-.68.064-.99.193-.3.129-.54.312-.73.551-.19.235-.28.515-.3.841h-2.02c.01-.652.2-1.223.56-1.716.36-.496.85-.882 1.45-1.159.61-.28 1.29-.42 2.04-.42.78 0 1.45.145 2.03.437.57.288 1.02.676 1.33 1.165.32.488.47 1.028.47 1.619.01.655-.19 1.205-.58 1.648-.38.443-.89.733-1.52.869v.091c.82.114 1.45.417 1.88.909.44.489.66 1.097.65 1.824a2.92 2.92 0 0 1-.55 1.75c-.37.511-.88.913-1.52 1.205-.65.291-1.39.437-2.22.437Z" fill="#fff"/> + <path d="M1101.57 577.541v5.479h2.28c.91 0 1.62-.241 2.13-.723.51-.482.77-1.156.77-2.022 0-.865-.25-1.536-.76-2.011-.51-.482-1.22-.723-2.14-.723h-2.28Zm-1.98-1.621h4.26c1.63 0 2.86.371 3.7 1.113.84.736 1.26 1.817 1.26 3.242 0 1.439-.42 2.526-1.26 3.262-.83.736-2.06 1.104-3.7 1.104h-2.28v5.859h-1.98v-14.58Zm21.41 5.898a3.985 3.985 0 0 0-1.17-.654c-.4-.137-.84-.205-1.31-.205-1.12 0-1.98.352-2.57 1.055-.59.703-.89 1.718-.89 3.047v5.439h-1.8v-10.938h1.8v2.139c.3-.775.76-1.367 1.38-1.777.62-.417 1.36-.625 2.22-.625.44 0 .85.055 1.24.166.38.111.75.283 1.1.517v1.836Zm11.71 2.315v.879h-7.78v.058c-.06 1.491.23 2.562.85 3.213.62.651 1.5.977 2.63.977a6.4 6.4 0 0 0 1.8-.274c.62-.182 1.29-.459 2-.83v1.787c-.68.28-1.34.489-1.98.625a8.22 8.22 0 0 1-1.84.215c-1.7 0-3.02-.508-3.98-1.523-.96-1.022-1.44-2.429-1.44-4.219 0-1.745.47-3.138 1.41-4.18.94-1.041 2.19-1.562 3.75-1.562 1.39 0 2.49.472 3.29 1.416.81.944 1.24 2.083 1.29 3.418Zm-1.8-.528c-.08-.755-.35-1.406-.83-1.953-.47-.553-1.14-.83-2.03-.83-.87 0-1.58.287-2.14.86s-.86 1.217-.92 1.933l5.92-.01Zm3.89-4.043h1.86l3.18 9.18 3.18-9.18h1.87L1141 590.5h-2.31l-3.89-10.938Zm14.16.04h4.6v9.502h3.56v1.396h-8.92v-1.396h3.56v-8.106h-2.8v-1.396Zm2.8-4.248h1.8v2.265h-1.8v-2.265Zm17.1 8.779v.879h-7.78v.058c-.05 1.491.23 2.562.85 3.213.62.651 1.5.977 2.63.977.58 0 1.18-.091 1.8-.274.63-.182 1.29-.459 2-.83v1.787c-.68.28-1.34.489-1.98.625a8.22 8.22 0 0 1-1.84.215c-1.69 0-3.02-.508-3.98-1.523-.96-1.022-1.44-2.429-1.44-4.219 0-1.745.47-3.138 1.41-4.18.94-1.041 2.19-1.562 3.75-1.562 1.39 0 2.49.472 3.29 1.416.81.944 1.24 2.083 1.29 3.418Zm-1.8-.528a3.358 3.358 0 0 0-.83-1.953c-.46-.553-1.14-.83-2.03-.83-.86 0-1.58.287-2.14.86s-.86 1.217-.91 1.933l5.91-.01Zm2.91-4.043h1.78l1.91 8.838 1.56-5.644h1.53l1.58 5.644 1.91-8.838h1.78l-2.56 10.938h-1.72l-1.75-5.996-1.74 5.996h-1.72l-2.56-10.938Zm26.38-3.642h8.59v1.66h-6.61v3.906h5.99v1.66h-5.99v7.354h-1.98v-14.58Zm21.06 5.898a4.162 4.162 0 0 0-1.18-.654 3.93 3.93 0 0 0-1.3-.205c-1.12 0-1.98.352-2.57 1.055-.6.703-.89 1.718-.89 3.047v5.439h-1.81v-10.938h1.81v2.139c.3-.775.76-1.367 1.38-1.777.62-.417 1.36-.625 2.21-.625.45 0 .86.055 1.24.166.39.111.75.283 1.11.517v1.836Zm7.62 3.184h-.59c-1.05 0-1.84.186-2.38.557-.52.364-.79.911-.79 1.64 0 .658.2 1.169.6 1.533.4.365.95.547 1.65.547.99 0 1.77-.341 2.33-1.025.57-.69.86-1.641.86-2.852v-.4h-1.68Zm3.49-.742v6.24h-1.81v-1.621c-.38.651-.87 1.133-1.45 1.445-.58.306-1.29.459-2.12.459-1.11 0-2-.312-2.67-.937-.66-.632-.99-1.475-.99-2.53 0-1.217.4-2.142 1.22-2.773.82-.632 2.02-.947 3.6-.947h2.41v-.284c0-.872-.22-1.503-.66-1.894-.44-.397-1.13-.596-2.09-.596-.61 0-1.23.088-1.86.264-.62.176-1.23.433-1.82.771v-1.796c.66-.254 1.3-.443 1.9-.567.61-.13 1.21-.195 1.78-.195.9 0 1.68.133 2.31.4.65.267 1.17.668 1.57 1.201.24.326.42.73.52 1.211.11.476.16 1.192.16 2.149Zm8.31-3.584c.22-.469.5-.814.84-1.035.34-.228.76-.342 1.24-.342.88 0 1.5.342 1.86 1.025.36.677.54 1.957.54 3.838v6.338h-1.64v-6.26c0-1.543-.09-2.5-.26-2.871-.17-.377-.48-.566-.94-.566-.52 0-.88.202-1.07.605-.19.397-.29 1.341-.29 2.832v6.26h-1.64v-6.26c0-1.562-.09-2.526-.28-2.89-.18-.365-.51-.547-1-.547-.47 0-.8.202-.99.605-.19.397-.28 1.341-.28 2.832v6.26h-1.63v-10.938h1.63v.938c.22-.391.49-.687.8-.889.33-.208.7-.312 1.11-.312.49 0 .9.114 1.23.342.33.227.59.573.77 1.035Zm16.39 3.457v.879h-7.79v.058c-.05 1.491.23 2.562.85 3.213.63.651 1.51.977 2.64.977.57 0 1.17-.091 1.8-.274.62-.182 1.29-.459 2-.83v1.787c-.68.28-1.35.489-1.98.625-.64.144-1.25.215-1.84.215-1.7 0-3.03-.508-3.98-1.523-.96-1.022-1.44-2.429-1.44-4.219 0-1.745.47-3.138 1.41-4.18.93-1.041 2.18-1.562 3.75-1.562 1.39 0 2.49.472 3.29 1.416.81.944 1.23 2.083 1.29 3.418Zm-1.8-.528c-.08-.755-.35-1.406-.83-1.953-.47-.553-1.15-.83-2.03-.83-.87 0-1.58.287-2.14.86s-.87 1.217-.92 1.933l5.92-.01Z" fill="#1E1E1E"/> + </g> + <defs> + <filter id="a" x="260" y="11" width="368" height="1102" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB"> + <feFlood flood-opacity="0" result="BackgroundImageFix"/> + <feColorMatrix in="SourceAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/> + <feOffset dy="4"/> + <feGaussianBlur stdDeviation="2"/> + <feComposite in2="hardAlpha" operator="out"/> + <feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.01 0"/> + <feBlend in2="BackgroundImageFix" result="effect1_dropShadow_15996_31435"/> + <feColorMatrix in="SourceAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/> + <feOffset dy="3"/> + <feGaussianBlur stdDeviation="1.5"/> + <feComposite in2="hardAlpha" operator="out"/> + <feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.02 0"/> + <feBlend in2="effect1_dropShadow_15996_31435" result="effect2_dropShadow_15996_31435"/> + <feColorMatrix in="SourceAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/> + <feOffset dy="1"/> + <feGaussianBlur stdDeviation="1"/> + <feComposite in2="hardAlpha" operator="out"/> + <feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.02 0"/> + <feBlend in2="effect2_dropShadow_15996_31435" result="effect3_dropShadow_15996_31435"/> + <feColorMatrix in="SourceAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/> + <feOffset dy="1"/> + <feGaussianBlur stdDeviation=".5"/> + <feComposite in2="hardAlpha" operator="out"/> + <feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.03 0"/> + <feBlend in2="effect3_dropShadow_15996_31435" result="effect4_dropShadow_15996_31435"/> + <feBlend in="SourceGraphic" in2="effect4_dropShadow_15996_31435" result="shape"/> + </filter> + <filter id="b" x="632" y="11" width="1088" height="1102" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB"> + <feFlood flood-opacity="0" result="BackgroundImageFix"/> + <feColorMatrix in="SourceAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/> + <feOffset dy="4"/> + <feGaussianBlur stdDeviation="2"/> + <feComposite in2="hardAlpha" operator="out"/> + <feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.01 0"/> + <feBlend in2="BackgroundImageFix" result="effect1_dropShadow_15996_31435"/> + <feColorMatrix in="SourceAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/> + <feOffset dy="3"/> + <feGaussianBlur stdDeviation="1.5"/> + <feComposite in2="hardAlpha" operator="out"/> + <feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.02 0"/> + <feBlend in2="effect1_dropShadow_15996_31435" result="effect2_dropShadow_15996_31435"/> + <feColorMatrix in="SourceAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/> + <feOffset dy="1"/> + <feGaussianBlur stdDeviation="1"/> + <feComposite in2="hardAlpha" operator="out"/> + <feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.02 0"/> + <feBlend in2="effect2_dropShadow_15996_31435" result="effect3_dropShadow_15996_31435"/> + <feColorMatrix in="SourceAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/> + <feOffset dy="1"/> + <feGaussianBlur stdDeviation=".5"/> + <feComposite in2="hardAlpha" operator="out"/> + <feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.03 0"/> + <feBlend in2="effect3_dropShadow_15996_31435" result="effect4_dropShadow_15996_31435"/> + <feBlend in="SourceGraphic" in2="effect4_dropShadow_15996_31435" result="shape"/> + </filter> + </defs> +</svg> \ No newline at end of file diff --git a/storybook/stories/foundations/static/page-layout-example-1.svg b/storybook/stories/foundations/static/page-layout-example-1.svg new file mode 100644 index 00000000000000..5d7ded14e9285c --- /dev/null +++ b/storybook/stories/foundations/static/page-layout-example-1.svg @@ -0,0 +1,72 @@ +<svg width="1728" height="1117" fill="none" xmlns="http://www.w3.org/2000/svg"> + <path fill="#E0E0E0" d="M0 0h1728v1117H0z"/> + <path d="M99.705 551.418v2.002a7.764 7.764 0 0 0-1.807-.869 5.806 5.806 0 0 0-1.816-.293c-.931 0-1.667.218-2.207.654-.54.43-.81 1.012-.81 1.748 0 .645.175 1.136.527 1.475.358.338 1.022.622 1.992.849l1.035.235c1.367.319 2.363.82 2.988 1.504.625.683.938 1.614.938 2.793 0 1.386-.43 2.444-1.29 3.173-.858.73-2.108 1.094-3.75 1.094-.683 0-1.37-.075-2.06-.224a11.333 11.333 0 0 1-2.08-.655v-2.099c.749.475 1.455.823 2.12 1.045.67.221 1.344.332 2.02.332.997 0 1.772-.222 2.325-.664.553-.45.83-1.075.83-1.875 0-.73-.192-1.286-.576-1.67-.378-.384-1.038-.681-1.982-.889l-1.055-.244c-1.354-.306-2.337-.768-2.95-1.387-.611-.618-.917-1.448-.917-2.49 0-1.302.436-2.344 1.308-3.125.88-.788 2.045-1.182 3.496-1.182.56 0 1.15.065 1.768.196.618.123 1.27.312 1.953.566Zm5.254 3.184h4.6v9.502h3.564v1.396h-8.926v-1.396h3.565v-8.106h-2.803v-1.396Zm2.803-4.248h1.797v2.265h-1.797v-2.265Zm14.541 5.605v-5.654h1.797V565.5h-1.797v-1.377c-.3.54-.7.954-1.201 1.24-.495.28-1.068.42-1.719.42-1.322 0-2.363-.511-3.125-1.533-.755-1.029-1.133-2.445-1.133-4.248 0-1.777.381-3.171 1.143-4.18.761-1.015 1.8-1.523 3.115-1.523.657 0 1.237.143 1.738.43.501.279.895.69 1.182 1.23Zm-5.283 4.082c0 1.393.221 2.445.664 3.154.442.71 1.097 1.065 1.962 1.065.866 0 1.524-.358 1.973-1.074.456-.717.684-1.765.684-3.145 0-1.387-.228-2.435-.684-3.145-.449-.716-1.107-1.074-1.973-1.074-.865 0-1.52.355-1.962 1.065-.443.709-.664 1.761-.664 3.154Zm19.892-.908v.879h-7.783v.058c-.052 1.491.231 2.562.85 3.213.625.651 1.503.977 2.636.977a6.39 6.39 0 0 0 1.797-.274 11.02 11.02 0 0 0 2.002-.83v1.787c-.684.28-1.344.489-1.982.625a8.253 8.253 0 0 1-1.836.215c-1.7 0-3.028-.508-3.985-1.523-.957-1.022-1.435-2.429-1.435-4.219 0-1.745.469-3.138 1.406-4.18.938-1.041 2.188-1.562 3.75-1.562 1.393 0 2.49.472 3.291 1.416.807.944 1.237 2.083 1.289 3.418Zm-1.797-.528a3.443 3.443 0 0 0-.83-1.953c-.469-.553-1.146-.83-2.031-.83-.866 0-1.579.287-2.139.86-.56.573-.866 1.217-.918 1.933l5.918-.01Zm11.875 1.436c0-1.393-.221-2.445-.664-3.154-.443-.71-1.097-1.065-1.963-1.065-.872 0-1.533.358-1.982 1.074-.449.71-.674 1.758-.674 3.145 0 1.38.225 2.428.674 3.145.449.716 1.11 1.074 1.982 1.074.866 0 1.52-.355 1.963-1.065.443-.709.664-1.761.664-3.154Zm-5.283-4.082c.286-.534.68-.944 1.182-1.23.507-.287 1.093-.43 1.757-.43 1.316 0 2.351.508 3.106 1.523.755 1.009 1.133 2.403 1.133 4.18 0 1.803-.381 3.219-1.143 4.248-.755 1.022-1.793 1.533-3.115 1.533-.651 0-1.227-.14-1.729-.42a3.177 3.177 0 0 1-1.191-1.24v1.377h-1.797v-15.195h1.797v5.654Zm15.225 4.043h-.596c-1.048 0-1.839.186-2.373.557-.527.364-.791.911-.791 1.64 0 .658.198 1.169.596 1.533.397.365.947.547 1.65.547.99 0 1.768-.341 2.334-1.025.566-.69.853-1.641.859-2.852v-.4h-1.679Zm3.486-.742v6.24h-1.807v-1.621c-.384.651-.869 1.133-1.455 1.445-.579.306-1.286.459-2.119.459-1.113 0-2.002-.312-2.666-.937-.664-.632-.996-1.475-.996-2.53 0-1.217.407-2.142 1.221-2.773.82-.632 2.021-.947 3.603-.947h2.412v-.284c-.006-.872-.228-1.503-.664-1.894-.436-.397-1.133-.596-2.09-.596-.612 0-1.23.088-1.855.264a8.109 8.109 0 0 0-1.826.771v-1.796a12.214 12.214 0 0 1 1.904-.567 8.55 8.55 0 0 1 1.777-.195c.905 0 1.677.133 2.315.4a3.648 3.648 0 0 1 1.562 1.201c.248.326.424.73.528 1.211.104.476.156 1.192.156 2.149Zm12.988-2.442a4.071 4.071 0 0 0-1.172-.654 3.987 3.987 0 0 0-1.308-.205c-1.12 0-1.976.352-2.569 1.055-.592.703-.888 1.718-.888 3.047v5.439h-1.807v-10.938h1.807v2.139c.299-.775.758-1.367 1.377-1.777.625-.417 1.364-.625 2.216-.625.443 0 .857.055 1.241.166.384.111.752.283 1.103.517v1.836Z" fill="#1E1E1E"/> + <g filter="url(#a)"> + <path d="M264 20a8 8 0 0 1 8-8h344a8 8 0 0 1 8 8v1077a8 8 0 0 1-8 8H272a8 8 0 0 1-8-8V20Z" fill="#F0F0F0"/> + <path d="M264.5 20a7.5 7.5 0 0 1 7.5-7.5h344a7.5 7.5 0 0 1 7.5 7.5v1077c0 4.14-3.358 7.5-7.5 7.5H272c-4.142 0-7.5-3.36-7.5-7.5V20Z" stroke="#949494"/> + <path d="M376.148 564.982a6.657 6.657 0 0 1-1.543.596 6.653 6.653 0 0 1-1.679.205c-1.869 0-3.321-.661-4.356-1.982-1.028-1.322-1.543-3.181-1.543-5.576 0-2.383.518-4.239 1.553-5.567 1.042-1.334 2.49-2.002 4.346-2.002.592 0 1.152.069 1.679.205a6.702 6.702 0 0 1 1.543.596v2.022a5.442 5.442 0 0 0-1.552-.909 4.718 4.718 0 0 0-1.67-.312c-1.283 0-2.243.495-2.881 1.484-.638.99-.957 2.484-.957 4.483 0 1.992.319 3.483.957 4.472.638.99 1.598 1.485 2.881 1.485.573 0 1.133-.105 1.679-.313a5.302 5.302 0 0 0 1.543-.908v2.021Zm7.588-9.16c-.911 0-1.601.355-2.07 1.065-.469.709-.703 1.761-.703 3.154 0 1.387.234 2.438.703 3.154.469.71 1.159 1.065 2.07 1.065.918 0 1.612-.355 2.08-1.065.469-.716.704-1.767.704-3.154 0-1.393-.235-2.445-.704-3.154-.468-.71-1.162-1.065-2.08-1.065Zm0-1.523c1.517 0 2.676.491 3.477 1.474.807.984 1.211 2.406 1.211 4.268 0 1.869-.401 3.294-1.201 4.277-.801.977-1.963 1.465-3.487 1.465-1.517 0-2.675-.488-3.476-1.465-.801-.983-1.201-2.408-1.201-4.277 0-1.862.4-3.284 1.201-4.268.801-.983 1.959-1.474 3.476-1.474Zm16.299 4.424v6.777h-1.806v-6.777c0-.983-.173-1.706-.518-2.168-.345-.463-.885-.694-1.621-.694-.84 0-1.488.3-1.944.899-.449.592-.673 1.445-.673 2.558v6.182h-1.797v-10.938h1.797v1.641c.319-.625.752-1.097 1.298-1.416.547-.325 1.195-.488 1.944-.488 1.113 0 1.943.368 2.49 1.103.553.73.83 1.836.83 3.321Zm7.783-7.266v3.105h4.082v1.397h-4.082v5.937c0 .808.153 1.371.459 1.69.306.319.84.478 1.602.478h2.021v1.436h-2.197c-1.348 0-2.298-.27-2.851-.811-.554-.54-.831-1.471-.831-2.793v-5.937h-2.919v-1.397h2.919v-3.105h1.797Zm16.993 7.676v.879h-7.784v.058c-.052 1.491.231 2.562.85 3.213.625.651 1.504.977 2.637.977a6.39 6.39 0 0 0 1.797-.274 10.983 10.983 0 0 0 2.001-.83v1.787a11.64 11.64 0 0 1-1.982.625 8.24 8.24 0 0 1-1.836.215c-1.699 0-3.027-.508-3.984-1.523-.957-1.022-1.436-2.429-1.436-4.219 0-1.745.469-3.138 1.406-4.18.938-1.041 2.188-1.562 3.75-1.562 1.394 0 2.491.472 3.291 1.416.808.944 1.237 2.083 1.29 3.418Zm-1.797-.528a3.443 3.443 0 0 0-.83-1.953c-.469-.553-1.146-.83-2.032-.83-.866 0-1.578.287-2.138.86-.56.573-.866 1.217-.918 1.933l5.918-.01Zm13.174.118v6.777h-1.807v-6.777c0-.983-.173-1.706-.518-2.168-.345-.463-.885-.694-1.621-.694-.84 0-1.487.3-1.943.899-.449.592-.674 1.445-.674 2.558v6.182h-1.797v-10.938h1.797v1.641c.319-.625.752-1.097 1.299-1.416.547-.325 1.194-.488 1.943-.488 1.113 0 1.944.368 2.49 1.103.554.73.831 1.836.831 3.321Zm7.783-7.266v3.105h4.082v1.397h-4.082v5.937c0 .808.153 1.371.459 1.69.306.319.84.478 1.601.478h2.022v1.436h-2.198c-1.347 0-2.298-.27-2.851-.811-.553-.54-.83-1.471-.83-2.793v-5.937h-2.92v-1.397h2.92v-3.105h1.797Zm20.381-.537h8.584v1.66h-6.602v3.906h5.986v1.66h-5.986v7.354h-1.982v-14.58Zm21.054 5.898a4.071 4.071 0 0 0-1.172-.654 3.987 3.987 0 0 0-1.308-.205c-1.12 0-1.976.352-2.569 1.055-.592.703-.888 1.718-.888 3.047v5.439h-1.807v-10.938h1.807v2.139c.299-.775.758-1.367 1.377-1.777.625-.417 1.364-.625 2.216-.625.443 0 .857.055 1.241.166.384.111.752.283 1.103.517v1.836Zm7.627 3.184h-.595c-1.049 0-1.84.186-2.374.557-.527.364-.791.911-.791 1.64 0 .658.199 1.169.596 1.533.397.365.947.547 1.651.547.989 0 1.767-.341 2.334-1.025.566-.69.852-1.641.859-2.852v-.4h-1.68Zm3.487-.742v6.24h-1.807v-1.621c-.384.651-.869 1.133-1.455 1.445-.58.306-1.286.459-2.119.459-1.114 0-2.002-.312-2.666-.937-.664-.632-.996-1.475-.996-2.53 0-1.217.406-2.142 1.22-2.773.821-.632 2.022-.947 3.604-.947h2.412v-.284c-.007-.872-.228-1.503-.664-1.894-.436-.397-1.133-.596-2.09-.596-.612 0-1.23.088-1.855.264a8.136 8.136 0 0 0-1.827.771v-1.796a12.192 12.192 0 0 1 1.905-.567 8.543 8.543 0 0 1 1.777-.195c.905 0 1.676.133 2.314.4a3.645 3.645 0 0 1 1.563 1.201c.247.326.423.73.527 1.211.104.476.157 1.192.157 2.149Zm8.31-3.584c.221-.469.501-.814.84-1.035.345-.228.758-.342 1.24-.342.879 0 1.498.342 1.856 1.025.364.677.546 1.957.546 3.838v6.338h-1.64v-6.26c0-1.543-.088-2.5-.264-2.871-.169-.377-.482-.566-.937-.566-.521 0-.879.202-1.075.605-.188.397-.283 1.341-.283 2.832v6.26h-1.64v-6.26c0-1.562-.095-2.526-.284-2.89-.182-.365-.514-.547-.996-.547-.475 0-.807.202-.996.605-.182.397-.273 1.341-.273 2.832v6.26h-1.631v-10.938h1.631v.938c.215-.391.482-.687.801-.889a2.003 2.003 0 0 1 1.103-.312c.495 0 .905.114 1.231.342.332.227.589.573.771 1.035Zm16.387 3.457v.879h-7.783v.058c-.052 1.491.231 2.562.849 3.213.625.651 1.504.977 2.637.977a6.39 6.39 0 0 0 1.797-.274 11.02 11.02 0 0 0 2.002-.83v1.787c-.684.28-1.345.489-1.983.625a8.24 8.24 0 0 1-1.836.215c-1.699 0-3.027-.508-3.984-1.523-.957-1.022-1.436-2.429-1.436-4.219 0-1.745.469-3.138 1.407-4.18.937-1.041 2.187-1.562 3.75-1.562 1.393 0 2.49.472 3.291 1.416.807.944 1.237 2.083 1.289 3.418Zm-1.797-.528a3.443 3.443 0 0 0-.83-1.953c-.469-.553-1.146-.83-2.031-.83-.866 0-1.579.287-2.139.86-.56.573-.866 1.217-.918 1.933l5.918-.01Z" fill="#1E1E1E"/> + </g> + <g filter="url(#b)"> + <path d="M636 20a8 8 0 0 1 8-8h1064c4.42 0 8 3.582 8 8v1077c0 4.42-3.58 8-8 8H644a8 8 0 0 1-8-8V20Z" fill="#fff"/> + <path d="M636.5 20a7.5 7.5 0 0 1 7.5-7.5h1064c4.14 0 7.5 3.358 7.5 7.5v1077c0 4.14-3.36 7.5-7.5 7.5H644c-4.142 0-7.5-3.36-7.5-7.5V20Z" stroke="#949494"/> + <path d="M1101.57 552.541v5.479h2.28c.91 0 1.62-.241 2.13-.723.51-.482.77-1.156.77-2.022 0-.865-.25-1.536-.76-2.011-.51-.482-1.22-.723-2.14-.723h-2.28Zm-1.98-1.621h4.26c1.63 0 2.86.371 3.7 1.113.84.736 1.26 1.817 1.26 3.242 0 1.439-.42 2.526-1.26 3.262-.83.736-2.06 1.104-3.7 1.104h-2.28v5.859h-1.98v-14.58Zm21.41 5.898a3.985 3.985 0 0 0-1.17-.654c-.4-.137-.84-.205-1.31-.205-1.12 0-1.98.352-2.57 1.055-.59.703-.89 1.718-.89 3.047v5.439h-1.8v-10.938h1.8v2.139c.3-.775.76-1.367 1.38-1.777.62-.417 1.36-.625 2.22-.625.44 0 .85.055 1.24.166.38.111.75.283 1.1.517v1.836Zm11.71 2.315v.879h-7.78v.058c-.06 1.491.23 2.562.85 3.213.62.651 1.5.977 2.63.977a6.4 6.4 0 0 0 1.8-.274c.62-.182 1.29-.459 2-.83v1.787c-.68.28-1.34.489-1.98.625a8.22 8.22 0 0 1-1.84.215c-1.7 0-3.02-.508-3.98-1.523-.96-1.022-1.44-2.429-1.44-4.219 0-1.745.47-3.138 1.41-4.18.94-1.041 2.19-1.562 3.75-1.562 1.39 0 2.49.472 3.29 1.416.81.944 1.24 2.083 1.29 3.418Zm-1.8-.528c-.08-.755-.35-1.406-.83-1.953-.47-.553-1.14-.83-2.03-.83-.87 0-1.58.287-2.14.86s-.86 1.217-.92 1.933l5.92-.01Zm3.89-4.043h1.86l3.18 9.18 3.18-9.18h1.87L1141 565.5h-2.31l-3.89-10.938Zm14.16.04h4.6v9.502h3.56v1.396h-8.92v-1.396h3.56v-8.106h-2.8v-1.396Zm2.8-4.248h1.8v2.265h-1.8v-2.265Zm17.1 8.779v.879h-7.78v.058c-.05 1.491.23 2.562.85 3.213.62.651 1.5.977 2.63.977.58 0 1.18-.091 1.8-.274.63-.182 1.29-.459 2-.83v1.787c-.68.28-1.34.489-1.98.625a8.22 8.22 0 0 1-1.84.215c-1.69 0-3.02-.508-3.98-1.523-.96-1.022-1.44-2.429-1.44-4.219 0-1.745.47-3.138 1.41-4.18.94-1.041 2.19-1.562 3.75-1.562 1.39 0 2.49.472 3.29 1.416.81.944 1.24 2.083 1.29 3.418Zm-1.8-.528a3.358 3.358 0 0 0-.83-1.953c-.46-.553-1.14-.83-2.03-.83-.86 0-1.58.287-2.14.86s-.86 1.217-.91 1.933l5.91-.01Zm2.91-4.043h1.78l1.91 8.838 1.56-5.644h1.53l1.58 5.644 1.91-8.838h1.78l-2.56 10.938h-1.72l-1.75-5.996-1.74 5.996h-1.72l-2.56-10.938Zm26.38-3.642h8.59v1.66h-6.61v3.906h5.99v1.66h-5.99v7.354h-1.98v-14.58Zm21.06 5.898a4.162 4.162 0 0 0-1.18-.654 3.93 3.93 0 0 0-1.3-.205c-1.12 0-1.98.352-2.57 1.055-.6.703-.89 1.718-.89 3.047v5.439h-1.81v-10.938h1.81v2.139c.3-.775.76-1.367 1.38-1.777.62-.417 1.36-.625 2.21-.625.45 0 .86.055 1.24.166.39.111.75.283 1.11.517v1.836Zm7.62 3.184h-.59c-1.05 0-1.84.186-2.38.557-.52.364-.79.911-.79 1.64 0 .658.2 1.169.6 1.533.4.365.95.547 1.65.547.99 0 1.77-.341 2.33-1.025.57-.69.86-1.641.86-2.852v-.4h-1.68Zm3.49-.742v6.24h-1.81v-1.621c-.38.651-.87 1.133-1.45 1.445-.58.306-1.29.459-2.12.459-1.11 0-2-.312-2.67-.937-.66-.632-.99-1.475-.99-2.53 0-1.217.4-2.142 1.22-2.773.82-.632 2.02-.947 3.6-.947h2.41v-.284c0-.872-.22-1.503-.66-1.894-.44-.397-1.13-.596-2.09-.596-.61 0-1.23.088-1.86.264-.62.176-1.23.433-1.82.771v-1.796c.66-.254 1.3-.443 1.9-.567.61-.13 1.21-.195 1.78-.195.9 0 1.68.133 2.31.4.65.267 1.17.668 1.57 1.201.24.326.42.73.52 1.211.11.476.16 1.192.16 2.149Zm8.31-3.584c.22-.469.5-.814.84-1.035.34-.228.76-.342 1.24-.342.88 0 1.5.342 1.86 1.025.36.677.54 1.957.54 3.838v6.338h-1.64v-6.26c0-1.543-.09-2.5-.26-2.871-.17-.377-.48-.566-.94-.566-.52 0-.88.202-1.07.605-.19.397-.29 1.341-.29 2.832v6.26h-1.64v-6.26c0-1.562-.09-2.526-.28-2.89-.18-.365-.51-.547-1-.547-.47 0-.8.202-.99.605-.19.397-.28 1.341-.28 2.832v6.26h-1.63v-10.938h1.63v.938c.22-.391.49-.687.8-.889.33-.208.7-.312 1.11-.312.49 0 .9.114 1.23.342.33.227.59.573.77 1.035Zm16.39 3.457v.879h-7.79v.058c-.05 1.491.23 2.562.85 3.213.63.651 1.51.977 2.64.977.57 0 1.17-.091 1.8-.274.62-.182 1.29-.459 2-.83v1.787c-.68.28-1.35.489-1.98.625-.64.144-1.25.215-1.84.215-1.7 0-3.03-.508-3.98-1.523-.96-1.022-1.44-2.429-1.44-4.219 0-1.745.47-3.138 1.41-4.18.93-1.041 2.18-1.562 3.75-1.562 1.39 0 2.49.472 3.29 1.416.81.944 1.23 2.083 1.29 3.418Zm-1.8-.528c-.08-.755-.35-1.406-.83-1.953-.47-.553-1.15-.83-2.03-.83-.87 0-1.58.287-2.14.86s-.87 1.217-.92 1.933l5.92-.01Z" fill="#1E1E1E"/> + </g> + <defs> + <filter id="a" x="260" y="11" width="368" height="1102" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB"> + <feFlood flood-opacity="0" result="BackgroundImageFix"/> + <feColorMatrix in="SourceAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/> + <feOffset dy="4"/> + <feGaussianBlur stdDeviation="2"/> + <feComposite in2="hardAlpha" operator="out"/> + <feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.01 0"/> + <feBlend in2="BackgroundImageFix" result="effect1_dropShadow_15996_31447"/> + <feColorMatrix in="SourceAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/> + <feOffset dy="3"/> + <feGaussianBlur stdDeviation="1.5"/> + <feComposite in2="hardAlpha" operator="out"/> + <feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.02 0"/> + <feBlend in2="effect1_dropShadow_15996_31447" result="effect2_dropShadow_15996_31447"/> + <feColorMatrix in="SourceAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/> + <feOffset dy="1"/> + <feGaussianBlur stdDeviation="1"/> + <feComposite in2="hardAlpha" operator="out"/> + <feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.02 0"/> + <feBlend in2="effect2_dropShadow_15996_31447" result="effect3_dropShadow_15996_31447"/> + <feColorMatrix in="SourceAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/> + <feOffset dy="1"/> + <feGaussianBlur stdDeviation=".5"/> + <feComposite in2="hardAlpha" operator="out"/> + <feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.03 0"/> + <feBlend in2="effect3_dropShadow_15996_31447" result="effect4_dropShadow_15996_31447"/> + <feBlend in="SourceGraphic" in2="effect4_dropShadow_15996_31447" result="shape"/> + </filter> + <filter id="b" x="632" y="11" width="1088" height="1102" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB"> + <feFlood flood-opacity="0" result="BackgroundImageFix"/> + <feColorMatrix in="SourceAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/> + <feOffset dy="4"/> + <feGaussianBlur stdDeviation="2"/> + <feComposite in2="hardAlpha" operator="out"/> + <feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.01 0"/> + <feBlend in2="BackgroundImageFix" result="effect1_dropShadow_15996_31447"/> + <feColorMatrix in="SourceAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/> + <feOffset dy="3"/> + <feGaussianBlur stdDeviation="1.5"/> + <feComposite in2="hardAlpha" operator="out"/> + <feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.02 0"/> + <feBlend in2="effect1_dropShadow_15996_31447" result="effect2_dropShadow_15996_31447"/> + <feColorMatrix in="SourceAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/> + <feOffset dy="1"/> + <feGaussianBlur stdDeviation="1"/> + <feComposite in2="hardAlpha" operator="out"/> + <feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.02 0"/> + <feBlend in2="effect2_dropShadow_15996_31447" result="effect3_dropShadow_15996_31447"/> + <feColorMatrix in="SourceAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/> + <feOffset dy="1"/> + <feGaussianBlur stdDeviation=".5"/> + <feComposite in2="hardAlpha" operator="out"/> + <feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.03 0"/> + <feBlend in2="effect3_dropShadow_15996_31447" result="effect4_dropShadow_15996_31447"/> + <feBlend in="SourceGraphic" in2="effect4_dropShadow_15996_31447" result="shape"/> + </filter> + </defs> +</svg> \ No newline at end of file diff --git a/storybook/stories/foundations/static/page-layout-example-2.svg b/storybook/stories/foundations/static/page-layout-example-2.svg new file mode 100644 index 00000000000000..66fce86101d159 --- /dev/null +++ b/storybook/stories/foundations/static/page-layout-example-2.svg @@ -0,0 +1,68 @@ +<svg width="1728" height="1117" fill="none" xmlns="http://www.w3.org/2000/svg"> + <path fill="#E0E0E0" d="M0 0h1728v1117H0z"/> + <path d="M99.705 551.418v2.002a7.764 7.764 0 0 0-1.807-.869 5.806 5.806 0 0 0-1.816-.293c-.931 0-1.667.218-2.207.654-.54.43-.81 1.012-.81 1.748 0 .645.175 1.136.527 1.475.358.338 1.022.622 1.992.849l1.035.235c1.367.319 2.363.82 2.988 1.504.625.683.938 1.614.938 2.793 0 1.386-.43 2.444-1.29 3.173-.858.73-2.108 1.094-3.75 1.094-.683 0-1.37-.075-2.06-.224a11.333 11.333 0 0 1-2.08-.655v-2.099c.749.475 1.455.823 2.12 1.045.67.221 1.344.332 2.02.332.997 0 1.772-.222 2.325-.664.553-.45.83-1.075.83-1.875 0-.73-.192-1.286-.576-1.67-.378-.384-1.038-.681-1.982-.889l-1.055-.244c-1.354-.306-2.337-.768-2.95-1.387-.611-.618-.917-1.448-.917-2.49 0-1.302.436-2.344 1.308-3.125.88-.788 2.045-1.182 3.496-1.182.56 0 1.15.065 1.768.196.618.123 1.27.312 1.953.566Zm5.254 3.184h4.6v9.502h3.564v1.396h-8.926v-1.396h3.565v-8.106h-2.803v-1.396Zm2.803-4.248h1.797v2.265h-1.797v-2.265Zm14.541 5.605v-5.654h1.797V565.5h-1.797v-1.377c-.3.54-.7.954-1.201 1.24-.495.28-1.068.42-1.719.42-1.322 0-2.363-.511-3.125-1.533-.755-1.029-1.133-2.445-1.133-4.248 0-1.777.381-3.171 1.143-4.18.761-1.015 1.8-1.523 3.115-1.523.657 0 1.237.143 1.738.43.501.279.895.69 1.182 1.23Zm-5.283 4.082c0 1.393.221 2.445.664 3.154.442.71 1.097 1.065 1.962 1.065.866 0 1.524-.358 1.973-1.074.456-.717.684-1.765.684-3.145 0-1.387-.228-2.435-.684-3.145-.449-.716-1.107-1.074-1.973-1.074-.865 0-1.52.355-1.962 1.065-.443.709-.664 1.761-.664 3.154Zm19.892-.908v.879h-7.783v.058c-.052 1.491.231 2.562.85 3.213.625.651 1.503.977 2.636.977a6.39 6.39 0 0 0 1.797-.274 11.02 11.02 0 0 0 2.002-.83v1.787c-.684.28-1.344.489-1.982.625a8.253 8.253 0 0 1-1.836.215c-1.7 0-3.028-.508-3.985-1.523-.957-1.022-1.435-2.429-1.435-4.219 0-1.745.469-3.138 1.406-4.18.938-1.041 2.188-1.562 3.75-1.562 1.393 0 2.49.472 3.291 1.416.807.944 1.237 2.083 1.289 3.418Zm-1.797-.528a3.443 3.443 0 0 0-.83-1.953c-.469-.553-1.146-.83-2.031-.83-.866 0-1.579.287-2.139.86-.56.573-.866 1.217-.918 1.933l5.918-.01Zm11.875 1.436c0-1.393-.221-2.445-.664-3.154-.443-.71-1.097-1.065-1.963-1.065-.872 0-1.533.358-1.982 1.074-.449.71-.674 1.758-.674 3.145 0 1.38.225 2.428.674 3.145.449.716 1.11 1.074 1.982 1.074.866 0 1.52-.355 1.963-1.065.443-.709.664-1.761.664-3.154Zm-5.283-4.082c.286-.534.68-.944 1.182-1.23.507-.287 1.093-.43 1.757-.43 1.316 0 2.351.508 3.106 1.523.755 1.009 1.133 2.403 1.133 4.18 0 1.803-.381 3.219-1.143 4.248-.755 1.022-1.793 1.533-3.115 1.533-.651 0-1.227-.14-1.729-.42a3.177 3.177 0 0 1-1.191-1.24v1.377h-1.797v-15.195h1.797v5.654Zm15.225 4.043h-.596c-1.048 0-1.839.186-2.373.557-.527.364-.791.911-.791 1.64 0 .658.198 1.169.596 1.533.397.365.947.547 1.65.547.99 0 1.768-.341 2.334-1.025.566-.69.853-1.641.859-2.852v-.4h-1.679Zm3.486-.742v6.24h-1.807v-1.621c-.384.651-.869 1.133-1.455 1.445-.579.306-1.286.459-2.119.459-1.113 0-2.002-.312-2.666-.937-.664-.632-.996-1.475-.996-2.53 0-1.217.407-2.142 1.221-2.773.82-.632 2.021-.947 3.603-.947h2.412v-.284c-.006-.872-.228-1.503-.664-1.894-.436-.397-1.133-.596-2.09-.596-.612 0-1.23.088-1.855.264a8.109 8.109 0 0 0-1.826.771v-1.796a12.214 12.214 0 0 1 1.904-.567 8.55 8.55 0 0 1 1.777-.195c.905 0 1.677.133 2.315.4a3.648 3.648 0 0 1 1.562 1.201c.248.326.424.73.528 1.211.104.476.156 1.192.156 2.149Zm12.988-2.442a4.071 4.071 0 0 0-1.172-.654 3.987 3.987 0 0 0-1.308-.205c-1.12 0-1.976.352-2.569 1.055-.592.703-.888 1.718-.888 3.047v5.439h-1.807v-10.938h1.807v2.139c.299-.775.758-1.367 1.377-1.777.625-.417 1.364-.625 2.216-.625.443 0 .857.055 1.241.166.384.111.752.283 1.103.517v1.836Z" fill="#1E1E1E"/> + <g opacity=".5" filter="url(#a)"> + <path d="M324 29.84a7.84 7.84 0 0 1 7.84-7.84h356.72a7.84 7.84 0 0 1 7.84 7.84v964.32a7.84 7.84 0 0 1-7.84 7.84H331.84a7.84 7.84 0 0 1-7.84-7.84V29.84Z" fill="#fff"/> + <path d="M395.391 495.387c-4.766 0-7.775-3.284-7.775-8.579v-.011c0-5.295 3.02-8.556 7.775-8.556 3.813 0 6.615 2.377 7.051 5.754l.012.114h-2.906l-.057-.218c-.483-1.895-1.941-3.078-4.1-3.078-2.894 0-4.743 2.309-4.743 5.984v.011c0 3.687 1.86 6.007 4.743 6.007 2.113 0 3.595-1.137 4.134-3.182l.035-.114h2.905l-.023.114c-.425 3.4-3.238 5.754-7.051 5.754Zm13.954-.046c-3.583 0-5.892-2.389-5.892-6.328v-.023c0-3.904 2.343-6.316 5.88-6.316 3.549 0 5.903 2.389 5.903 6.316v.023c0 3.951-2.319 6.328-5.891 6.328Zm.011-2.308c1.803 0 2.963-1.47 2.963-4.02v-.023c0-2.538-1.171-3.996-2.986-3.996-1.78 0-2.963 1.47-2.963 3.996v.023c0 2.561 1.16 4.02 2.986 4.02Zm7.247 2.067v-12.173h2.86v1.906h.058c.631-1.309 1.871-2.159 3.72-2.159 2.676 0 4.204 1.723 4.204 4.548v7.878h-2.86v-7.304c0-1.734-.804-2.71-2.435-2.71-1.642 0-2.687 1.194-2.687 2.951v7.063h-2.86Zm17.193.241c-2.549 0-3.755-1.033-3.755-3.537v-6.672h-1.723v-2.205h1.723v-3.09h2.905v3.09h2.263v2.205h-2.263v6.431c0 1.194.528 1.573 1.574 1.573.275 0 .493-.034.689-.046v2.148a10.4 10.4 0 0 1-1.413.103Zm8.028 0c-3.652 0-5.857-2.423-5.857-6.293v-.012c0-3.836 2.24-6.362 5.719-6.362 3.48 0 5.639 2.446 5.639 6.121v.919h-8.521c.046 2.136 1.217 3.399 3.078 3.399 1.424 0 2.354-.758 2.629-1.573l.023-.081h2.676l-.034.127c-.379 1.872-2.171 3.755-5.352 3.755Zm-.103-10.428c-1.516 0-2.653 1.022-2.883 2.894h5.708c-.207-1.929-1.309-2.894-2.825-2.894Zm6.983 10.187v-12.173h2.859v1.906h.058c.631-1.309 1.872-2.159 3.721-2.159 2.676 0 4.203 1.723 4.203 4.548v7.878h-2.86v-7.304c0-1.734-.804-2.71-2.434-2.71-1.643 0-2.688 1.194-2.688 2.951v7.063h-2.859Zm17.192.241c-2.549 0-3.755-1.033-3.755-3.537v-6.672h-1.723v-2.205h1.723v-3.09h2.905v3.09h2.263v2.205h-2.263v6.431c0 1.194.529 1.573 1.574 1.573.275 0 .494-.034.689-.046v2.148a10.4 10.4 0 0 1-1.413.103Zm10.739-.034c-2.435 0-4.077-1.505-4.077-3.675v-.023c0-2.159 1.677-3.457 4.628-3.641l3.181-.195v-.838c0-1.229-.803-1.976-2.25-1.976-1.356 0-2.182.632-2.366 1.528l-.023.103h-2.653l.011-.138c.173-2.159 2.045-3.778 5.134-3.778 3.043 0 5.007 1.608 5.007 4.054v8.372h-2.86v-1.872h-.068c-.701 1.275-2.056 2.079-3.664 2.079Zm-1.229-3.79c0 .999.827 1.608 2.079 1.608 1.642 0 2.882-1.091 2.882-2.538v-.919l-2.779.172c-1.412.092-2.182.701-2.182 1.665v.012Zm9.659 3.583v-12.173h2.86v2.101h.057c.379-1.47 1.39-2.354 2.791-2.354.356 0 .689.057.907.115v2.584c-.241-.092-.678-.161-1.16-.161-1.619 0-2.595 1.022-2.595 2.825v7.063h-2.86Zm12.507.241c-3.652 0-5.857-2.423-5.857-6.293v-.012c0-3.836 2.239-6.362 5.719-6.362 3.48 0 5.639 2.446 5.639 6.121v.919h-8.521c.046 2.136 1.217 3.399 3.077 3.399 1.424 0 2.355-.758 2.63-1.573l.023-.081h2.676l-.034.127c-.379 1.872-2.171 3.755-5.352 3.755Zm-.103-10.428c-1.516 0-2.653 1.022-2.883 2.894h5.708c-.207-1.929-1.309-2.894-2.825-2.894Zm10.577 10.394c-2.434 0-4.077-1.505-4.077-3.675v-.023c0-2.159 1.677-3.457 4.628-3.641l3.182-.195v-.838c0-1.229-.804-1.976-2.251-1.976-1.355 0-2.182.632-2.366 1.528l-.023.103h-2.653l.012-.138c.172-2.159 2.044-3.778 5.133-3.778 3.043 0 5.007 1.608 5.007 4.054v8.372h-2.859v-1.872h-.069c-.701 1.275-2.056 2.079-3.664 2.079Zm-1.229-3.79c0 .999.827 1.608 2.079 1.608 1.642 0 2.883-1.091 2.883-2.538v-.919l-2.78.172c-1.412.092-2.182.701-2.182 1.665v.012Z" fill="#1E1E1E"/> + <path d="M390.465 523.9v-8.582h-3.115v-1.085h7.436v1.085h-3.115v8.582h-1.206Zm6.103 0v-10.089h1.166v3.952h.107c.362-.763 1.038-1.212 2.103-1.212 1.622 0 2.526.958 2.526 2.673v4.676h-1.166v-4.395c0-1.299-.536-1.922-1.688-1.922s-1.882.777-1.882 2.043v4.274h-1.166Zm10.96.127c-2.063 0-3.309-1.447-3.309-3.711v-.007c0-2.231 1.272-3.758 3.235-3.758 1.963 0 3.156 1.46 3.156 3.617v.456h-5.199c.034 1.494.858 2.365 2.144 2.365.978 0 1.581-.462 1.775-.898l.027-.06h1.166l-.014.053c-.248.979-1.279 1.943-2.981 1.943Zm-.08-6.438c-1.072 0-1.89.731-2.017 2.104h3.993c-.121-1.427-.911-2.104-1.976-2.104Zm11.784 6.438c-2.024 0-3.317-1.467-3.317-3.758v-.013c0-2.245 1.287-3.705 3.31-3.705 1.755 0 2.807 1.012 3.021 2.298l.007.04h-1.159l-.007-.02c-.181-.71-.824-1.286-1.862-1.286-1.3 0-2.117 1.045-2.117 2.673v.013c0 1.661.831 2.727 2.117 2.727.971 0 1.588-.429 1.856-1.233l.013-.04 1.152-.007-.013.074c-.295 1.306-1.266 2.237-3.001 2.237Zm7.69 0c-2.056 0-3.329-1.42-3.329-3.731v-.014c0-2.318 1.273-3.731 3.329-3.731 2.057 0 3.33 1.413 3.33 3.731v.014c0 2.311-1.273 3.731-3.33 3.731Zm0-1.031c1.367 0 2.137-.999 2.137-2.7v-.014c0-1.708-.77-2.699-2.137-2.699-1.366 0-2.137.991-2.137 2.699v.014c0 1.701.771 2.7 2.137 2.7Zm5.139.904v-7.222h1.165v1.085h.107c.362-.763 1.039-1.212 2.104-1.212 1.621 0 2.526.958 2.526 2.673v4.676h-1.166v-4.395c0-1.299-.536-1.922-1.688-1.922-1.153 0-1.883.777-1.883 2.043v4.274h-1.165Zm10.578.054c-1.488 0-2.111-.55-2.111-1.93v-4.381h-1.139v-.965h1.139v-1.869h1.206v1.869h1.581v.965h-1.581v4.086c0 .851.295 1.193 1.045 1.193.208 0 .322-.007.536-.027v.992c-.228.04-.449.067-.676.067Zm5.258.073c-2.063 0-3.309-1.447-3.309-3.711v-.007c0-2.231 1.273-3.758 3.236-3.758 1.963 0 3.155 1.46 3.155 3.617v.456h-5.198c.033 1.494.857 2.365 2.143 2.365.978 0 1.581-.462 1.776-.898l.026-.06h1.166l-.013.053c-.248.979-1.28 1.943-2.982 1.943Zm-.08-6.438c-1.072 0-1.889.731-2.016 2.104h3.992c-.12-1.427-.911-2.104-1.976-2.104Zm4.971 6.311v-7.222h1.166v1.085h.107c.362-.763 1.038-1.212 2.103-1.212 1.622 0 2.526.958 2.526 2.673v4.676h-1.166v-4.395c0-1.299-.536-1.922-1.688-1.922s-1.882.777-1.882 2.043v4.274h-1.166Zm10.578.054c-1.487 0-2.11-.55-2.11-1.93v-4.381h-1.139v-.965h1.139v-1.869h1.206v1.869h1.581v.965h-1.581v4.086c0 .851.294 1.193 1.045 1.193.207 0 .321-.007.536-.027v.992c-.228.04-.449.067-.677.067Zm8.26.073c-1.373 0-2.412-.83-2.412-2.144v-.013c0-1.286.952-2.03 2.633-2.13l2.07-.128v-.656c0-.891-.542-1.373-1.621-1.373-.864 0-1.434.321-1.621.884l-.007.02h-1.166l.007-.04c.188-1.132 1.286-1.896 2.827-1.896 1.762 0 2.747.904 2.747 2.405v4.944h-1.166v-1.065h-.107c-.455.77-1.212 1.192-2.184 1.192Zm-1.219-2.17c0 .73.623 1.159 1.46 1.159 1.186 0 2.05-.777 2.05-1.809v-.657l-1.929.121c-1.099.067-1.581.455-1.581 1.172v.014Zm6.806 2.043v-7.222h1.166v1.072h.107c.275-.757.951-1.199 1.929-1.199.222 0 .469.027.583.047v1.132a4.208 4.208 0 0 0-.716-.067c-1.113 0-1.903.703-1.903 1.762v4.475h-1.166Zm7.972.127c-2.063 0-3.309-1.447-3.309-3.711v-.007c0-2.231 1.273-3.758 3.236-3.758 1.963 0 3.155 1.46 3.155 3.617v.456h-5.199c.034 1.494.858 2.365 2.144 2.365.978 0 1.581-.462 1.775-.898l.027-.06h1.166l-.014.053c-.247.979-1.279 1.943-2.981 1.943Zm-.08-6.438c-1.072 0-1.889.731-2.017 2.104h3.993c-.12-1.427-.911-2.104-1.976-2.104Zm6.954 6.438c-1.374 0-2.412-.83-2.412-2.144v-.013c0-1.286.951-2.03 2.633-2.13l2.07-.128v-.656c0-.891-.543-1.373-1.621-1.373-.865 0-1.434.321-1.622.884l-.006.02h-1.166l.007-.04c.187-1.132 1.286-1.896 2.827-1.896 1.762 0 2.747.904 2.747 2.405v4.944h-1.166v-1.065h-.107c-.456.77-1.213 1.192-2.184 1.192Zm-1.22-2.17c0 .73.624 1.159 1.461 1.159 1.186 0 2.05-.777 2.05-1.809v-.657l-1.93.121c-1.098.067-1.581.455-1.581 1.172v.014Zm11.282-6.572a.807.807 0 0 1-.804-.804c0-.442.362-.804.804-.804.442 0 .804.362.804.804a.807.807 0 0 1-.804.804Zm-.59 8.615v-7.222h1.166v7.222h-1.166Zm5.842.127c-1.608 0-2.713-.777-2.847-1.969h1.192c.195.603.771.998 1.695.998.972 0 1.662-.462 1.662-1.119v-.013c0-.489-.369-.831-1.26-1.045l-1.118-.268c-1.354-.322-1.963-.911-1.963-1.95v-.007c0-1.199 1.152-2.103 2.686-2.103 1.514 0 2.566.757 2.727 1.936h-1.146c-.154-.569-.717-.965-1.588-.965-.857 0-1.487.442-1.487 1.079v.013c0 .489.362.797 1.213 1.005l1.112.268c1.36.328 2.016.918 2.016 1.95v.013c0 1.286-1.253 2.177-2.894 2.177Zm11.128-.073c-1.488 0-2.111-.55-2.111-1.93v-4.381h-1.139v-.965h1.139v-1.869h1.206v1.869h1.581v.965h-1.581v4.086c0 .851.295 1.193 1.045 1.193.208 0 .322-.007.536-.027v.992c-.228.04-.449.067-.676.067Zm2.485-.054v-10.089h1.166v3.952h.107c.362-.763 1.038-1.212 2.103-1.212 1.622 0 2.526.958 2.526 2.673v4.676h-1.166v-4.395c0-1.299-.536-1.922-1.688-1.922s-1.882.777-1.882 2.043v4.274h-1.166Zm10.96.127c-2.063 0-3.31-1.447-3.31-3.711v-.007c0-2.231 1.273-3.758 3.236-3.758 1.963 0 3.156 1.46 3.156 3.617v.456h-5.199c.033 1.494.857 2.365 2.144 2.365.978 0 1.581-.462 1.775-.898l.027-.06h1.165l-.013.053c-.248.979-1.28 1.943-2.981 1.943Zm-.081-6.438c-1.071 0-1.889.731-2.016 2.104h3.993c-.121-1.427-.911-2.104-1.977-2.104Zm8.83 8.723v-9.634h1.166v1.152h.107c.442-.803 1.246-1.279 2.251-1.279 1.829 0 3.028 1.48 3.028 3.731v.014c0 2.264-1.193 3.731-3.028 3.731-.992 0-1.856-.495-2.251-1.266h-.107v3.551h-1.166Zm3.256-3.316c1.32 0 2.103-1.019 2.103-2.7v-.014c0-1.681-.783-2.699-2.103-2.699-1.313 0-2.117 1.031-2.117 2.699v.014c0 1.668.804 2.7 2.117 2.7Zm5.118.904v-7.222h1.166v1.072h.107c.275-.757.951-1.199 1.929-1.199.221 0 .469.027.583.047v1.132a4.21 4.21 0 0 0-.717-.067c-1.112 0-1.902.703-1.902 1.762v4.475h-1.166Zm5.842-8.615a.807.807 0 0 1-.804-.804c0-.442.362-.804.804-.804.442 0 .804.362.804.804a.807.807 0 0 1-.804.804Zm-.59 8.615v-7.222h1.166v7.222h-1.166Zm3.363 0v-7.222h1.166v1.099h.107c.322-.777 1.005-1.226 1.95-1.226.978 0 1.661.516 1.996 1.266h.107c.389-.764 1.213-1.266 2.211-1.266 1.474 0 2.298.844 2.298 2.358v4.991h-1.166v-4.723c0-1.065-.489-1.594-1.507-1.594-1.005 0-1.668.757-1.668 1.674v4.643h-1.166v-4.904c0-.857-.59-1.413-1.501-1.413-.944 0-1.661.824-1.661 1.842v4.475h-1.166Zm13.921.127c-1.373 0-2.411-.83-2.411-2.144v-.013c0-1.286.951-2.03 2.632-2.13l2.07-.128v-.656c0-.891-.542-1.373-1.621-1.373-.864 0-1.433.321-1.621.884l-.007.02h-1.165l.006-.04c.188-1.132 1.287-1.896 2.827-1.896 1.762 0 2.747.904 2.747 2.405v4.944h-1.166v-1.065h-.107c-.455.77-1.212 1.192-2.184 1.192Zm-1.219-2.17c0 .73.623 1.159 1.46 1.159 1.186 0 2.05-.777 2.05-1.809v-.657l-1.929.121c-1.099.067-1.581.455-1.581 1.172v.014Zm6.806 2.043v-7.222h1.166v1.072h.107c.275-.757.952-1.199 1.93-1.199.221 0 .469.027.582.047v1.132a4.208 4.208 0 0 0-.716-.067c-1.112 0-1.903.703-1.903 1.762v4.475h-1.166Zm5.842 2.532a3.39 3.39 0 0 1-.516-.04v-.958c.141.027.322.034.483.034.663 0 1.065-.302 1.326-1.133l.134-.428-2.673-7.229h1.246l1.983 5.942h.107l1.977-5.942h1.226l-2.821 7.664c-.596 1.621-1.192 2.09-2.472 2.09Zm13.171-2.405c-1.608 0-2.713-.777-2.847-1.969h1.192c.194.603.77.998 1.695.998.971 0 1.661-.462 1.661-1.119v-.013c0-.489-.368-.831-1.259-1.045l-1.119-.268c-1.353-.322-1.963-.911-1.963-1.95v-.007c0-1.199 1.153-2.103 2.687-2.103 1.514 0 2.565.757 2.726 1.936h-1.145c-.154-.569-.717-.965-1.588-.965-.858 0-1.487.442-1.487 1.079v.013c0 .489.362.797 1.212 1.005l1.112.268c1.36.328 2.017.918 2.017 1.95v.013c0 1.286-1.253 2.177-2.894 2.177Zm4.703 2.285v-9.634h1.165v1.152h.107c.443-.803 1.247-1.279 2.251-1.279 1.829 0 3.028 1.48 3.028 3.731v.014c0 2.264-1.192 3.731-3.028 3.731-.991 0-1.855-.495-2.251-1.266h-.107v3.551h-1.165Zm3.255-3.316c1.32 0 2.104-1.019 2.104-2.7v-.014c0-1.681-.784-2.699-2.104-2.699-1.313 0-2.116 1.031-2.116 2.699v.014c0 1.668.803 2.7 2.116 2.7Zm7.102 1.031c-1.374 0-2.412-.83-2.412-2.144v-.013c0-1.286.951-2.03 2.633-2.13l2.07-.128v-.656c0-.891-.543-1.373-1.621-1.373-.865 0-1.434.321-1.622.884l-.006.02h-1.166l.007-.04c.187-1.132 1.286-1.896 2.827-1.896 1.762 0 2.746.904 2.746 2.405v4.944h-1.165v-1.065h-.107c-.456.77-1.213 1.192-2.184 1.192Zm-1.22-2.17c0 .73.623 1.159 1.461 1.159 1.186 0 2.05-.777 2.05-1.809v-.657l-1.93.121c-1.098.067-1.581.455-1.581 1.172v.014Zm9.761 2.17c-2.023 0-3.316-1.467-3.316-3.758v-.013c0-2.245 1.286-3.705 3.309-3.705 1.756 0 2.807 1.012 3.022 2.298l.006.04h-1.158l-.007-.02c-.181-.71-.824-1.286-1.863-1.286-1.299 0-2.117 1.045-2.117 2.673v.013c0 1.661.831 2.727 2.117 2.727.972 0 1.588-.429 1.856-1.233l.014-.04 1.152-.007-.014.074c-.294 1.306-1.266 2.237-3.001 2.237Zm7.671 0c-2.064 0-3.31-1.447-3.31-3.711v-.007c0-2.231 1.273-3.758 3.236-3.758 1.963 0 3.155 1.46 3.155 3.617v.456h-5.198c.033 1.494.857 2.365 2.144 2.365.978 0 1.581-.462 1.775-.898l.027-.06h1.165l-.013.053c-.248.979-1.28 1.943-2.981 1.943Zm-.081-6.438c-1.072 0-1.889.731-2.016 2.104h3.993c-.121-1.427-.911-2.104-1.977-2.104ZM388.288 542.9v-6.257h-1.199v-.965h1.199v-.804c0-1.44.723-2.13 2.097-2.13.281 0 .536.02.777.067v.931a3.16 3.16 0 0 0-.543-.033c-.831 0-1.166.408-1.166 1.199v.77h1.642v.965h-1.642v6.257h-1.165Zm7.148.127c-2.057 0-3.33-1.42-3.33-3.731v-.014c0-2.318 1.273-3.731 3.33-3.731 2.057 0 3.329 1.413 3.329 3.731v.014c0 2.311-1.272 3.731-3.329 3.731Zm0-1.031c1.367 0 2.137-.999 2.137-2.7v-.014c0-1.708-.77-2.699-2.137-2.699s-2.137.991-2.137 2.699v.014c0 1.701.77 2.7 2.137 2.7Zm5.138.904v-7.222h1.166v1.072h.107c.275-.757.951-1.199 1.929-1.199.222 0 .469.027.583.047v1.132a4.21 4.21 0 0 0-.717-.067c-1.112 0-1.902.703-1.902 1.762v4.475h-1.166Zm9.701-8.615a.807.807 0 0 1-.804-.804c0-.442.362-.804.804-.804.442 0 .804.362.804.804a.807.807 0 0 1-.804.804Zm-.59 8.615v-7.222h1.166v7.222h-1.166Zm3.363 0v-7.222h1.166v1.085h.107c.362-.763 1.038-1.212 2.104-1.212 1.621 0 2.525.958 2.525 2.673v4.676h-1.166v-4.395c0-1.299-.535-1.922-1.688-1.922-1.152 0-1.882.777-1.882 2.043v4.274h-1.166Zm10.679.127c-1.829 0-3.028-1.48-3.028-3.731v-.014c0-2.264 1.192-3.731 3.028-3.731.991 0 1.855.496 2.251 1.266h.107v-4.006h1.165V542.9h-1.165v-1.152h-.107c-.443.804-1.246 1.279-2.251 1.279Zm.268-1.031c1.313 0 2.117-1.032 2.117-2.7v-.014c0-1.668-.804-2.699-2.117-2.699-1.32 0-2.104 1.018-2.104 2.699v.014c0 1.681.784 2.7 2.104 2.7Zm8.441 1.031c-2.064 0-3.31-1.447-3.31-3.711v-.007c0-2.231 1.273-3.758 3.236-3.758 1.963 0 3.155 1.46 3.155 3.617v.456h-5.198c.033 1.494.857 2.365 2.143 2.365.979 0 1.581-.462 1.776-.898l.027-.06h1.165l-.013.053c-.248.979-1.28 1.943-2.981 1.943Zm-.081-6.438c-1.072 0-1.889.731-2.016 2.104h3.993c-.121-1.427-.912-2.104-1.977-2.104Zm4.127 6.311 2.472-3.611-2.499-3.611h1.374l1.715 2.687h.107l1.695-2.687h1.306l-2.452 3.564 2.485 3.658h-1.366l-1.708-2.727h-.108l-1.715 2.727h-1.306Zm7.476 2.459.583-3.812h1.333l-1.051 3.812h-.865Zm10.893-2.332c-1.829 0-3.028-1.48-3.028-3.731v-.014c0-2.264 1.193-3.731 3.028-3.731.992 0 1.856.496 2.251 1.266h.107v-4.006h1.166V542.9h-1.166v-1.152h-.107c-.442.804-1.246 1.279-2.251 1.279Zm.268-1.031c1.313 0 2.117-1.032 2.117-2.7v-.014c0-1.668-.804-2.699-2.117-2.699-1.32 0-2.103 1.018-2.103 2.699v.014c0 1.681.783 2.7 2.103 2.7Zm8.441 1.031c-2.063 0-3.309-1.447-3.309-3.711v-.007c0-2.231 1.273-3.758 3.236-3.758 1.962 0 3.155 1.46 3.155 3.617v.456h-5.199c.034 1.494.858 2.365 2.144 2.365.978 0 1.581-.462 1.775-.898l.027-.06h1.166l-.014.053c-.247.979-1.279 1.943-2.981 1.943Zm-.08-6.438c-1.072 0-1.889.731-2.017 2.104h3.993c-.12-1.427-.911-2.104-1.976-2.104Zm7.536 6.365c-1.487 0-2.11-.55-2.11-1.93v-4.381h-1.139v-.965h1.139v-1.869h1.206v1.869h1.581v.965h-1.581v4.086c0 .851.295 1.193 1.045 1.193.208 0 .322-.007.536-.027v.992c-.228.04-.449.067-.677.067Zm4.402.073c-1.373 0-2.412-.83-2.412-2.144v-.013c0-1.286.951-2.03 2.633-2.13l2.07-.128v-.656c0-.891-.543-1.373-1.621-1.373-.864 0-1.434.321-1.621.884l-.007.02h-1.166l.007-.04c.187-1.132 1.286-1.896 2.827-1.896 1.762 0 2.747.904 2.747 2.405v4.944h-1.166v-1.065h-.107c-.456.77-1.213 1.192-2.184 1.192Zm-1.219-2.17c0 .73.623 1.159 1.46 1.159 1.186 0 2.05-.777 2.05-1.809v-.657l-1.929.121c-1.099.067-1.581.455-1.581 1.172v.014Zm7.422-6.572a.807.807 0 0 1-.804-.804c0-.442.362-.804.804-.804.442 0 .804.362.804.804a.807.807 0 0 1-.804.804Zm-.589 8.615v-7.222h1.165v7.222h-1.165Zm3.43 0v-10.089h1.165V542.9h-1.165Zm3.108 2.459.583-3.812h1.333l-1.052 3.812h-.864Zm10.21-2.332c-1.373 0-2.412-.83-2.412-2.144v-.013c0-1.286.951-2.03 2.633-2.13l2.07-.128v-.656c0-.891-.543-1.373-1.621-1.373-.864 0-1.434.321-1.621.884l-.007.02h-1.166l.007-.04c.187-1.132 1.286-1.896 2.827-1.896 1.762 0 2.747.904 2.747 2.405v4.944h-1.166v-1.065h-.107c-.456.77-1.213 1.192-2.184 1.192Zm-1.219-2.17c0 .73.623 1.159 1.46 1.159 1.186 0 2.05-.777 2.05-1.809v-.657l-1.929.121c-1.099.067-1.581.455-1.581 1.172v.014Zm6.806 2.043v-7.222h1.166v1.085h.107c.362-.763 1.038-1.212 2.103-1.212 1.622 0 2.526.958 2.526 2.673v4.676h-1.166v-4.395c0-1.299-.536-1.922-1.688-1.922s-1.882.777-1.882 2.043v4.274h-1.166Zm10.679.127c-1.829 0-3.028-1.48-3.028-3.731v-.014c0-2.264 1.192-3.731 3.028-3.731.991 0 1.855.496 2.251 1.266h.107v-4.006h1.165V542.9h-1.165v-1.152h-.107c-.443.804-1.247 1.279-2.251 1.279Zm.268-1.031c1.313 0 2.116-1.032 2.116-2.7v-.014c0-1.668-.803-2.699-2.116-2.699-1.32 0-2.104 1.018-2.104 2.699v.014c0 1.681.784 2.7 2.104 2.7Zm11.83 1.031c-1.607 0-2.713-.777-2.847-1.969h1.193c.194.603.77.998 1.695.998.971 0 1.661-.462 1.661-1.119v-.013c0-.489-.369-.831-1.259-1.045l-1.119-.268c-1.354-.322-1.963-.911-1.963-1.95v-.007c0-1.199 1.152-2.103 2.686-2.103 1.514 0 2.566.757 2.727 1.936h-1.146c-.154-.569-.717-.965-1.587-.965-.858 0-1.488.442-1.488 1.079v.013c0 .489.362.797 1.213 1.005l1.112.268c1.36.328 2.016.918 2.016 1.95v.013c0 1.286-1.252 2.177-2.894 2.177Zm7.651 0c-2.063 0-3.31-1.447-3.31-3.711v-.007c0-2.231 1.273-3.758 3.236-3.758 1.963 0 3.156 1.46 3.156 3.617v.456h-5.199c.033 1.494.857 2.365 2.144 2.365.978 0 1.581-.462 1.775-.898l.027-.06h1.165l-.013.053c-.248.979-1.28 1.943-2.981 1.943Zm-.081-6.438c-1.071 0-1.889.731-2.016 2.104h3.993c-.121-1.427-.911-2.104-1.977-2.104Zm7.537 6.365c-1.487 0-2.11-.55-2.11-1.93v-4.381h-1.139v-.965h1.139v-1.869h1.206v1.869h1.581v.965h-1.581v4.086c0 .851.294 1.193 1.045 1.193.207 0 .321-.007.536-.027v.992c-.228.04-.449.067-.677.067Zm4.984 0c-1.487 0-2.11-.55-2.11-1.93v-4.381h-1.139v-.965h1.139v-1.869h1.206v1.869h1.581v.965h-1.581v4.086c0 .851.295 1.193 1.045 1.193.208 0 .322-.007.536-.027v.992c-.228.04-.449.067-.677.067Zm3.035-8.669a.807.807 0 0 1-.804-.804c0-.442.362-.804.804-.804.442 0 .804.362.804.804a.807.807 0 0 1-.804.804Zm-.589 8.615v-7.222h1.165v7.222h-1.165Zm3.363 0v-7.222h1.165v1.085h.107c.362-.763 1.039-1.212 2.104-1.212 1.621 0 2.526.958 2.526 2.673v4.676h-1.166v-4.395c0-1.299-.536-1.922-1.688-1.922-1.153 0-1.883.777-1.883 2.043v4.274h-1.165Zm10.986 2.546c-1.742 0-2.854-.784-3.034-1.97l.013-.007h1.206l.006.007c.128.549.771.938 1.809.938 1.293 0 2.064-.61 2.064-1.675v-1.46h-.108c-.455.797-1.272 1.246-2.264 1.246-1.869 0-3.028-1.447-3.028-3.47v-.014c0-2.023 1.166-3.49 3.055-3.49 1.018 0 1.809.502 2.251 1.32h.08v-1.193h1.166v7.121c0 1.615-1.246 2.647-3.216 2.647Zm-.053-3.953c1.34 0 2.143-1.005 2.143-2.438v-.014c0-1.433-.81-2.458-2.143-2.458-1.34 0-2.09 1.025-2.09 2.458v.014c0 1.433.75 2.438 2.09 2.438Zm7.918 1.534c-1.608 0-2.713-.777-2.847-1.969h1.192c.195.603.771.998 1.695.998.972 0 1.662-.462 1.662-1.119v-.013c0-.489-.369-.831-1.26-1.045l-1.118-.268c-1.354-.322-1.963-.911-1.963-1.95v-.007c0-1.199 1.152-2.103 2.686-2.103 1.514 0 2.566.757 2.727 1.936h-1.146c-.154-.569-.717-.965-1.588-.965-.857 0-1.487.442-1.487 1.079v.013c0 .489.362.797 1.213 1.005l1.112.268c1.36.328 2.016.918 2.016 1.95v.013c0 1.286-1.253 2.177-2.894 2.177Zm10.605-.127-2.673-7.222h1.233l1.976 5.949h.107l1.976-5.949h1.233l-2.673 7.222H581.5Zm6.009-8.615a.807.807 0 0 1-.804-.804c0-.442.362-.804.804-.804.442 0 .804.362.804.804a.807.807 0 0 1-.804.804Zm-.589 8.615v-7.222h1.165v7.222h-1.165Zm6.31.127c-2.063 0-3.309-1.447-3.309-3.711v-.007c0-2.231 1.273-3.758 3.236-3.758 1.963 0 3.155 1.46 3.155 3.617v.456h-5.199c.034 1.494.858 2.365 2.144 2.365.978 0 1.581-.462 1.775-.898l.027-.06h1.166l-.014.053c-.247.979-1.279 1.943-2.981 1.943Zm-.08-6.438c-1.072 0-1.889.731-2.017 2.104h3.993c-.12-1.427-.911-2.104-1.976-2.104Zm6.364 6.311-2.023-7.222h1.166l1.42 5.775h.107l1.615-5.775h1.105l1.615 5.775h.107l1.42-5.775h1.159l-2.023 7.222h-1.173l-1.614-5.587h-.107l-1.608 5.587h-1.166Zm11.509.127c-1.607 0-2.713-.777-2.847-1.969h1.193c.194.603.77.998 1.695.998.971 0 1.661-.462 1.661-1.119v-.013c0-.489-.368-.831-1.259-1.045l-1.119-.268c-1.353-.322-1.963-.911-1.963-1.95v-.007c0-1.199 1.152-2.103 2.686-2.103 1.514 0 2.566.757 2.727 1.936h-1.146c-.154-.569-.716-.965-1.587-.965-.858 0-1.488.442-1.488 1.079v.013c0 .489.362.797 1.213 1.005l1.112.268c1.36.328 2.017.918 2.017 1.95v.013c0 1.286-1.253 2.177-2.895 2.177Zm5.655-.06a.878.878 0 0 1-.871-.871c0-.482.395-.871.871-.871.482 0 .87.389.87.871a.873.873 0 0 1-.87.871Z" fill="#757575"/> + </g> + <g filter="url(#b)"> + <path d="M264 20a8 8 0 0 1 8-8h1436c4.42 0 8 3.582 8 8v1077c0 4.42-3.58 8-8 8H272a8 8 0 0 1-8-8V20Z" fill="#fff"/> + <path d="M264.5 20a7.5 7.5 0 0 1 7.5-7.5h1436c4.14 0 7.5 3.358 7.5 7.5v1077c0 4.14-3.36 7.5-7.5 7.5H272c-4.142 0-7.5-3.36-7.5-7.5V20Z" stroke="#949494"/> + <path d="M915.566 552.541v5.479h2.286c.911 0 1.621-.241 2.128-.723.515-.482.772-1.156.772-2.022 0-.865-.254-1.536-.762-2.011-.508-.482-1.22-.723-2.138-.723h-2.286Zm-1.972-1.621h4.258c1.627 0 2.861.371 3.701 1.113.84.736 1.259 1.817 1.259 3.242 0 1.439-.419 2.526-1.259 3.262-.834.736-2.067 1.104-3.701 1.104h-2.286v5.859h-1.972v-14.58ZM935 556.818a4.084 4.084 0 0 0-1.172-.654 3.987 3.987 0 0 0-1.308-.205c-1.12 0-1.976.352-2.569 1.055-.592.703-.889 1.718-.889 3.047v5.439h-1.806v-10.938h1.806v2.139c.3-.775.759-1.367 1.377-1.777.625-.417 1.364-.625 2.217-.625.443 0 .856.055 1.24.166.385.111.752.283 1.104.517v1.836Zm11.709 2.315v.879h-7.783v.058c-.052 1.491.231 2.562.849 3.213.625.651 1.504.977 2.637.977a6.39 6.39 0 0 0 1.797-.274 11.02 11.02 0 0 0 2.002-.83v1.787c-.684.28-1.344.489-1.982.625a8.253 8.253 0 0 1-1.836.215c-1.7 0-3.028-.508-3.985-1.523-.957-1.022-1.435-2.429-1.435-4.219 0-1.745.468-3.138 1.406-4.18.937-1.041 2.187-1.562 3.75-1.562 1.393 0 2.49.472 3.291 1.416.807.944 1.237 2.083 1.289 3.418Zm-1.797-.528a3.443 3.443 0 0 0-.83-1.953c-.469-.553-1.146-.83-2.031-.83-.866 0-1.579.287-2.139.86-.56.573-.866 1.217-.918 1.933l5.918-.01Zm3.887-4.043h1.865l3.174 9.18 3.183-9.18h1.866L955 565.5h-2.314l-3.887-10.938Zm14.16.04h4.6v9.502h3.564v1.396h-8.926v-1.396h3.565v-8.106h-2.803v-1.396Zm2.803-4.248h1.797v2.265h-1.797v-2.265Zm17.099 8.779v.879h-7.783v.058c-.052 1.491.231 2.562.85 3.213.625.651 1.504.977 2.636.977a6.39 6.39 0 0 0 1.797-.274 10.99 10.99 0 0 0 2.002-.83v1.787a11.64 11.64 0 0 1-1.982.625 8.253 8.253 0 0 1-1.836.215c-1.699 0-3.027-.508-3.984-1.523-.957-1.022-1.436-2.429-1.436-4.219 0-1.745.469-3.138 1.406-4.18.938-1.041 2.188-1.562 3.75-1.562 1.393 0 2.49.472 3.291 1.416.808.944 1.237 2.083 1.289 3.418Zm-1.797-.528a3.437 3.437 0 0 0-.83-1.953c-.468-.553-1.145-.83-2.031-.83-.866 0-1.579.287-2.139.86-.559.573-.865 1.217-.918 1.933l5.918-.01Zm2.911-4.043h1.777l1.904 8.838 1.563-5.644h1.533l1.582 5.644 1.904-8.838h1.778l-2.559 10.938h-1.719l-1.748-5.996-1.738 5.996h-1.719l-2.558-10.938Zm26.375-3.642h8.59v1.66h-6.61v3.906h5.99v1.66h-5.99v7.354h-1.98v-14.58Zm21.06 5.898a4.162 4.162 0 0 0-1.18-.654 3.93 3.93 0 0 0-1.3-.205c-1.12 0-1.98.352-2.57 1.055-.6.703-.89 1.718-.89 3.047v5.439h-1.81v-10.938h1.81v2.139c.3-.775.76-1.367 1.38-1.777.62-.417 1.36-.625 2.21-.625.45 0 .86.055 1.24.166.39.111.75.283 1.11.517v1.836Zm7.62 3.184h-.59c-1.05 0-1.84.186-2.38.557-.52.364-.79.911-.79 1.64 0 .658.2 1.169.6 1.533.4.365.95.547 1.65.547.99 0 1.77-.341 2.33-1.025.57-.69.86-1.641.86-2.852v-.4h-1.68Zm3.49-.742v6.24h-1.81v-1.621c-.38.651-.87 1.133-1.45 1.445-.58.306-1.29.459-2.12.459-1.11 0-2-.312-2.67-.937-.66-.632-.99-1.475-.99-2.53 0-1.217.4-2.142 1.22-2.773.82-.632 2.02-.947 3.6-.947h2.41v-.284c0-.872-.22-1.503-.66-1.894-.44-.397-1.13-.596-2.09-.596-.61 0-1.23.088-1.86.264-.62.176-1.23.433-1.82.771v-1.796c.66-.254 1.3-.443 1.9-.567.61-.13 1.21-.195 1.78-.195.9 0 1.68.133 2.31.4.65.267 1.17.668 1.57 1.201.24.326.42.73.52 1.211.11.476.16 1.192.16 2.149Zm8.31-3.584c.22-.469.5-.814.84-1.035.34-.228.76-.342 1.24-.342.88 0 1.5.342 1.86 1.025.36.677.54 1.957.54 3.838v6.338h-1.64v-6.26c0-1.543-.09-2.5-.26-2.871-.17-.377-.48-.566-.94-.566-.52 0-.88.202-1.07.605-.19.397-.29 1.341-.29 2.832v6.26h-1.64v-6.26c0-1.562-.09-2.526-.28-2.89-.18-.365-.51-.547-1-.547-.47 0-.8.202-.99.605-.19.397-.28 1.341-.28 2.832v6.26h-1.63v-10.938h1.63v.938c.22-.391.49-.687.8-.889.33-.208.7-.312 1.11-.312.49 0 .9.114 1.23.342.33.227.59.573.77 1.035Zm16.39 3.457v.879h-7.79v.058c-.05 1.491.23 2.562.85 3.213.63.651 1.51.977 2.64.977.57 0 1.17-.091 1.8-.274.62-.182 1.29-.459 2-.83v1.787c-.68.28-1.35.489-1.98.625-.64.144-1.25.215-1.84.215-1.7 0-3.03-.508-3.98-1.523-.96-1.022-1.44-2.429-1.44-4.219 0-1.745.47-3.138 1.41-4.18.93-1.041 2.18-1.562 3.75-1.562 1.39 0 2.49.472 3.29 1.416.81.944 1.23 2.083 1.29 3.418Zm-1.8-.528c-.08-.755-.35-1.406-.83-1.953-.47-.553-1.15-.83-2.03-.83-.87 0-1.58.287-2.14.86s-.87 1.217-.92 1.933l5.92-.01Z" fill="#1E1E1E"/> + </g> + <defs> + <filter id="a" x="317.14" y="21.02" width="386.12" height="1004.5" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB"> + <feFlood flood-opacity="0" result="BackgroundImageFix"/> + <feColorMatrix in="SourceAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/> + <feOffset dy=".98"/> + <feGaussianBlur stdDeviation=".98"/> + <feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.05 0"/> + <feBlend in2="BackgroundImageFix" result="effect1_dropShadow_15996_31458"/> + <feColorMatrix in="SourceAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/> + <feOffset dy="3.92"/> + <feGaussianBlur stdDeviation="1.96"/> + <feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.04 0"/> + <feBlend in2="effect1_dropShadow_15996_31458" result="effect2_dropShadow_15996_31458"/> + <feColorMatrix in="SourceAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/> + <feOffset dy="8.82"/> + <feGaussianBlur stdDeviation="2.94"/> + <feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.03 0"/> + <feBlend in2="effect2_dropShadow_15996_31458" result="effect3_dropShadow_15996_31458"/> + <feColorMatrix in="SourceAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/> + <feOffset dy="16.66"/> + <feGaussianBlur stdDeviation="3.43"/> + <feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.01 0"/> + <feBlend in2="effect3_dropShadow_15996_31458" result="effect4_dropShadow_15996_31458"/> + <feBlend in="SourceGraphic" in2="effect4_dropShadow_15996_31458" result="shape"/> + </filter> + <filter id="b" x="260" y="11" width="1460" height="1102" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB"> + <feFlood flood-opacity="0" result="BackgroundImageFix"/> + <feColorMatrix in="SourceAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/> + <feOffset dy="4"/> + <feGaussianBlur stdDeviation="2"/> + <feComposite in2="hardAlpha" operator="out"/> + <feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.01 0"/> + <feBlend in2="BackgroundImageFix" result="effect1_dropShadow_15996_31458"/> + <feColorMatrix in="SourceAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/> + <feOffset dy="3"/> + <feGaussianBlur stdDeviation="1.5"/> + <feComposite in2="hardAlpha" operator="out"/> + <feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.02 0"/> + <feBlend in2="effect1_dropShadow_15996_31458" result="effect2_dropShadow_15996_31458"/> + <feColorMatrix in="SourceAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/> + <feOffset dy="1"/> + <feGaussianBlur stdDeviation="1"/> + <feComposite in2="hardAlpha" operator="out"/> + <feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.02 0"/> + <feBlend in2="effect2_dropShadow_15996_31458" result="effect3_dropShadow_15996_31458"/> + <feColorMatrix in="SourceAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/> + <feOffset dy="1"/> + <feGaussianBlur stdDeviation=".5"/> + <feComposite in2="hardAlpha" operator="out"/> + <feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.03 0"/> + <feBlend in2="effect3_dropShadow_15996_31458" result="effect4_dropShadow_15996_31458"/> + <feBlend in="SourceGraphic" in2="effect4_dropShadow_15996_31458" result="shape"/> + </filter> + </defs> +</svg> \ No newline at end of file diff --git a/storybook/stories/foundations/static/page-layout-example-3.svg b/storybook/stories/foundations/static/page-layout-example-3.svg new file mode 100644 index 00000000000000..0eecead116e1a0 --- /dev/null +++ b/storybook/stories/foundations/static/page-layout-example-3.svg @@ -0,0 +1,39 @@ +<svg width="1728" height="1117" fill="none" xmlns="http://www.w3.org/2000/svg"> + <path fill="#E0E0E0" d="M0 0h1728v1117H0z"/> + <path d="M99.705 551.418v2.002a7.764 7.764 0 0 0-1.807-.869 5.806 5.806 0 0 0-1.816-.293c-.931 0-1.667.218-2.207.654-.54.43-.81 1.012-.81 1.748 0 .645.175 1.136.527 1.475.358.338 1.022.622 1.992.849l1.035.235c1.367.319 2.363.82 2.988 1.504.625.683.938 1.614.938 2.793 0 1.386-.43 2.444-1.29 3.173-.858.73-2.108 1.094-3.75 1.094-.683 0-1.37-.075-2.06-.224a11.333 11.333 0 0 1-2.08-.655v-2.099c.749.475 1.455.823 2.12 1.045.67.221 1.344.332 2.02.332.997 0 1.772-.222 2.325-.664.553-.45.83-1.075.83-1.875 0-.73-.192-1.286-.576-1.67-.378-.384-1.038-.681-1.982-.889l-1.055-.244c-1.354-.306-2.337-.768-2.95-1.387-.611-.618-.917-1.448-.917-2.49 0-1.302.436-2.344 1.308-3.125.88-.788 2.045-1.182 3.496-1.182.56 0 1.15.065 1.768.196.618.123 1.27.312 1.953.566Zm5.254 3.184h4.6v9.502h3.564v1.396h-8.926v-1.396h3.565v-8.106h-2.803v-1.396Zm2.803-4.248h1.797v2.265h-1.797v-2.265Zm14.541 5.605v-5.654h1.797V565.5h-1.797v-1.377c-.3.54-.7.954-1.201 1.24-.495.28-1.068.42-1.719.42-1.322 0-2.363-.511-3.125-1.533-.755-1.029-1.133-2.445-1.133-4.248 0-1.777.381-3.171 1.143-4.18.761-1.015 1.8-1.523 3.115-1.523.657 0 1.237.143 1.738.43.501.279.895.69 1.182 1.23Zm-5.283 4.082c0 1.393.221 2.445.664 3.154.442.71 1.097 1.065 1.962 1.065.866 0 1.524-.358 1.973-1.074.456-.717.684-1.765.684-3.145 0-1.387-.228-2.435-.684-3.145-.449-.716-1.107-1.074-1.973-1.074-.865 0-1.52.355-1.962 1.065-.443.709-.664 1.761-.664 3.154Zm19.892-.908v.879h-7.783v.058c-.052 1.491.231 2.562.85 3.213.625.651 1.503.977 2.636.977a6.39 6.39 0 0 0 1.797-.274 11.02 11.02 0 0 0 2.002-.83v1.787c-.684.28-1.344.489-1.982.625a8.253 8.253 0 0 1-1.836.215c-1.7 0-3.028-.508-3.985-1.523-.957-1.022-1.435-2.429-1.435-4.219 0-1.745.469-3.138 1.406-4.18.938-1.041 2.188-1.562 3.75-1.562 1.393 0 2.49.472 3.291 1.416.807.944 1.237 2.083 1.289 3.418Zm-1.797-.528a3.443 3.443 0 0 0-.83-1.953c-.469-.553-1.146-.83-2.031-.83-.866 0-1.579.287-2.139.86-.56.573-.866 1.217-.918 1.933l5.918-.01Zm11.875 1.436c0-1.393-.221-2.445-.664-3.154-.443-.71-1.097-1.065-1.963-1.065-.872 0-1.533.358-1.982 1.074-.449.71-.674 1.758-.674 3.145 0 1.38.225 2.428.674 3.145.449.716 1.11 1.074 1.982 1.074.866 0 1.52-.355 1.963-1.065.443-.709.664-1.761.664-3.154Zm-5.283-4.082c.286-.534.68-.944 1.182-1.23.507-.287 1.093-.43 1.757-.43 1.316 0 2.351.508 3.106 1.523.755 1.009 1.133 2.403 1.133 4.18 0 1.803-.381 3.219-1.143 4.248-.755 1.022-1.793 1.533-3.115 1.533-.651 0-1.227-.14-1.729-.42a3.177 3.177 0 0 1-1.191-1.24v1.377h-1.797v-15.195h1.797v5.654Zm15.225 4.043h-.596c-1.048 0-1.839.186-2.373.557-.527.364-.791.911-.791 1.64 0 .658.198 1.169.596 1.533.397.365.947.547 1.65.547.99 0 1.768-.341 2.334-1.025.566-.69.853-1.641.859-2.852v-.4h-1.679Zm3.486-.742v6.24h-1.807v-1.621c-.384.651-.869 1.133-1.455 1.445-.579.306-1.286.459-2.119.459-1.113 0-2.002-.312-2.666-.937-.664-.632-.996-1.475-.996-2.53 0-1.217.407-2.142 1.221-2.773.82-.632 2.021-.947 3.603-.947h2.412v-.284c-.006-.872-.228-1.503-.664-1.894-.436-.397-1.133-.596-2.09-.596-.612 0-1.23.088-1.855.264a8.109 8.109 0 0 0-1.826.771v-1.796a12.214 12.214 0 0 1 1.904-.567 8.55 8.55 0 0 1 1.777-.195c.905 0 1.677.133 2.315.4a3.648 3.648 0 0 1 1.562 1.201c.248.326.424.73.528 1.211.104.476.156 1.192.156 2.149Zm12.988-2.442a4.071 4.071 0 0 0-1.172-.654 3.987 3.987 0 0 0-1.308-.205c-1.12 0-1.976.352-2.569 1.055-.592.703-.888 1.718-.888 3.047v5.439h-1.807v-10.938h1.807v2.139c.299-.775.758-1.367 1.377-1.777.625-.417 1.364-.625 2.216-.625.443 0 .857.055 1.241.166.384.111.752.283 1.103.517v1.836Z" fill="#1E1E1E"/> + <g filter="url(#a)"> + <path d="M264 20a8 8 0 0 1 8-8h1436c4.42 0 8 3.582 8 8v1077c0 4.42-3.58 8-8 8H272a8 8 0 0 1-8-8V20Z" fill="#F0F0F0"/> + <path d="M264.5 20a7.5 7.5 0 0 1 7.5-7.5h1436c4.14 0 7.5 3.358 7.5 7.5v1077c0 4.14-3.36 7.5-7.5 7.5H272c-4.142 0-7.5-3.36-7.5-7.5V20Z" stroke="#949494"/> + <path d="M922.148 564.982a6.657 6.657 0 0 1-1.543.596 6.653 6.653 0 0 1-1.679.205c-1.869 0-3.321-.661-4.356-1.982-1.028-1.322-1.543-3.181-1.543-5.576 0-2.383.518-4.239 1.553-5.567 1.042-1.334 2.49-2.002 4.346-2.002.592 0 1.152.069 1.679.205a6.702 6.702 0 0 1 1.543.596v2.022a5.442 5.442 0 0 0-1.552-.909 4.718 4.718 0 0 0-1.67-.312c-1.283 0-2.243.495-2.881 1.484-.638.99-.957 2.484-.957 4.483 0 1.992.319 3.483.957 4.472.638.99 1.598 1.485 2.881 1.485.573 0 1.133-.105 1.679-.313a5.302 5.302 0 0 0 1.543-.908v2.021Zm7.588-9.16c-.911 0-1.601.355-2.07 1.065-.469.709-.703 1.761-.703 3.154 0 1.387.234 2.438.703 3.154.469.71 1.159 1.065 2.07 1.065.918 0 1.612-.355 2.08-1.065.469-.716.704-1.767.704-3.154 0-1.393-.235-2.445-.704-3.154-.468-.71-1.162-1.065-2.08-1.065Zm0-1.523c1.517 0 2.676.491 3.477 1.474.807.984 1.211 2.406 1.211 4.268 0 1.869-.401 3.294-1.201 4.277-.801.977-1.963 1.465-3.487 1.465-1.517 0-2.675-.488-3.476-1.465-.801-.983-1.201-2.408-1.201-4.277 0-1.862.4-3.284 1.201-4.268.801-.983 1.959-1.474 3.476-1.474Zm16.299 4.424v6.777h-1.806v-6.777c0-.983-.173-1.706-.518-2.168-.345-.463-.885-.694-1.621-.694-.84 0-1.488.3-1.944.899-.449.592-.673 1.445-.673 2.558v6.182h-1.797v-10.938h1.797v1.641c.319-.625.752-1.097 1.298-1.416.547-.325 1.195-.488 1.944-.488 1.113 0 1.943.368 2.49 1.103.553.73.83 1.836.83 3.321Zm7.783-7.266v3.105h4.082v1.397h-4.082v5.937c0 .808.153 1.371.459 1.69.306.319.84.478 1.602.478h2.021v1.436h-2.197c-1.348 0-2.298-.27-2.851-.811-.554-.54-.831-1.471-.831-2.793v-5.937h-2.919v-1.397h2.919v-3.105h1.797Zm16.993 7.676v.879h-7.784v.058c-.052 1.491.231 2.562.85 3.213.625.651 1.504.977 2.637.977a6.39 6.39 0 0 0 1.797-.274 10.983 10.983 0 0 0 2.001-.83v1.787a11.64 11.64 0 0 1-1.982.625 8.24 8.24 0 0 1-1.836.215c-1.699 0-3.027-.508-3.984-1.523-.957-1.022-1.436-2.429-1.436-4.219 0-1.745.469-3.138 1.406-4.18.938-1.041 2.188-1.562 3.75-1.562 1.394 0 2.491.472 3.291 1.416.808.944 1.237 2.083 1.29 3.418Zm-1.797-.528a3.443 3.443 0 0 0-.83-1.953c-.469-.553-1.146-.83-2.032-.83-.866 0-1.578.287-2.138.86-.56.573-.866 1.217-.918 1.933l5.918-.01Zm13.174.118v6.777h-1.807v-6.777c0-.983-.173-1.706-.518-2.168-.345-.463-.885-.694-1.621-.694-.84 0-1.487.3-1.943.899-.449.592-.674 1.445-.674 2.558v6.182h-1.797v-10.938h1.797v1.641c.319-.625.752-1.097 1.299-1.416.547-.325 1.194-.488 1.943-.488 1.113 0 1.944.368 2.49 1.103.554.73.831 1.836.831 3.321Zm7.783-7.266v3.105h4.082v1.397h-4.082v5.937c0 .808.153 1.371.459 1.69.306.319.84.478 1.601.478h2.022v1.436h-2.198c-1.347 0-2.298-.27-2.851-.811-.553-.54-.83-1.471-.83-2.793v-5.937h-2.92v-1.397h2.92v-3.105h1.797Zm20.379-.537h8.59v1.66h-6.61v3.906h5.99v1.66h-5.99v7.354h-1.98v-14.58Zm21.06 5.898a4.162 4.162 0 0 0-1.18-.654 3.93 3.93 0 0 0-1.3-.205c-1.12 0-1.98.352-2.57 1.055-.6.703-.89 1.718-.89 3.047v5.439h-1.81v-10.938h1.81v2.139c.3-.775.76-1.367 1.38-1.777.62-.417 1.36-.625 2.21-.625.45 0 .86.055 1.24.166.39.111.75.283 1.11.517v1.836Zm7.62 3.184h-.59c-1.05 0-1.84.186-2.38.557-.52.364-.79.911-.79 1.64 0 .658.2 1.169.6 1.533.4.365.95.547 1.65.547.99 0 1.77-.341 2.33-1.025.57-.69.86-1.641.86-2.852v-.4h-1.68Zm3.49-.742v6.24h-1.81v-1.621c-.38.651-.87 1.133-1.45 1.445-.58.306-1.29.459-2.12.459-1.11 0-2-.312-2.67-.937-.66-.632-.99-1.475-.99-2.53 0-1.217.4-2.142 1.22-2.773.82-.632 2.02-.947 3.6-.947h2.41v-.284c0-.872-.22-1.503-.66-1.894-.44-.397-1.13-.596-2.09-.596-.61 0-1.23.088-1.86.264-.62.176-1.23.433-1.82.771v-1.796c.66-.254 1.3-.443 1.9-.567.61-.13 1.21-.195 1.78-.195.9 0 1.68.133 2.31.4.65.267 1.17.668 1.57 1.201.24.326.42.73.52 1.211.11.476.16 1.192.16 2.149Zm8.31-3.584c.22-.469.5-.814.84-1.035.34-.228.76-.342 1.24-.342.88 0 1.5.342 1.86 1.025.36.677.54 1.957.54 3.838v6.338h-1.64v-6.26c0-1.543-.09-2.5-.26-2.871-.17-.377-.48-.566-.94-.566-.52 0-.88.202-1.07.605-.19.397-.29 1.341-.29 2.832v6.26h-1.64v-6.26c0-1.562-.09-2.526-.28-2.89-.18-.365-.51-.547-1-.547-.47 0-.8.202-.99.605-.19.397-.28 1.341-.28 2.832v6.26h-1.63v-10.938h1.63v.938c.22-.391.49-.687.8-.889.33-.208.7-.312 1.11-.312.49 0 .9.114 1.23.342.33.227.59.573.77 1.035Zm16.39 3.457v.879h-7.79v.058c-.05 1.491.23 2.562.85 3.213.63.651 1.51.977 2.64.977.57 0 1.17-.091 1.8-.274.62-.182 1.29-.459 2-.83v1.787c-.68.28-1.35.489-1.98.625-.64.144-1.25.215-1.84.215-1.7 0-3.03-.508-3.98-1.523-.96-1.022-1.44-2.429-1.44-4.219 0-1.745.47-3.138 1.41-4.18.93-1.041 2.18-1.562 3.75-1.562 1.39 0 2.49.472 3.29 1.416.81.944 1.23 2.083 1.29 3.418Zm-1.8-.528c-.08-.755-.35-1.406-.83-1.953-.47-.553-1.15-.83-2.03-.83-.87 0-1.58.287-2.14.86s-.87 1.217-.92 1.933l5.92-.01Z" fill="#1E1E1E"/> + </g> + <defs> + <filter id="a" x="260" y="11" width="1460" height="1102" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB"> + <feFlood flood-opacity="0" result="BackgroundImageFix"/> + <feColorMatrix in="SourceAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/> + <feOffset dy="4"/> + <feGaussianBlur stdDeviation="2"/> + <feComposite in2="hardAlpha" operator="out"/> + <feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.01 0"/> + <feBlend in2="BackgroundImageFix" result="effect1_dropShadow_15996_31469"/> + <feColorMatrix in="SourceAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/> + <feOffset dy="3"/> + <feGaussianBlur stdDeviation="1.5"/> + <feComposite in2="hardAlpha" operator="out"/> + <feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.02 0"/> + <feBlend in2="effect1_dropShadow_15996_31469" result="effect2_dropShadow_15996_31469"/> + <feColorMatrix in="SourceAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/> + <feOffset dy="1"/> + <feGaussianBlur stdDeviation="1"/> + <feComposite in2="hardAlpha" operator="out"/> + <feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.02 0"/> + <feBlend in2="effect2_dropShadow_15996_31469" result="effect3_dropShadow_15996_31469"/> + <feColorMatrix in="SourceAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/> + <feOffset dy="1"/> + <feGaussianBlur stdDeviation=".5"/> + <feComposite in2="hardAlpha" operator="out"/> + <feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.03 0"/> + <feBlend in2="effect3_dropShadow_15996_31469" result="effect4_dropShadow_15996_31469"/> + <feBlend in="SourceGraphic" in2="effect4_dropShadow_15996_31469" result="shape"/> + </filter> + </defs> +</svg> \ No newline at end of file diff --git a/storybook/stories/foundations/static/page-layout-example-4.svg b/storybook/stories/foundations/static/page-layout-example-4.svg new file mode 100644 index 00000000000000..7d5ace28927bb0 --- /dev/null +++ b/storybook/stories/foundations/static/page-layout-example-4.svg @@ -0,0 +1,72 @@ +<svg width="1728" height="1117" fill="none" xmlns="http://www.w3.org/2000/svg"> + <path fill="#E0E0E0" d="M0 0h1728v1117H0z"/> + <path d="M99.705 551.418v2.002a7.764 7.764 0 0 0-1.807-.869 5.806 5.806 0 0 0-1.816-.293c-.931 0-1.667.218-2.207.654-.54.43-.81 1.012-.81 1.748 0 .645.175 1.136.527 1.475.358.338 1.022.622 1.992.849l1.035.235c1.367.319 2.363.82 2.988 1.504.625.683.938 1.614.938 2.793 0 1.386-.43 2.444-1.29 3.173-.858.73-2.108 1.094-3.75 1.094-.683 0-1.37-.075-2.06-.224a11.333 11.333 0 0 1-2.08-.655v-2.099c.749.475 1.455.823 2.12 1.045.67.221 1.344.332 2.02.332.997 0 1.772-.222 2.325-.664.553-.45.83-1.075.83-1.875 0-.73-.192-1.286-.576-1.67-.378-.384-1.038-.681-1.982-.889l-1.055-.244c-1.354-.306-2.337-.768-2.95-1.387-.611-.618-.917-1.448-.917-2.49 0-1.302.436-2.344 1.308-3.125.88-.788 2.045-1.182 3.496-1.182.56 0 1.15.065 1.768.196.618.123 1.27.312 1.953.566Zm5.254 3.184h4.6v9.502h3.564v1.396h-8.926v-1.396h3.565v-8.106h-2.803v-1.396Zm2.803-4.248h1.797v2.265h-1.797v-2.265Zm14.541 5.605v-5.654h1.797V565.5h-1.797v-1.377c-.3.54-.7.954-1.201 1.24-.495.28-1.068.42-1.719.42-1.322 0-2.363-.511-3.125-1.533-.755-1.029-1.133-2.445-1.133-4.248 0-1.777.381-3.171 1.143-4.18.761-1.015 1.8-1.523 3.115-1.523.657 0 1.237.143 1.738.43.501.279.895.69 1.182 1.23Zm-5.283 4.082c0 1.393.221 2.445.664 3.154.442.71 1.097 1.065 1.962 1.065.866 0 1.524-.358 1.973-1.074.456-.717.684-1.765.684-3.145 0-1.387-.228-2.435-.684-3.145-.449-.716-1.107-1.074-1.973-1.074-.865 0-1.52.355-1.962 1.065-.443.709-.664 1.761-.664 3.154Zm19.892-.908v.879h-7.783v.058c-.052 1.491.231 2.562.85 3.213.625.651 1.503.977 2.636.977a6.39 6.39 0 0 0 1.797-.274 11.02 11.02 0 0 0 2.002-.83v1.787c-.684.28-1.344.489-1.982.625a8.253 8.253 0 0 1-1.836.215c-1.7 0-3.028-.508-3.985-1.523-.957-1.022-1.435-2.429-1.435-4.219 0-1.745.469-3.138 1.406-4.18.938-1.041 2.188-1.562 3.75-1.562 1.393 0 2.49.472 3.291 1.416.807.944 1.237 2.083 1.289 3.418Zm-1.797-.528a3.443 3.443 0 0 0-.83-1.953c-.469-.553-1.146-.83-2.031-.83-.866 0-1.579.287-2.139.86-.56.573-.866 1.217-.918 1.933l5.918-.01Zm11.875 1.436c0-1.393-.221-2.445-.664-3.154-.443-.71-1.097-1.065-1.963-1.065-.872 0-1.533.358-1.982 1.074-.449.71-.674 1.758-.674 3.145 0 1.38.225 2.428.674 3.145.449.716 1.11 1.074 1.982 1.074.866 0 1.52-.355 1.963-1.065.443-.709.664-1.761.664-3.154Zm-5.283-4.082c.286-.534.68-.944 1.182-1.23.507-.287 1.093-.43 1.757-.43 1.316 0 2.351.508 3.106 1.523.755 1.009 1.133 2.403 1.133 4.18 0 1.803-.381 3.219-1.143 4.248-.755 1.022-1.793 1.533-3.115 1.533-.651 0-1.227-.14-1.729-.42a3.177 3.177 0 0 1-1.191-1.24v1.377h-1.797v-15.195h1.797v5.654Zm15.225 4.043h-.596c-1.048 0-1.839.186-2.373.557-.527.364-.791.911-.791 1.64 0 .658.198 1.169.596 1.533.397.365.947.547 1.65.547.99 0 1.768-.341 2.334-1.025.566-.69.853-1.641.859-2.852v-.4h-1.679Zm3.486-.742v6.24h-1.807v-1.621c-.384.651-.869 1.133-1.455 1.445-.579.306-1.286.459-2.119.459-1.113 0-2.002-.312-2.666-.937-.664-.632-.996-1.475-.996-2.53 0-1.217.407-2.142 1.221-2.773.82-.632 2.021-.947 3.603-.947h2.412v-.284c-.006-.872-.228-1.503-.664-1.894-.436-.397-1.133-.596-2.09-.596-.612 0-1.23.088-1.855.264a8.109 8.109 0 0 0-1.826.771v-1.796a12.214 12.214 0 0 1 1.904-.567 8.55 8.55 0 0 1 1.777-.195c.905 0 1.677.133 2.315.4a3.648 3.648 0 0 1 1.562 1.201c.248.326.424.73.528 1.211.104.476.156 1.192.156 2.149Zm12.988-2.442a4.071 4.071 0 0 0-1.172-.654 3.987 3.987 0 0 0-1.308-.205c-1.12 0-1.976.352-2.569 1.055-.592.703-.888 1.718-.888 3.047v5.439h-1.807v-10.938h1.807v2.139c.299-.775.758-1.367 1.377-1.777.625-.417 1.364-.625 2.216-.625.443 0 .857.055 1.241.166.384.111.752.283 1.103.517v1.836Z" fill="#1E1E1E"/> + <g filter="url(#a)"> + <path d="M264 20a8 8 0 0 1 8-8h344a8 8 0 0 1 8 8v1077a8 8 0 0 1-8 8H272a8 8 0 0 1-8-8V20Z" fill="#F0F0F0"/> + <path d="M272 12.5h344a7.5 7.5 0 0 1 7.5 7.5v1077c0 4.14-3.358 7.5-7.5 7.5H272c-4.142 0-7.5-3.36-7.5-7.5V20a7.5 7.5 0 0 1 7.5-7.5Z" stroke="#949494"/> + <path d="M376.148 564.982a6.657 6.657 0 0 1-1.543.596 6.653 6.653 0 0 1-1.679.205c-1.869 0-3.321-.661-4.356-1.982-1.028-1.322-1.543-3.181-1.543-5.576 0-2.383.518-4.239 1.553-5.567 1.042-1.334 2.49-2.002 4.346-2.002.592 0 1.152.069 1.679.205a6.702 6.702 0 0 1 1.543.596v2.022a5.442 5.442 0 0 0-1.552-.909 4.718 4.718 0 0 0-1.67-.312c-1.283 0-2.243.495-2.881 1.484-.638.99-.957 2.484-.957 4.483 0 1.992.319 3.483.957 4.472.638.99 1.598 1.485 2.881 1.485.573 0 1.133-.105 1.679-.313a5.302 5.302 0 0 0 1.543-.908v2.021Zm7.588-9.16c-.911 0-1.601.355-2.07 1.065-.469.709-.703 1.761-.703 3.154 0 1.387.234 2.438.703 3.154.469.71 1.159 1.065 2.07 1.065.918 0 1.612-.355 2.08-1.065.469-.716.704-1.767.704-3.154 0-1.393-.235-2.445-.704-3.154-.468-.71-1.162-1.065-2.08-1.065Zm0-1.523c1.517 0 2.676.491 3.477 1.474.807.984 1.211 2.406 1.211 4.268 0 1.869-.401 3.294-1.201 4.277-.801.977-1.963 1.465-3.487 1.465-1.517 0-2.675-.488-3.476-1.465-.801-.983-1.201-2.408-1.201-4.277 0-1.862.4-3.284 1.201-4.268.801-.983 1.959-1.474 3.476-1.474Zm16.299 4.424v6.777h-1.806v-6.777c0-.983-.173-1.706-.518-2.168-.345-.463-.885-.694-1.621-.694-.84 0-1.488.3-1.944.899-.449.592-.673 1.445-.673 2.558v6.182h-1.797v-10.938h1.797v1.641c.319-.625.752-1.097 1.298-1.416.547-.325 1.195-.488 1.944-.488 1.113 0 1.943.368 2.49 1.103.553.73.83 1.836.83 3.321Zm7.783-7.266v3.105h4.082v1.397h-4.082v5.937c0 .808.153 1.371.459 1.69.306.319.84.478 1.602.478h2.021v1.436h-2.197c-1.348 0-2.298-.27-2.851-.811-.554-.54-.831-1.471-.831-2.793v-5.937h-2.919v-1.397h2.919v-3.105h1.797Zm16.993 7.676v.879h-7.784v.058c-.052 1.491.231 2.562.85 3.213.625.651 1.504.977 2.637.977a6.39 6.39 0 0 0 1.797-.274 10.983 10.983 0 0 0 2.001-.83v1.787a11.64 11.64 0 0 1-1.982.625 8.24 8.24 0 0 1-1.836.215c-1.699 0-3.027-.508-3.984-1.523-.957-1.022-1.436-2.429-1.436-4.219 0-1.745.469-3.138 1.406-4.18.938-1.041 2.188-1.562 3.75-1.562 1.394 0 2.491.472 3.291 1.416.808.944 1.237 2.083 1.29 3.418Zm-1.797-.528a3.443 3.443 0 0 0-.83-1.953c-.469-.553-1.146-.83-2.032-.83-.866 0-1.578.287-2.138.86-.56.573-.866 1.217-.918 1.933l5.918-.01Zm13.174.118v6.777h-1.807v-6.777c0-.983-.173-1.706-.518-2.168-.345-.463-.885-.694-1.621-.694-.84 0-1.487.3-1.943.899-.449.592-.674 1.445-.674 2.558v6.182h-1.797v-10.938h1.797v1.641c.319-.625.752-1.097 1.299-1.416.547-.325 1.194-.488 1.943-.488 1.113 0 1.944.368 2.49 1.103.554.73.831 1.836.831 3.321Zm7.783-7.266v3.105h4.082v1.397h-4.082v5.937c0 .808.153 1.371.459 1.69.306.319.84.478 1.601.478h2.022v1.436h-2.198c-1.347 0-2.298-.27-2.851-.811-.553-.54-.83-1.471-.83-2.793v-5.937h-2.92v-1.397h2.92v-3.105h1.797Zm20.381-.537h8.584v1.66h-6.602v3.906h5.986v1.66h-5.986v7.354h-1.982v-14.58Zm21.054 5.898a4.071 4.071 0 0 0-1.172-.654 3.987 3.987 0 0 0-1.308-.205c-1.12 0-1.976.352-2.569 1.055-.592.703-.888 1.718-.888 3.047v5.439h-1.807v-10.938h1.807v2.139c.299-.775.758-1.367 1.377-1.777.625-.417 1.364-.625 2.216-.625.443 0 .857.055 1.241.166.384.111.752.283 1.103.517v1.836Zm7.627 3.184h-.595c-1.049 0-1.84.186-2.374.557-.527.364-.791.911-.791 1.64 0 .658.199 1.169.596 1.533.397.365.947.547 1.651.547.989 0 1.767-.341 2.334-1.025.566-.69.852-1.641.859-2.852v-.4h-1.68Zm3.487-.742v6.24h-1.807v-1.621c-.384.651-.869 1.133-1.455 1.445-.58.306-1.286.459-2.119.459-1.114 0-2.002-.312-2.666-.937-.664-.632-.996-1.475-.996-2.53 0-1.217.406-2.142 1.22-2.773.821-.632 2.022-.947 3.604-.947h2.412v-.284c-.007-.872-.228-1.503-.664-1.894-.436-.397-1.133-.596-2.09-.596-.612 0-1.23.088-1.855.264a8.136 8.136 0 0 0-1.827.771v-1.796a12.192 12.192 0 0 1 1.905-.567 8.543 8.543 0 0 1 1.777-.195c.905 0 1.676.133 2.314.4a3.645 3.645 0 0 1 1.563 1.201c.247.326.423.73.527 1.211.104.476.157 1.192.157 2.149Zm8.31-3.584c.221-.469.501-.814.84-1.035.345-.228.758-.342 1.24-.342.879 0 1.498.342 1.856 1.025.364.677.546 1.957.546 3.838v6.338h-1.64v-6.26c0-1.543-.088-2.5-.264-2.871-.169-.377-.482-.566-.937-.566-.521 0-.879.202-1.075.605-.188.397-.283 1.341-.283 2.832v6.26h-1.64v-6.26c0-1.562-.095-2.526-.284-2.89-.182-.365-.514-.547-.996-.547-.475 0-.807.202-.996.605-.182.397-.273 1.341-.273 2.832v6.26h-1.631v-10.938h1.631v.938c.215-.391.482-.687.801-.889a2.003 2.003 0 0 1 1.103-.312c.495 0 .905.114 1.231.342.332.227.589.573.771 1.035Zm16.387 3.457v.879h-7.783v.058c-.052 1.491.231 2.562.849 3.213.625.651 1.504.977 2.637.977a6.39 6.39 0 0 0 1.797-.274 11.02 11.02 0 0 0 2.002-.83v1.787c-.684.28-1.345.489-1.983.625a8.24 8.24 0 0 1-1.836.215c-1.699 0-3.027-.508-3.984-1.523-.957-1.022-1.436-2.429-1.436-4.219 0-1.745.469-3.138 1.407-4.18.937-1.041 2.187-1.562 3.75-1.562 1.393 0 2.49.472 3.291 1.416.807.944 1.237 2.083 1.289 3.418Zm-1.797-.528a3.443 3.443 0 0 0-.83-1.953c-.469-.553-1.146-.83-2.031-.83-.866 0-1.579.287-2.139.86-.56.573-.866 1.217-.918 1.933l5.918-.01Z" fill="#1E1E1E"/> + </g> + <g filter="url(#b)"> + <path d="M636 20a8 8 0 0 1 8-8h1064c4.42 0 8 3.582 8 8v1077c0 4.42-3.58 8-8 8H644a8 8 0 0 1-8-8V20Z" fill="#F0F0F0"/> + <path d="M644 12.5h1064c4.14 0 7.5 3.358 7.5 7.5v1077c0 4.14-3.36 7.5-7.5 7.5H644c-4.142 0-7.5-3.36-7.5-7.5V20a7.5 7.5 0 0 1 7.5-7.5Z" stroke="#949494"/> + <path d="M1108.15 564.982a6.758 6.758 0 0 1-3.22.801c-1.87 0-3.32-.661-4.36-1.982-1.03-1.322-1.54-3.181-1.54-5.576 0-2.383.51-4.239 1.55-5.567 1.04-1.334 2.49-2.002 4.35-2.002a6.736 6.736 0 0 1 3.22.801v2.022c-.48-.398-1-.7-1.55-.909a4.742 4.742 0 0 0-1.67-.312c-1.29 0-2.25.495-2.89 1.484-.63.99-.95 2.484-.95 4.483 0 1.992.32 3.483.95 4.472.64.99 1.6 1.485 2.89 1.485.57 0 1.13-.105 1.68-.313a5.407 5.407 0 0 0 1.54-.908v2.021Zm7.59-9.16c-.92 0-1.61.355-2.07 1.065-.47.709-.71 1.761-.71 3.154 0 1.387.24 2.438.71 3.154.46.71 1.15 1.065 2.07 1.065.91 0 1.61-.355 2.08-1.065.47-.716.7-1.767.7-3.154 0-1.393-.23-2.445-.7-3.154-.47-.71-1.17-1.065-2.08-1.065Zm0-1.523c1.51 0 2.67.491 3.47 1.474.81.984 1.21 2.406 1.21 4.268 0 1.869-.4 3.294-1.2 4.277-.8.977-1.96 1.465-3.48 1.465s-2.68-.488-3.48-1.465c-.8-.983-1.2-2.408-1.2-4.277 0-1.862.4-3.284 1.2-4.268.8-.983 1.96-1.474 3.48-1.474Zm16.3 4.424v6.777h-1.81v-6.777c0-.983-.17-1.706-.52-2.168-.34-.463-.88-.694-1.62-.694-.84 0-1.49.3-1.94.899-.45.592-.68 1.445-.68 2.558v6.182h-1.79v-10.938h1.79v1.641c.32-.625.75-1.097 1.3-1.416.55-.325 1.2-.488 1.94-.488 1.12 0 1.95.368 2.5 1.103.55.73.83 1.836.83 3.321Zm7.78-7.266v3.105h4.08v1.397h-4.08v5.937c0 .808.15 1.371.46 1.69.3.319.84.478 1.6.478h2.02v1.436h-2.2c-1.34 0-2.3-.27-2.85-.811-.55-.54-.83-1.471-.83-2.793v-5.937h-2.92v-1.397h2.92v-3.105h1.8Zm16.99 7.676v.879h-7.78v.058c-.05 1.491.23 2.562.85 3.213.62.651 1.5.977 2.63.977.58 0 1.18-.091 1.8-.274.63-.182 1.29-.459 2-.83v1.787c-.68.28-1.34.489-1.98.625a8.22 8.22 0 0 1-1.84.215c-1.7 0-3.02-.508-3.98-1.523-.96-1.022-1.44-2.429-1.44-4.219 0-1.745.47-3.138 1.41-4.18.94-1.041 2.19-1.562 3.75-1.562 1.39 0 2.49.472 3.29 1.416.81.944 1.24 2.083 1.29 3.418Zm-1.8-.528a3.358 3.358 0 0 0-.83-1.953c-.47-.553-1.14-.83-2.03-.83-.86 0-1.58.287-2.14.86s-.86 1.217-.91 1.933l5.91-.01Zm13.18.118v6.777h-1.81v-6.777c0-.983-.17-1.706-.52-2.168-.34-.463-.88-.694-1.62-.694-.84 0-1.49.3-1.94.899-.45.592-.68 1.445-.68 2.558v6.182h-1.79v-10.938h1.79v1.641c.32-.625.76-1.097 1.3-1.416.55-.325 1.2-.488 1.95-.488 1.11 0 1.94.368 2.49 1.103.55.73.83 1.836.83 3.321Zm7.78-7.266v3.105h4.08v1.397h-4.08v5.937c0 .808.15 1.371.46 1.69.31.319.84.478 1.6.478h2.02v1.436h-2.19c-1.35 0-2.3-.27-2.86-.811-.55-.54-.83-1.471-.83-2.793v-5.937h-2.92v-1.397h2.92v-3.105h1.8Zm20.38-.537h8.59v1.66h-6.61v3.906h5.99v1.66h-5.99v7.354h-1.98v-14.58Zm21.06 5.898a4.162 4.162 0 0 0-1.18-.654 3.93 3.93 0 0 0-1.3-.205c-1.12 0-1.98.352-2.57 1.055-.6.703-.89 1.718-.89 3.047v5.439h-1.81v-10.938h1.81v2.139c.3-.775.76-1.367 1.38-1.777.62-.417 1.36-.625 2.21-.625.45 0 .86.055 1.24.166.39.111.75.283 1.11.517v1.836Zm7.62 3.184h-.59c-1.05 0-1.84.186-2.38.557-.52.364-.79.911-.79 1.64 0 .658.2 1.169.6 1.533.4.365.95.547 1.65.547.99 0 1.77-.341 2.33-1.025.57-.69.86-1.641.86-2.852v-.4h-1.68Zm3.49-.742v6.24h-1.81v-1.621c-.38.651-.87 1.133-1.45 1.445-.58.306-1.29.459-2.12.459-1.11 0-2-.312-2.67-.937-.66-.632-.99-1.475-.99-2.53 0-1.217.4-2.142 1.22-2.773.82-.632 2.02-.947 3.6-.947h2.41v-.284c0-.872-.22-1.503-.66-1.894-.44-.397-1.13-.596-2.09-.596-.61 0-1.23.088-1.86.264-.62.176-1.23.433-1.82.771v-1.796c.66-.254 1.3-.443 1.9-.567.61-.13 1.21-.195 1.78-.195.9 0 1.68.133 2.31.4.65.267 1.17.668 1.57 1.201.24.326.42.73.52 1.211.11.476.16 1.192.16 2.149Zm8.31-3.584c.22-.469.5-.814.84-1.035.34-.228.76-.342 1.24-.342.88 0 1.5.342 1.86 1.025.36.677.54 1.957.54 3.838v6.338h-1.64v-6.26c0-1.543-.09-2.5-.26-2.871-.17-.377-.48-.566-.94-.566-.52 0-.88.202-1.07.605-.19.397-.29 1.341-.29 2.832v6.26h-1.64v-6.26c0-1.562-.09-2.526-.28-2.89-.18-.365-.51-.547-1-.547-.47 0-.8.202-.99.605-.19.397-.28 1.341-.28 2.832v6.26h-1.63v-10.938h1.63v.938c.22-.391.49-.687.8-.889.33-.208.7-.312 1.11-.312.49 0 .9.114 1.23.342.33.227.59.573.77 1.035Zm16.39 3.457v.879h-7.79v.058c-.05 1.491.23 2.562.85 3.213.63.651 1.51.977 2.64.977.57 0 1.17-.091 1.8-.274.62-.182 1.29-.459 2-.83v1.787c-.68.28-1.35.489-1.98.625-.64.144-1.25.215-1.84.215-1.7 0-3.03-.508-3.98-1.523-.96-1.022-1.44-2.429-1.44-4.219 0-1.745.47-3.138 1.41-4.18.93-1.041 2.18-1.562 3.75-1.562 1.39 0 2.49.472 3.29 1.416.81.944 1.23 2.083 1.29 3.418Zm-1.8-.528c-.08-.755-.35-1.406-.83-1.953-.47-.553-1.15-.83-2.03-.83-.87 0-1.58.287-2.14.86s-.87 1.217-.92 1.933l5.92-.01Z" fill="#1E1E1E"/> + </g> + <defs> + <filter id="a" x="260" y="11" width="368" height="1102" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB"> + <feFlood flood-opacity="0" result="BackgroundImageFix"/> + <feColorMatrix in="SourceAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/> + <feOffset dy="4"/> + <feGaussianBlur stdDeviation="2"/> + <feComposite in2="hardAlpha" operator="out"/> + <feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.01 0"/> + <feBlend in2="BackgroundImageFix" result="effect1_dropShadow_18808_33283"/> + <feColorMatrix in="SourceAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/> + <feOffset dy="3"/> + <feGaussianBlur stdDeviation="1.5"/> + <feComposite in2="hardAlpha" operator="out"/> + <feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.02 0"/> + <feBlend in2="effect1_dropShadow_18808_33283" result="effect2_dropShadow_18808_33283"/> + <feColorMatrix in="SourceAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/> + <feOffset dy="1"/> + <feGaussianBlur stdDeviation="1"/> + <feComposite in2="hardAlpha" operator="out"/> + <feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.02 0"/> + <feBlend in2="effect2_dropShadow_18808_33283" result="effect3_dropShadow_18808_33283"/> + <feColorMatrix in="SourceAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/> + <feOffset dy="1"/> + <feGaussianBlur stdDeviation=".5"/> + <feComposite in2="hardAlpha" operator="out"/> + <feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.03 0"/> + <feBlend in2="effect3_dropShadow_18808_33283" result="effect4_dropShadow_18808_33283"/> + <feBlend in="SourceGraphic" in2="effect4_dropShadow_18808_33283" result="shape"/> + </filter> + <filter id="b" x="632" y="11" width="1088" height="1102" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB"> + <feFlood flood-opacity="0" result="BackgroundImageFix"/> + <feColorMatrix in="SourceAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/> + <feOffset dy="4"/> + <feGaussianBlur stdDeviation="2"/> + <feComposite in2="hardAlpha" operator="out"/> + <feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.01 0"/> + <feBlend in2="BackgroundImageFix" result="effect1_dropShadow_18808_33283"/> + <feColorMatrix in="SourceAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/> + <feOffset dy="3"/> + <feGaussianBlur stdDeviation="1.5"/> + <feComposite in2="hardAlpha" operator="out"/> + <feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.02 0"/> + <feBlend in2="effect1_dropShadow_18808_33283" result="effect2_dropShadow_18808_33283"/> + <feColorMatrix in="SourceAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/> + <feOffset dy="1"/> + <feGaussianBlur stdDeviation="1"/> + <feComposite in2="hardAlpha" operator="out"/> + <feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.02 0"/> + <feBlend in2="effect2_dropShadow_18808_33283" result="effect3_dropShadow_18808_33283"/> + <feColorMatrix in="SourceAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/> + <feOffset dy="1"/> + <feGaussianBlur stdDeviation=".5"/> + <feComposite in2="hardAlpha" operator="out"/> + <feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.03 0"/> + <feBlend in2="effect3_dropShadow_18808_33283" result="effect4_dropShadow_18808_33283"/> + <feBlend in="SourceGraphic" in2="effect4_dropShadow_18808_33283" result="shape"/> + </filter> + </defs> +</svg> \ No newline at end of file diff --git a/storybook/stories/playground/box/index.js b/storybook/stories/playground/box/index.js index cca522a90c1441..35656c7d6edc04 100644 --- a/storybook/stories/playground/box/index.js +++ b/storybook/stories/playground/box/index.js @@ -12,7 +12,7 @@ import { /** * Internal dependencies */ -import editorStyles from '../editor-styles'; +import { editorStyles } from '../editor-styles'; import './style.css'; export default function EditorBox() { diff --git a/storybook/stories/playground/with-undo-redo/index.js b/storybook/stories/playground/with-undo-redo/index.js index b5a6067cad24b7..0952543679ce06 100644 --- a/storybook/stories/playground/with-undo-redo/index.js +++ b/storybook/stories/playground/with-undo-redo/index.js @@ -15,7 +15,7 @@ import { undo as undoIcon, redo as redoIcon } from '@wordpress/icons'; /** * Internal dependencies */ -import editorStyles from '../editor-styles'; +import { editorStyles } from '../editor-styles'; import './style.css'; export default function EditorWithUndoRedo() { diff --git a/storybook/stories/playground/zoom-out/index.js b/storybook/stories/playground/zoom-out/index.js index 8b72a831d710e8..c4d9a716c90694 100644 --- a/storybook/stories/playground/zoom-out/index.js +++ b/storybook/stories/playground/zoom-out/index.js @@ -16,7 +16,7 @@ import { parse } from '@wordpress/blocks'; /** * Internal dependencies */ -import editorStyles from '../editor-styles'; +import { editorStyles } from '../editor-styles'; // eslint-disable-next-line @wordpress/dependency-group import contentCss from '!!raw-loader!../../../../packages/block-editor/build-style/content.css'; import { pattern } from './pattern'; diff --git a/storybook/stories/tokens/elevation.mdx b/storybook/stories/tokens/elevation.mdx index 5afb7e75266c4a..b3b0cd1a881b40 100644 --- a/storybook/stories/tokens/elevation.mdx +++ b/storybook/stories/tokens/elevation.mdx @@ -1,4 +1,4 @@ -import { Meta } from '@storybook/addon-docs/blocks'; +import { Meta } from '@storybook/blocks'; import { TokensTable } from './components.tsx'; <Meta title="Tokens/Elevation" name="page" /> diff --git a/storybook/stories/tokens/typography.mdx b/storybook/stories/tokens/typography.mdx index c3b49b2865f186..9d0e4aedf8c017 100644 --- a/storybook/stories/tokens/typography.mdx +++ b/storybook/stories/tokens/typography.mdx @@ -1,4 +1,4 @@ -import { Meta, Typeset } from '@storybook/addon-docs/blocks'; +import { Meta, Typeset } from '@storybook/blocks'; <Meta title="Tokens/Typography" name="page" /> diff --git a/test/e2e/playwright.config.ts b/test/e2e/playwright.config.ts index f5410f2230372b..bb93f342f9bf8c 100644 --- a/test/e2e/playwright.config.ts +++ b/test/e2e/playwright.config.ts @@ -8,7 +8,7 @@ import { defineConfig, devices } from '@playwright/test'; /** * WordPress dependencies */ -const baseConfig = require( '@wordpress/scripts/config/playwright.config' ); +import baseConfig from '@wordpress/scripts/config/playwright.config.js'; const config = defineConfig( { ...baseConfig, diff --git a/test/e2e/specs/editor/blocks/buttons.spec.js b/test/e2e/specs/editor/blocks/buttons.spec.js index d6b0a0a15c4ea2..554bd8947f0bf5 100644 --- a/test/e2e/specs/editor/blocks/buttons.spec.js +++ b/test/e2e/specs/editor/blocks/buttons.spec.js @@ -263,12 +263,14 @@ test.describe( 'Buttons', () => { await editor.insertBlock( { name: 'core/buttons' } ); await page.keyboard.type( 'Content' ); await editor.openDocumentSettingsSidebar(); - await page.click( - `role=region[name="Editor settings"i] >> role=tab[name="Settings"i]` - ); - await page.click( - 'role=group[name="Button width"i] >> role=button[name="25%"i]' - ); + await page + .getByRole( 'region', { name: 'Editor settings' } ) + .getByRole( 'tab', { name: 'Settings' } ) + .click(); + await page + .getByRole( 'radiogroup', { name: 'Width' } ) + .getByRole( 'radio', { name: '25%' } ) + .click(); // Check the content. const content = await editor.getEditedPostContent(); @@ -293,11 +295,11 @@ test.describe( 'Buttons', () => { await page.click( 'role=region[name="Editor settings"i] >> role=button[name="Text"i]' ); - await page.click( 'role=option[name="Color: Cyan bluish gray"i]' ); + await page.click( 'role=option[name="Cyan bluish gray"i]' ); await page.click( 'role=region[name="Editor settings"i] >> role=button[name="Background"i]' ); - await page.click( 'role=option[name="Color: Vivid red"i]' ); + await page.click( 'role=option[name="Vivid red"i]' ); // Check the content. const content = await editor.getEditedPostContent(); diff --git a/test/e2e/specs/editor/blocks/classic.spec.js b/test/e2e/specs/editor/blocks/classic.spec.js index 95d39906b0d8bb..bd7ccb2280d09e 100644 --- a/test/e2e/specs/editor/blocks/classic.spec.js +++ b/test/e2e/specs/editor/blocks/classic.spec.js @@ -106,7 +106,7 @@ test.describe( 'Classic', () => { page, pageUtils, } ) => { - // Based on docs routing diables caching. + // Based on docs routing disables caching. // See: https://playwright.dev/docs/api/class-page#page-route await page.route( '**', async ( route ) => { await route.continue(); diff --git a/test/e2e/specs/editor/blocks/columns.spec.js b/test/e2e/specs/editor/blocks/columns.spec.js index eea6e321aacb11..29262aef810d21 100644 --- a/test/e2e/specs/editor/blocks/columns.spec.js +++ b/test/e2e/specs/editor/blocks/columns.spec.js @@ -63,7 +63,7 @@ test.describe( 'Columns', () => { ); await editor.clickBlockToolbarButton( 'Options' ); await page.click( 'role=menuitem[name="Lock"i]' ); - await page.locator( 'role=checkbox[name="Prevent removal"i]' ).check(); + await page.locator( 'role=checkbox[name="Lock removal"i]' ).check(); await page.click( 'role=button[name="Apply"i]' ); // Select columns block diff --git a/test/e2e/specs/editor/blocks/cover.spec.js b/test/e2e/specs/editor/blocks/cover.spec.js index a9a93caa4f4341..87c244a7306dc6 100644 --- a/test/e2e/specs/editor/blocks/cover.spec.js +++ b/test/e2e/specs/editor/blocks/cover.spec.js @@ -34,7 +34,7 @@ test.describe( 'Cover', () => { // Locate the Black color swatch. const blackColorSwatch = coverBlock.getByRole( 'option', { - name: 'Color: Black', + name: 'Black', } ); await expect( blackColorSwatch ).toBeVisible(); @@ -106,7 +106,7 @@ test.describe( 'Cover', () => { // a functioning block. await coverBlock .getByRole( 'option', { - name: 'Color: Black', + name: 'Black', } ) .click(); @@ -129,7 +129,7 @@ test.describe( 'Cover', () => { } ); await coverBlock .getByRole( 'option', { - name: 'Color: Black', + name: 'Black', } ) .click(); @@ -177,7 +177,7 @@ test.describe( 'Cover', () => { expect( coverBlockBox.height ).toBeTruthy(); expect( coverBlockResizeHandleBox.height ).toBeTruthy(); - // Increse the Cover block height by 100px. + // Increase the Cover block height by 100px. await coverBlockResizeHandle.hover(); await page.mouse.down(); @@ -241,7 +241,7 @@ test.describe( 'Cover', () => { // a functioning block. await coverBlock .getByRole( 'option', { - name: 'Color: Black', + name: 'Black', } ) .click(); @@ -267,7 +267,7 @@ test.describe( 'Cover', () => { // a functioning block. await secondCoverBlock .getByRole( 'option', { - name: 'Color: Black', + name: 'Black', } ) .click(); diff --git a/test/e2e/specs/editor/blocks/heading.spec.js b/test/e2e/specs/editor/blocks/heading.spec.js index 4670196c485f5f..906095cad9d080 100644 --- a/test/e2e/specs/editor/blocks/heading.spec.js +++ b/test/e2e/specs/editor/blocks/heading.spec.js @@ -214,13 +214,13 @@ test.describe( 'Heading', () => { .getByRole( 'region', { name: 'Editor settings', } ) - .getByRole( 'button', { name: 'Text' } ); + .getByRole( 'button', { name: 'Text', exact: true } ); await textColor.click(); await page .getByRole( 'option', { - name: 'Color: Luminous vivid orange', + name: 'Luminous vivid orange', } ) .click(); diff --git a/test/e2e/specs/editor/blocks/image.spec.js b/test/e2e/specs/editor/blocks/image.spec.js index 6110a125ff6f7e..35c9340e9b923c 100644 --- a/test/e2e/specs/editor/blocks/image.spec.js +++ b/test/e2e/specs/editor/blocks/image.spec.js @@ -424,9 +424,6 @@ test.describe( 'Image', () => { page, editor, } ) => { - // This is a temp workaround for dragging and dropping images from the inserter. - // This should be removed when we have the zoom out view for media categories. - await page.setViewportSize( { width: 1400, height: 800 } ); await editor.insertBlock( { name: 'core/image' } ); const imageBlock = editor.canvas.getByRole( 'document', { name: 'Block: Image', @@ -528,14 +525,13 @@ test.describe( 'Image', () => { name: 'Block: Image', } ); - const html = ` - <figure> - <img src="https://live.staticflickr.com/3894/14962688165_04759a8b03_b.jpg" alt="Cat"> - <figcaption>"Cat" by tomhouslay is licensed under <a href="https://creativecommons.org/licenses/by-nc/2.0/?ref=openverse">CC BY-NC 2.0</a>.</figcaption> - </figure> - `; - - await page.evaluate( ( _html ) => { + await page.evaluate( () => { + const { createBlock } = window.wp.blocks; + const block = createBlock( 'core/image', { + url: 'https://live.staticflickr.com/3894/14962688165_04759a8b03_b.jpg', + alt: 'Cat', + caption: `"Cat" by tomhouslay is licensed under <a href="https://creativecommons.org/licenses/by-nc/2.0/?ref=openverse">CC BY-NC 2.0</a>.`, + } ); const dummy = document.createElement( 'div' ); dummy.style.width = '10px'; dummy.style.height = '10px'; @@ -545,13 +541,17 @@ test.describe( 'Image', () => { dummy.style.left = 0; dummy.draggable = 'true'; dummy.addEventListener( 'dragstart', ( event ) => { - event.dataTransfer.setData( 'text/html', _html ); + event.dataTransfer.setData( + 'wp-blocks', + JSON.stringify( { blocks: [ block ] } ) + ); + event.dataTransfer.setData( 'wp-block:core/image', '' ); setTimeout( () => { dummy.remove(); }, 0 ); } ); document.body.appendChild( dummy ); - }, html ); + } ); await page.mouse.move( 0, 0 ); await page.mouse.down(); @@ -859,17 +859,17 @@ test.describe( 'Image', () => { } ) ).toBeFocused(); - // Select "Expand on click", then remove it. + // Select "Enlarge on click", then remove it. await pageUtils.pressKeys( 'Tab' ); await page.keyboard.press( 'Enter' ); await pageUtils.pressKeys( 'Tab', { times: 5 } ); await expect( - page.getByRole( 'menuitem', { name: 'Expand on click' } ) + page.getByRole( 'menuitem', { name: 'Enlarge on click' } ) ).toBeFocused(); await page.keyboard.press( 'Enter' ); await expect( page.getByRole( 'button', { - name: 'Disable expand on click', + name: 'Disable enlarge on click', } ) ).toBeFocused(); await page.keyboard.press( 'Enter' ); @@ -933,7 +933,7 @@ test.describe( 'Image - lightbox', () => { await expect( page.getByRole( 'menuitem', { - name: 'Expand on click', + name: 'Enlarge on click', } ) ).toBeHidden(); } ); @@ -958,13 +958,13 @@ test.describe( 'Image - lightbox', () => { await page .getByRole( 'button', { - name: 'Disable expand on click', + name: 'Disable enlarge on click', } ) .click(); await expect( page.getByRole( 'menuitem', { - name: 'Expand on click', + name: 'Enlarge on click', } ) ).toBeHidden(); } ); diff --git a/test/e2e/specs/editor/blocks/list.spec.js b/test/e2e/specs/editor/blocks/list.spec.js index 16126cf9cd29f6..ec7b12293aa82b 100644 --- a/test/e2e/specs/editor/blocks/list.spec.js +++ b/test/e2e/specs/editor/blocks/list.spec.js @@ -680,7 +680,7 @@ test.describe( 'List (@firefox)', () => { ); } ); - test( 'should be immeadiately saved on indentation', async ( { + test( 'should be immediately saved on indentation', async ( { editor, page, } ) => { diff --git a/test/e2e/specs/editor/blocks/navigation-colors.spec.js b/test/e2e/specs/editor/blocks/navigation-colors.spec.js index 8692b184fb13f9..80b36d54980b1e 100644 --- a/test/e2e/specs/editor/blocks/navigation-colors.spec.js +++ b/test/e2e/specs/editor/blocks/navigation-colors.spec.js @@ -93,14 +93,14 @@ test.describe( 'Navigation colors', () => { await page.getByRole( 'tab', { name: 'Styles' } ).click(); await page.getByRole( 'button', { name: 'Text' } ).click(); await page - .getByRole( 'option', { name: 'Color: White' } ) + .getByRole( 'option', { name: 'White' } ) .click( { force: true } ); await page .getByRole( 'button', { name: 'Background', exact: true } ) .click(); await page - .getByRole( 'option', { name: 'Color: Black' } ) + .getByRole( 'option', { name: 'Black' } ) .click( { force: true } ); // Close the sidebar so our selectors don't accidentally select the sidebar links instead of the editor canvas. @@ -151,12 +151,12 @@ test.describe( 'Navigation colors', () => { await page.getByRole( 'button', { name: 'Link', exact: true } ).click(); // rga(207, 46 ,46) is the color of the "vivid red" color preset. await page - .getByRole( 'option', { name: 'Color: Vivid red' } ) + .getByRole( 'option', { name: 'Vivid red' } ) .click( { force: true } ); await page.getByRole( 'tab', { name: 'Hover' } ).click(); // rgb(155, 81, 224) is the color of the "vivid purple" color preset. await page - .getByRole( 'option', { name: 'Color: Vivid purple' } ) + .getByRole( 'option', { name: 'Vivid purple' } ) .click( { force: true } ); // Close the sidebar so our selectors don't accidentally select the sidebar links instead of the editor canvas. @@ -198,7 +198,7 @@ test.describe( 'Navigation colors', () => { // 247, 141, 167 is the color of the "Pale pink" color preset. const palePink = 'rgb(247, 141, 167)'; await page - .getByRole( 'option', { name: 'Color: Pale pink' } ) + .getByRole( 'option', { name: 'Pale pink' } ) .click( { force: true } ); // Close the sidebar so our selectors don't accidentally select the sidebar links instead of the editor canvas. @@ -242,7 +242,7 @@ test.describe( 'Navigation colors', () => { // 142, 209, 252 is the color of the "Pale cyan blue" color preset. const paleCyan = 'rgb(142, 209, 252)'; await page - .getByRole( 'option', { name: 'Color: Pale cyan blue' } ) + .getByRole( 'option', { name: 'Pale cyan blue' } ) .click( { force: true } ); // Close the sidebar so our selectors don't accidentally select the sidebar links instead of the editor canvas. @@ -285,7 +285,7 @@ test.describe( 'Navigation colors', () => { // 247, 141, 167 is the color of the "Pale pink" color preset. const palePink = 'rgb(247, 141, 167)'; await page - .getByRole( 'option', { name: 'Color: Pale pink' } ) + .getByRole( 'option', { name: 'Pale pink' } ) .click( { force: true } ); // Pale cyan blue for the background color. await page @@ -294,7 +294,7 @@ test.describe( 'Navigation colors', () => { // 142, 209, 252 is the color of the "Pale cyan blue" color preset. const paleCyan = 'rgb(142, 209, 252)'; await page - .getByRole( 'option', { name: 'Color: Pale cyan blue' } ) + .getByRole( 'option', { name: 'Pale cyan blue' } ) .click( { force: true } ); // Cyan bluish gray for the submenu and overlay text color. await page @@ -303,7 +303,7 @@ test.describe( 'Navigation colors', () => { // 171, 184, 195 is the color of the "Cyan bluish gray" color preset. const cyanBluishGray = 'rgb(171, 184, 195)'; await page - .getByRole( 'option', { name: 'Color: Cyan bluish gray' } ) + .getByRole( 'option', { name: 'Cyan bluish gray' } ) .click( { force: true } ); // Luminous vivid amber for the submenu and overlay background color. await page @@ -312,7 +312,7 @@ test.describe( 'Navigation colors', () => { // 252, 185, 0 is the color of the "Luminous vivid amber" color preset. const vividAmber = 'rgb(252, 185, 0)'; await page - .getByRole( 'option', { name: 'Color: Luminous vivid amber' } ) + .getByRole( 'option', { name: 'Luminous vivid amber' } ) .click( { force: true } ); // Close the sidebar so our selectors don't accidentally select the sidebar links instead of the editor canvas. diff --git a/test/e2e/specs/editor/blocks/navigation-frontend-interactivity.spec.js b/test/e2e/specs/editor/blocks/navigation-frontend-interactivity.spec.js index b31533d0d17c24..75ba370072e342 100644 --- a/test/e2e/specs/editor/blocks/navigation-frontend-interactivity.spec.js +++ b/test/e2e/specs/editor/blocks/navigation-frontend-interactivity.spec.js @@ -86,7 +86,7 @@ test.describe( 'Navigation block - Frontend interactivity', () => { /** * These are already tested within the Overlay Interactions test above, but Safari is flakey on the Tab * keypresses (passes 50 - 70% of the time). Tab keypresses are testing fine manually in Safari, but not - * in the test. nce we figure out why the Tab keypresses are flakey in the test, we can + * in the test. Once we figure out why the Tab keypresses are flakey in the test, we can * remove this test and only rely on the Overlay Interactions test above and add a (@firefox, @webkit) * directive to the describe() statement. https://github.com/WordPress/gutenberg/pull/55198 */ diff --git a/test/e2e/specs/editor/blocks/navigation-list-view.spec.js b/test/e2e/specs/editor/blocks/navigation-list-view.spec.js index 2f9963169a5230..47313590f65794 100644 --- a/test/e2e/specs/editor/blocks/navigation-list-view.spec.js +++ b/test/e2e/specs/editor/blocks/navigation-list-view.spec.js @@ -555,7 +555,10 @@ test.describe( 'Navigation block - List view editing', () => { await editor.openDocumentSettingsSidebar(); - await page.getByLabel( 'Test Menu' ).click(); + await page + .getByRole( 'tabpanel' ) + .getByRole( 'button', { name: 'Test Menu' } ) + .click(); await page.keyboard.press( 'ArrowUp' ); diff --git a/test/e2e/specs/editor/blocks/navigation.spec.js b/test/e2e/specs/editor/blocks/navigation.spec.js index 83e95a08c0f6a2..769e30c99dab36 100644 --- a/test/e2e/specs/editor/blocks/navigation.spec.js +++ b/test/e2e/specs/editor/blocks/navigation.spec.js @@ -276,7 +276,7 @@ test.describe( 'Navigation block', () => { await pageUtils.pressKeys( 'ArrowDown' ); // remove the child link - await pageUtils.pressKeys( 'access+z' ); + await pageUtils.pressKeys( 'shift+Backspace' ); const submenuBlock2 = editor.canvas.getByRole( 'document', { name: 'Block: Submenu', @@ -494,7 +494,7 @@ test.describe( 'Navigation block', () => { await pageUtils.pressKeys( 'ArrowDown', { times: 4 } ); await navigation.checkLabelFocus( 'wordpress.org' ); // Delete the nav link - await pageUtils.pressKeys( 'access+z' ); + await pageUtils.pressKeys( 'shift+Backspace' ); // Focus moved to sibling await navigation.checkLabelFocus( 'Dog' ); // Add a link back so we can delete the first submenu link and see if focus returns to the parent submenu item @@ -507,15 +507,15 @@ test.describe( 'Navigation block', () => { await pageUtils.pressKeys( 'ArrowUp', { times: 2 } ); await navigation.checkLabelFocus( 'Dog' ); // Delete the nav link - await pageUtils.pressKeys( 'access+z' ); + await pageUtils.pressKeys( 'shift+Backspace' ); await pageUtils.pressKeys( 'ArrowDown' ); // Focus moved to parent submenu item await navigation.checkLabelFocus( 'example.com' ); // Deleting this should move focus to the sibling item - await pageUtils.pressKeys( 'access+z' ); + await pageUtils.pressKeys( 'shift+Backspace' ); await navigation.checkLabelFocus( 'Cat' ); // Deleting with no more siblings should focus the navigation block again - await pageUtils.pressKeys( 'access+z' ); + await pageUtils.pressKeys( 'shift+Backspace' ); await expect( navBlock ).toBeFocused(); // Wait until the nav block inserter is visible before we continue. await expect( navBlockInserter ).toBeVisible(); diff --git a/test/e2e/specs/editor/blocks/spacer.spec.js b/test/e2e/specs/editor/blocks/spacer.spec.js index f089402514623c..da262c9b4e26d9 100644 --- a/test/e2e/specs/editor/blocks/spacer.spec.js +++ b/test/e2e/specs/editor/blocks/spacer.spec.js @@ -43,7 +43,9 @@ test.describe( 'Spacer', () => { expect( await editor.getEditedPostContent() ).toMatchSnapshot(); await expect( - editor.canvas.locator( 'role=document[name="Block: Spacer"i]' ) + editor.canvas.locator( + 'role=document[name="Block: Spacer"i] >> css=.components-resizable-box__handle >> [tabindex]' + ) ).toBeFocused(); } ); } ); diff --git a/test/e2e/specs/editor/various/block-bindings/custom-sources.spec.js b/test/e2e/specs/editor/various/block-bindings/custom-sources.spec.js index d6563ce9cb5f5f..fab6e10a7568cf 100644 --- a/test/e2e/specs/editor/various/block-bindings/custom-sources.spec.js +++ b/test/e2e/specs/editor/various/block-bindings/custom-sources.spec.js @@ -168,7 +168,10 @@ test.describe( 'Registered sources', () => { name: 'Block: Image', } ) .locator( 'img' ); - await imageBlockImg.click(); + // Playwright will complain that the pointer events are captured by + // the parent, but that's fine. + // eslint-disable-next-line playwright/no-force-option + await imageBlockImg.click( { force: true } ); // Image src is the custom field value. await expect( imageBlockImg ).toHaveAttribute( @@ -261,7 +264,7 @@ test.describe( 'Registered sources', () => { } ); test.describe( 'should lock editing', () => { - // Logic reused accross all the tests that check paragraph editing is locked. + // Logic reused across all the tests that check paragraph editing is locked. async function testParagraphControlsAreLocked( { source, editor, @@ -735,7 +738,10 @@ test.describe( 'Registered sources', () => { name: 'Block: Image', } ) .locator( 'img' ); - await imageBlockImg.click(); + // Playwright will complain that the pointer events are captured by + // the parent, but that's fine. + // eslint-disable-next-line playwright/no-force-option + await imageBlockImg.click( { force: true } ); // Edit the custom field value in the alt textarea. const altInputArea = page diff --git a/test/e2e/specs/editor/various/block-bindings/post-meta.spec.js b/test/e2e/specs/editor/various/block-bindings/post-meta.spec.js index 32334bfc777f2a..318707e22f098d 100644 --- a/test/e2e/specs/editor/various/block-bindings/post-meta.spec.js +++ b/test/e2e/specs/editor/various/block-bindings/post-meta.spec.js @@ -524,6 +524,47 @@ test.describe( 'Post Meta source', () => { previewPage.locator( '#connected-paragraph' ) ).toHaveText( 'new value' ); } ); + + test( 'should be possible to edit the value of the connected custom fields in the inspector control registered by the plugin', async ( { + editor, + page, + } ) => { + await editor.insertBlock( { + name: 'core/paragraph', + attributes: { + anchor: 'connected-paragraph', + content: 'fallback content', + metadata: { + bindings: { + content: { + source: 'core/post-meta', + args: { + key: 'movie_field', + }, + }, + }, + }, + }, + } ); + const contentInput = page.getByRole( 'textbox', { + name: 'Content', + } ); + await expect( contentInput ).toHaveValue( + 'Movie field default value' + ); + await contentInput.fill( 'new value' ); + // Check that the paragraph content attribute didn't change. + const [ paragraphBlockObject ] = await editor.getBlocks(); + expect( paragraphBlockObject.attributes.content ).toBe( + 'fallback content' + ); + // Check the value of the custom field is being updated by visiting the frontend. + const previewPage = await editor.openPreviewPage(); + await expect( + previewPage.locator( '#connected-paragraph' ) + ).toHaveText( 'new value' ); + } ); + test( 'should be possible to connect movie fields through the attributes panel', async ( { editor, page, diff --git a/test/e2e/specs/editor/various/block-deletion.spec.js b/test/e2e/specs/editor/various/block-deletion.spec.js index 9346412c46bcb2..5e4ead97c986fd 100644 --- a/test/e2e/specs/editor/various/block-deletion.spec.js +++ b/test/e2e/specs/editor/various/block-deletion.spec.js @@ -134,7 +134,7 @@ test.describe( 'Block deletion', () => { ).toBeFocused(); // Remove the current paragraph via dedicated keyboard shortcut. - await pageUtils.pressKeys( 'access+z' ); + await pageUtils.pressKeys( 'shift+Backspace' ); // Ensure the last block was removed. await expect.poll( editor.getBlocks ).toMatchObject( [ diff --git a/test/e2e/specs/editor/various/block-locking.spec.js b/test/e2e/specs/editor/various/block-locking.spec.js index eafb468902ef92..b31fc9e2cd1402 100644 --- a/test/e2e/specs/editor/various/block-locking.spec.js +++ b/test/e2e/specs/editor/various/block-locking.spec.js @@ -16,7 +16,7 @@ test.describe( 'Block Locking', () => { await editor.clickBlockOptionsMenuItem( 'Lock' ); - await page.click( 'role=checkbox[name="Prevent removal"]' ); + await page.click( 'role=checkbox[name="Lock removal"]' ); await page.click( 'role=button[name="Apply"]' ); await expect( @@ -35,7 +35,7 @@ test.describe( 'Block Locking', () => { await editor.clickBlockOptionsMenuItem( 'Lock' ); - await page.click( 'role=checkbox[name="Disable movement"]' ); + await page.click( 'role=checkbox[name="Lock movement"]' ); await page.click( 'role=button[name="Apply"]' ); // Hide options. @@ -133,7 +133,7 @@ test.describe( 'Block Locking', () => { ).toBeVisible(); await paragraph.click(); - await pageUtils.pressKeys( 'access+z' ); + await pageUtils.pressKeys( 'shift+Backspace' ); await expect.poll( editor.getBlocks ).toMatchObject( [ { diff --git a/test/e2e/specs/editor/various/block-renaming.spec.js b/test/e2e/specs/editor/various/block-renaming.spec.js index 6998683cb96163..5b7ac1226d1589 100644 --- a/test/e2e/specs/editor/various/block-renaming.spec.js +++ b/test/e2e/specs/editor/various/block-renaming.spec.js @@ -126,8 +126,9 @@ test.describe( 'Block Renaming', () => { await pageUtils.pressKeys( 'primary+a' ); await page.keyboard.press( 'Delete' ); - // Check placeholder for input is the original block name. + // Check that input is empty and placeholder is the original block name. await expect( nameInput ).toHaveAttribute( 'placeholder', 'Group' ); + await expect( nameInput ).toHaveValue( '' ); // It should be possible to submit empty. await expect( saveButton ).toBeEnabled(); diff --git a/test/e2e/specs/editor/various/block-switcher.spec.js b/test/e2e/specs/editor/various/block-switcher.spec.js index 41b0b1ee49c88c..cb95c4d395c664 100644 --- a/test/e2e/specs/editor/various/block-switcher.spec.js +++ b/test/e2e/specs/editor/various/block-switcher.spec.js @@ -107,7 +107,7 @@ test.describe( 'Block Switcher', () => { await expect( button ).toBeEnabled(); await editor.clickBlockOptionsMenuItem( 'Lock' ); - await page.click( 'role=checkbox[name="Prevent removal"]' ); + await page.click( 'role=checkbox[name="Lock removal"]' ); await page.click( 'role=button[name="Apply"]' ); // Verify the block switcher isn't enabled. diff --git a/test/e2e/specs/editor/various/datepicker.spec.js b/test/e2e/specs/editor/various/datepicker.spec.js index 00030efa1fe274..f6337016c18ed1 100644 --- a/test/e2e/specs/editor/various/datepicker.spec.js +++ b/test/e2e/specs/editor/various/datepicker.spec.js @@ -14,9 +14,10 @@ const TIMEZONES = [ 'Pacific/Honolulu', 'UTC', 'Australia/Sydney' ]; TIMEZONES.forEach( ( timezone ) => { test.describe( `Datepicker: ${ timezone }`, () => { - let orignalTimezone; + let originalTimezone; test.beforeAll( async ( { requestUtils } ) => { - orignalTimezone = ( await requestUtils.getSiteSettings() ).timezone; + originalTimezone = ( await requestUtils.getSiteSettings() ) + .timezone; await requestUtils.updateSiteSettings( { timezone } ); } ); @@ -27,7 +28,7 @@ TIMEZONES.forEach( ( timezone ) => { test.afterAll( async ( { requestUtils } ) => { await requestUtils.updateSiteSettings( { - timezone: orignalTimezone, + timezone: originalTimezone, } ); } ); diff --git a/test/e2e/specs/editor/various/draggable-blocks.spec.js b/test/e2e/specs/editor/various/draggable-blocks.spec.js index e08030191dd60b..704817f4a2c38a 100644 --- a/test/e2e/specs/editor/various/draggable-blocks.spec.js +++ b/test/e2e/specs/editor/various/draggable-blocks.spec.js @@ -18,6 +18,14 @@ test.use( { }, } ); +async function dragTo( page, x, y ) { + // Call the move function twice to make sure the `dragOver` event is sent. + // @see https://github.com/microsoft/playwright/issues/17153 + for ( let i = 0; i < 2; i += 1 ) { + await page.mouse.move( x, y ); + } +} + test.describe( 'Draggable block', () => { test.beforeEach( async ( { admin } ) => { await admin.createNewPost(); @@ -60,14 +68,7 @@ test.describe( 'Draggable block', () => { 'role=document[name="Block: Paragraph"i] >> text=1' ); const firstParagraphBound = await firstParagraph.boundingBox(); - // Call the move function twice to make sure the `dragOver` event is sent. - // @see https://github.com/microsoft/playwright/issues/17153 - for ( let i = 0; i < 2; i += 1 ) { - await page.mouse.move( - firstParagraphBound.x, - firstParagraphBound.y - ); - } + await dragTo( page, firstParagraphBound.x, firstParagraphBound.y ); await expect( page.locator( 'data-testid=block-draggable-chip >> visible=true' ) @@ -132,15 +133,11 @@ test.describe( 'Draggable block', () => { 'role=document[name="Block: Paragraph"i] >> text=2' ); const secondParagraphBound = await secondParagraph.boundingBox(); - // Call the move function twice to make sure the `dragOver` event is sent. - // @see https://github.com/microsoft/playwright/issues/17153 - // Make sure mouse is > 30px within the block for bottom drop indicator to appear. - for ( let i = 0; i < 2; i += 1 ) { - await page.mouse.move( - secondParagraphBound.x + 32, - secondParagraphBound.y + secondParagraphBound.height * 0.75 - ); - } + await dragTo( + page, + secondParagraphBound.x + 32, + secondParagraphBound.y + secondParagraphBound.height * 0.75 + ); await expect( page.locator( 'data-testid=block-draggable-chip >> visible=true' ) @@ -216,14 +213,11 @@ test.describe( 'Draggable block', () => { 'role=document[name="Block: Paragraph"i] >> text=1' ); const firstParagraphBound = await firstParagraph.boundingBox(); - // Call the move function twice to make sure the `dragOver` event is sent. - // @see https://github.com/microsoft/playwright/issues/17153 - for ( let i = 0; i < 2; i += 1 ) { - await page.mouse.move( - firstParagraphBound.x + firstParagraphBound.width * 0.25, - firstParagraphBound.y - ); - } + await dragTo( + page, + firstParagraphBound.x + firstParagraphBound.width * 0.25, + firstParagraphBound.y + ); await expect( page.locator( 'data-testid=block-draggable-chip >> visible=true' ) @@ -297,14 +291,11 @@ test.describe( 'Draggable block', () => { 'role=document[name="Block: Paragraph"i] >> text=2' ); const secondParagraphBound = await secondParagraph.boundingBox(); - // Call the move function twice to make sure the `dragOver` event is sent. - // @see https://github.com/microsoft/playwright/issues/17153 - for ( let i = 0; i < 2; i += 1 ) { - await page.mouse.move( - secondParagraphBound.x + secondParagraphBound.width * 0.75, - secondParagraphBound.y - ); - } + await dragTo( + page, + secondParagraphBound.x + secondParagraphBound.width * 0.75, + secondParagraphBound.y + ); await expect( page.locator( 'data-testid=block-draggable-chip >> visible=true' ) @@ -465,4 +456,47 @@ test.describe( 'Draggable block', () => { ] ); } } ); + + test( 'can directly drag an image', async ( { page, editor } ) => { + await editor.insertBlock( { name: 'core/image' } ); + await editor.insertBlock( { + name: 'core/group', + attributes: { layout: { type: 'constrained' } }, + innerBlocks: [ { name: 'core/paragraph' } ], + } ); + + const imageBlock = editor.canvas.getByRole( 'document', { + name: 'Block: Image', + } ); + + const groupBlock = editor.canvas.getByRole( 'document', { + name: 'Block: Group', + } ); + + await imageBlock.hover(); + await page.mouse.down(); + const groupBlockBox = await groupBlock.boundingBox(); + await dragTo( + page, + groupBlockBox.x + groupBlockBox.width * 0.5, + groupBlockBox.y + groupBlockBox.height * 0.5 + ); + await page.mouse.up(); + + await expect.poll( editor.getBlocks ).toMatchObject( [ + { + name: 'core/group', + attributes: { + tagName: 'div', + layout: { type: 'constrained' }, + }, + innerBlocks: [ + { + name: 'core/image', + attributes: { alt: '', caption: '' }, + }, + ], + }, + ] ); + } ); } ); diff --git a/test/e2e/specs/editor/various/embedding.spec.js b/test/e2e/specs/editor/various/embedding.spec.js index fe488f91301749..8f64ab16fce406 100644 --- a/test/e2e/specs/editor/various/embedding.spec.js +++ b/test/e2e/specs/editor/various/embedding.spec.js @@ -98,13 +98,13 @@ test.describe( 'Embedding content', () => { MOCK_EMBED_PHOTO_SUCCESS_RESPONSE, } ); - const currenEmbedBlock = editor.canvas + const currentEmbedBlock = editor.canvas .getByRole( 'document', { name: 'Block' } ) .last(); await embedUtils.insertEmbed( 'https://twitter.com/notnownikki' ); await expect( - currenEmbedBlock.locator( 'iframe' ), + currentEmbedBlock.locator( 'iframe' ), 'Valid embed. Should render valid element.' ).toHaveAttribute( 'title', 'Embedded content from twitter.com' ); @@ -112,7 +112,7 @@ test.describe( 'Embedding content', () => { 'https://twitter.com/wooyaygutenberg123454312' ); await expect( - currenEmbedBlock.getByRole( 'textbox', { name: 'Embed URL' } ), + currentEmbedBlock.getByRole( 'textbox', { name: 'Embed URL' } ), 'Valid provider; invalid content. Should render failed, edit state.' ).toHaveValue( 'https://twitter.com/wooyaygutenberg123454312' ); @@ -120,13 +120,13 @@ test.describe( 'Embedding content', () => { 'https://wordpress.org/gutenberg/handbook/' ); await expect( - currenEmbedBlock.getByRole( 'textbox', { name: 'Embed URL' } ), + currentEmbedBlock.getByRole( 'textbox', { name: 'Embed URL' } ), 'WordPress invalid content. Should render failed, edit state.' ).toHaveValue( 'https://wordpress.org/gutenberg/handbook' ); await embedUtils.insertEmbed( 'https://twitter.com/thatbunty' ); await expect( - currenEmbedBlock.getByRole( 'textbox', { name: 'Embed URL' } ), + currentEmbedBlock.getByRole( 'textbox', { name: 'Embed URL' } ), 'Provider whose oembed API has gone wrong. Should render failed, edit state.' ).toHaveValue( 'https://twitter.com/thatbunty' ); @@ -134,7 +134,7 @@ test.describe( 'Embedding content', () => { 'https://developer.wordpress.org/block-editor/reference-guides/block-api/block-attributes/' ); await expect( - currenEmbedBlock, + currentEmbedBlock, 'WordPress valid content. Should render valid figure element.' ).toHaveClass( /wp-block-embed/ ); @@ -142,13 +142,13 @@ test.describe( 'Embedding content', () => { 'https://www.youtube.com/watch?v=lXMskKTw3Bc' ); await expect( - currenEmbedBlock, + currentEmbedBlock, 'Video content. Should render valid figure element, and include the aspect ratio class.' ).toHaveClass( /wp-embed-aspect-16-9/ ); await embedUtils.insertEmbed( 'https://cloudup.com/cQFlxqtY4ob' ); await expect( - currenEmbedBlock.locator( 'iframe' ), + currentEmbedBlock.locator( 'iframe' ), 'Photo content. Should render valid iframe element.' ).toHaveAttribute( 'title', 'Embedded content from cloudup.com' ); } ); diff --git a/test/e2e/specs/editor/various/format-library/text-color.spec.js b/test/e2e/specs/editor/various/format-library/text-color.spec.js index aba77251e38336..3e3b2147e0a4f7 100644 --- a/test/e2e/specs/editor/various/format-library/text-color.spec.js +++ b/test/e2e/specs/editor/various/format-library/text-color.spec.js @@ -28,7 +28,7 @@ test.describe( 'Format Library - Text color', () => { // active. Previously we had a broken regular expression. const color = page .getByRole( 'listbox', { name: 'Custom color picker' } ) - .getByRole( 'option', { name: 'Color: Cyan bluish gray' } ); + .getByRole( 'option', { name: 'Cyan bluish gray' } ); await color.click(); await expect.poll( editor.getBlocks ).toMatchObject( [ diff --git a/test/e2e/specs/editor/various/keep-styles-on-block-transforms.spec.js b/test/e2e/specs/editor/various/keep-styles-on-block-transforms.spec.js index 32e3def9154ed1..074db6e1bac685 100644 --- a/test/e2e/specs/editor/various/keep-styles-on-block-transforms.spec.js +++ b/test/e2e/specs/editor/various/keep-styles-on-block-transforms.spec.js @@ -18,7 +18,7 @@ test.describe( 'Keep styles on block transforms', () => { .click(); await page.keyboard.type( '## Heading' ); await page.click( 'role=button[name="Text"i]' ); - await page.click( 'role=option[name="Color: Luminous vivid orange"i]' ); + await page.click( 'role=option[name="Luminous vivid orange"i]' ); await page.click( 'role=button[name="Heading"i]' ); await page.click( 'role=menuitem[name="Paragraph"i]' ); diff --git a/test/e2e/specs/editor/various/list-view.spec.js b/test/e2e/specs/editor/various/list-view.spec.js index 531faa8bea049d..17f82ce1b76e36 100644 --- a/test/e2e/specs/editor/various/list-view.spec.js +++ b/test/e2e/specs/editor/various/list-view.spec.js @@ -162,7 +162,10 @@ test.describe( 'List View', () => { // make the inner blocks appear. await editor.canvas .getByRole( 'document', { name: 'Block: Cover' } ) - .getByRole( 'option', { name: /Color: /i } ) + .getByRole( 'listbox', { + name: 'Custom color picker.', + } ) + .getByRole( 'option' ) .first() .click(); @@ -809,8 +812,8 @@ test.describe( 'List View', () => { // Delete remaining blocks. // Keyboard shortcut should also work. - await pageUtils.pressKeys( 'access+z' ); - await pageUtils.pressKeys( 'access+z' ); + await pageUtils.pressKeys( 'shift+Backspace' ); + await pageUtils.pressKeys( 'shift+Backspace' ); await expect .poll( listViewUtils.getBlocksWithA11yAttributes, @@ -842,7 +845,7 @@ test.describe( 'List View', () => { { name: 'core/heading', selected: false }, ] ); - await pageUtils.pressKeys( 'access+z' ); + await pageUtils.pressKeys( 'shift+Backspace' ); await expect .poll( listViewUtils.getBlocksWithA11yAttributes, @@ -865,7 +868,7 @@ test.describe( 'List View', () => { .getByRole( 'gridcell', { name: 'File' } ) .getByRole( 'link' ) .focus(); - for ( const keys of [ 'Delete', 'Backspace', 'access+z' ] ) { + for ( const keys of [ 'Delete', 'Backspace', 'shift+Backspace' ] ) { await pageUtils.pressKeys( keys ); await expect .poll( @@ -1133,7 +1136,7 @@ test.describe( 'List View', () => { optionsForFileMenu, 'Pressing Space should also open the menu dropdown' ).toBeVisible(); - await pageUtils.pressKeys( 'access+z' ); // Keyboard shortcut for Delete. + await pageUtils.pressKeys( 'shift+Backspace' ); // Keyboard shortcut for Delete. await expect .poll( listViewUtils.getBlocksWithA11yAttributes, @@ -1153,7 +1156,7 @@ test.describe( 'List View', () => { optionsForFileMenu.getByRole( 'menuitem', { name: 'Delete' } ), 'The delete menu item should be hidden for locked blocks' ).toBeHidden(); - await pageUtils.pressKeys( 'access+z' ); + await pageUtils.pressKeys( 'shift+Backspace' ); await expect .poll( listViewUtils.getBlocksWithA11yAttributes, diff --git a/test/e2e/specs/editor/various/nux.spec.js b/test/e2e/specs/editor/various/nux.spec.js index ff55dbfa54e478..424647f4072eaa 100644 --- a/test/e2e/specs/editor/various/nux.spec.js +++ b/test/e2e/specs/editor/various/nux.spec.js @@ -12,7 +12,7 @@ test.describe( 'New User Experience (NUX)', () => { await admin.createNewPost( { showWelcomeGuide: true } ); const welcomeGuide = page.getByRole( 'dialog', { - name: 'Welcome to the block editor', + name: 'Welcome to the editor', } ); const guideHeading = welcomeGuide.getByRole( 'heading', { level: 1 } ); const nextButton = welcomeGuide.getByRole( 'button', { name: 'Next' } ); @@ -20,36 +20,28 @@ test.describe( 'New User Experience (NUX)', () => { name: 'Previous', } ); - await expect( guideHeading ).toHaveText( - 'Welcome to the block editor' - ); + await expect( guideHeading ).toHaveText( 'Welcome to the editor' ); await nextButton.click(); - await expect( guideHeading ).toHaveText( 'Make each block your own' ); + await expect( guideHeading ).toHaveText( 'Customize each block' ); await prevButton.click(); // Guide should be on page 1 of 4 - await expect( guideHeading ).toHaveText( - 'Welcome to the block editor' - ); + await expect( guideHeading ).toHaveText( 'Welcome to the editor' ); // Press the button for Page 2. await welcomeGuide .getByRole( 'button', { name: 'Page 2 of 4' } ) .click(); - await expect( guideHeading ).toHaveText( 'Make each block your own' ); + await expect( guideHeading ).toHaveText( 'Customize each block' ); // Press the right arrow key for Page 3. await page.keyboard.press( 'ArrowRight' ); - await expect( guideHeading ).toHaveText( - 'Get to know the block library' - ); + await expect( guideHeading ).toHaveText( 'Explore all blocks' ); // Press the right arrow key for Page 4. await page.keyboard.press( 'ArrowRight' ); - await expect( guideHeading ).toHaveText( - 'Learn how to use the block editor' - ); + await expect( guideHeading ).toHaveText( 'Learn more' ); // Click on the *visible* 'Get started' button. await welcomeGuide @@ -77,7 +69,7 @@ test.describe( 'New User Experience (NUX)', () => { await admin.createNewPost( { showWelcomeGuide: true } ); const welcomeGuide = page.getByRole( 'dialog', { - name: 'Welcome to the block editor', + name: 'Welcome to the editor', } ); await expect( welcomeGuide ).toBeVisible(); @@ -100,7 +92,7 @@ test.describe( 'New User Experience (NUX)', () => { await admin.createNewPost( { showWelcomeGuide: true } ); const welcomeGuide = page.getByRole( 'dialog', { - name: 'Welcome to the block editor', + name: 'Welcome to the editor', } ); await expect( welcomeGuide ).toBeVisible(); @@ -117,7 +109,7 @@ test.describe( 'New User Experience (NUX)', () => { } ) => { await admin.createNewPost(); const welcomeGuide = page.getByRole( 'dialog', { - name: 'Welcome to the block editor', + name: 'Welcome to the editor', } ); await expect( welcomeGuide ).toBeHidden(); diff --git a/test/e2e/specs/editor/various/parsing-patterns.spec.js b/test/e2e/specs/editor/various/parsing-patterns.spec.js index d8edc544ffa03c..98261804acb586 100644 --- a/test/e2e/specs/editor/various/parsing-patterns.spec.js +++ b/test/e2e/specs/editor/various/parsing-patterns.spec.js @@ -37,9 +37,8 @@ test.describe( 'Parsing patterns', () => { } ); } ); - // Exit zoom out mode and select the inner buttons block to ensure + // Select the inner buttons block to ensure // the correct insertion point is selected. - await page.getByRole( 'button', { name: 'Zoom Out' } ).click(); await editor.selectBlocks( editor.canvas.locator( 'role=document[name="Block: Button"i]' ) ); diff --git a/test/e2e/specs/editor/various/pattern-overrides.spec.js b/test/e2e/specs/editor/various/pattern-overrides.spec.js index 6f4a5929300520..145fa9a93bab13 100644 --- a/test/e2e/specs/editor/various/pattern-overrides.spec.js +++ b/test/e2e/specs/editor/various/pattern-overrides.spec.js @@ -128,7 +128,11 @@ test.describe( 'Pattern Overrides', () => { page.getByRole( 'button', { name: 'Dismiss this notice' } ) ).toBeVisible(); - patternId = new URL( page.url() ).searchParams.get( 'postId' ); + patternId = await page.evaluate( () => { + return window.wp.data + .select( 'core/editor' ) + .getCurrentPostId(); + } ); } ); await test.step( 'Create a post and insert the pattern with overrides', async () => { @@ -226,6 +230,373 @@ test.describe( 'Pattern Overrides', () => { } ); } ); + test.describe( 'block editing modes', () => { + test.beforeEach( async ( { page } ) => { + await page.addInitScript( () => { + window.__experimentalEditorWriteMode = true; + } ); + } ); + + test( 'blocks with bindings in a synced pattern are editable, and all other blocks are disabled', async ( { + admin, + editor, + page, + requestUtils, + } ) => { + const content = ` + <!-- wp:paragraph {"metadata":{"name":"Pattern Overrides","bindings":{"__default":{"source":"core/pattern-overrides"}}}} --> + <p>Pattern Overrides</p> + <!-- /wp:paragraph --> + <!-- wp:paragraph {"metadata":{"name":"Post Meta Binding","bindings":{"content":{"source":"core/post-meta","args":{"key":"Post Meta Binding"}}}}} --> + <p>Post Meta Binding</p> + <!-- /wp:paragraph --> + <!-- wp:paragraph {"metadata":{"name":"No Overrides or Binding"}} --> + <p>No Overrides or Binding</p> + <!-- /wp:paragraph --> + `; + + const { id } = await requestUtils.createBlock( { + title: 'Pattern', + content, + status: 'publish', + } ); + + await admin.visitSiteEditor( { + postId: 'emptytheme//index', + postType: 'wp_template', + canvas: 'edit', + } ); + + await editor.setContent( '' ); + await editor.switchEditorTool( 'Design' ); + + // Insert a `<main>` group block. + // In zoomed out and write mode it acts as the section root. + // Inside is a pattern that acts as a section. + await editor.insertBlock( { + name: 'core/group', + attributes: { tagName: 'main' }, + innerBlocks: [ + { + name: 'core/block', + attributes: { ref: id }, + }, + ], + } ); + + const groupBlock = editor.canvas.getByRole( 'document', { + name: 'Block: Group', + } ); + const patternBlock = editor.canvas.getByRole( 'document', { + name: 'Block: Pattern', + } ); + const paragraphs = editor.canvas.getByRole( 'document', { + name: 'Block: Paragraph', + } ); + const blockWithOverrides = paragraphs.filter( { + hasText: 'Pattern Overrides', + } ); + const blockWithBindings = paragraphs.filter( { + hasText: 'Post Meta Binding', + } ); + const blockWithoutOverridesOrBindings = paragraphs.filter( { + hasText: 'No Overrides or Binding', + } ); + + await test.step( 'Click-through behavior', async () => { + // With the group block selected, all the inner blocks of the pattern + // are inert due to the 'click-through' behavior, that requires the + // pattern block be selected first before its inner blocks are selectable. + await editor.selectBlocks( groupBlock ); + await expect( patternBlock ).not.toHaveAttribute( + 'inert', + 'true' + ); + await expect( blockWithOverrides ).toHaveAttribute( + 'inert', + 'true' + ); + await expect( blockWithBindings ).toHaveAttribute( + 'inert', + 'true' + ); + await expect( blockWithoutOverridesOrBindings ).toHaveAttribute( + 'inert', + 'true' + ); + } ); + + await test.step( 'Zoomed in / Design mode', async () => { + await editor.selectBlocks( patternBlock ); + + // Once selected and in zoomed in/design mode the child blocks + // of the pattern with bindings are editable, but unbound + // blocks are inert. + await expect( blockWithOverrides ).not.toHaveAttribute( + 'inert', + 'true' + ); + await expect( blockWithBindings ).not.toHaveAttribute( + 'inert', + 'true' + ); + await expect( blockWithoutOverridesOrBindings ).toHaveAttribute( + 'inert', + 'true' + ); + } ); + + await test.step( 'Zoomed in / Write mode - pattern as a section', async () => { + await editor.switchEditorTool( 'Write' ); + + // The pattern block is still editable as a section. + await expect( patternBlock ).not.toHaveAttribute( + 'inert', + 'true' + ); + + // Ensure the pattern block is selected. + await editor.selectBlocks( patternBlock ); + + // Child blocks of the pattern with bindings are editable. + await expect( blockWithOverrides ).not.toHaveAttribute( + 'inert', + 'true' + ); + await expect( blockWithBindings ).not.toHaveAttribute( + 'inert', + 'true' + ); + await expect( blockWithoutOverridesOrBindings ).toHaveAttribute( + 'inert', + 'true' + ); + } ); + + await test.step( 'Zoomed out / Write mode - pattern as a section', async () => { + await page.getByLabel( 'Zoom Out' ).click(); + // In zoomed out only the pattern block is editable, + // as in this scenario it's a section. + await expect( patternBlock ).not.toHaveAttribute( + 'inert', + 'true' + ); + + // Ensure the pattern block is selected before checking the child blocks + // to ensure the click-through behavior isn't interfering. + await editor.selectBlocks( patternBlock ); + + // None of the child blocks are editable in zoomed out mode. + await expect( blockWithOverrides ).toHaveAttribute( + 'inert', + 'true' + ); + await expect( blockWithBindings ).toHaveAttribute( + 'inert', + 'true' + ); + await expect( blockWithoutOverridesOrBindings ).toHaveAttribute( + 'inert', + 'true' + ); + } ); + + await test.step( 'Zoomed out / Design mode - pattern as a section', async () => { + await editor.switchEditorTool( 'Design' ); + // In zoomed out only the pattern block is editable, + // as in this scenario it's a section. + await expect( patternBlock ).not.toHaveAttribute( + 'inert', + 'true' + ); + + // Ensure the pattern block is selected before checking the child blocks + // to ensure the click-through behavior isn't interfering. + await editor.selectBlocks( patternBlock ); + + await expect( blockWithOverrides ).toHaveAttribute( + 'inert', + 'true' + ); + await expect( blockWithBindings ).toHaveAttribute( + 'inert', + 'true' + ); + await expect( blockWithoutOverridesOrBindings ).toHaveAttribute( + 'inert', + 'true' + ); + } ); + + // Zoom out and group the pattern so that it's no longer a section. + await page.getByLabel( 'Zoom Out' ).click(); + await editor.selectBlocks( patternBlock ); + await editor.clickBlockOptionsMenuItem( 'Group' ); + + await test.step( 'Zoomed in / Write mode - pattern nested in a section', async () => { + await editor.switchEditorTool( 'Write' ); + // The pattern block is not inert as it has editable content, but it shouldn't be selectable. + // TODO: find a way to test that the block is not selectable. + await expect( patternBlock ).not.toHaveAttribute( + 'inert', + 'true' + ); + // Child blocks of the pattern are editable as normal. + await expect( blockWithOverrides ).not.toHaveAttribute( + 'inert', + 'true' + ); + await expect( blockWithBindings ).not.toHaveAttribute( + 'inert', + 'true' + ); + await expect( blockWithoutOverridesOrBindings ).toHaveAttribute( + 'inert', + 'true' + ); + } ); + + await test.step( 'Zoomed out / Write mode - pattern nested in a section', async () => { + await page.getByLabel( 'Zoom Out' ).click(); + // None of the pattern is editable in zoomed out when nested in a section. + await expect( patternBlock ).toHaveAttribute( 'inert', 'true' ); + await expect( blockWithOverrides ).toHaveAttribute( + 'inert', + 'true' + ); + await expect( blockWithBindings ).toHaveAttribute( + 'inert', + 'true' + ); + await expect( blockWithoutOverridesOrBindings ).toHaveAttribute( + 'inert', + 'true' + ); + } ); + + await test.step( 'Zoomed out / Design mode - pattern nested in a section', async () => { + await editor.switchEditorTool( 'Design' ); + // None of the pattern is editable in zoomed out when nested in a section. + await expect( patternBlock ).toHaveAttribute( 'inert', 'true' ); + await expect( blockWithOverrides ).toHaveAttribute( + 'inert', + 'true' + ); + await expect( blockWithBindings ).toHaveAttribute( + 'inert', + 'true' + ); + await expect( blockWithoutOverridesOrBindings ).toHaveAttribute( + 'inert', + 'true' + ); + } ); + } ); + + test( 'disables editing of nested patterns', async ( { + page, + admin, + requestUtils, + editor, + } ) => { + const paragraphName = 'Editable paragraph'; + const headingName = 'Editable heading'; + const innerPattern = await requestUtils.createBlock( { + title: 'Inner Pattern', + content: `<!-- wp:paragraph {"metadata":{"name":"${ paragraphName }","bindings":{"__default":{"source":"core/pattern-overrides"}}}} --> + <p>Inner paragraph</p> + <!-- /wp:paragraph -->`, + status: 'publish', + } ); + const outerPattern = await requestUtils.createBlock( { + title: 'Outer Pattern', + content: `<!-- wp:heading {"metadata":{"name":"${ headingName }","bindings":{"__default":{"source":"core/pattern-overrides"}}}} --> + <h2 class="wp-block-heading">Outer heading</h2> + <!-- /wp:heading --> + <!-- wp:block {"ref":${ innerPattern.id },"content":{"${ paragraphName }":{"content":"Inner paragraph (edited)"}}} /-->`, + status: 'publish', + } ); + + await admin.createNewPost(); + + await editor.insertBlock( { + name: 'core/block', + attributes: { ref: outerPattern.id }, + } ); + + // Make an edit to the outer pattern heading. + await editor.canvas + .getByRole( 'document', { name: 'Block: Heading' } ) + .fill( 'Outer heading (edited)' ); + + const postId = await editor.publishPost(); + + // Check the pattern has the correct attributes. + await expect.poll( editor.getBlocks ).toMatchObject( [ + { + name: 'core/block', + attributes: { + ref: outerPattern.id, + content: { + [ headingName ]: { + content: 'Outer heading (edited)', + }, + }, + }, + innerBlocks: [], + }, + ] ); + // Check it renders correctly. + const headingBlock = editor.canvas.getByRole( 'document', { + name: 'Block: Heading', + } ); + const paragraphBlock = editor.canvas.getByRole( 'document', { + name: 'Block: Paragraph', + } ); + await expect( headingBlock ).toHaveText( 'Outer heading (edited)' ); + await expect( headingBlock ).not.toHaveAttribute( 'inert', 'true' ); + await expect( paragraphBlock ).toHaveText( + 'Inner paragraph (edited)' + ); + await expect( paragraphBlock ).toHaveAttribute( 'inert', 'true' ); + + // Edit the outer pattern. + await editor.selectBlocks( + editor.canvas + .getByRole( 'document', { name: 'Block: Pattern' } ) + .first() + ); + await editor.showBlockToolbar(); + await page + .getByRole( 'toolbar', { name: 'Block tools' } ) + .getByRole( 'button', { name: 'Edit original' } ) + .click(); + + // The inner paragraph should be editable in the pattern focus mode. + await editor.selectBlocks( + editor.canvas + .getByRole( 'document', { name: 'Block: Pattern' } ) + .first() + ); + await expect( + editor.canvas.getByRole( 'document', { + name: 'Block: Paragraph', + } ), + 'The inner paragraph should be editable' + ).not.toHaveAttribute( 'inert', 'true' ); + + // Visit the post on the frontend. + await page.goto( `/?p=${ postId }` ); + + await expect( + page.getByRole( 'heading', { level: 2 } ) + ).toHaveText( 'Outer heading (edited)' ); + await expect( + page.getByText( 'Inner paragraph (edited)' ) + ).toBeVisible(); + } ); + } ); + test( 'retains override values when converting a pattern block to regular blocks', async ( { page, admin, @@ -425,107 +796,6 @@ test.describe( 'Pattern Overrides', () => { await expect( buttonLink ).toHaveAttribute( 'rel', /^\s*nofollow\s*$/ ); } ); - test( 'disables editing of nested patterns', async ( { - page, - admin, - requestUtils, - editor, - } ) => { - const paragraphName = 'Editable paragraph'; - const headingName = 'Editable heading'; - const innerPattern = await requestUtils.createBlock( { - title: 'Inner Pattern', - content: `<!-- wp:paragraph {"metadata":{"name":"${ paragraphName }","bindings":{"__default":{"source":"core/pattern-overrides"}}}} --> -<p>Inner paragraph</p> -<!-- /wp:paragraph -->`, - status: 'publish', - } ); - const outerPattern = await requestUtils.createBlock( { - title: 'Outer Pattern', - content: `<!-- wp:heading {"metadata":{"name":"${ headingName }","bindings":{"__default":{"source":"core/pattern-overrides"}}}} --> -<h2 class="wp-block-heading">Outer heading</h2> -<!-- /wp:heading --> -<!-- wp:block {"ref":${ innerPattern.id },"content":{"${ paragraphName }":{"content":"Inner paragraph (edited)"}}} /-->`, - status: 'publish', - } ); - - await admin.createNewPost(); - - await editor.insertBlock( { - name: 'core/block', - attributes: { ref: outerPattern.id }, - } ); - - // Make an edit to the outer pattern heading. - await editor.canvas - .getByRole( 'document', { name: 'Block: Heading' } ) - .fill( 'Outer heading (edited)' ); - - const postId = await editor.publishPost(); - - // Check the pattern has the correct attributes. - await expect.poll( editor.getBlocks ).toMatchObject( [ - { - name: 'core/block', - attributes: { - ref: outerPattern.id, - content: { - [ headingName ]: { - content: 'Outer heading (edited)', - }, - }, - }, - innerBlocks: [], - }, - ] ); - // Check it renders correctly. - const headingBlock = editor.canvas.getByRole( 'document', { - name: 'Block: Heading', - } ); - const paragraphBlock = editor.canvas.getByRole( 'document', { - name: 'Block: Paragraph', - } ); - await expect( headingBlock ).toHaveText( 'Outer heading (edited)' ); - await expect( headingBlock ).not.toHaveAttribute( 'inert', 'true' ); - await expect( paragraphBlock ).toHaveText( 'Inner paragraph (edited)' ); - await expect( paragraphBlock ).toHaveAttribute( 'inert', 'true' ); - - // Edit the outer pattern. - await editor.selectBlocks( - editor.canvas - .getByRole( 'document', { name: 'Block: Pattern' } ) - .first() - ); - await editor.showBlockToolbar(); - await page - .getByRole( 'toolbar', { name: 'Block tools' } ) - .getByRole( 'button', { name: 'Edit original' } ) - .click(); - - // The inner paragraph should be editable in the pattern focus mode. - await editor.selectBlocks( - editor.canvas - .getByRole( 'document', { name: 'Block: Pattern' } ) - .first() - ); - await expect( - editor.canvas.getByRole( 'document', { - name: 'Block: Paragraph', - } ), - 'The inner paragraph should be editable' - ).not.toHaveAttribute( 'inert', 'true' ); - - // Visit the post on the frontend. - await page.goto( `/?p=${ postId }` ); - - await expect( page.getByRole( 'heading', { level: 2 } ) ).toHaveText( - 'Outer heading (edited)' - ); - await expect( - page.getByText( 'Inner paragraph (edited)' ) - ).toBeVisible(); - } ); - test( 'resets overrides after clicking the reset button', async ( { page, admin, @@ -993,7 +1263,11 @@ test.describe( 'Pattern Overrides', () => { page.getByRole( 'button', { name: 'Dismiss this notice' } ) ).toBeVisible(); - patternId = new URL( page.url() ).searchParams.get( 'postId' ); + patternId = await page.evaluate( () => { + return window.wp.data + .select( 'core/editor' ) + .getCurrentPostId(); + } ); } ); await test.step( 'create a post and insert the pattern with synced values', async () => { @@ -1018,10 +1292,10 @@ test.describe( 'Pattern Overrides', () => { } ) .last(); - await firstParagraph.fill( 'overriden content' ); - await expect( headingBlock ).toHaveText( 'overriden content' ); - await expect( firstParagraph ).toHaveText( 'overriden content' ); - await expect( secondParagraph ).toHaveText( 'overriden content' ); + await firstParagraph.fill( 'overridden content' ); + await expect( headingBlock ).toHaveText( 'overridden content' ); + await expect( firstParagraph ).toHaveText( 'overridden content' ); + await expect( secondParagraph ).toHaveText( 'overridden content' ); } ); } ); diff --git a/test/e2e/specs/editor/various/patterns.spec.js b/test/e2e/specs/editor/various/patterns.spec.js index 00a68a9f08ea55..a3af79289f2701 100644 --- a/test/e2e/specs/editor/various/patterns.spec.js +++ b/test/e2e/specs/editor/various/patterns.spec.js @@ -517,7 +517,7 @@ test.describe( 'Synced pattern', () => { test( 'should show a proper message when the reusable block is missing', async ( { editor, } ) => { - // Insert a non-existant reusable block. + // Insert a non-existent reusable block. await editor.insertBlock( { name: 'core/block', attributes: { ref: 123456 }, diff --git a/test/e2e/specs/editor/various/post-editor-template-mode.spec.js b/test/e2e/specs/editor/various/post-editor-template-mode.spec.js index 981f8ccbbd5d38..b78a059a6be2c8 100644 --- a/test/e2e/specs/editor/various/post-editor-template-mode.spec.js +++ b/test/e2e/specs/editor/various/post-editor-template-mode.spec.js @@ -77,7 +77,7 @@ test.describe( 'Post Editor Template mode', () => { ).toBeVisible(); } ); - test( 'Swap templates and proper template resolution when switching to default template', async ( { + test( 'Change templates and proper template resolution when switching to default template', async ( { editor, page, requestUtils, @@ -88,10 +88,10 @@ test.describe( 'Post Editor Template mode', () => { await page.reload(); await postEditorTemplateMode.disableTemplateWelcomeGuide(); await postEditorTemplateMode.openTemplatePopover(); - // Swap to a custom template, save and reload. + // Change to a custom template, save and reload. await page .getByRole( 'menuitem', { - name: 'Swap template', + name: 'Change template', } ) .click(); await page @@ -101,7 +101,7 @@ test.describe( 'Post Editor Template mode', () => { .click(); await editor.saveDraft(); await page.reload(); - // Swap to the default template. + // Change to the default template. await postEditorTemplateMode.openTemplatePopover(); await page .getByRole( 'menuitem', { diff --git a/test/e2e/specs/editor/various/post-title.spec.js b/test/e2e/specs/editor/various/post-title.spec.js index 1abf94f821574b..5181efae598aa2 100644 --- a/test/e2e/specs/editor/various/post-title.spec.js +++ b/test/e2e/specs/editor/various/post-title.spec.js @@ -22,7 +22,7 @@ test.describe( 'Post title', () => { editor.canvas.getByRole( 'document', { name: 'Empty block', } ), - 'sould move focus to an empty paragraph block when the Enter key is pressed' + 'should move focus to an empty paragraph block when the Enter key is pressed' ).toBeFocused(); } ); diff --git a/test/e2e/specs/editor/various/rich-text.spec.js b/test/e2e/specs/editor/various/rich-text.spec.js index 29b4fb3d589018..b38fae9dafaf78 100644 --- a/test/e2e/specs/editor/various/rich-text.spec.js +++ b/test/e2e/specs/editor/various/rich-text.spec.js @@ -758,7 +758,7 @@ test.describe( 'RichText (@firefox, @webkit)', () => { ); } ); - test( 'should navigate arround emoji', async ( { page, editor } ) => { + test( 'should navigate around emoji', async ( { page, editor } ) => { await editor.canvas .locator( 'role=button[name="Add default block"i]' ) .click(); diff --git a/test/e2e/specs/editor/various/scheduling.spec.js b/test/e2e/specs/editor/various/scheduling.spec.js index 2ea05589d02744..8d46f0d64ab55b 100644 --- a/test/e2e/specs/editor/various/scheduling.spec.js +++ b/test/e2e/specs/editor/various/scheduling.spec.js @@ -10,9 +10,9 @@ const TIMEZONES = [ 'Pacific/Honolulu', 'UTC', 'Australia/Sydney' ]; test.describe( 'Scheduling', () => { TIMEZONES.forEach( ( timezone ) => { test.describe( `Timezone ${ timezone }`, () => { - let orignalTimezone; + let originalTimezone; test.beforeAll( async ( { requestUtils } ) => { - orignalTimezone = ( await requestUtils.getSiteSettings() ) + originalTimezone = ( await requestUtils.getSiteSettings() ) .timezone; await requestUtils.updateSiteSettings( { timezone } ); @@ -20,7 +20,7 @@ test.describe( 'Scheduling', () => { test.afterAll( async ( { requestUtils } ) => { await requestUtils.updateSiteSettings( { - timezone: orignalTimezone, + timezone: originalTimezone, } ); } ); diff --git a/test/e2e/specs/editor/various/splitting-merging.spec.js b/test/e2e/specs/editor/various/splitting-merging.spec.js index 146039a7c7d1bf..e094b5d60468c4 100644 --- a/test/e2e/specs/editor/various/splitting-merging.spec.js +++ b/test/e2e/specs/editor/various/splitting-merging.spec.js @@ -539,7 +539,7 @@ test.describe( 'splitting and merging blocks (@firefox, @webkit)', () => { expect( await editor.getBlocks() ).toMatchObject( snap1 ); await page.keyboard.press( 'Delete' ); - // Carret should be in the first block and at the proper position. + // Caret should be in the first block and at the proper position. await page.keyboard.type( '-' ); // Check the content. @@ -560,7 +560,7 @@ test.describe( 'splitting and merging blocks (@firefox, @webkit)', () => { expect( await editor.getBlocks() ).toMatchObject( snap1 ); await page.keyboard.press( 'Backspace' ); - // Carret should be in the first block and at the proper position. + // Caret should be in the first block and at the proper position. await page.keyboard.type( '-' ); // Check the content. diff --git a/test/e2e/specs/editor/various/template-resolution.spec.js b/test/e2e/specs/editor/various/template-resolution.spec.js index 13503ddaf23d5b..82e336feff7334 100644 --- a/test/e2e/specs/editor/various/template-resolution.spec.js +++ b/test/e2e/specs/editor/various/template-resolution.spec.js @@ -55,12 +55,15 @@ test.describe( 'Template resolution', () => { status: 'publish', } ); await admin.editPost( newPage.id ); + await page.locator( 'role=button[name="Block Inserter"i]' ).click(); await editor.openDocumentSettingsSidebar(); await expect( page.getByRole( 'button', { name: 'Template options' } ) ).toHaveText( 'Single Entries' ); await updateSiteSettings( { requestUtils, pageId: newPage.id } ); await page.reload(); + await page.locator( 'role=button[name="Block Inserter"i]' ).click(); + await editor.openDocumentSettingsSidebar(); await expect( page.getByRole( 'button', { name: 'Template options' } ) ).toHaveText( 'Index' ); @@ -81,6 +84,7 @@ test.describe( 'Template resolution', () => { postType: 'page', canvas: 'edit', } ); + await page.locator( 'role=button[name="Block Inserter"i]' ).click(); await editor.openDocumentSettingsSidebar(); await expect( page.getByRole( 'button', { name: 'Template options' } ) diff --git a/test/e2e/specs/editor/various/typewriter.spec.js b/test/e2e/specs/editor/various/typewriter.spec.js index abf24cbfc298ec..7597b8ea71e13b 100644 --- a/test/e2e/specs/editor/various/typewriter.spec.js +++ b/test/e2e/specs/editor/various/typewriter.spec.js @@ -231,14 +231,14 @@ test.describe( 'Typewriter', () => { activeElement.offsetHeight + 10; } ); - const bottomPostition = await typewriterUtils.getCaretPosition(); + const bottomPosition = await typewriterUtils.getCaretPosition(); // Should scroll the caret back into view (preserve browser behaviour). await page.keyboard.type( 'a' ); const newBottomPosition = await typewriterUtils.getCaretPosition(); - expect( newBottomPosition ).toBeLessThanOrEqual( bottomPostition ); + expect( newBottomPosition ).toBeLessThanOrEqual( bottomPosition ); // Should maintain new caret position. await page.keyboard.press( 'Enter' ); @@ -263,14 +263,14 @@ test.describe( 'Typewriter', () => { activeElement.offsetHeight + 10; } ); - const topPostition = await typewriterUtils.getCaretPosition(); + const topPosition = await typewriterUtils.getCaretPosition(); // Should scroll the caret back into view (preserve browser behaviour). await page.keyboard.type( 'a' ); const newTopPosition = await typewriterUtils.getCaretPosition(); - expect( newTopPosition ).toBeGreaterThan( topPostition ); + expect( newTopPosition ).toBeGreaterThan( topPosition ); // Should maintain new caret position. await page.keyboard.press( 'Enter' ); diff --git a/test/e2e/specs/editor/various/write-design-mode.spec.js b/test/e2e/specs/editor/various/write-design-mode.spec.js index 2116f9042685af..fb3e231e6fff60 100644 --- a/test/e2e/specs/editor/various/write-design-mode.spec.js +++ b/test/e2e/specs/editor/various/write-design-mode.spec.js @@ -7,19 +7,19 @@ test.describe( 'Write/Design mode', () => { test.beforeAll( async ( { requestUtils } ) => { await requestUtils.activateTheme( 'emptytheme' ); } ); - - test.beforeEach( async ( { admin } ) => { + test.beforeEach( async ( { admin, page } ) => { + await page.addInitScript( () => { + window.__experimentalEditorWriteMode = true; + } ); await admin.visitSiteEditor( { postId: 'emptytheme//index', postType: 'wp_template', canvas: 'edit', } ); } ); - test.afterAll( async ( { requestUtils } ) => { await requestUtils.activateTheme( 'twentytwentyone' ); } ); - test( 'Should prevent selecting intermediary blocks', async ( { editor, page, @@ -100,6 +100,17 @@ test.describe( 'Write/Design mode', () => { expect( await getSelectedBlock() ).toEqual( sectionClientId ); + // open the block toolbar more settings menu + await page.getByLabel( 'Block tools' ).getByLabel( 'Options' ).click(); + + // get the length of the options menu + const optionsMenu = page + .getByRole( 'menu', { name: 'Options' } ) + .getByRole( 'menuitem' ); + + // we expect 3 items in the options menu + await expect( optionsMenu ).toHaveCount( 3 ); + // We should be able to select the paragraph block and write in it. await paragraph.click(); await page.keyboard.type( ' something' ); @@ -121,4 +132,59 @@ test.describe( 'Write/Design mode', () => { editorSettings.getByRole( 'button', { name: 'Content' } ) ).toBeVisible(); } ); + + test( 'hides the blocks that cannot be interacted with in List View', async ( { + editor, + page, + pageUtils, + } ) => { + await editor.setContent( '' ); + + // Insert a section with a nested block and an editable block. + await editor.insertBlock( { + name: 'core/group', + attributes: {}, + innerBlocks: [ + { + name: 'core/group', + attributes: { + metadata: { + name: 'Non-content block', + }, + }, + innerBlocks: [ + { + name: 'core/paragraph', + attributes: { + content: 'Something', + }, + }, + ], + }, + ], + } ); + + // Select the inner paragraph block so that List View is expanded. + await editor.canvas + .getByRole( 'document', { + name: 'Block: Paragraph', + } ) + .click(); + + // Open List View. + await pageUtils.pressKeys( 'access+o' ); + const listView = page.getByRole( 'treegrid', { + name: 'Block navigation structure', + } ); + const nonContentBlock = listView.getByRole( 'link', { + name: 'Non-content block', + } ); + + await expect( nonContentBlock ).toBeVisible(); + + // Switch to write mode. + await editor.switchEditorTool( 'Write' ); + + await expect( nonContentBlock ).toBeHidden(); + } ); } ); diff --git a/test/e2e/specs/interactivity/__snapshots__/Router-styles-should-support-relative-URLs-in-referenced-style-sheets-1-chromium.png b/test/e2e/specs/interactivity/__snapshots__/Router-styles-should-support-relative-URLs-in-referenced-style-sheets-1-chromium.png new file mode 100644 index 00000000000000..4bc0f7a6b1dd70 Binary files /dev/null and b/test/e2e/specs/interactivity/__snapshots__/Router-styles-should-support-relative-URLs-in-referenced-style-sheets-1-chromium.png differ diff --git a/test/e2e/specs/interactivity/__snapshots__/Router-styles-should-support-relative-URLs-in-referenced-style-sheets-2-chromium.png b/test/e2e/specs/interactivity/__snapshots__/Router-styles-should-support-relative-URLs-in-referenced-style-sheets-2-chromium.png new file mode 100644 index 00000000000000..7339cccdb78f28 Binary files /dev/null and b/test/e2e/specs/interactivity/__snapshots__/Router-styles-should-support-relative-URLs-in-referenced-style-sheets-2-chromium.png differ diff --git a/test/e2e/specs/interactivity/__snapshots__/Router-styles-should-support-relative-URLs-in-referenced-style-sheets-3-chromium.png b/test/e2e/specs/interactivity/__snapshots__/Router-styles-should-support-relative-URLs-in-referenced-style-sheets-3-chromium.png new file mode 100644 index 00000000000000..97943030eb1e88 Binary files /dev/null and b/test/e2e/specs/interactivity/__snapshots__/Router-styles-should-support-relative-URLs-in-referenced-style-sheets-3-chromium.png differ diff --git a/test/e2e/specs/interactivity/__snapshots__/Router-styles-should-support-relative-URLs-in-referenced-style-sheets-4-chromium.png b/test/e2e/specs/interactivity/__snapshots__/Router-styles-should-support-relative-URLs-in-referenced-style-sheets-4-chromium.png new file mode 100644 index 00000000000000..b7c455784e8a42 Binary files /dev/null and b/test/e2e/specs/interactivity/__snapshots__/Router-styles-should-support-relative-URLs-in-referenced-style-sheets-4-chromium.png differ diff --git a/test/e2e/specs/interactivity/__snapshots__/Router-styles-should-support-relative-URLs-in-referenced-style-sheets-5-chromium.png b/test/e2e/specs/interactivity/__snapshots__/Router-styles-should-support-relative-URLs-in-referenced-style-sheets-5-chromium.png new file mode 100644 index 00000000000000..b7c455784e8a42 Binary files /dev/null and b/test/e2e/specs/interactivity/__snapshots__/Router-styles-should-support-relative-URLs-in-referenced-style-sheets-5-chromium.png differ diff --git a/test/e2e/specs/interactivity/directive-bind.spec.ts b/test/e2e/specs/interactivity/directive-bind.spec.ts index 11902018e0753a..8c637875b16343 100644 --- a/test/e2e/specs/interactivity/directive-bind.spec.ts +++ b/test/e2e/specs/interactivity/directive-bind.spec.ts @@ -231,7 +231,7 @@ test.describe( 'data-wp-bind', () => { ] ); // Only check the rendered value if the new value is not - // `undefined` and the attibute is neither `value` nor + // `undefined` and the attribute is neither `value` nor // `disabled` because Preact doesn't update the attribute // for those cases. // See https://github.com/preactjs/preact/blob/099c38c6ef92055428afbc116d18a6b9e0c2ea2c/src/diff/index.js#L471-L494 diff --git a/test/e2e/specs/interactivity/directive-each.spec.ts b/test/e2e/specs/interactivity/directive-each.spec.ts index 511b38e7ddbb8b..3c015e63fe4bc1 100644 --- a/test/e2e/specs/interactivity/directive-each.spec.ts +++ b/test/e2e/specs/interactivity/directive-each.spec.ts @@ -18,7 +18,7 @@ test.describe( 'data-wp-each', () => { await utils.deleteAllPosts(); } ); - test( 'should use `item` as the defaul item name in the context', async ( { + test( 'should use `item` as the default item name in the context', async ( { page, } ) => { const elements = page.getByTestId( 'letters' ).getByTestId( 'item' ); @@ -500,4 +500,37 @@ test.describe( 'data-wp-each', () => { await expect( element ).toHaveText( 'beta' ); await expect( callbackRunCount ).toHaveText( '1' ); } ); + + for ( const testId of [ + 'each-with-unset', + 'each-with-null', + 'each-with-undefined', + ] ) { + test( `does not error with non-iterable values: ${ testId }`, async ( { + page, + } ) => { + await expect( page.getByTestId( testId ) ).toBeEmpty(); + } ); + } + + for ( const [ testId, values ] of [ + [ 'each-with-array', [ 'an', 'array' ] ], + [ 'each-with-set', [ 'a', 'set' ] ], + [ 'each-with-string', [ 's', 't', 'r' ] ], + [ 'each-with-generator', [ 'a', 'generator' ] ], + + // TODO: Is there a problem with proxies here? + // [ 'each-with-iterator', [ 'implements', 'iterator' ] ], + ] as const ) { + test( `support different each iterable values: ${ testId }`, async ( { + page, + } ) => { + const element = page.getByTestId( testId ); + for ( const value of values ) { + await expect( + element.getByText( value, { exact: true } ) + ).toBeVisible(); + } + } ); + } } ); diff --git a/test/e2e/specs/interactivity/fixtures/index.ts b/test/e2e/specs/interactivity/fixtures/index.ts index 607221ffb1ec43..08a72d20ef5ff7 100644 --- a/test/e2e/specs/interactivity/fixtures/index.ts +++ b/test/e2e/specs/interactivity/fixtures/index.ts @@ -18,8 +18,8 @@ export const test = base.extend< Fixtures >( { async ( { requestUtils }, use ) => { await use( new InteractivityUtils( { requestUtils } ) ); }, - // @ts-ignore: The required type is 'test', but can be 'worker' too. See + // This is a hack, 'worker' is a valid value but the type is wrong. // https://playwright.dev/docs/test-fixtures#worker-scoped-fixtures - { scope: 'worker' }, + { scope: 'worker' as 'test' }, ], } ); diff --git a/test/e2e/specs/interactivity/fixtures/interactivity-utils.ts b/test/e2e/specs/interactivity/fixtures/interactivity-utils.ts index fd850a6e39fae2..74436673f10b79 100644 --- a/test/e2e/specs/interactivity/fixtures/interactivity-utils.ts +++ b/test/e2e/specs/interactivity/fixtures/interactivity-utils.ts @@ -6,6 +6,30 @@ import type { RequestUtils } from '@wordpress/e2e-test-utils-playwright'; type AddPostWithBlockOptions = { alias?: string; attributes?: Record< string, any >; + innerBlocks?: Block[]; +}; + +type Block = [ + type: string, + attributes?: Record< string, any >, + innerBlocks?: Block[], +]; + +const generateBlockMarkup = ( [ + type, + attributes, + innerBlocks, +]: Block ): string => { + const typeAndAttributes = attributes + ? `${ type } ${ JSON.stringify( attributes ) }` + : type; + + if ( ! innerBlocks ) { + return `<!-- wp:${ typeAndAttributes } /-->`; + } + return `<!-- wp:${ typeAndAttributes } -->${ innerBlocks + .map( generateBlockMarkup ) + .join( '' ) }<!--/ wp:${ type } -->`; }; export default class InteractivityUtils { @@ -40,7 +64,7 @@ export default class InteractivityUtils { async addPostWithBlock( name: string, - { attributes, alias }: AddPostWithBlockOptions = {} + { attributes, alias, innerBlocks }: AddPostWithBlockOptions = {} ) { const block = attributes ? `${ name } ${ JSON.stringify( attributes ) }` @@ -50,8 +74,14 @@ export default class InteractivityUtils { alias = block; } + const content = generateBlockMarkup( [ + name, + attributes, + innerBlocks, + ] ); + const payload = { - content: `<!-- wp:${ block } /-->`, + content, status: 'publish' as 'publish', date_gmt: '2023-01-01T00:00:00', title: alias, diff --git a/test/e2e/specs/interactivity/router-navigate.spec.ts b/test/e2e/specs/interactivity/router-navigate.spec.ts index d1ac30783ee2b7..324800aed60473 100644 --- a/test/e2e/specs/interactivity/router-navigate.spec.ts +++ b/test/e2e/specs/interactivity/router-navigate.spec.ts @@ -264,7 +264,7 @@ test.describe( 'Router navigate', () => { const count = page.getByTestId( 'router navigations count' ); const title = page.getByTestId( 'title' ); - // Check the cound to ensure the page has hydrated. + // Check the count to ensure the page has hydrated. await expect( count ).toHaveText( '0' ); // Navigate to a page without clientNavigationDisabled. diff --git a/test/e2e/specs/interactivity/router-styles.spec.ts b/test/e2e/specs/interactivity/router-styles.spec.ts new file mode 100644 index 00000000000000..7bc575af37816c --- /dev/null +++ b/test/e2e/specs/interactivity/router-styles.spec.ts @@ -0,0 +1,232 @@ +/** + * Internal dependencies + */ +import { test, expect } from './fixtures'; + +const COLOR_RED = 'rgb(255, 0, 0)'; +const COLOR_GREEN = 'rgb(0, 255, 0)'; +const COLOR_BLUE = 'rgb(0, 0, 255)'; +const COLOR_WRAPPER = 'rgb(160, 12, 60)'; + +test.describe( 'Router styles', () => { + test.beforeAll( async ( { interactivityUtils: utils } ) => { + await utils.activatePlugins(); + const red = await utils.addPostWithBlock( + 'test/router-styles-wrapper', + { + alias: 'red', + innerBlocks: [ [ 'test/router-styles-red' ] ], + } + ); + const green = await utils.addPostWithBlock( + 'test/router-styles-wrapper', + { + alias: 'green', + innerBlocks: [ [ 'test/router-styles-green' ] ], + } + ); + const blue = await utils.addPostWithBlock( + 'test/router-styles-wrapper', + { + alias: 'blue', + innerBlocks: [ [ 'test/router-styles-blue' ] ], + } + ); + + const all = await utils.addPostWithBlock( + 'test/router-styles-wrapper', + { + alias: 'all', + innerBlocks: [ + [ 'test/router-styles-red' ], + [ 'test/router-styles-green' ], + [ 'test/router-styles-blue' ], + ], + } + ); + + await utils.addPostWithBlock( 'test/router-styles-wrapper', { + alias: 'none', + attributes: { links: { red, green, blue, all } }, + } ); + } ); + + test.beforeEach( async ( { page, interactivityUtils: utils } ) => { + await page.goto( utils.getLink( 'none' ) ); + } ); + + test.afterAll( async ( { interactivityUtils: utils } ) => { + await utils.deactivatePlugins(); + await utils.deleteAllPosts(); + } ); + + test( 'should add and remove styles from style tags', async ( { + page, + } ) => { + const csn = page.getByTestId( 'client-side navigation' ); + const red = page.getByTestId( 'red' ); + const green = page.getByTestId( 'green' ); + const blue = page.getByTestId( 'blue' ); + const all = page.getByTestId( 'all' ); + + await expect( red ).toHaveCSS( 'color', COLOR_WRAPPER ); + await expect( green ).toHaveCSS( 'color', COLOR_WRAPPER ); + await expect( blue ).toHaveCSS( 'color', COLOR_WRAPPER ); + await expect( all ).toHaveCSS( 'color', COLOR_WRAPPER ); + + await page.getByTestId( 'link red' ).click(); + + await expect( csn ).toBeVisible(); + await expect( red ).toHaveCSS( 'color', COLOR_RED ); + await expect( green ).toHaveCSS( 'color', COLOR_WRAPPER ); + await expect( blue ).toHaveCSS( 'color', COLOR_WRAPPER ); + await expect( all ).toHaveCSS( 'color', COLOR_RED ); + + await page.getByTestId( 'link green' ).click(); + + await expect( csn ).toBeVisible(); + await expect( red ).toHaveCSS( 'color', COLOR_WRAPPER ); + await expect( green ).toHaveCSS( 'color', COLOR_GREEN ); + await expect( blue ).toHaveCSS( 'color', COLOR_WRAPPER ); + await expect( all ).toHaveCSS( 'color', COLOR_GREEN ); + + await page.getByTestId( 'link blue' ).click(); + + await expect( csn ).toBeVisible(); + await expect( red ).toHaveCSS( 'color', COLOR_WRAPPER ); + await expect( green ).toHaveCSS( 'color', COLOR_WRAPPER ); + await expect( blue ).toHaveCSS( 'color', COLOR_BLUE ); + await expect( all ).toHaveCSS( 'color', COLOR_BLUE ); + + await page.getByTestId( 'link all' ).click(); + + await expect( csn ).toBeVisible(); + await expect( red ).toHaveCSS( 'color', COLOR_RED ); + await expect( green ).toHaveCSS( 'color', COLOR_GREEN ); + await expect( blue ).toHaveCSS( 'color', COLOR_BLUE ); + await expect( all ).toHaveCSS( 'color', COLOR_BLUE ); + } ); + + test( 'should add and remove styles from referenced style sheets', async ( { + page, + } ) => { + const csn = page.getByTestId( 'client-side navigation' ); + const red = page.getByTestId( 'red-from-link' ); + const green = page.getByTestId( 'green-from-link' ); + const blue = page.getByTestId( 'blue-from-link' ); + const all = page.getByTestId( 'all-from-link' ); + + await expect( red ).toHaveCSS( 'color', COLOR_WRAPPER ); + await expect( green ).toHaveCSS( 'color', COLOR_WRAPPER ); + await expect( blue ).toHaveCSS( 'color', COLOR_WRAPPER ); + await expect( all ).toHaveCSS( 'color', COLOR_WRAPPER ); + + await page.getByTestId( 'link red' ).click(); + + await expect( csn ).toBeVisible(); + await expect( red ).toHaveCSS( 'color', COLOR_RED ); + await expect( green ).toHaveCSS( 'color', COLOR_WRAPPER ); + await expect( blue ).toHaveCSS( 'color', COLOR_WRAPPER ); + await expect( all ).toHaveCSS( 'color', COLOR_RED ); + + await page.getByTestId( 'link green' ).click(); + + await expect( csn ).toBeVisible(); + await expect( red ).toHaveCSS( 'color', COLOR_WRAPPER ); + await expect( green ).toHaveCSS( 'color', COLOR_GREEN ); + await expect( blue ).toHaveCSS( 'color', COLOR_WRAPPER ); + await expect( all ).toHaveCSS( 'color', COLOR_GREEN ); + + await page.getByTestId( 'link blue' ).click(); + + await expect( csn ).toBeVisible(); + await expect( red ).toHaveCSS( 'color', COLOR_WRAPPER ); + await expect( green ).toHaveCSS( 'color', COLOR_WRAPPER ); + await expect( blue ).toHaveCSS( 'color', COLOR_BLUE ); + await expect( all ).toHaveCSS( 'color', COLOR_BLUE ); + + await page.getByTestId( 'link all' ).click(); + + await expect( csn ).toBeVisible(); + await expect( red ).toHaveCSS( 'color', COLOR_RED ); + await expect( green ).toHaveCSS( 'color', COLOR_GREEN ); + await expect( blue ).toHaveCSS( 'color', COLOR_BLUE ); + await expect( all ).toHaveCSS( 'color', COLOR_BLUE ); + } ); + + test( 'should support relative URLs in referenced style sheets', async ( { + page, + } ) => { + const csn = page.getByTestId( 'client-side navigation' ); + const background = page.getByTestId( 'background-from-link' ); + + await expect( background ).toHaveScreenshot(); + + await page.getByTestId( 'link red' ).click(); + + await expect( csn ).toBeVisible(); + await expect( background ).toHaveScreenshot(); + + await page.getByTestId( 'link green' ).click(); + + await expect( csn ).toBeVisible(); + await expect( background ).toHaveScreenshot(); + + await page.getByTestId( 'link blue' ).click(); + + await expect( csn ).toBeVisible(); + await expect( background ).toHaveScreenshot(); + + await page.getByTestId( 'link all' ).click(); + + await expect( csn ).toBeVisible(); + await expect( background ).toHaveScreenshot(); + } ); + + test( 'should update style tags with modified content', async ( { + page, + } ) => { + const csn = page.getByTestId( 'client-side navigation' ); + const red = page.getByTestId( 'red-from-inline' ); + const green = page.getByTestId( 'green-from-inline' ); + const blue = page.getByTestId( 'blue-from-inline' ); + const all = page.getByTestId( 'all-from-inline' ); + + await expect( red ).toHaveCSS( 'color', COLOR_WRAPPER ); + await expect( green ).toHaveCSS( 'color', COLOR_WRAPPER ); + await expect( blue ).toHaveCSS( 'color', COLOR_WRAPPER ); + await expect( all ).toHaveCSS( 'color', COLOR_WRAPPER ); + + await page.getByTestId( 'link red' ).click(); + + await expect( csn ).toBeVisible(); + await expect( red ).toHaveCSS( 'color', COLOR_RED ); + await expect( green ).toHaveCSS( 'color', COLOR_WRAPPER ); + await expect( blue ).toHaveCSS( 'color', COLOR_WRAPPER ); + await expect( all ).toHaveCSS( 'color', COLOR_RED ); + + await page.getByTestId( 'link green' ).click(); + + await expect( csn ).toBeVisible(); + await expect( red ).toHaveCSS( 'color', COLOR_WRAPPER ); + await expect( green ).toHaveCSS( 'color', COLOR_GREEN ); + await expect( blue ).toHaveCSS( 'color', COLOR_WRAPPER ); + await expect( all ).toHaveCSS( 'color', COLOR_GREEN ); + + await page.getByTestId( 'link blue' ).click(); + + await expect( csn ).toBeVisible(); + await expect( red ).toHaveCSS( 'color', COLOR_WRAPPER ); + await expect( green ).toHaveCSS( 'color', COLOR_WRAPPER ); + await expect( blue ).toHaveCSS( 'color', COLOR_BLUE ); + await expect( all ).toHaveCSS( 'color', COLOR_BLUE ); + + await page.getByTestId( 'link all' ).click(); + + await expect( csn ).toBeVisible(); + await expect( red ).toHaveCSS( 'color', COLOR_RED ); + await expect( green ).toHaveCSS( 'color', COLOR_GREEN ); + await expect( blue ).toHaveCSS( 'color', COLOR_BLUE ); + await expect( all ).toHaveCSS( 'color', COLOR_BLUE ); + } ); +} ); diff --git a/test/e2e/specs/site-editor/block-style-variations.spec.js b/test/e2e/specs/site-editor/block-style-variations.spec.js index 03fc5398f4a0a5..1fa8972d34d6c8 100644 --- a/test/e2e/specs/site-editor/block-style-variations.spec.js +++ b/test/e2e/specs/site-editor/block-style-variations.spec.js @@ -317,9 +317,7 @@ async function draftNewPage( page ) { // Create a Group block with 2 nested Group blocks. async function addPageContent( editor, page ) { - const inserterButton = page.locator( - 'role=button[name="Block Inserter"i]' - ); + const inserterButton = page.locator( 'role=tab[name="Blocks"i]' ); await inserterButton.click(); await page.type( 'role=searchbox[name="Search"i]', 'Group' ); await page.click( diff --git a/test/e2e/specs/site-editor/browser-history.spec.js b/test/e2e/specs/site-editor/browser-history.spec.js index eaafb3aad1b3fd..c3eb2ac5e3a2fa 100644 --- a/test/e2e/specs/site-editor/browser-history.spec.js +++ b/test/e2e/specs/site-editor/browser-history.spec.js @@ -19,15 +19,17 @@ test.describe( 'Site editor browser history', () => { // Navigate to a single template await page.click( 'role=button[name="Templates"]' ); - await page.getByRole( 'link', { name: 'Index' } ).click(); + await page + .locator( '.fields-field__title', { hasText: 'Index' } ) + .click(); await expect( page ).toHaveURL( - '/wp-admin/site-editor.php?postId=emptytheme%2F%2Findex&postType=wp_template&canvas=edit' + '/wp-admin/site-editor.php?p=%2Fwp_template%2Femptytheme%2F%2Findex&canvas=edit' ); // Navigate back to the template list await page.goBack(); await expect( page ).toHaveURL( - '/wp-admin/site-editor.php?postType=wp_template' + '/wp-admin/site-editor.php?p=%2Ftemplate' ); // Navigate back to the dashboard diff --git a/test/e2e/specs/site-editor/command-center.spec.js b/test/e2e/specs/site-editor/command-center.spec.js index 19318081aa171b..f7270d4172ca0a 100644 --- a/test/e2e/specs/site-editor/command-center.spec.js +++ b/test/e2e/specs/site-editor/command-center.spec.js @@ -9,7 +9,10 @@ test.describe( 'Site editor command palette', () => { } ); test.afterAll( async ( { requestUtils } ) => { - await requestUtils.activateTheme( 'twentytwentyone' ); + await Promise.all( [ + requestUtils.activateTheme( 'twentytwentyone' ), + requestUtils.deleteAllPages(), + ] ); } ); test.beforeEach( async ( { admin } ) => { @@ -28,7 +31,7 @@ test.describe( 'Site editor command palette', () => { await page.keyboard.type( 'new page' ); await page.getByRole( 'option', { name: 'Add new page' } ).click(); await expect( page ).toHaveURL( - /\/wp-admin\/site-editor.php\?postId=(\d+)&postType=page&canvas=edit/ + /\/wp-admin\/site-editor.php\?p=%2Fpage%2F(\d+)&canvas=edit/ ); await expect( editor.canvas diff --git a/test/e2e/specs/site-editor/font-library.spec.js b/test/e2e/specs/site-editor/font-library.spec.js index 7928ef8f71c534..1824257df12fd3 100644 --- a/test/e2e/specs/site-editor/font-library.spec.js +++ b/test/e2e/specs/site-editor/font-library.spec.js @@ -186,7 +186,8 @@ test.describe( 'Font Library', () => { await page .getByRole( 'button', { name: 'Headings', exact: true } ) .click(); - await page.getByLabel( 'Font' ).selectOption( 'Exo 2' ); + await page.getByRole( 'combobox', { name: 'Font' } ).click(); + await page.getByRole( 'option', { name: 'Exo 2' } ).click(); await expect( editor.canvas.locator( '.is-root-container h1' ) ).toHaveCSS( 'font-family', '"Exo 2"' ); diff --git a/test/e2e/specs/site-editor/homepage-settings.spec.js b/test/e2e/specs/site-editor/homepage-settings.spec.js new file mode 100644 index 00000000000000..e80e14830364ce --- /dev/null +++ b/test/e2e/specs/site-editor/homepage-settings.spec.js @@ -0,0 +1,110 @@ +/** + * WordPress dependencies + */ +const { test, expect } = require( '@wordpress/e2e-test-utils-playwright' ); + +test.describe( 'Homepage Settings via Editor', () => { + test.beforeAll( async ( { requestUtils } ) => { + await Promise.all( [ requestUtils.activateTheme( 'emptytheme' ) ] ); + await requestUtils.createPage( { + title: 'Homepage', + status: 'publish', + } ); + await requestUtils.createPage( { + title: 'Sample page', + status: 'publish', + } ); + await requestUtils.createPage( { + title: 'Draft page', + status: 'draft', + } ); + } ); + + test.beforeEach( async ( { admin, page } ) => { + await admin.visitSiteEditor(); + await page.getByRole( 'button', { name: 'Pages' } ).click(); + } ); + + test.afterAll( async ( { requestUtils } ) => { + await Promise.all( [ + requestUtils.deleteAllPages(), + requestUtils.updateSiteSettings( { + show_on_front: 'posts', + page_on_front: 0, + page_for_posts: 0, + } ), + ] ); + } ); + + test( 'should not show "Set as homepage" and "Set as posts page" action on pages with `draft` status', async ( { + page, + } ) => { + const draftPage = page + .getByRole( 'gridcell' ) + .getByLabel( 'Draft page' ); + const draftPageRow = page + .getByRole( 'row' ) + .filter( { has: draftPage } ); + await draftPageRow.hover(); + await draftPageRow + .getByRole( 'button', { + name: 'Actions', + } ) + .click(); + await expect( + page.getByRole( 'menuitem', { name: 'Set as homepage' } ) + ).toBeHidden(); + await expect( + page.getByRole( 'menuitem', { name: 'Set as posts page' } ) + ).toBeHidden(); + } ); + + test( 'should show correct homepage actions based on current homepage or posts page', async ( { + page, + } ) => { + const samplePage = page + .getByRole( 'gridcell' ) + .getByLabel( 'Homepage' ); + const samplePageRow = page + .getByRole( 'row' ) + .filter( { has: samplePage } ); + await samplePageRow.click(); + await samplePageRow + .getByRole( 'button', { + name: 'Actions', + } ) + .click(); + await page.getByRole( 'menuitem', { name: 'Set as homepage' } ).click(); + await page.getByRole( 'button', { name: 'Set homepage' } ).click(); + await expect( + page.getByRole( 'menuitem', { name: 'Set as homepage' } ) + ).toBeHidden(); + await expect( + page.getByRole( 'menuitem', { name: 'Set as posts page' } ) + ).toBeHidden(); + + const samplePageTwo = page + .getByRole( 'gridcell' ) + .getByLabel( 'Sample page' ); + const samplePageTwoRow = page + .getByRole( 'row' ) + .filter( { has: samplePageTwo } ); + // eslint-disable-next-line playwright/no-force-option + await samplePageTwoRow.click( { force: true } ); + await samplePageTwoRow + .getByRole( 'button', { + name: 'Actions', + } ) + .click(); + await page + .getByRole( 'menuitem', { name: 'Set as posts page' } ) + .click(); + await page.getByRole( 'button', { name: 'Set posts page' } ).click(); + await expect( + page.getByRole( 'menuitem', { name: 'Set as homepage' } ) + ).toBeHidden(); + await expect( + page.getByRole( 'menuitem', { name: 'Set as posts page' } ) + ).toBeHidden(); + } ); +} ); diff --git a/test/e2e/specs/site-editor/hybrid-theme.spec.js b/test/e2e/specs/site-editor/hybrid-theme.spec.js index b568aaf4445b5c..042cb1042cac22 100644 --- a/test/e2e/specs/site-editor/hybrid-theme.spec.js +++ b/test/e2e/specs/site-editor/hybrid-theme.spec.js @@ -33,7 +33,7 @@ test.describe( 'Hybrid theme', () => { ); await expect( page ).toHaveURL( - '/wp-admin/site-editor.php?postType=wp_template_part' + '/wp-admin/site-editor.php?p=%2Fpattern&postType=wp_template_part' ); await expect( diff --git a/test/e2e/specs/site-editor/navigation-editor.spec.js b/test/e2e/specs/site-editor/navigation-editor.spec.js index 64a80e814d6298..2ceaea06ec2d44 100644 --- a/test/e2e/specs/site-editor/navigation-editor.spec.js +++ b/test/e2e/specs/site-editor/navigation-editor.spec.js @@ -40,6 +40,10 @@ test.describe( 'Editing Navigation Menus', () => { canvas: 'edit', } ); + await expect( + page.getByRole( 'button', { name: 'Document Overview' } ) + ).toBeVisible(); + // Open List View. await pageUtils.pressKeys( 'access+o' ); @@ -54,7 +58,7 @@ test.describe( 'Editing Navigation Menus', () => { await expect( listView ).toBeVisible(); const navBlockNode = listView.getByRole( 'link', { - name: 'Navigation', + name: 'Primary Menu', exact: true, } ); diff --git a/test/e2e/specs/site-editor/navigation.spec.js b/test/e2e/specs/site-editor/navigation.spec.js index 1b92ef2e850e67..18eb6c9904b449 100644 --- a/test/e2e/specs/site-editor/navigation.spec.js +++ b/test/e2e/specs/site-editor/navigation.spec.js @@ -45,13 +45,6 @@ test.describe( 'Site editor navigation', () => { page.getByRole( 'button', { name: 'Pages' } ) ).toBeFocused(); - // Navigate to the Saved button first, as it precedes the editor iframe. - await editorNavigationUtils.tabToLabel( 'Saved' ); - const savedButton = page.getByRole( 'button', { - name: 'Saved', - } ); - await expect( savedButton ).toBeFocused(); - // Get the iframe when it has a role=button and Edit label. const editorCanvasRegion = page.getByRole( 'region', { name: 'Editor content', @@ -60,6 +53,15 @@ test.describe( 'Site editor navigation', () => { name: 'Edit', } ); + await expect( editorCanvasButton ).toBeVisible(); + + // Navigate to the Saved button first, as it precedes the editor iframe. + await editorNavigationUtils.tabToLabel( 'Saved' ); + const savedButton = page.getByRole( 'button', { + name: 'Saved', + } ); + await expect( savedButton ).toBeFocused(); + // Test that there are no tab stops between the Saved button and the // focusable iframe with role=button. await pageUtils.pressKeys( 'Tab' ); diff --git a/test/e2e/specs/site-editor/new-templates-list.spec.js b/test/e2e/specs/site-editor/new-templates-list.spec.js index 6faa85a2659cf1..d26306a6c8e3b5 100644 --- a/test/e2e/specs/site-editor/new-templates-list.spec.js +++ b/test/e2e/specs/site-editor/new-templates-list.spec.js @@ -27,13 +27,7 @@ test.describe( 'Templates', () => { page.locator( '[aria-label="Templates"]' ) ).toBeVisible(); - const firstTitle = page - .getByRole( 'region', { - name: 'Template', - includeHidden: true, - } ) - .getByRole( 'link', { includeHidden: true } ) - .first(); + const firstTitle = page.locator( '.fields-field__title' ).first(); // Descending by title. await page.getByRole( 'button', { name: 'View options' } ).click(); @@ -54,9 +48,7 @@ test.describe( 'Templates', () => { await admin.visitSiteEditor( { postType: 'wp_template' } ); // Global search. await page.getByRole( 'searchbox', { name: 'Search' } ).fill( 'tag' ); - const titles = page - .getByRole( 'region', { name: 'Template' } ) - .getByRole( 'link', { includeHidden: true } ); + const titles = page.locator( '.fields-field__title' ); await expect( titles ).toHaveCount( 1 ); await expect( titles.first() ).toHaveText( 'Tag Archives' ); await page diff --git a/test/e2e/specs/site-editor/page-list.spec.js b/test/e2e/specs/site-editor/page-list.spec.js new file mode 100644 index 00000000000000..88c8c16ff482ad --- /dev/null +++ b/test/e2e/specs/site-editor/page-list.spec.js @@ -0,0 +1,416 @@ +/** + * WordPress dependencies + */ +const { test, expect } = require( '@wordpress/e2e-test-utils-playwright' ); +/** + * External dependencies + */ +const path = require( 'path' ); + +const createPages = async ( requestUtils ) => { + await requestUtils.createPage( { + title: 'Privacy Policy', + status: 'publish', + } ); + await requestUtils.createPage( { + title: 'Sample Page', + status: 'publish', + } ); +}; + +test.describe( 'Page List', () => { + test.beforeAll( async ( { requestUtils } ) => { + // Activate a theme with permissions to access the site editor. + await requestUtils.activateTheme( 'emptytheme' ); + await createPages( requestUtils ); + } ); + + test.afterAll( async ( { requestUtils } ) => { + // Go back to the default theme. + await Promise.all( [ + requestUtils.activateTheme( 'twentytwentyone' ), + requestUtils.deleteAllPages(), + ] ); + } ); + + test.beforeEach( async ( { admin, page } ) => { + // Go to the pages page, as it has the list layout enabled by default. + await admin.visitSiteEditor(); + await page.getByRole( 'button', { name: 'Pages' } ).click(); + } ); + + test( 'Persists filter/search when switching layout', async ( { + page, + } ) => { + // Search pages + await page + .getByRole( 'searchbox', { name: 'Search' } ) + .fill( 'Privacy' ); + + // Switch layout + await page.getByRole( 'button', { name: 'Layout' } ).click(); + await page.getByRole( 'menuitemradio', { name: 'Table' } ).click(); + + // Confirm the table is visible + await expect( page.getByRole( 'table' ) ).toContainText( + 'Privacy Policy' + ); + + // The search should still contain the search term + await expect( + page.getByRole( 'searchbox', { name: 'Search' } ) + ).toHaveValue( 'Privacy' ); + } ); + + test.describe( 'Quick Edit Mode', () => { + const fields = { + featuredImage: { + performEdit: async ( page ) => { + const placeholder = page.getByRole( 'button', { + name: 'Choose an image…', + } ); + await placeholder.click(); + const mediaLibrary = page.getByRole( 'dialog' ); + const TEST_IMAGE_FILE_PATH = path.resolve( + __dirname, + '../../assets/10x10_e2e_test_image_z9T8jK.png' + ); + + const fileChooserPromise = + page.waitForEvent( 'filechooser' ); + await mediaLibrary.getByText( 'Select files' ).click(); + const fileChooser = await fileChooserPromise; + await fileChooser.setFiles( TEST_IMAGE_FILE_PATH ); + await mediaLibrary + .locator( '.media-frame-toolbar' ) + .waitFor( { + state: 'hidden', + } ); + + await mediaLibrary + .getByRole( 'button', { name: 'Select', exact: true } ) + .click(); + }, + assertInitialState: async ( page ) => { + const el = page.getByText( 'Choose an image…' ); + const placeholder = page.getByRole( 'button', { + name: 'Choose an image…', + } ); + await expect( el ).toBeVisible(); + await expect( placeholder ).toBeVisible(); + }, + assertEditedState: async ( page ) => { + const placeholder = page.getByRole( 'button', { + name: 'Choose an image…', + } ); + await expect( placeholder ).toBeHidden(); + const img = page.locator( + '.fields-controls__featured-image-image' + ); + await expect( img ).toBeVisible(); + }, + }, + statusVisibility: { + performEdit: async ( page ) => { + const statusAndVisibility = page.getByLabel( + 'Status & Visibility' + ); + await statusAndVisibility.click(); + const options = [ + 'Published', + 'Draft', + 'Pending Review', + 'Private', + ]; + + for ( const option of options ) { + await page + .getByRole( 'radio', { name: option } ) + .click(); + await expect( statusAndVisibility ).toContainText( + option + ); + + if ( option !== 'Private' ) { + await page + .getByRole( 'checkbox', { + name: 'Password protected', + } ) + .check(); + } + } + }, + assertInitialState: async ( page ) => { + const statusAndVisibility = page.getByLabel( + 'Status & Visibility' + ); + await expect( statusAndVisibility ).toContainText( + 'Published' + ); + }, + assertEditedState: async ( page ) => { + const statusAndVisibility = page.getByLabel( + 'Status & Visibility' + ); + await expect( statusAndVisibility ).toContainText( + 'Private' + ); + }, + }, + author: { + assertInitialState: async ( page ) => { + const author = page.getByLabel( 'Author' ); + await expect( author ).toContainText( 'admin' ); + }, + performEdit: async ( page ) => { + const author = page.getByLabel( 'Author' ); + await author.click(); + const selectElement = page.locator( + 'select:has(option[value="1"])' + ); + await selectElement.selectOption( { value: '1' } ); + }, + assertEditedState: async () => {}, + }, + date: { + assertInitialState: async ( page ) => { + const dateEl = page.getByLabel( 'Edit Date' ); + const date = new Date(); + const yy = String( date.getFullYear() ); + + await expect( dateEl ).toContainText( yy ); + }, + performEdit: async ( page ) => { + const dateEl = page.getByLabel( 'Edit Date' ); + await dateEl.click(); + const date = new Date(); + const yy = Number( date.getFullYear() ); + const yyEl = page.locator( + `input[type="number"][value="${ yy }"]` + ); + + await yyEl.focus(); + await page.keyboard.press( 'ArrowUp' ); + }, + assertEditedState: async ( page ) => { + const date = new Date(); + const yy = Number( date.getFullYear() ); + const dateEl = page.getByLabel( 'Edit Date' ); + await expect( dateEl ).toContainText( String( yy + 1 ) ); + }, + }, + slug: { + assertInitialState: async ( page ) => { + const slug = page.getByLabel( 'Edit Slug' ); + await expect( slug ).toContainText( 'privacy-policy' ); + }, + performEdit: async ( page ) => { + const slug = page.getByLabel( 'Edit Slug' ); + await slug.click(); + await expect( + page.getByRole( 'link', { + name: 'http://localhost:8889/?', + } ) + ).toBeVisible(); + }, + assertEditedState: async () => {}, + }, + parent: { + assertInitialState: async ( page ) => { + const parent = page.getByLabel( 'Edit Parent' ); + await expect( parent ).toContainText( 'None' ); + }, + performEdit: async ( page ) => { + const parent = page.getByLabel( 'Edit Parent' ); + await parent.click(); + await page + .getByLabel( 'Parent', { exact: true } ) + .fill( 'Sample' ); + + await page + .getByRole( 'option', { name: 'Sample Page' } ) + .click(); + }, + assertEditedState: async ( page ) => { + const parent = page.getByLabel( 'Edit Parent' ); + await expect( parent ).toContainText( 'Sample Page' ); + }, + }, + // TODO: Wrap up this test once https://github.com/WordPress/gutenberg/issues/68173 is fixed + // template: { + // assertInitialState: async ( page ) => { + // const template = page.getByRole( 'button', { + // name: 'Single Entries', + // } ); + // await expect( template ).toContainText( 'Single Entries' ); + // }, + // edit: async ( page ) => { + // const template = page.getByRole( 'button', { + // name: 'Single Entries', + // } ); + // await template.click(); + // await page + // .getByRole( 'menuitem', { name: 'Swap template' } ) + // .click(); + // }, + // assertEditedState: async ( page ) => { + // + // }, + // }, + discussion: { + assertInitialState: async ( page ) => { + const discussion = page.getByLabel( 'Edit Discussion' ); + await expect( discussion ).toContainText( 'Closed' ); + }, + performEdit: async ( page ) => { + const discussion = page.getByLabel( 'Edit Discussion' ); + await discussion.click(); + await page + .getByLabel( 'Open', { + exact: true, + } ) + .check(); + }, + assertEditedState: async ( page ) => { + const discussion = page.getByLabel( 'Edit Discussion' ); + await expect( discussion ).toContainText( 'Open' ); + }, + }, + }; + + test.beforeAll( async ( { requestUtils } ) => { + await requestUtils.setGutenbergExperiments( [ + 'gutenberg-quick-edit-dataviews', + ] ); + } ); + + test.beforeEach( async ( { admin, page } ) => { + await admin.visitSiteEditor(); + await page.getByRole( 'button', { name: 'Pages' } ).click(); + await page.getByRole( 'button', { name: 'Layout' } ).click(); + await page.getByRole( 'menuitemradio', { name: 'Table' } ).click(); + const privacyPolicyCheckbox = page.getByRole( 'checkbox', { + name: 'Privacy Policy', + } ); + + await privacyPolicyCheckbox.check(); + + await page.getByRole( 'button', { name: 'Details' } ).click(); + } ); + + Object.entries( fields ).forEach( + ( [ + key, + { performEdit, assertInitialState, assertEditedState }, + ] ) => { + // Asserts are done in the individual functions + // eslint-disable-next-line playwright/expect-expect + test( `should initialize, edit, and update ${ key } field correctly`, async ( { + page, + } ) => { + await assertInitialState( page ); + await performEdit( page ); + await assertEditedState( page ); + } ); + } + ); + + test( 'should save multiple field changes and update Data Views UI', async ( { + page, + requestUtils, + } ) => { + const selectedItem = page.locator( '.is-selected' ); + const imagePlaceholder = selectedItem.locator( + '.fields-controls__featured-image-placeholder' + ); + const status = selectedItem.getByRole( 'cell', { + name: 'Published', + } ); + await expect( status ).toBeVisible(); + + const { featuredImage, statusVisibility } = fields; + await statusVisibility.performEdit( page ); + await featuredImage.performEdit( page ); + // Ensure that no dropdown is open + await page.getByRole( 'button', { name: 'Close' } ).click(); + const saveButton = page.getByLabel( 'Review 1 change…' ); + await saveButton.click(); + await page.getByRole( 'button', { name: 'Save' } ).click(); + const updatedStatus = selectedItem.getByRole( 'cell', { + name: 'Private', + } ); + await expect( imagePlaceholder ).toBeHidden(); + await expect( updatedStatus ).toBeVisible(); + + // Reset the page to its original state + await requestUtils.deleteAllPages(); + await createPages( requestUtils ); + } ); + + // TODO: Wrap up this test once https://github.com/WordPress/gutenberg/pull/67584 is merged + // test( 'should update pages according to the changes', async ( { + // page, + // } ) => { + // const samplePage = page.getByRole( 'checkbox', { + // name: 'Sample Page', + // } ); + + // await samplePage.check(); + + // const table = page.getByRole( 'table' ); + + // const selectedItems = table.locator( '.is-selected', { + // strict: false, + // } ); + + // expect( await selectedItems.all() ).toHaveLength( 2 ); + + // const imagePlaceholders = selectedItems.locator( + // '.fields-controls__featured-image-placeholder', + // { strict: false } + // ); + + // for ( const imagePlaceholder of await imagePlaceholders.all() ) { + // await expect( imagePlaceholder ).toBeVisible(); + // } + + // const statuses = selectedItems.getByRole( 'cell', { + // name: 'Public', + // } ); + + // for ( const status of await statuses.all() ) { + // await expect( status ).toBeVisible(); + // } + + // const { featuredImage, statusVisibility } = fields; + // await statusVisibility.edit( page ); + // await featuredImage.edit( page ); + // // Ensure that no dropdown is open + // await page.getByRole( 'button', { name: 'Close' } ).click(); + // const saveButton = page.getByLabel( 'Review 1 change…' ); + // await saveButton.click(); + // await page.getByRole( 'button', { name: 'Save' } ).click(); + // const updatedStatus = selectedItems.getByRole( + // 'cell', + // { + // name: 'Private', + // }, + // { + // strict: false, + // } + // ); + + // for ( const imagePlaceholder of await imagePlaceholders.all() ) { + // await expect( imagePlaceholder ).toBeHidden(); + // } + + // for ( const status of await updatedStatus.all() ) { + // await expect( status ).toBeVisible(); + // } + // } ); + + test.afterAll( async ( { requestUtils } ) => { + await requestUtils.setGutenbergExperiments( [] ); + } ); + } ); +} ); diff --git a/test/e2e/specs/site-editor/pages.spec.js b/test/e2e/specs/site-editor/pages.spec.js index 4817651bac8f9d..54f8a64e067cbf 100644 --- a/test/e2e/specs/site-editor/pages.spec.js +++ b/test/e2e/specs/site-editor/pages.spec.js @@ -272,6 +272,7 @@ test.describe( 'Pages', () => { // Create new page that has the default template so as to swap it. await draftNewPage( page ); + await page.locator( 'role=button[name="Block Inserter"i]' ).click(); await editor.openDocumentSettingsSidebar(); const templateOptionsButton = page .getByRole( 'region', { name: 'Editor settings' } ) @@ -280,7 +281,7 @@ test.describe( 'Pages', () => { await templateOptionsButton.click(); await page .getByRole( 'menu', { name: 'Template options' } ) - .getByText( 'Swap template' ) + .getByText( 'Change template' ) .click(); const templateItem = page.locator( '.block-editor-block-patterns-list__item-title' @@ -294,6 +295,7 @@ test.describe( 'Pages', () => { } ); // Now reset, and apply the default template back. + await editor.openDocumentSettingsSidebar(); await templateOptionsButton.click(); const resetButton = page .getByRole( 'menu', { name: 'Template options' } ) @@ -303,11 +305,12 @@ test.describe( 'Pages', () => { await expect( templateOptionsButton ).toHaveText( 'Single Entries' ); } ); - test( 'swap template options should respect the declared `postTypes`', async ( { + test( 'change template options should respect the declared `postTypes`', async ( { page, editor, } ) => { await draftNewPage( page ); + await page.locator( 'role=button[name="Block Inserter"i]' ).click(); await editor.openDocumentSettingsSidebar(); const templateOptionsButton = page .getByRole( 'region', { name: 'Editor settings' } ) @@ -318,7 +321,7 @@ test.describe( 'Pages', () => { await expect( page .getByRole( 'menu', { name: 'Template options' } ) - .getByText( 'Swap template' ) + .getByText( 'Change template' ) ).toHaveCount( 0 ); } ); } ); diff --git a/test/e2e/specs/site-editor/preload.spec.js b/test/e2e/specs/site-editor/preload.spec.js new file mode 100644 index 00000000000000..e731e932e30523 --- /dev/null +++ b/test/e2e/specs/site-editor/preload.spec.js @@ -0,0 +1,54 @@ +/** + * WordPress dependencies + */ +const { test, expect } = require( '@wordpress/e2e-test-utils-playwright' ); + +test.describe( 'Preload', () => { + test.beforeAll( async ( { requestUtils } ) => { + await requestUtils.activateTheme( 'emptytheme' ); + await requestUtils.resetPreferences(); + } ); + + test.afterAll( async ( { requestUtils } ) => { + await requestUtils.activateTheme( 'twentytwentyone' ); + } ); + + test( 'Should make no requests before the iframe is loaded', async ( { + page, + admin, + } ) => { + const requests = []; + + function onRequest( request ) { + if ( + request.resourceType() === 'document' && + request.url().startsWith( 'blob:' ) + ) { + // Stop recording when the iframe is initialized. + page.off( 'request', onRequest ); + } else if ( request.resourceType() === 'fetch' ) { + const url = request.url(); + const urlObject = new URL( url ); + const restRoute = urlObject.searchParams.get( 'rest_route' ); + if ( restRoute ) { + requests.push( restRoute ); + } else { + requests.push( url ); + } + } + } + + page.on( 'request', onRequest ); + + await admin.visitSiteEditor(); + + // To do: these should all be removed or preloaded. + expect( requests ).toEqual( [ + // There are two separate settings OPTIONS requests. We should fix + // so the one for canUser and getEntityRecord are reused. + '/wp/v2/settings', + // Seems to be coming from `enableComplementaryArea`. + '/wp/v2/users/me', + ] ); + } ); +} ); diff --git a/test/e2e/specs/site-editor/site-editor-inserter.spec.js b/test/e2e/specs/site-editor/site-editor-inserter.spec.js index 04075cbedab308..23646576131082 100644 --- a/test/e2e/specs/site-editor/site-editor-inserter.spec.js +++ b/test/e2e/specs/site-editor/site-editor-inserter.spec.js @@ -5,8 +5,9 @@ const { test, expect } = require( '@wordpress/e2e-test-utils-playwright' ); test.describe( 'Site Editor Inserter', () => { test.beforeAll( async ( { requestUtils } ) => { + // We need the theme to have a section root so zoom out is enabled await Promise.all( [ - requestUtils.activateTheme( 'emptytheme' ), + requestUtils.activateTheme( 'twentytwentyfour' ), requestUtils.deleteAllTemplates( 'wp_template' ), requestUtils.deleteAllTemplates( 'wp_template_part' ), ] ); @@ -21,47 +22,322 @@ test.describe( 'Site Editor Inserter', () => { await editor.canvas.locator( 'body' ).click(); } ); - test( 'inserter toggle button should toggle global inserter', async ( { - page, - } ) => { - await page.click( 'role=button[name="Block Inserter"i]' ); - - // Visibility check - await expect( - page.locator( 'role=searchbox[name="Search"i]' ) - ).toBeVisible(); - await page.click( 'role=button[name="Block Inserter"i]' ); - //Hidden State check - await expect( - page.locator( 'role=searchbox[name="Search"i]' ) - ).toBeHidden(); + test.use( { + InserterUtils: async ( { editor, page }, use ) => { + await use( new InserterUtils( { editor, page } ) ); + }, } ); - // A test for https://github.com/WordPress/gutenberg/issues/43090. - test( 'should close the inserter when clicking on the toggle button', async ( { + test( 'inserter toggle button should toggle global inserter', async ( { + InserterUtils, page, editor, } ) => { - const inserterButton = page.getByRole( 'button', { - name: 'Block Inserter', - exact: true, + await InserterUtils.openBlockLibrary(); + await InserterUtils.closeBlockLibrary(); + + await test.step( 'should open the inserter via enter keypress on toggle button', async () => { + await InserterUtils.inserterButton.focus(); + await page.keyboard.press( 'Enter' ); + await expect( InserterUtils.blockLibrary ).toBeVisible(); } ); - const blockLibrary = page.getByRole( 'region', { - name: 'Block Library', + + await test.step( 'should set focus to the blocks tab when opening the inserter', async () => { + await expect( + InserterUtils.getBlockLibraryTab( 'Blocks' ) + ).toBeFocused(); + } ); + + await test.step( 'should close the inserter via escape keypress', async () => { + await page.keyboard.press( 'Escape' ); + await expect( InserterUtils.blockLibrary ).toBeHidden(); + } ); + + await test.step( 'should focus inserter toggle button after closing the inserter via escape keypress', async () => { + await expect( InserterUtils.inserterButton ).toBeFocused(); + } ); + + // A test for https://github.com/WordPress/gutenberg/issues/43090. + await test.step( 'should close the inserter when clicking on the toggle button', async () => { + const beforeBlocks = await editor.getBlocks(); + + await InserterUtils.openBlockLibrary(); + await InserterUtils.expectActiveTab( 'Blocks' ); + await InserterUtils.blockLibrary + .getByRole( 'option', { name: 'Buttons' } ) + .click(); + + await expect + .poll( editor.getBlocks ) + .toMatchObject( [ ...beforeBlocks, { name: 'core/buttons' } ] ); + + await InserterUtils.closeBlockLibrary(); + } ); + } ); + + test.describe( 'Inserter Zoom Level UX', () => { + test.use( { + ZoomUtils: async ( { editor, page }, use ) => { + await use( new ZoomUtils( { editor, page } ) ); + }, + } ); + + test( 'should intialize correct active tab based on zoom level', async ( { + InserterUtils, + ZoomUtils, + } ) => { + await test.step( 'should open the inserter to blocks tab from default zoom level', async () => { + await InserterUtils.openBlockLibrary(); + await InserterUtils.expectActiveTab( 'Blocks' ); + + // Zoom canvas should not be active + await expect( ZoomUtils.zoomCanvas ).toBeHidden(); + + await InserterUtils.closeBlockLibrary(); + + // Zoom canvas should not be active + await expect( ZoomUtils.zoomCanvas ).toBeHidden(); + } ); + + await test.step( 'should open the inserter to patterns tab if zoomed out', async () => { + await ZoomUtils.enterZoomOut(); + await InserterUtils.openBlockLibrary(); + await InserterUtils.expectActiveTab( 'Patterns' ); + + // Zoom canvas should still be active + await expect( ZoomUtils.zoomCanvas ).toBeVisible(); + + await InserterUtils.closeBlockLibrary(); + + // We should still be in Zoom Out + await expect( ZoomUtils.zoomCanvas ).toBeVisible(); + } ); + } ); + + test( 'should set the correct zoom level when changing tabs', async ( { + InserterUtils, + ZoomUtils, + } ) => { + await InserterUtils.openBlockLibrary(); + await InserterUtils.expectActiveTab( 'Blocks' ); + await expect( ZoomUtils.zoomCanvas ).toBeHidden(); + + await test.step( 'should zoom out when activating patterns tab', async () => { + await InserterUtils.activateTab( 'Patterns' ); + await expect( ZoomUtils.zoomCanvas ).toBeVisible(); + } ); + + await test.step( 'should reset zoom level when activating blocks tab', async () => { + await InserterUtils.activateTab( 'Blocks' ); + await expect( ZoomUtils.zoomCanvas ).toBeHidden(); + } ); + + await test.step( 'should zoom out when activating media tab', async () => { + await InserterUtils.activateTab( 'Media' ); + await expect( ZoomUtils.zoomCanvas ).toBeVisible(); + } ); + } ); + + test( 'should reset the zoom level when closing the inserter if we most recently changed the zoom level', async ( { + InserterUtils, + ZoomUtils, + } ) => { + await test.step( 'should reset zoom when closing from patterns tab', async () => { + await InserterUtils.openBlockLibrary(); + await InserterUtils.expectActiveTab( 'Blocks' ); + await expect( ZoomUtils.zoomCanvas ).toBeHidden(); + + await InserterUtils.activateTab( 'Patterns' ); + await expect( ZoomUtils.zoomCanvas ).toBeVisible(); + + await InserterUtils.closeBlockLibrary(); + + // Zoom Level should be reset + await expect( ZoomUtils.zoomCanvas ).toBeHidden(); + } ); + + await test.step( 'should preserve default zoom level when closing from blocks tab', async () => { + await InserterUtils.openBlockLibrary(); + await InserterUtils.expectActiveTab( 'Blocks' ); + await expect( ZoomUtils.zoomCanvas ).toBeHidden(); + + await InserterUtils.activateTab( 'Media' ); + await expect( ZoomUtils.zoomCanvas ).toBeVisible(); + + await InserterUtils.activateTab( 'Blocks' ); + await expect( ZoomUtils.zoomCanvas ).toBeHidden(); + + await InserterUtils.closeBlockLibrary(); + + // Zoom Level should stay at default level + await expect( ZoomUtils.zoomCanvas ).toBeHidden(); + } ); + + await test.step( 'should preserve default zoom level when closing from blocks tab even if user manually toggled zoom level on previous tab', async () => { + await InserterUtils.openBlockLibrary(); + await InserterUtils.expectActiveTab( 'Blocks' ); + await expect( ZoomUtils.zoomCanvas ).toBeHidden(); + + await InserterUtils.activateTab( 'Media' ); + await expect( ZoomUtils.zoomCanvas ).toBeVisible(); + + // Toggle zoom level manually + await ZoomUtils.exitZoomOut(); + + await InserterUtils.activateTab( 'Blocks' ); + await expect( ZoomUtils.zoomCanvas ).toBeHidden(); + + await InserterUtils.closeBlockLibrary(); + + // Zoom Level should stay at default level + await expect( ZoomUtils.zoomCanvas ).toBeHidden(); + } ); + + await test.step( 'should preserve default zoom level when closing from blocks tab even if user manually toggled zoom level on previous tab twice', async () => { + // Open inserter + await InserterUtils.openBlockLibrary(); + await InserterUtils.expectActiveTab( 'Blocks' ); + await expect( ZoomUtils.zoomCanvas ).toBeHidden(); + + await InserterUtils.activateTab( 'Media' ); + await expect( ZoomUtils.zoomCanvas ).toBeVisible(); + + // Toggle zoom level manually twice + await ZoomUtils.exitZoomOut(); + await ZoomUtils.enterZoomOut(); + + await InserterUtils.activateTab( 'Blocks' ); + await expect( ZoomUtils.zoomCanvas ).toBeHidden(); + + await InserterUtils.closeBlockLibrary(); + + // Zoom Level should stay at default level + await expect( ZoomUtils.zoomCanvas ).toBeHidden(); + } ); } ); - const beforeBlocks = await editor.getBlocks(); + test( 'should keep the zoom level manually set by the user if the user most recently set the zoom level', async ( { + InserterUtils, + ZoomUtils, + } ) => { + await test.step( 'should respect manual zoom level set when closing from patterns tab', async () => { + await InserterUtils.openBlockLibrary(); + await InserterUtils.expectActiveTab( 'Blocks' ); + await expect( ZoomUtils.zoomCanvas ).toBeHidden(); + + await InserterUtils.activateTab( 'Patterns' ); + await expect( ZoomUtils.zoomCanvas ).toBeVisible(); + + await ZoomUtils.exitZoomOut(); + + await InserterUtils.closeBlockLibrary(); + + // Zoom Level should stay reset + await expect( ZoomUtils.zoomCanvas ).toBeHidden(); + } ); + + await test.step( 'should respect manual zoom level set when closing from patterns tab when toggled twice', async () => { + await InserterUtils.openBlockLibrary(); + await InserterUtils.expectActiveTab( 'Blocks' ); + await expect( ZoomUtils.zoomCanvas ).toBeHidden(); + + await InserterUtils.activateTab( 'Patterns' ); + await expect( ZoomUtils.zoomCanvas ).toBeVisible(); + + await ZoomUtils.exitZoomOut(); + + await ZoomUtils.enterZoomOut(); - await inserterButton.click(); - await blockLibrary.getByRole( 'tab', { name: 'Blocks' } ).click(); - await blockLibrary.getByRole( 'option', { name: 'Buttons' } ).click(); + await InserterUtils.closeBlockLibrary(); - await expect - .poll( editor.getBlocks ) - .toMatchObject( [ ...beforeBlocks, { name: 'core/buttons' } ] ); + // Should stay zoomed out since it was manually engaged + await expect( ZoomUtils.zoomCanvas ).toBeVisible(); - await inserterButton.click(); + // Reset test + await ZoomUtils.exitZoomOut(); + } ); - await expect( blockLibrary ).toBeHidden(); + await test.step( 'should not reset zoom level if zoom level manually changed from blocks tab', async () => { + await InserterUtils.openBlockLibrary(); + await expect( InserterUtils.blockLibrary ).toBeVisible(); + + await InserterUtils.expectActiveTab( 'Blocks' ); + await expect( ZoomUtils.zoomCanvas ).toBeHidden(); + + await ZoomUtils.enterZoomOut(); + + await InserterUtils.closeBlockLibrary(); + + // Should stay zoomed out since it was manually engaged + await expect( ZoomUtils.zoomCanvas ).toBeVisible(); + } ); + } ); } ); } ); + +class InserterUtils { + constructor( { editor, page } ) { + this.editor = editor; + this.page = page; + this.blockLibrary = this.page.getByRole( 'region', { + name: 'Block Library', + } ); + this.inserterButton = this.page.getByRole( 'button', { + name: 'Block Inserter', + exact: true, + } ); + } + + // Manually naming as open and close these makes it clearer when reading + // through the test instead of using a toggle method with a boolean + async openBlockLibrary() { + await this.inserterButton.click(); + await expect( this.blockLibrary ).toBeVisible(); + } + + async closeBlockLibrary() { + await this.inserterButton.click(); + await expect( this.blockLibrary ).toBeHidden(); + } + + getBlockLibraryTab( name ) { + return this.page.getByRole( 'tab', { name } ); + } + + async expectActiveTab( name ) { + await expect( this.getBlockLibraryTab( name ) ).toHaveAttribute( + 'aria-selected', + 'true' + ); + } + + async activateTab( name ) { + await this.getBlockLibraryTab( name ).click(); + // For brevity, adding this check here. It should always be done after the tab is clicked + await this.expectActiveTab( name ); + } +} + +class ZoomUtils { + constructor( { editor, page } ) { + this.editor = editor; + this.page = page; + this.zoomCanvas = this.page.locator( '.is-zoomed-out' ); + this.zoomButton = this.page.getByRole( 'button', { + name: 'Zoom Out', + exact: true, + } ); + } + + // Manually naming as enter and exit these makes it clearer when reading + // through the test instead of using a toggle method with a boolean + async enterZoomOut() { + await this.zoomButton.click(); + await expect( this.zoomCanvas ).toBeVisible(); + } + + async exitZoomOut() { + await this.zoomButton.click(); + await expect( this.zoomCanvas ).toBeHidden(); + } +} diff --git a/test/e2e/specs/site-editor/site-editor-url-navigation.spec.js b/test/e2e/specs/site-editor/site-editor-url-navigation.spec.js index f26fb8e13b8c3c..a0cc0af5463aed 100644 --- a/test/e2e/specs/site-editor/site-editor-url-navigation.spec.js +++ b/test/e2e/specs/site-editor/site-editor-url-navigation.spec.js @@ -44,7 +44,7 @@ test.describe( 'Site editor url navigation', () => { .click(); await page.getByRole( 'option', { name: 'Demo' } ).click(); await expect( page ).toHaveURL( - '/wp-admin/site-editor.php?postId=emptytheme%2F%2Fsingle-post-demo&postType=wp_template&canvas=edit' + '/wp-admin/site-editor.php?p=%2Fwp_template%2Femptytheme%2F%2Fsingle-post-demo&canvas=edit' ); } ); @@ -63,7 +63,7 @@ test.describe( 'Site editor url navigation', () => { await page.type( 'role=dialog >> role=textbox[name="Name"i]', 'Demo' ); await page.keyboard.press( 'Enter' ); await expect( page ).toHaveURL( - '/wp-admin/site-editor.php?postId=emptytheme%2F%2Fdemo&postType=wp_template_part&canvas=edit' + '/wp-admin/site-editor.php?p=%2Fwp_template_part%2Femptytheme%2F%2Fdemo&canvas=edit' ); } ); diff --git a/test/e2e/specs/site-editor/style-book.spec.js b/test/e2e/specs/site-editor/style-book.spec.js index d860b05bc8f06d..350b1925385063 100644 --- a/test/e2e/specs/site-editor/style-book.spec.js +++ b/test/e2e/specs/site-editor/style-book.spec.js @@ -19,6 +19,9 @@ test.describe( 'Style Book', () => { } ); test.beforeEach( async ( { admin, editor, styleBook, page } ) => { + await page.addInitScript( () => { + window.__experimentalEditorWriteMode = true; + } ); await admin.visitSiteEditor(); await editor.canvas.locator( 'body' ).click(); await styleBook.open(); @@ -186,6 +189,30 @@ test.describe( 'Style Book', () => { } ); } ); +test.describe( 'Style Book for classic themes', () => { + test( 'Should show Style Book for a theme that supports it', async ( { + page, + admin, + requestUtils, + } ) => { + // Make sure a classic theme is active. + await requestUtils.activateTheme( 'twentytwentyone' ); + // Go to site editor. + await admin.visitAdminPage( 'site-editor.php' ); + + // Open the Style Book. + await page.getByRole( 'button', { name: 'Styles' } ).click(); + + // Block examples should be visible. + const blockExamples = page + .frameLocator( '[name="style-book-canvas"]' ) + .getByRole( 'grid', { + name: 'Examples of blocks', + } ); + await expect( blockExamples ).toBeVisible(); + } ); +} ); + class StyleBook { constructor( { page } ) { this.page = page; diff --git a/test/e2e/specs/site-editor/style-variations.spec.js b/test/e2e/specs/site-editor/style-variations.spec.js index 9c4243b0d171f6..8ec92ff657391b 100644 --- a/test/e2e/specs/site-editor/style-variations.spec.js +++ b/test/e2e/specs/site-editor/style-variations.spec.js @@ -160,15 +160,15 @@ test.describe( 'Global styles variations', () => { await page.click( 'role=button[name="Edit palette"i]' ); await expect( - page.locator( 'role=option[name="Color: Foreground"i]' ) + page.locator( 'role=option[name="Foreground"i]' ) ).toHaveCSS( 'background-color', 'rgb(74, 7, 74)' ); await expect( - page.locator( 'role=option[name="Color: Background"i]' ) + page.locator( 'role=option[name="Background"i]' ) ).toHaveCSS( 'background-color', 'rgb(202, 105, 211)' ); await expect( - page.locator( 'role=option[name="Color: Awesome pink"i]' ) + page.locator( 'role=option[name="Awesome pink"i]' ) ).toHaveCSS( 'background-color', 'rgba(204, 0, 255, 0.77)' ); } ); diff --git a/test/e2e/specs/site-editor/template-part-focus-mode.spec.js b/test/e2e/specs/site-editor/template-part-focus-mode.spec.js new file mode 100644 index 00000000000000..29e6788779ed98 --- /dev/null +++ b/test/e2e/specs/site-editor/template-part-focus-mode.spec.js @@ -0,0 +1,50 @@ +/** + * WordPress dependencies + */ +const { test, expect } = require( '@wordpress/e2e-test-utils-playwright' ); + +test.describe( 'Template Part Focus mode', () => { + test.beforeAll( async ( { requestUtils } ) => { + await requestUtils.activateTheme( 'twentytwentyfour' ); + } ); + + test.afterEach( async ( { requestUtils } ) => { + await requestUtils.activateTheme( 'twentytwentyone' ); + } ); + + test( 'Should navigate to template part and back.', async ( { + admin, + page, + editor, + } ) => { + await admin.visitAdminPage( 'site-editor.php?canvas=edit' ); + await editor.setPreferences( 'core/edit-site', { + welcomeGuide: false, + } ); + + // Check that we're editing the template + await expect( page.locator( 'h1' ) ).toContainText( 'Blog Home' ); + await expect( page.locator( 'h1' ) ).toContainText( 'Template' ); + + // Click Template Part + await editor.canvas + .getByRole( 'document', { + name: 'Header', + } ) + .click(); + + // Navigate to Focus mode + await editor.clickBlockToolbarButton( 'Edit' ); + + // Check if focus mode is active + await expect( page.locator( 'h1' ) ).toContainText( 'Header' ); + await expect( page.locator( 'h1' ) ).toContainText( 'Template Part' ); + + // Go back + await page.getByRole( 'button', { name: 'Back' } ).click(); + + // Check that we're editing the template + await expect( page.locator( 'h1' ) ).toContainText( 'Blog Home' ); + await expect( page.locator( 'h1' ) ).toContainText( 'Template' ); + } ); +} ); diff --git a/test/e2e/specs/site-editor/template-part.spec.js b/test/e2e/specs/site-editor/template-part.spec.js index d88273574bc4b0..9d5c0ca05b0d9c 100644 --- a/test/e2e/specs/site-editor/template-part.spec.js +++ b/test/e2e/specs/site-editor/template-part.spec.js @@ -375,7 +375,7 @@ test.describe( 'Template Part', () => { await editor.selectBlocks( siteTitle ); // Remove the default site title block. - await pageUtils.pressKeys( 'access+z' ); + await pageUtils.pressKeys( 'shift+Backspace' ); // Insert a group block with a Site Title block inside. await editor.insertBlock( { diff --git a/test/e2e/specs/site-editor/template-registration.spec.js b/test/e2e/specs/site-editor/template-registration.spec.js index 90e56645813c30..9856db97130386 100644 --- a/test/e2e/specs/site-editor/template-registration.spec.js +++ b/test/e2e/specs/site-editor/template-registration.spec.js @@ -94,23 +94,22 @@ test.describe( 'Block template registration', () => { ).toBeHidden(); } ); - test( 'registered templates are available in the Swap template screen', async ( { + test( 'registered templates are available in the Change template screen', async ( { admin, editor, page, } ) => { // Create a post. - await admin.visitAdminPage( '/post-new.php' ); - await page.getByLabel( 'Close', { exact: true } ).click(); + await admin.createNewPost(); await editor.insertBlock( { name: 'core/paragraph', attributes: { content: 'User-created post.' }, } ); - // Swap template. + // Change template. await page.getByRole( 'button', { name: 'Post', exact: true } ).click(); await page.getByRole( 'button', { name: 'Template options' } ).click(); - await page.getByRole( 'menuitem', { name: 'Swap template' } ).click(); + await page.getByRole( 'menuitem', { name: 'Change template' } ).click(); await page.getByText( 'Plugin Template' ).click(); // Verify the template is applied. @@ -128,16 +127,16 @@ test.describe( 'Block template registration', () => { blockTemplateRegistrationUtils, } ) => { // Create a post. - await admin.visitAdminPage( '/post-new.php' ); + await admin.createNewPost(); await editor.insertBlock( { name: 'core/paragraph', attributes: { content: 'User-created post.' }, } ); - // Swap template. + // Change template. await page.getByRole( 'button', { name: 'Post', exact: true } ).click(); await page.getByRole( 'button', { name: 'Template options' } ).click(); - await page.getByRole( 'menuitem', { name: 'Swap template' } ).click(); + await page.getByRole( 'menuitem', { name: 'Change template' } ).click(); await page.getByText( 'Custom', { exact: true } ).click(); // Verify the theme template is applied. @@ -319,7 +318,9 @@ test.describe( 'Block template registration', () => { .getByLabel( 'Dismiss this notice' ) .getByText( `"Author: Admin" reset.` ); await page.getByPlaceholder( 'Search' ).fill( 'Author: admin' ); - await page.getByRole( 'link', { name: 'Author: Admin' } ).click(); + await page + .locator( '.fields-field__title', { hasText: 'Author: Admin' } ) + .click(); const actions = page.getByLabel( 'Actions' ); await actions.first().click(); await page.getByRole( 'menuitem', { name: 'Reset' } ).click(); diff --git a/test/e2e/specs/site-editor/templates.spec.js b/test/e2e/specs/site-editor/templates.spec.js index c0dbadad2dbfb9..0c43fa02fc73b8 100644 --- a/test/e2e/specs/site-editor/templates.spec.js +++ b/test/e2e/specs/site-editor/templates.spec.js @@ -40,4 +40,27 @@ test.describe( 'Templates', () => { ) ).toBeVisible(); } ); + + test( 'Persists filter/search when switching layout', async ( { + page, + admin, + } ) => { + await admin.visitSiteEditor(); + await page.getByRole( 'button', { name: 'Templates' } ).click(); + + // Search templates + await page.getByRole( 'searchbox', { name: 'Search' } ).fill( 'Index' ); + + // Switch layout + await page.getByRole( 'button', { name: 'Layout' } ).click(); + await page.getByRole( 'menuitemradio', { name: 'Table' } ).click(); + + // Confirm the table is visible + await expect( page.getByRole( 'table' ) ).toContainText( 'Index' ); + + // The search should still contain the search term + await expect( + page.getByRole( 'searchbox', { name: 'Search' } ) + ).toHaveValue( 'Index' ); + } ); } ); diff --git a/test/e2e/specs/site-editor/user-global-styles-revisions.spec.js b/test/e2e/specs/site-editor/user-global-styles-revisions.spec.js index 91142d5395640c..1b5ab7b6cd557d 100644 --- a/test/e2e/specs/site-editor/user-global-styles-revisions.spec.js +++ b/test/e2e/specs/site-editor/user-global-styles-revisions.spec.js @@ -76,9 +76,11 @@ test.describe( 'Style Revisions', () => { await editor.canvas.locator( 'body' ).click(); await userGlobalStylesRevisions.openStylesPanel(); await page.getByRole( 'button', { name: 'Colors' } ).click(); - await page.getByRole( 'button', { name: 'Background' } ).click(); await page - .getByRole( 'option', { name: 'Color: Luminous vivid amber' } ) + .getByRole( 'button', { name: 'Background', exact: true } ) + .click(); + await page + .getByRole( 'option', { name: 'Luminous vivid amber' } ) .click( { force: true } ); await page.getByRole( 'button', { name: 'Revisions' } ).click(); diff --git a/test/e2e/specs/site-editor/zoom-out.spec.js b/test/e2e/specs/site-editor/zoom-out.spec.js index 464bd4a4a4efad..493b566671f8be 100644 --- a/test/e2e/specs/site-editor/zoom-out.spec.js +++ b/test/e2e/specs/site-editor/zoom-out.spec.js @@ -3,6 +3,79 @@ */ const { test, expect } = require( '@wordpress/e2e-test-utils-playwright' ); +const EDITOR_ZOOM_OUT_CONTENT = ` +<!-- wp:group {"tagName":"main","layout":{"type":"constrained"}} --> +<main class="wp-block-group"><!-- wp:group {"style":{"spacing":{"padding":{"top":"0","bottom":"0","left":"0","right":"0"}},"dimensions":{"minHeight":"100vh"}},"backgroundColor":"base-2","layout":{"type":"flex","orientation":"vertical","verticalAlignment":"space-between"}} --> +<div class="wp-block-group has-base-2-background-color has-background" style="min-height:100vh;padding-top:0;padding-right:0;padding-bottom:0;padding-left:0"><!-- wp:paragraph --> +<p>First Section Start</p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph {"style":{"layout":{"selfStretch":"fit","flexSize":null}}} --> +<p>First Section Center</p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>First Section End</p> +<!-- /wp:paragraph --></div> +<!-- /wp:group --> + +<!-- wp:group {"style":{"spacing":{"padding":{"top":"0","bottom":"0","left":"0","right":"0"}},"dimensions":{"minHeight":"100vh"}},"backgroundColor":"base","layout":{"type":"flex","orientation":"vertical","verticalAlignment":"space-between"}} --> +<div class="wp-block-group has-base-background-color has-background" style="min-height:100vh;padding-top:0;padding-right:0;padding-bottom:0;padding-left:0"><!-- wp:paragraph --> +<p>Second Section Start</p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph {"style":{"layout":{"selfStretch":"fit","flexSize":null}}} --> +<p>Second Section Center</p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Second Section End</p> +<!-- /wp:paragraph --></div> +<!-- /wp:group --> + +<!-- wp:group {"style":{"spacing":{"padding":{"top":"0","bottom":"0","left":"0","right":"0"}},"dimensions":{"minHeight":"100vh"}},"backgroundColor":"base-2","layout":{"type":"flex","orientation":"vertical","verticalAlignment":"space-between"}} --> +<div class="wp-block-group has-base-2-background-color has-background" style="min-height:100vh;padding-top:0;padding-right:0;padding-bottom:0;padding-left:0"><!-- wp:paragraph --> +<p>Third Section Start</p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph {"style":{"layout":{"selfStretch":"fit","flexSize":null}}} --> +<p>Third Section Center</p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Third Section End</p> +<!-- /wp:paragraph --></div> +<!-- /wp:group --> + +<!-- wp:group {"style":{"spacing":{"padding":{"top":"0","bottom":"0","left":"0","right":"0"}},"dimensions":{"minHeight":"100vh"}},"backgroundColor":"base","layout":{"type":"flex","orientation":"vertical","verticalAlignment":"space-between"}} --> +<div class="wp-block-group has-base-background-color has-background" style="min-height:100vh;padding-top:0;padding-right:0;padding-bottom:0;padding-left:0"><!-- wp:paragraph --> +<p>Fourth Section Start</p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph {"style":{"layout":{"selfStretch":"fit","flexSize":null}}} --> +<p>Fourth Section Center</p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Fourth Section End</p> +<!-- /wp:paragraph --></div> +<!-- /wp:group --></main> +<!-- /wp:group -->`; + +const EDITOR_ZOOM_OUT_CONTENT_NO_SECTION_ROOT = `<!-- wp:group {"style":{"spacing":{"padding":{"top":"0","bottom":"0","left":"0","right":"0"}},"dimensions":{"minHeight":"100vh"}},"backgroundColor":"base-2","layout":{"type":"flex","orientation":"vertical","verticalAlignment":"space-between"}} --> +<div class="wp-block-group has-base-2-background-color has-background" style="min-height:100vh;padding-top:0;padding-right:0;padding-bottom:0;padding-left:0"><!-- wp:paragraph --> +<p>First Section Start</p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph {"style":{"layout":{"selfStretch":"fit","flexSize":null}}} --> +<p>First Section Center</p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>First Section End</p> +<!-- /wp:paragraph --></div> +<!-- /wp:group -->`; + test.describe( 'Zoom Out', () => { test.beforeAll( async ( { requestUtils } ) => { await requestUtils.activateTheme( 'twentytwentyfour' ); @@ -10,6 +83,8 @@ test.describe( 'Zoom Out', () => { test.afterAll( async ( { requestUtils } ) => { await requestUtils.activateTheme( 'twentytwentyone' ); + await requestUtils.deleteAllTemplates( 'wp_template' ); + await requestUtils.deleteAllTemplates( 'wp_template_part' ); } ); test.beforeEach( async ( { admin } ) => { @@ -47,4 +122,173 @@ test.describe( 'Zoom Out', () => { expect( htmlRect.y + paddingTop ).toBeGreaterThan( iframeRect.y ); expect( htmlRect.x ).toBeGreaterThan( iframeRect.x ); } ); + + test( 'Toggling zoom state should keep content centered', async ( { + page, + editor, + } ) => { + // Add some patterns into the page. + await editor.setContent( EDITOR_ZOOM_OUT_CONTENT ); + // Find the scroll container element + await page.evaluate( () => { + const { activeElement } = + document.activeElement?.contentDocument ?? document; + window.scrollContainer = + window.wp.dom.getScrollContainer( activeElement ); + return window.scrollContainer; + } ); + + // Test: Test from top of page (scrollTop 0) + // Enter Zoom Out + await page.getByRole( 'button', { name: 'Zoom Out' } ).click(); + + const scrollTopZoomed = await page.evaluate( () => { + return window.scrollContainer.scrollTop; + } ); + + expect( scrollTopZoomed ).toBe( 0 ); + + // Exit Zoom Out + await page.getByRole( 'button', { name: 'Zoom Out' } ).click(); + + const scrollTopNoZoom = await page.evaluate( () => { + return window.scrollContainer.scrollTop; + } ); + + expect( scrollTopNoZoom ).toBe( 0 ); + + // Test: Should center the scroll position when zooming out/in + const firstSectionEnd = editor.canvas.locator( + 'text=First Section End' + ); + const secondSectionStart = editor.canvas.locator( + 'text=Second Section Start' + ); + const secondSectionCenter = editor.canvas.locator( + 'text=Second Section Center' + ); + const secondSectionEnd = editor.canvas.locator( + 'text=Second Section End' + ); + const thirdSectionStart = editor.canvas.locator( + 'text=Third Section Start' + ); + const thirdSectionCenter = editor.canvas.locator( + 'text=Third Section Center' + ); + const thirdSectionEnd = editor.canvas.locator( + 'text=Third Section End' + ); + const fourthSectionStart = editor.canvas.locator( + 'text=Fourth Section Start' + ); + + // Test for second section + // Playwright scrolls it to the center of the viewport, so this is what we scroll to. + await secondSectionCenter.scrollIntoViewIfNeeded(); + + // Because the text is spread with a group height of 100vh, they should both be visible. + await expect( firstSectionEnd ).not.toBeInViewport(); + await expect( secondSectionStart ).toBeInViewport(); + await expect( secondSectionEnd ).toBeInViewport(); + await expect( thirdSectionStart ).not.toBeInViewport(); + + // After zooming, if we zoomed out with the correct central point, they should both still be visible when toggling zoom out state + // Enter Zoom Out + await page.getByRole( 'button', { name: 'Zoom Out' } ).click(); + await expect( firstSectionEnd ).toBeInViewport(); + await expect( secondSectionStart ).toBeInViewport(); + await expect( secondSectionEnd ).toBeInViewport(); + await expect( thirdSectionStart ).toBeInViewport(); + + // Exit Zoom Out + await page.getByRole( 'button', { name: 'Zoom Out' } ).click(); + await expect( firstSectionEnd ).not.toBeInViewport(); + await expect( secondSectionStart ).toBeInViewport(); + await expect( secondSectionEnd ).toBeInViewport(); + await expect( thirdSectionStart ).not.toBeInViewport(); + + // Test for third section + // Playwright scrolls it to the center of the viewport, so this is what we scroll to. + await thirdSectionCenter.scrollIntoViewIfNeeded(); + + // Because the text is spread with a group height of 100vh, they should both be visible. + await expect( secondSectionEnd ).not.toBeInViewport(); + await expect( thirdSectionStart ).toBeInViewport(); + await expect( thirdSectionEnd ).toBeInViewport(); + await expect( fourthSectionStart ).not.toBeInViewport(); + + // After zooming, if we zoomed out with the correct central point, they should both still be visible when toggling zoom out state + // Enter Zoom Out + await page.getByRole( 'button', { name: 'Zoom Out' } ).click(); + await expect( secondSectionEnd ).toBeInViewport(); + await expect( thirdSectionStart ).toBeInViewport(); + await expect( thirdSectionEnd ).toBeInViewport(); + await expect( fourthSectionStart ).toBeInViewport(); + + // Exit Zoom Out + await page.getByRole( 'button', { name: 'Zoom Out' } ).click(); + await expect( secondSectionEnd ).not.toBeInViewport(); + await expect( thirdSectionStart ).toBeInViewport(); + await expect( thirdSectionEnd ).toBeInViewport(); + await expect( fourthSectionStart ).not.toBeInViewport(); + } ); + + test( 'Zoom out selected section has three items in options menu', async ( { + page, + } ) => { + // open the inserter + await page + .getByRole( 'button', { + name: 'Block Inserter', + exact: true, + } ) + .click(); + // switch to patterns tab + await page.getByRole( 'tab', { name: 'Patterns' } ).click(); + // search for a pattern + await page + .getByRole( 'searchbox', { name: 'Search' } ) + .fill( 'Footer' ); + // click on Footer with colophon, 3 columns + await page + .getByRole( 'option', { name: 'Footer with colophon, 3 columns' } ) + .click(); + + // open the block toolbar more settings menu + await page.getByLabel( 'Block tools' ).getByLabel( 'Options' ).click(); + + // get the length of the options menu + const optionsMenu = page + .getByRole( 'menu', { name: 'Options' } ) + .getByRole( 'menuitem' ); + + // we expect 3 items in the options menu + await expect( optionsMenu ).toHaveCount( 3 ); + } ); + + test( 'Zoom Out cannot be activated when the section root is missing', async ( { + page, + editor, + } ) => { + await editor.setContent( EDITOR_ZOOM_OUT_CONTENT_NO_SECTION_ROOT ); + + // Check that the Zoom Out toggle button is not visible. + await expect( + page.getByRole( 'button', { name: 'Zoom Out' } ) + ).toBeHidden(); + + // Check that activating the Patterns tab in the Inserter does not activate + // Zoom Out. + await page + .getByRole( 'button', { + name: 'Block Inserter', + exact: true, + } ) + .click(); + + await page.getByRole( 'tab', { name: 'Patterns' } ).click(); + + await expect( page.locator( '.is-zoomed-out' ) ).toBeHidden(); + } ); } ); diff --git a/test/e2e/specs/widgets/editing-widgets.spec.js b/test/e2e/specs/widgets/editing-widgets.spec.js index 019e07fe87daac..f4d160f8a36db3 100644 --- a/test/e2e/specs/widgets/editing-widgets.spec.js +++ b/test/e2e/specs/widgets/editing-widgets.spec.js @@ -573,7 +573,7 @@ test.describe( 'Widgets screen', () => { .getByRole( 'document', { name: 'Block: Paragraph' } ) .filter( { hasText: 'Second Paragraph' } ) .focus(); - await pageUtils.pressKeys( 'access+z' ); + await pageUtils.pressKeys( 'shift+Backspace' ); await widgetsScreen.saveWidgets(); await expect.poll( widgetsScreen.getWidgetAreaBlocks ).toMatchObject( { diff --git a/test/e2e/tsconfig.json b/test/e2e/tsconfig.json index 28d349fc19bef7..080d514f6f3634 100644 --- a/test/e2e/tsconfig.json +++ b/test/e2e/tsconfig.json @@ -2,11 +2,11 @@ "$schema": "https://json.schemastore.org/tsconfig.json", "extends": "../../tsconfig.base.json", "compilerOptions": { + "checkJs": false, "noEmit": true, - "emitDeclarationOnly": false, - "allowJs": true, - "checkJs": false + "rootDir": ".", + "types": [ "node" ] }, - "include": [ "**/*" ], + "include": [ "." ], "exclude": [] } diff --git a/test/integration/fixtures/blocks/core__button__deprecated-v10.serialized.html b/test/integration/fixtures/blocks/core__button__deprecated-v10.serialized.html index e4c7b89c794619..0bebe131629f29 100644 --- a/test/integration/fixtures/blocks/core__button__deprecated-v10.serialized.html +++ b/test/integration/fixtures/blocks/core__button__deprecated-v10.serialized.html @@ -1,3 +1,3 @@ <!-- wp:button {"fontFamily":"cambria-georgia"} --> -<div class="wp-block-button has-cambria-georgia-font-family"><a class="wp-block-button__link wp-element-button">My button</a></div> +<div class="wp-block-button"><a class="wp-block-button__link has-cambria-georgia-font-family wp-element-button">My button</a></div> <!-- /wp:button --> diff --git a/test/integration/fixtures/blocks/core__button__deprecated-v12.html b/test/integration/fixtures/blocks/core__button__deprecated-v12.html new file mode 100644 index 00000000000000..b62b6f0020569f --- /dev/null +++ b/test/integration/fixtures/blocks/core__button__deprecated-v12.html @@ -0,0 +1,15 @@ +<!-- wp:button {"fontSize":"xx-large"} --> +<div class="wp-block-button has-custom-font-size has-xx-large-font-size"><a class="wp-block-button__link wp-element-button">My button 1</a></div> +<!-- /wp:button --> + +<!-- wp:button {"style":{"typography":{"fontStyle":"normal","fontWeight":"800"}}} --> +<div class="wp-block-button" style="font-style:normal;font-weight:800"><a class="wp-block-button__link wp-element-button">My button 2</a></div> +<!-- /wp:button --> + +<!-- wp:button {"style":{"typography":{"letterSpacing":"39px"}}} --> +<div class="wp-block-button" style="letter-spacing:39px"><a class="wp-block-button__link wp-element-button">My button 3</a></div> +<!-- /wp:button --> + +<!-- wp:button {"tagName":"button","style":{"typography":{"letterSpacing":"39px"}}} --> +<div class="wp-block-button" style="letter-spacing:39px"><button type="button" class="wp-block-button__link wp-element-button">My button 4</button></div> +<!-- /wp:button --> diff --git a/test/integration/fixtures/blocks/core__button__deprecated-v12.json b/test/integration/fixtures/blocks/core__button__deprecated-v12.json new file mode 100644 index 00000000000000..2c204623dc252f --- /dev/null +++ b/test/integration/fixtures/blocks/core__button__deprecated-v12.json @@ -0,0 +1,59 @@ +[ + { + "name": "core/button", + "isValid": true, + "attributes": { + "tagName": "a", + "type": "button", + "text": "My button 1", + "fontSize": "xx-large" + }, + "innerBlocks": [] + }, + { + "name": "core/button", + "isValid": true, + "attributes": { + "tagName": "a", + "type": "button", + "text": "My button 2", + "style": { + "typography": { + "fontStyle": "normal", + "fontWeight": "800" + } + } + }, + "innerBlocks": [] + }, + { + "name": "core/button", + "isValid": true, + "attributes": { + "tagName": "a", + "type": "button", + "text": "My button 3", + "style": { + "typography": { + "letterSpacing": "39px" + } + } + }, + "innerBlocks": [] + }, + { + "name": "core/button", + "isValid": true, + "attributes": { + "tagName": "button", + "type": "button", + "text": "My button 4", + "style": { + "typography": { + "letterSpacing": "39px" + } + } + }, + "innerBlocks": [] + } +] diff --git a/test/integration/fixtures/blocks/core__button__deprecated-v12.parsed.json b/test/integration/fixtures/blocks/core__button__deprecated-v12.parsed.json new file mode 100644 index 00000000000000..d631bc600e49ac --- /dev/null +++ b/test/integration/fixtures/blocks/core__button__deprecated-v12.parsed.json @@ -0,0 +1,81 @@ +[ + { + "blockName": "core/button", + "attrs": { + "fontSize": "xx-large" + }, + "innerBlocks": [], + "innerHTML": "\n<div class=\"wp-block-button has-custom-font-size has-xx-large-font-size\"><a class=\"wp-block-button__link wp-element-button\">My button 1</a></div>\n", + "innerContent": [ + "\n<div class=\"wp-block-button has-custom-font-size has-xx-large-font-size\"><a class=\"wp-block-button__link wp-element-button\">My button 1</a></div>\n" + ] + }, + { + "blockName": null, + "attrs": {}, + "innerBlocks": [], + "innerHTML": "\n\n", + "innerContent": [ "\n\n" ] + }, + { + "blockName": "core/button", + "attrs": { + "style": { + "typography": { + "fontStyle": "normal", + "fontWeight": "800" + } + } + }, + "innerBlocks": [], + "innerHTML": "\n<div class=\"wp-block-button\" style=\"font-style:normal;font-weight:800\"><a class=\"wp-block-button__link wp-element-button\">My button 2</a></div>\n", + "innerContent": [ + "\n<div class=\"wp-block-button\" style=\"font-style:normal;font-weight:800\"><a class=\"wp-block-button__link wp-element-button\">My button 2</a></div>\n" + ] + }, + { + "blockName": null, + "attrs": {}, + "innerBlocks": [], + "innerHTML": "\n\n", + "innerContent": [ "\n\n" ] + }, + { + "blockName": "core/button", + "attrs": { + "style": { + "typography": { + "letterSpacing": "39px" + } + } + }, + "innerBlocks": [], + "innerHTML": "\n<div class=\"wp-block-button\" style=\"letter-spacing:39px\"><a class=\"wp-block-button__link wp-element-button\">My button 3</a></div>\n", + "innerContent": [ + "\n<div class=\"wp-block-button\" style=\"letter-spacing:39px\"><a class=\"wp-block-button__link wp-element-button\">My button 3</a></div>\n" + ] + }, + { + "blockName": null, + "attrs": {}, + "innerBlocks": [], + "innerHTML": "\n\n", + "innerContent": [ "\n\n" ] + }, + { + "blockName": "core/button", + "attrs": { + "tagName": "button", + "style": { + "typography": { + "letterSpacing": "39px" + } + } + }, + "innerBlocks": [], + "innerHTML": "\n<div class=\"wp-block-button\" style=\"letter-spacing:39px\"><button type=\"button\" class=\"wp-block-button__link wp-element-button\">My button 4</button></div>\n", + "innerContent": [ + "\n<div class=\"wp-block-button\" style=\"letter-spacing:39px\"><button type=\"button\" class=\"wp-block-button__link wp-element-button\">My button 4</button></div>\n" + ] + } +] diff --git a/test/integration/fixtures/blocks/core__button__deprecated-v12.serialized.html b/test/integration/fixtures/blocks/core__button__deprecated-v12.serialized.html new file mode 100644 index 00000000000000..8de25b59343b3f --- /dev/null +++ b/test/integration/fixtures/blocks/core__button__deprecated-v12.serialized.html @@ -0,0 +1,15 @@ +<!-- wp:button {"fontSize":"xx-large"} --> +<div class="wp-block-button"><a class="wp-block-button__link has-xx-large-font-size has-custom-font-size wp-element-button">My button 1</a></div> +<!-- /wp:button --> + +<!-- wp:button {"style":{"typography":{"fontStyle":"normal","fontWeight":"800"}}} --> +<div class="wp-block-button"><a class="wp-block-button__link wp-element-button" style="font-style:normal;font-weight:800">My button 2</a></div> +<!-- /wp:button --> + +<!-- wp:button {"style":{"typography":{"letterSpacing":"39px"}}} --> +<div class="wp-block-button"><a class="wp-block-button__link wp-element-button" style="letter-spacing:39px">My button 3</a></div> +<!-- /wp:button --> + +<!-- wp:button {"tagName":"button","style":{"typography":{"letterSpacing":"39px"}}} --> +<div class="wp-block-button"><button type="button" class="wp-block-button__link wp-element-button" style="letter-spacing:39px">My button 4</button></div> +<!-- /wp:button --> diff --git a/test/integration/fixtures/blocks/core__query-total.html b/test/integration/fixtures/blocks/core__query-total.html new file mode 100644 index 00000000000000..c561ea6fcf7d8f --- /dev/null +++ b/test/integration/fixtures/blocks/core__query-total.html @@ -0,0 +1 @@ +<!-- wp:query-total /--> \ No newline at end of file diff --git a/test/integration/fixtures/blocks/core__query-total.json b/test/integration/fixtures/blocks/core__query-total.json new file mode 100644 index 00000000000000..cf342c6a35f14b --- /dev/null +++ b/test/integration/fixtures/blocks/core__query-total.json @@ -0,0 +1,10 @@ +[ + { + "name": "core/query-total", + "isValid": true, + "attributes": { + "displayType": "total-results" + }, + "innerBlocks": [] + } +] diff --git a/test/integration/fixtures/blocks/core__query-total.parsed.json b/test/integration/fixtures/blocks/core__query-total.parsed.json new file mode 100644 index 00000000000000..99a770a68adfb2 --- /dev/null +++ b/test/integration/fixtures/blocks/core__query-total.parsed.json @@ -0,0 +1,9 @@ +[ + { + "blockName": "core/query-total", + "attrs": {}, + "innerBlocks": [], + "innerHTML": "", + "innerContent": [] + } +] diff --git a/test/integration/fixtures/blocks/core__query-total.serialized.html b/test/integration/fixtures/blocks/core__query-total.serialized.html new file mode 100644 index 00000000000000..059289daf7dada --- /dev/null +++ b/test/integration/fixtures/blocks/core__query-total.serialized.html @@ -0,0 +1 @@ +<!-- wp:query-total /--> diff --git a/test/integration/fixtures/blocks/core__separator-color.json b/test/integration/fixtures/blocks/core__separator-color.json index d32cb34f1871ec..d66a8b2e1a242e 100644 --- a/test/integration/fixtures/blocks/core__separator-color.json +++ b/test/integration/fixtures/blocks/core__separator-color.json @@ -4,7 +4,8 @@ "isValid": true, "attributes": { "opacity": "alpha-channel", - "backgroundColor": "accent" + "backgroundColor": "accent", + "tagName": "hr" }, "innerBlocks": [] } diff --git a/test/integration/fixtures/blocks/core__separator-custom-color.json b/test/integration/fixtures/blocks/core__separator-custom-color.json index 7b0e6c3a7127af..9b126f2b5be6c7 100644 --- a/test/integration/fixtures/blocks/core__separator-custom-color.json +++ b/test/integration/fixtures/blocks/core__separator-custom-color.json @@ -8,7 +8,8 @@ "color": { "background": "#5da54c" } - } + }, + "tagName": "hr" }, "innerBlocks": [] } diff --git a/test/integration/fixtures/blocks/core__separator.json b/test/integration/fixtures/blocks/core__separator.json index 3d69d4e24413c7..aaeb9bc916f38d 100644 --- a/test/integration/fixtures/blocks/core__separator.json +++ b/test/integration/fixtures/blocks/core__separator.json @@ -3,7 +3,8 @@ "name": "core/separator", "isValid": true, "attributes": { - "opacity": "alpha-channel" + "opacity": "alpha-channel", + "tagName": "hr" }, "innerBlocks": [] } diff --git a/test/integration/fixtures/blocks/core__separator__deprecated-color-v1.json b/test/integration/fixtures/blocks/core__separator__deprecated-color-v1.json index 6e0ddf0dbef553..29f520c266f597 100644 --- a/test/integration/fixtures/blocks/core__separator__deprecated-color-v1.json +++ b/test/integration/fixtures/blocks/core__separator__deprecated-color-v1.json @@ -4,7 +4,8 @@ "isValid": true, "attributes": { "backgroundColor": "accent", - "opacity": "css" + "opacity": "css", + "tagName": "hr" }, "innerBlocks": [] } diff --git a/test/integration/fixtures/blocks/core__separator__deprecated-custom-color-v1.json b/test/integration/fixtures/blocks/core__separator__deprecated-custom-color-v1.json index bc2de191b19cd3..556305633861bb 100644 --- a/test/integration/fixtures/blocks/core__separator__deprecated-custom-color-v1.json +++ b/test/integration/fixtures/blocks/core__separator__deprecated-custom-color-v1.json @@ -8,7 +8,8 @@ "color": { "background": "#cc1d1d" } - } + }, + "tagName": "hr" }, "innerBlocks": [] } diff --git a/test/integration/helpers/integration-test-editor.js b/test/integration/helpers/integration-test-editor.js index bc2e6f71a954fc..693c4f246fede4 100644 --- a/test/integration/helpers/integration-test-editor.js +++ b/test/integration/helpers/integration-test-editor.js @@ -36,7 +36,7 @@ const { ExperimentalBlockCanvas: BlockCanvas } = unlock( blockEditorPrivateApis ); -// Polyfill for String.prototype.replaceAll until CI is runnig Node 15 or higher. +// Polyfill for String.prototype.replaceAll until CI is running Node 15 or higher. if ( ! String.prototype.replaceAll ) { String.prototype.replaceAll = function ( str, newStr ) { // If a regex pattern diff --git a/test/native/integration-test-helpers/initialize-editor.js b/test/native/integration-test-helpers/initialize-editor.js index 511f0223e11356..3b89da979aee3a 100644 --- a/test/native/integration-test-helpers/initialize-editor.js +++ b/test/native/integration-test-helpers/initialize-editor.js @@ -10,6 +10,8 @@ import { v4 as uuid } from 'uuid'; import { createElement, cloneElement } from '@wordpress/element'; // eslint-disable-next-line no-restricted-imports import { initializeEditor as internalInitializeEditor } from '@wordpress/edit-post'; +import { store as coreStore } from '@wordpress/core-data'; +import { select } from '@wordpress/data'; /** * Internal dependencies @@ -28,6 +30,21 @@ import { getGlobalStyles } from './get-global-styles'; * @return {import('@testing-library/react-native').RenderAPI} A Testing Library screen. */ export async function initializeEditor( props, { component } = {} ) { + const resolutionSpy = jest.spyOn( + select( coreStore ), + 'hasFinishedResolution' + ); + const actualResolution = resolutionSpy.getMockImplementation(); + resolutionSpy.mockImplementation( ( selectorName, args ) => { + // The mobile editor only supports the `post-only` rendering mode, so we + // presume a resolved `getPostType` selector to unblock editor rendering. + if ( 'getPostType' === selectorName ) { + return true; + } + + return actualResolution( selectorName, args ); + } ); + const uniqueId = uuid(); const postId = `post-id-${ uniqueId }`; const postType = 'post'; diff --git a/test/native/integration/editor-history.native.js b/test/native/integration/editor-history.native.js index 9b2c212d17ee75..e111b4fcd40987 100644 --- a/test/native/integration/editor-history.native.js +++ b/test/native/integration/editor-history.native.js @@ -110,7 +110,7 @@ describe( 'Editor History', () => { const paragraphTextInput = within( paragraphBlock ).getByPlaceholderText( 'Start writing…' ); typeInRichText( paragraphTextInput, 'A quick brown fox' ); - // Artifical delay to create two history entries for typing + // Artificial delay to create two history entries for typing await new Promise( ( resolve ) => setTimeout( resolve, 1000 ) ); typeInRichText( paragraphTextInput, ' jumps over the lazy dog.' ); @@ -177,7 +177,7 @@ describe( 'Editor History', () => { 'A quick brown fox jumps over the lazy dog.', { finalSelectionStart: 2, finalSelectionEnd: 7 } ); - // Artifical delay to create two history entries for typing and formatting. + // Artificial delay to create two history entries for typing and formatting. await new Promise( ( resolve ) => setTimeout( resolve, 1000 ) ); fireEvent.press( screen.getByLabelText( 'Bold' ) ); fireEvent.press( screen.getByLabelText( 'Italic' ) ); diff --git a/test/performance/fixtures/perf-utils.ts b/test/performance/fixtures/perf-utils.ts index 592e8194852e3b..8d23d91ff91bfd 100644 --- a/test/performance/fixtures/perf-utils.ts +++ b/test/performance/fixtures/perf-utils.ts @@ -97,6 +97,26 @@ export class PerfUtils { return canvas; } + /** + * Change the rendering mode of the editor. + * + * Setting the rendering mode to something other than the default is sometimes + * needed when for example we want to update the contents of the editor from a + * HTML file. Calling the resetBlocks method of the core/block-editor store will + * replace the contents of the template if the rendering mode is not post-only. + * So this should always be called before the resetBlocks method is used. + * + * @param newRenderingMode Rendering mode to set + * + * @return Promise<void> + */ + async setRenderingMode( newRenderingMode: string ) { + await this.page.evaluate( ( _newRenderingMode ) => { + const { dispatch } = window.wp.data; + dispatch( 'core/editor' ).setRenderingMode( _newRenderingMode ); + }, newRenderingMode ); + } + /** * Loads blocks from the small post with containers fixture into the editor * canvas. diff --git a/test/performance/playwright.config.ts b/test/performance/playwright.config.ts index fafca3a589122f..75e87c4d2d0f00 100644 --- a/test/performance/playwright.config.ts +++ b/test/performance/playwright.config.ts @@ -8,7 +8,7 @@ import { defineConfig } from '@playwright/test'; /** * WordPress dependencies */ -const baseConfig = require( '@wordpress/scripts/config/playwright.config' ); +import baseConfig from '@wordpress/scripts/config/playwright.config.js'; process.env.ASSETS_PATH = path.join( __dirname, 'assets' ); diff --git a/test/performance/specs/site-editor.spec.js b/test/performance/specs/site-editor.spec.js index 9c9d8aec71da4a..5a0c7f0e952116 100644 --- a/test/performance/specs/site-editor.spec.js +++ b/test/performance/specs/site-editor.spec.js @@ -64,6 +64,7 @@ test.describe( 'Site Editor Performance', () => { test( 'Setup the test page', async ( { admin, perfUtils } ) => { await admin.createNewPost( { postType: 'page' } ); + await perfUtils.setRenderingMode( 'post-only' ); await perfUtils.loadBlocksForLargePost(); draftId = await perfUtils.saveDraft(); @@ -122,6 +123,7 @@ test.describe( 'Site Editor Performance', () => { test( 'Setup the test post', async ( { admin, editor, perfUtils } ) => { await admin.createNewPost( { postType: 'page' } ); + await perfUtils.setRenderingMode( 'post-only' ); await perfUtils.loadBlocksForLargePost(); await editor.insertBlock( { name: 'core/paragraph' } ); @@ -235,7 +237,9 @@ test.describe( 'Site Editor Performance', () => { } await metrics.startTracing(); - await page.getByText( 'Single Posts', { exact: true } ).click(); + await page + .getByText( 'Single Posts', { exact: true } ) + .click( { force: true } ); await metrics.stopTracing(); // Get the durations. @@ -391,7 +395,7 @@ test.describe( 'Site Editor Performance', () => { await requestUtils.activateTheme( 'twentytwentyfour' ); } ); - const perPage = 20; + const perPage = 9; test( 'Run the test', async ( { page, admin, requestUtils } ) => { await Promise.all( diff --git a/test/performance/tsconfig.json b/test/performance/tsconfig.json index 28d349fc19bef7..080d514f6f3634 100644 --- a/test/performance/tsconfig.json +++ b/test/performance/tsconfig.json @@ -2,11 +2,11 @@ "$schema": "https://json.schemastore.org/tsconfig.json", "extends": "../../tsconfig.base.json", "compilerOptions": { + "checkJs": false, "noEmit": true, - "emitDeclarationOnly": false, - "allowJs": true, - "checkJs": false + "rootDir": ".", + "types": [ "node" ] }, - "include": [ "**/*" ], + "include": [ "." ], "exclude": [] } diff --git a/test/storybook-playwright/storybook/main.js b/test/storybook-playwright/storybook/main.js index b80833ca725f96..f68f586f477200 100644 --- a/test/storybook-playwright/storybook/main.js +++ b/test/storybook-playwright/storybook/main.js @@ -5,7 +5,10 @@ const baseConfig = require( '../../../storybook/main' ); const config = { ...baseConfig, - addons: [ '@storybook/addon-toolbars' ], + addons: [ + '@storybook/addon-toolbars', + '@storybook/addon-webpack5-compiler-babel', + ], docs: undefined, staticDirs: undefined, stories: [ diff --git a/tools/webpack/blocks.js b/tools/webpack/blocks.js index 02a379d3cdae20..0bf72c58ba5688 100644 --- a/tools/webpack/blocks.js +++ b/tools/webpack/blocks.js @@ -3,27 +3,18 @@ */ const CopyWebpackPlugin = require( 'copy-webpack-plugin' ); const { join, sep, basename } = require( 'path' ); -const fastGlob = require( 'fast-glob' ); const { realpathSync } = require( 'fs' ); /** * WordPress dependencies */ -const DependencyExtractionWebpackPlugin = require( '@wordpress/dependency-extraction-webpack-plugin' ); -const { PhpFilePathsPlugin } = require( '@wordpress/scripts/utils' ); +const PhpFilePathsPlugin = require( '@wordpress/scripts/plugins/php-file-paths-plugin' ); /** * Internal dependencies */ const { baseConfig, plugins, stylesTransform } = require( './shared' ); -/* - * Matches a block's filepaths in the form build-module/<filename>.js - */ -const blockViewRegex = new RegExp( - /build-module\/(?<filename>.*\/view.*).js$/ -); - /** * We need to automatically rename some functions when they are called inside block files, * but have been declared elsewhere. This way we can call Gutenberg override functions, but @@ -50,48 +41,16 @@ function escapeRegExp( string ) { return string.replace( /[\\^$.*+?()[\]{}|]/g, '\\$&' ); } -const createEntrypoints = () => { - /* - * Returns an array of paths to block view files within the `@wordpress/block-library` package. - * These paths can be matched by the regex `blockViewRegex` in order to extract - * the block's filename. All blocks were migrated to script modules but the Form block. - * - * Returns an empty array if no files were found. - */ - const blockViewScriptPaths = fastGlob.sync( - './packages/block-library/build-module/form/view.js' - ); - - /* - * Go through the paths found above, in order to define webpack entry points for - * each block's view.js file. - */ - return blockViewScriptPaths.reduce( ( entries, scriptPath ) => { - const result = scriptPath.match( blockViewRegex ); - if ( ! result?.groups?.filename ) { - return entries; - } - - return { - ...entries, - [ result.groups.filename ]: scriptPath, - }; - }, {} ); -}; - module.exports = [ { ...baseConfig, name: 'blocks', - entry: createEntrypoints(), + entry: {}, output: { - devtoolNamespace: 'wp', - filename: './build/block-library/blocks/[name].min.js', path: join( __dirname, '..', '..' ), }, plugins: [ ...plugins, - new DependencyExtractionWebpackPlugin( { injectPolyfill: false } ), new PhpFilePathsPlugin( { context: './packages/block-library/src/', props: [ 'render', 'variations' ], diff --git a/tools/webpack/packages.js b/tools/webpack/packages.js index 1dc396a4d75a15..c99c25ee0127ce 100644 --- a/tools/webpack/packages.js +++ b/tools/webpack/packages.js @@ -36,10 +36,12 @@ const WORDPRESS_NAMESPACE = '@wordpress/'; // !! const BUNDLED_PACKAGES = [ '@wordpress/dataviews', + '@wordpress/dataviews/wp', '@wordpress/icons', '@wordpress/interface', '@wordpress/sync', '@wordpress/undo-manager', + '@wordpress/upload-media', '@wordpress/fields', ]; diff --git a/tsconfig.base.json b/tsconfig.base.json index a766eedaeddcaa..38c6ac761aab64 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -31,8 +31,12 @@ "resolveJsonModule": true, "typeRoots": [ "./typings", "./node_modules/@types" ], - "types": [] + "types": [], + + "rootDir": "${configDir}/src", + "declarationDir": "${configDir}/build-types" }, + "include": [ "${configDir}/src" ], "exclude": [ "**/*.android.js", "**/*.ios.js", diff --git a/tsconfig.json b/tsconfig.json index 51bb7f2d68924a..d6bbcb27f0adb6 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -21,6 +21,7 @@ { "path": "packages/dom" }, { "path": "packages/dom-ready" }, { "path": "packages/e2e-test-utils-playwright" }, + { "path": "packages/edit-site" }, { "path": "packages/editor" }, { "path": "packages/element" }, { "path": "packages/escape-html" }, @@ -50,14 +51,18 @@ { "path": "packages/report-flaky-tests" }, { "path": "packages/rich-text" }, { "path": "packages/router" }, + { "path": "packages/shortcode" }, { "path": "packages/style-engine" }, { "path": "packages/sync" }, { "path": "packages/token-list" }, { "path": "packages/undo-manager" }, + { "path": "packages/upload-media" }, { "path": "packages/url" }, { "path": "packages/vips" }, { "path": "packages/warning" }, - { "path": "packages/wordcount" } + { "path": "packages/wordcount" }, + { "path": "test/e2e" }, + { "path": "test/performance" } ], "files": [] }