diff --git a/.eslintrc.js b/.eslintrc.js index 22cb9209b2e9e8..3f2e466a78ff25 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -36,6 +36,161 @@ const typedFiles = glob( 'packages/*/package.json' ) .filter( ( fileName ) => require( join( __dirname, fileName ) ).types ) .map( ( fileName ) => fileName.replace( 'package.json', '**/*.js' ) ); +const restrictedImports = [ + { + name: 'framer-motion', + message: + 'Please use the Framer Motion API through `@wordpress/components` instead.', + }, + { + name: 'lodash', + importNames: [ + 'camelCase', + 'capitalize', + 'castArray', + 'chunk', + 'clamp', + 'cloneDeep', + 'compact', + 'concat', + 'countBy', + 'debounce', + 'deburr', + 'defaults', + 'defaultTo', + 'delay', + 'difference', + 'differenceWith', + 'dropRight', + 'each', + 'escape', + 'escapeRegExp', + 'every', + 'extend', + 'filter', + 'find', + 'findIndex', + 'findKey', + 'findLast', + 'first', + 'flatMap', + 'flatten', + 'flattenDeep', + 'flow', + 'flowRight', + 'forEach', + 'fromPairs', + 'has', + 'identity', + 'includes', + 'invoke', + 'isArray', + 'isBoolean', + 'isEqual', + 'isFinite', + 'isFunction', + 'isMatch', + 'isNil', + 'isNumber', + 'isObject', + 'isObjectLike', + 'isPlainObject', + 'isString', + 'isUndefined', + 'keyBy', + 'keys', + 'last', + 'lowerCase', + 'mapKeys', + 'maxBy', + 'memoize', + 'negate', + 'noop', + 'nth', + 'omitBy', + 'once', + 'orderby', + 'overEvery', + 'partial', + 'partialRight', + 'pick', + 'random', + 'reduce', + 'reject', + 'repeat', + 'reverse', + 'size', + 'snakeCase', + 'some', + 'sortBy', + 'startCase', + 'startsWith', + 'stubFalse', + 'stubTrue', + 'sum', + 'sumBy', + 'take', + 'throttle', + 'times', + 'toString', + 'trim', + 'truncate', + 'unionBy', + 'uniq', + 'uniqBy', + 'uniqueId', + 'uniqWith', + 'upperFirst', + 'values', + 'without', + 'words', + 'xor', + 'zip', + ], + message: + 'This Lodash method is not recommended. Please use native functionality instead. If using `memoize`, please use `memize` instead.', + }, + { + name: 'reakit', + message: + 'Please use Reakit API through `@wordpress/components` instead.', + }, + { + name: 'redux', + importNames: [ 'combineReducers' ], + message: 'Please use `combineReducers` from `@wordpress/data` instead.', + }, + { + name: 'puppeteer-testing-library', + message: '`puppeteer-testing-library` is still experimental.', + }, + { + name: '@emotion/css', + message: + 'Please use `@emotion/react` and `@emotion/styled` in order to maintain iframe support. As a replacement for the `cx` function, please use the `useCx` hook defined in `@wordpress/components` instead.', + }, + { + name: '@wordpress/edit-post', + message: + "edit-post is a WordPress top level package that shouldn't be imported into other packages", + }, + { + name: '@wordpress/edit-site', + message: + "edit-site is a WordPress top level package that shouldn't be imported into other packages", + }, + { + name: '@wordpress/edit-widgets', + message: + "edit-widgets is a WordPress top level package that shouldn't be imported into other packages", + }, + { + name: '@wordpress/edit-navigation', + message: + "edit-navigation is a WordPress top level package that shouldn't be imported into other packages", + }, +]; + module.exports = { root: true, extends: [ @@ -70,137 +225,7 @@ module.exports = { 'no-restricted-imports': [ 'error', { - paths: [ - { - name: 'framer-motion', - message: - 'Please use the Framer Motion API through `@wordpress/components` instead.', - }, - { - name: 'lodash', - importNames: [ - 'camelCase', - 'capitalize', - 'castArray', - 'chunk', - 'clamp', - 'cloneDeep', - 'compact', - 'concat', - 'countBy', - 'debounce', - 'deburr', - 'defaults', - 'defaultTo', - 'delay', - 'difference', - 'differenceWith', - 'dropRight', - 'each', - 'escape', - 'escapeRegExp', - 'every', - 'extend', - 'findIndex', - 'findKey', - 'findLast', - 'first', - 'flatMap', - 'flatten', - 'flattenDeep', - 'flow', - 'flowRight', - 'forEach', - 'fromPairs', - 'has', - 'identity', - 'includes', - 'invoke', - 'isArray', - 'isBoolean', - 'isFinite', - 'isFunction', - 'isMatch', - 'isNil', - 'isNumber', - 'isObject', - 'isObjectLike', - 'isPlainObject', - 'isString', - 'isUndefined', - 'keyBy', - 'keys', - 'last', - 'lowerCase', - 'mapKeys', - 'maxBy', - 'memoize', - 'negate', - 'noop', - 'nth', - 'omitBy', - 'once', - 'overEvery', - 'partial', - 'partialRight', - 'random', - 'reduce', - 'reject', - 'repeat', - 'reverse', - 'size', - 'snakeCase', - 'some', - 'sortBy', - 'startCase', - 'startsWith', - 'stubFalse', - 'stubTrue', - 'sum', - 'sumBy', - 'take', - 'throttle', - 'times', - 'toString', - 'trim', - 'truncate', - 'unionBy', - 'uniq', - 'uniqBy', - 'uniqueId', - 'uniqWith', - 'upperFirst', - 'values', - 'without', - 'words', - 'xor', - 'zip', - ], - message: - 'This Lodash method is not recommended. Please use native functionality instead. If using `memoize`, please use `memize` instead.', - }, - { - name: 'reakit', - message: - 'Please use Reakit API through `@wordpress/components` instead.', - }, - { - name: 'redux', - importNames: [ 'combineReducers' ], - message: - 'Please use `combineReducers` from `@wordpress/data` instead.', - }, - { - name: 'puppeteer-testing-library', - message: - '`puppeteer-testing-library` is still experimental.', - }, - { - name: '@emotion/css', - message: - 'Please use `@emotion/react` and `@emotion/styled` in order to maintain iframe support. As a replacement for the `cx` function, please use the `useCx` hook defined in `@wordpress/components` instead.', - }, - ], + paths: restrictedImports, }, ], '@typescript-eslint/no-restricted-imports': [ @@ -347,7 +372,6 @@ module.exports = { 'plugin:testing-library/react', ], rules: { - 'testing-library/no-container': 'off', 'testing-library/no-node-access': 'off', }, }, @@ -418,5 +442,28 @@ module.exports = { plugins: [ 'ssr-friendly' ], extends: [ 'plugin:ssr-friendly/recommended' ], }, + { + files: [ 'packages/block-editor/**' ], + rules: { + 'no-restricted-imports': [ + 'error', + { + paths: [ + ...restrictedImports, + { + name: '@wordpress/api-fetch', + message: + "block-editor is a generic package that doesn't depend on a server or WordPress backend. To provide WordPress integration, consider passing settings to the BlockEditorProvider components.", + }, + { + name: '@wordpress/core-data', + message: + "block-editor is a generic package that doesn't depend on a server or WordPress backend. To provide WordPress integration, consider passing settings to the BlockEditorProvider components.", + }, + ], + }, + ], + }, + }, ], }; diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 2cc7b0ce34bd1e..6427516035a5ec 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -87,7 +87,6 @@ /packages/compose @ajitbohra /packages/element @ajitbohra /packages/notices @ajitbohra -/packages/nux @ajitbohra /packages/viewport @ajitbohra /packages/base-styles /packages/icons diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 2ac98e4c82aef5..1798e22778e386 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -16,4 +16,7 @@ https://github.com/WordPress/gutenberg/blob/trunk/CONTRIBUTING.md --> +### Testing Instructions for Keyboard + + ## Screenshots or screencast diff --git a/.github/workflows/end2end-test-playwright.yml b/.github/workflows/end2end-test-playwright.yml deleted file mode 100644 index e0aabb17afa019..00000000000000 --- a/.github/workflows/end2end-test-playwright.yml +++ /dev/null @@ -1,66 +0,0 @@ -name: End-to-End Tests Playwright - -on: - pull_request: - push: - branches: - - trunk - - 'release/**' - - 'wp/**' - -# 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: - e2e: - name: E2E Tests - runs-on: ubuntu-latest - if: ${{ github.repository == 'WordPress/gutenberg' || github.event_name == 'pull_request' }} - strategy: - fail-fast: false - - steps: - - uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # v3.1.0 - - - name: Use desired version of NodeJS - uses: actions/setup-node@8c91899e586c5b171469028077307d293428b516 # v3.5.1 - with: - node-version-file: '.nvmrc' - cache: npm - - - name: Npm install and build - run: | - npm ci - npm run build - - - name: Install Playwright dependencies - run: | - npx playwright install chromium firefox webkit --with-deps - - - name: Install WordPress and start the server - run: | - npm run wp-env start - - - name: Run the tests - run: | - xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- npm run test:e2e:playwright - - - name: Archive debug artifacts (screenshots, traces) - uses: actions/upload-artifact@83fd05a356d7e2593de66fc9913b3002723633cb # v3.1.1 - if: always() - with: - name: failures-artifacts - path: artifacts/test-results - if-no-files-found: ignore - - - name: Archive flaky tests report - uses: actions/upload-artifact@83fd05a356d7e2593de66fc9913b3002723633cb # v3.1.1 - if: always() - with: - name: flaky-tests-report-playwright - path: flaky-tests - if-no-files-found: ignore diff --git a/.github/workflows/end2end-test.yml b/.github/workflows/end2end-test.yml index 172d3b090eaf0b..46278d6384a5b4 100644 --- a/.github/workflows/end2end-test.yml +++ b/.github/workflows/end2end-test.yml @@ -16,8 +16,8 @@ concurrency: cancel-in-progress: true jobs: - admin: - name: Admin - ${{ matrix.part }} + e2e-puppeteer: + name: Puppeteer - ${{ matrix.part }} runs-on: ubuntu-latest if: ${{ github.repository == 'WordPress/gutenberg' || github.event_name == 'pull_request' }} strategy: @@ -60,6 +60,97 @@ jobs: uses: actions/upload-artifact@83fd05a356d7e2593de66fc9913b3002723633cb # v3.1.1 if: always() with: - name: flaky-tests-report-${{ matrix.part }} + name: flaky-tests-report path: flaky-tests if-no-files-found: ignore + + e2e-playwright: + name: Playwright + runs-on: ubuntu-latest + if: ${{ github.repository == 'WordPress/gutenberg' || github.event_name == 'pull_request' }} + strategy: + fail-fast: false + + steps: + - uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # v3.1.0 + + - name: Use desired version of NodeJS + uses: actions/setup-node@8c91899e586c5b171469028077307d293428b516 # v3.5.1 + with: + node-version-file: '.nvmrc' + cache: npm + + - name: Npm install and build + run: | + npm ci + npm run build + + - name: Install Playwright dependencies + run: | + npx playwright install chromium firefox webkit --with-deps + + - name: Install WordPress and start the server + run: | + npm run wp-env start + + - name: Run the tests + run: | + xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- npm run test:e2e:playwright + + - name: Archive debug artifacts (screenshots, traces) + uses: actions/upload-artifact@83fd05a356d7e2593de66fc9913b3002723633cb # v3.1.1 + if: always() + with: + name: failures-artifacts + path: artifacts/test-results + if-no-files-found: ignore + + - name: Archive flaky tests report + uses: actions/upload-artifact@83fd05a356d7e2593de66fc9913b3002723633cb # v3.1.1 + if: always() + with: + name: flaky-tests-report + path: flaky-tests + if-no-files-found: ignore + + report-to-issues: + name: Report to GitHub + needs: [e2e-puppeteer, e2e-playwright] + if: ${{ always() }} + runs-on: ubuntu-latest + steps: + # Checkout defaults to using the branch which triggered the event, which + # isn't necessarily `trunk` (e.g. in the case of a merge). + - uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # v3.1.0 + with: + ref: trunk + + - uses: actions/download-artifact@v3 + id: download_artifact + # Don't fail the job if there isn't any flaky tests report. + continue-on-error: true + with: + name: flaky-tests-report + path: flaky-tests + + - name: Use desired version of NodeJS + if: ${{ steps.download_artifact.outcome == 'success' }} + uses: actions/setup-node@8c91899e586c5b171469028077307d293428b516 # v3.5.1 + with: + node-version-file: '.nvmrc' + cache: npm + + - name: Npm install and build + if: ${{ steps.download_artifact.outcome == 'success' }} + # TODO: We don't have to build the entire project, just the action itself. + run: | + npm ci + npm run build:packages + + - name: Report flaky tests + if: ${{ steps.download_artifact.outcome == 'success' }} + uses: ./packages/report-flaky-tests + with: + repo-token: '${{ secrets.GITHUB_TOKEN }}' + label: '[Type] Flaky Test' + artifact-path: flaky-tests diff --git a/.github/workflows/flaky-tests.yml b/.github/workflows/flaky-tests.yml deleted file mode 100644 index 6f457f6b2292c5..00000000000000 --- a/.github/workflows/flaky-tests.yml +++ /dev/null @@ -1,38 +0,0 @@ -name: Report Flaky Tests - -on: - workflow_run: - workflows: ['End-to-End Tests', 'End-to-End Tests Playwright'] - types: - - completed - -jobs: - report-to-issues: - name: Report to GitHub issues - runs-on: ubuntu-latest - if: ${{ github.event.workflow_run.conclusion == 'success' }} - steps: - # Checkout defaults to using the branch which triggered the event, which - # isn't necessarily `trunk` (e.g. in the case of a merge). - - uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # v3.1.0 - with: - ref: trunk - - - name: Use desired version of NodeJS - uses: actions/setup-node@8c91899e586c5b171469028077307d293428b516 # v3.5.1 - with: - node-version-file: '.nvmrc' - cache: npm - - - name: Npm install and build - # TODO: We don't have to build the entire project, just the action itself. - run: | - npm ci - npm run build:packages - - - name: Report flaky tests - uses: ./packages/report-flaky-tests - with: - repo-token: '${{ secrets.GITHUB_TOKEN }}' - label: '[Type] Flaky Test' - artifact-name-prefix: flaky-tests-report diff --git a/.github/workflows/gradle-wrapper-validation.yml b/.github/workflows/gradle-wrapper-validation.yml new file mode 100644 index 00000000000000..390f121b570f4a --- /dev/null +++ b/.github/workflows/gradle-wrapper-validation.yml @@ -0,0 +1,10 @@ +name: 'Validate Gradle Wrapper' +on: [push, pull_request] + +jobs: + validation: + name: 'Validation' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: gradle/wrapper-validation-action@v1 diff --git a/.github/workflows/rnmobile-android-runner.yml b/.github/workflows/rnmobile-android-runner.yml index fec513d88e6355..093c2a99ad4a07 100644 --- a/.github/workflows/rnmobile-android-runner.yml +++ b/.github/workflows/rnmobile-android-runner.yml @@ -14,11 +14,12 @@ concurrency: jobs: test: - runs-on: macos-latest + runs-on: macos-12 if: ${{ github.repository == 'WordPress/gutenberg' || github.event_name == 'pull_request' }} strategy: matrix: native-test-name: [gutenberg-editor-initial-html] + api-level: [29] steps: - name: checkout @@ -29,7 +30,6 @@ jobs: with: distribution: 'temurin' java-version: '11' - cache: 'gradle' - name: Use desired version of NodeJS uses: actions/setup-node@8c91899e586c5b171469028077307d293428b516 # v3.5.1 @@ -39,17 +39,39 @@ jobs: - run: npm ci - - name: Restore Gradle cache + - name: Gradle cache + uses: gradle/gradle-build-action@3fbe033aaae657f011f88f29be9e65ed26bd29ef # v2.3.3 + + - name: AVD cache uses: actions/cache@9b0c1fce7a93df8e3bb8926b0d6e9d89e92f20a7 # v3.0.11 + id: avd-cache + with: + path: | + ~/.android/avd/* + ~/.android/adb* + key: avd-${{ matrix.api-level }} + + - name: Create AVD and generate snapshot for caching + if: steps.avd-cache.outputs.cache-hit != 'true' + uses: reactivecircus/android-emulator-runner@50986b1464923454c95e261820bc626f38490ec0 # v2.27.0 with: - path: ~/.gradle/caches - key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }} + api-level: ${{ matrix.api-level }} + force-avd-creation: false + emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none + disable-animations: false + arch: x86_64 + profile: Nexus 6 + script: echo "Generated AVD snapshot for caching." - - uses: reactivecircus/android-emulator-runner@50986b1464923454c95e261820bc626f38490ec0 # v2.27.0 + - name: Run tests + uses: reactivecircus/android-emulator-runner@50986b1464923454c95e261820bc626f38490ec0 # v2.27.0 with: - api-level: 28 - emulator-build: 7425822 # https://github.com/ReactiveCircus/android-emulator-runner/issues/160#issuecomment-868615730 - profile: pixel_xl + api-level: ${{ matrix.api-level }} + force-avd-creation: false + emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none + disable-animations: true + arch: x86_64 + profile: Nexus 6 script: npm run native test:e2e:android:local ${{ matrix.native-test-name }} - uses: actions/upload-artifact@83fd05a356d7e2593de66fc9913b3002723633cb # v3.1.1 diff --git a/bin/plugin/commands/performance.js b/bin/plugin/commands/performance.js index a7f9c81e8cebb3..81aa39820c8b5a 100644 --- a/bin/plugin/commands/performance.js +++ b/bin/plugin/commands/performance.js @@ -37,6 +37,7 @@ const config = require( '../config' ); * @property {number[]} firstContentfulPaint Represents the time when the browser first renders any text or media. * @property {number[]} firstBlock Represents the time when Puppeteer first sees a block selector in the DOM. * @property {number[]} type Average type time. + * @property {number[]} typeContainer Average type time within a container. * @property {number[]} focus Average block selection time. * @property {number[]} inserterOpen Average time to open global inserter. * @property {number[]} inserterSearch Average time to search the inserter. @@ -56,6 +57,9 @@ const config = require( '../config' ); * @property {number=} type Average type time. * @property {number=} minType Minimum type time. * @property {number=} maxType Maximum type time. + * @property {number=} typeContainer Average type time within a container. + * @property {number=} minTypeContainer Minimum type time within a container. + * @property {number=} maxTypeContainer Maximum type time within a container. * @property {number=} focus Average block selection time. * @property {number=} minFocus Min block selection time. * @property {number=} maxFocus Max block selection time. @@ -129,6 +133,9 @@ function curateResults( results ) { type: average( results.type ), minType: Math.min( ...results.type ), maxType: Math.max( ...results.type ), + typeContainer: average( results.typeContainer ), + minTypeContainer: Math.min( ...results.typeContainer ), + maxTypeContainer: Math.max( ...results.typeContainer ), focus: average( results.focus ), minFocus: Math.min( ...results.focus ), maxFocus: Math.max( ...results.focus ), @@ -276,7 +283,7 @@ async function runPerformanceTests( branches, options ) { log( ` >> Building the ${ fancyBranch } branch` ); await runShellScript( - 'npm ci && node ./bin/packages/build.js', + 'npm ci && npm run prebuild:packages && node ./bin/packages/build.js && npx wp-scripts build', buildPath ); } @@ -393,6 +400,15 @@ async function runPerformanceTests( branches, options ) { type: rawResults.map( ( r ) => r[ branch ].type ), minType: rawResults.map( ( r ) => r[ branch ].minType ), maxType: rawResults.map( ( r ) => r[ branch ].maxType ), + typeContainer: rawResults.map( + ( r ) => r[ branch ].typeContainer + ), + minTypeContainer: rawResults.map( + ( r ) => r[ branch ].minTypeContainer + ), + maxTypeContainer: rawResults.map( + ( r ) => r[ branch ].maxTypeContainer + ), focus: rawResults.map( ( r ) => r[ branch ].focus ), minFocus: rawResults.map( ( r ) => r[ branch ].minFocus ), maxFocus: rawResults.map( ( r ) => r[ branch ].maxFocus ), diff --git a/changelog.txt b/changelog.txt index 809e3f5ebc6bc4..8774c72580adff 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,884 @@ == Changelog == += 14.8.0-rc.1 = + +## Changelog + +### Enhancements + +#### Block Library +- Add a current-menu-ancestor class to navigation items. ([40778](https://github.com/WordPress/gutenberg/pull/40778)) +- Page List Block: Adds a longdash tree to the parent selector. ([46336](https://github.com/WordPress/gutenberg/pull/46336)) +- Page List Block: Hide page list edit button if no pages are available. ([46331](https://github.com/WordPress/gutenberg/pull/46331)) +- Reusable block: Pluralize the message "Convert to regular blocks" depending on the number of blocks contained. ([45819](https://github.com/WordPress/gutenberg/pull/45819)) +- Add page list to Link UI transforms in Nav block. ([46426](https://github.com/WordPress/gutenberg/pull/46426)) +- Heading Block: Don't rely on the experimental selector anymore. ([46284](https://github.com/WordPress/gutenberg/pull/46284)) +- Media & Text Block: Create undo history when media width is changed. ([46084](https://github.com/WordPress/gutenberg/pull/46084)) +- Navigation block: Add location->primary to fallback nav creation for classic menus. ([45976](https://github.com/WordPress/gutenberg/pull/45976)) +- Navigation block: Update fallback nav creation to the most recently created menu. ([46286](https://github.com/WordPress/gutenberg/pull/46286)) +- Navigation: Add a 'open list view' button. ([46335](https://github.com/WordPress/gutenberg/pull/46335)) +- Navigation: Removes the header from the navigation list view in the experiment. ([46070](https://github.com/WordPress/gutenberg/pull/46070)) +- Page List: Add convert panel to Inspector Controls when within Nav block. ([46352](https://github.com/WordPress/gutenberg/pull/46352)) +- Page List: Prevent users from adding inner blocks to Page List. ([46269](https://github.com/WordPress/gutenberg/pull/46269)) +- Query: Remove color block supports. ([46147](https://github.com/WordPress/gutenberg/pull/46147)) +- Table block: Make `figcaption` styles consistent between editor and front end. ([46172](https://github.com/WordPress/gutenberg/pull/46172)) +- List/quote: Unwrap inner block when pressing Backspace at start. ([45075](https://github.com/WordPress/gutenberg/pull/45075)) + +#### Inspector Controls +- Sidebar Tabs: Refine the use of inspector tabs and disable filters for Nav blocks. ([46346](https://github.com/WordPress/gutenberg/pull/46346)) +- Sidebar Tabs: Use editor settings to override display. ([46321](https://github.com/WordPress/gutenberg/pull/46321)) +- Summary panel: Try improving spacing and grid. ([46267](https://github.com/WordPress/gutenberg/pull/46267)) + +#### Global Styles +- Add Style Book to Global Styles. ([45960](https://github.com/WordPress/gutenberg/pull/45960)) +- Add block preview component in global styles. ([45719](https://github.com/WordPress/gutenberg/pull/45719)) +- Move border from layout to own menu. ([45995](https://github.com/WordPress/gutenberg/pull/45995)) +- Add a css style to theme.json to allow setting of custom css strings. ([46255](https://github.com/WordPress/gutenberg/pull/46255)) +- Expose before filter hook in useSettings for injecting block settings in the editor. ([45089](https://github.com/WordPress/gutenberg/pull/45089)) +- Global styles: Add custom CSS panel to site editor. ([46141](https://github.com/WordPress/gutenberg/pull/46141)) + +#### Site Editor +- Allow adding new templates and template parts directly from the sidebar. ([46458](https://github.com/WordPress/gutenberg/pull/46458)) +- Synchronize the sidebar state in the URL. ([46433](https://github.com/WordPress/gutenberg/pull/46433)) +- Try template drill down on the shell sidebar (browse mode). ([45100](https://github.com/WordPress/gutenberg/pull/45100)) + +#### Block Editor +- Update the synced block hover styles in Inserter. ([46442](https://github.com/WordPress/gutenberg/pull/46442)) +- Add new selector getLastInsertedBlockClientId. ([46531](https://github.com/WordPress/gutenberg/pull/46531)) +- Block editor: Hide fixed contextual toolbar. ([46298](https://github.com/WordPress/gutenberg/pull/46298)) +- Inserter: Pattern title tooltip. ([46419](https://github.com/WordPress/gutenberg/pull/46419)) +- useNestedSettingsUpdate: Prevent unneeded syncing of falsy templateLock values. ([46357](https://github.com/WordPress/gutenberg/pull/46357)) +- Design: Augmented shadows for modals and popovers. ([46228](https://github.com/WordPress/gutenberg/pull/46228)) + +#### Components +- Tabs: Try a simpler tab focus style, alt. ([46276](https://github.com/WordPress/gutenberg/pull/46276)) +- BaseControl: Add convenience hook to generate id-related props. ([46170](https://github.com/WordPress/gutenberg/pull/46170)) +- Dashicon: Refactor to TypeScript. ([45924](https://github.com/WordPress/gutenberg/pull/45924)) +- Lighten borders to gray-600. ([46252](https://github.com/WordPress/gutenberg/pull/46252)) +- Popover: Check positioning by adding and testing is-positioned class. ([46429](https://github.com/WordPress/gutenberg/pull/46429)) + +### Icons +- Icons: Update the border icon. ([46264](https://github.com/WordPress/gutenberg/pull/46264)) + +#### Testing +- Tests: Fix `toBePositionedPopover` matcher message function. ([46239](https://github.com/WordPress/gutenberg/pull/46239)) + +#### Accessibility +- Reorganize the site editor to introduce Browse Mode. ([44770](https://github.com/WordPress/gutenberg/pull/44770)) + +#### Plugin +- Update the Gutenberg plugin to require at least the WP 6.0 version. ([46102](https://github.com/WordPress/gutenberg/pull/46102)) +- PHP: Backport changes from core theme resolver. ([46250](https://github.com/WordPress/gutenberg/pull/46250)) +- Update: Move gutenberg_register_core_block_patterns from 6.1 to 6.2. ([46249](https://github.com/WordPress/gutenberg/pull/46249)) +- Upgrade React packages to v18. ([45235](https://github.com/WordPress/gutenberg/pull/45235)) + +#### Themes +- Empty Theme: Add the `$schema` property in `theme.json` and rename template directories. ([46300](https://github.com/WordPress/gutenberg/pull/46300)) + +#### Mobile +- Mobile: Disable Unsupported Block Editor Tests (Android). ([46542](https://github.com/WordPress/gutenberg/pull/46542)) +- Mobile: Inserter - Remove `.done()` usage. ([46460](https://github.com/WordPress/gutenberg/pull/46460)) +- Mobile: Update Heading block end-to-end test. ([46220](https://github.com/WordPress/gutenberg/pull/46220)) +- Mobile: Updates packages to not use Git HTTPS URLs. ([46422](https://github.com/WordPress/gutenberg/pull/46422)) + +### Bug Fixes + +#### Block Library +- Fix Nav Submenu block Link UI text control. ([46243](https://github.com/WordPress/gutenberg/pull/46243)) +- Fix auto Nav menu creation due to page list inner blocks. ([46223](https://github.com/WordPress/gutenberg/pull/46223)) +- Handle innerContent too when removing innerBlocks. ([46377](https://github.com/WordPress/gutenberg/pull/46377)) +- Image Block: Ensure drag handle matches cursor position when resizing a center aligned image. ([46497](https://github.com/WordPress/gutenberg/pull/46497)) +- Navigation Block: Add social link singular to list of blocks to be allowed. ([46374](https://github.com/WordPress/gutenberg/pull/46374)) +- Navigation Block: Fixes adding a submenu. ([46364](https://github.com/WordPress/gutenberg/pull/46364)) +- Navigation Block: Prevent circular references in navigation block rendering. ([46387](https://github.com/WordPress/gutenberg/pull/46387)) +- Navigation Block: Recursively remove Navigation block’s from appearing inside Navigation block on front of site. ([46279](https://github.com/WordPress/gutenberg/pull/46279)) +- Navigation link: Use stripHTML. ([46317](https://github.com/WordPress/gutenberg/pull/46317)) +- Page List Block: Fix error loading page list parent options. ([46327](https://github.com/WordPress/gutenberg/pull/46327)) +- Query Loop Block: Add migration of colors to v2 deprecation. ([46522](https://github.com/WordPress/gutenberg/pull/46522)) +- Site Logo: Correctly set the image's natural height and width. ([46214](https://github.com/WordPress/gutenberg/pull/46214)) +- Strip markup from link label data in inspector. ([46171](https://github.com/WordPress/gutenberg/pull/46171)) +- Template Parts: Fix modal search stacking context. ([46421](https://github.com/WordPress/gutenberg/pull/46421)) +- Video: Avoid an error when removal is locked. ([46324](https://github.com/WordPress/gutenberg/pull/46324)) +- Layout child fixed size should not be fixed by default and should always have a value set. ([46139](https://github.com/WordPress/gutenberg/pull/46139)) + +#### Blocks +- Paste handler: Remove styles on inline paste. ([46402](https://github.com/WordPress/gutenberg/pull/46402)) +- Improve performance of gutenberg_render_layout_support_flag. ([46074](https://github.com/WordPress/gutenberg/pull/46074)) + +#### Global Styles +- Allow indirect properties when unfiltered_html is not allowed. ([46388](https://github.com/WordPress/gutenberg/pull/46388)) +- Fix Reset to defaults action by moving fills to be within context provider. ([46486](https://github.com/WordPress/gutenberg/pull/46486)) +- Fix duplication of synced block colors in CSS output. ([46297](https://github.com/WordPress/gutenberg/pull/46297)) +- Make style book label font size 11px. ([46341](https://github.com/WordPress/gutenberg/pull/46341)) +- Style Book: Clear Global Styles navigation history when selecting a block. ([46391](https://github.com/WordPress/gutenberg/pull/46391)) + +#### Block Editor +- Block Editor: Fix content locked patterns. ([46494](https://github.com/WordPress/gutenberg/pull/46494)) +- Block Editor: Fix memoized pattern selector dependant arguments. ([46238](https://github.com/WordPress/gutenberg/pull/46238)) +- Block Editor: Restore draggable chip styles. ([46396](https://github.com/WordPress/gutenberg/pull/46396)) +- Block Editor: Revert deoptimization useNestedSettingsUpdate. ([46350](https://github.com/WordPress/gutenberg/pull/46350)) +- Block Editor: Fix some usages of useSelect that return unstable results. ([46226](https://github.com/WordPress/gutenberg/pull/46226)) +- useInnerBlockTemplateSync: Cancel template sync on innerBlocks change or unmount. ([46307](https://github.com/WordPress/gutenberg/pull/46307)) + +#### Patterns +- Add new pattern categories. ([46144](https://github.com/WordPress/gutenberg/pull/46144)) +- Block Editor: Add initial view mode in `BlockPatternSetup`. ([46399](https://github.com/WordPress/gutenberg/pull/46399)) + +#### Site Editor +- Do not remount iframe. ([46431](https://github.com/WordPress/gutenberg/pull/46431)) +- Fix the top bar 'exit' animation. ([46533](https://github.com/WordPress/gutenberg/pull/46533)) +- Keep edited entity in sync when Editor canvas isn't mounted. ([46524](https://github.com/WordPress/gutenberg/pull/46524)) +- [Site Editor]: Add default white background for themes with no `background color` set. ([46314](https://github.com/WordPress/gutenberg/pull/46314)) + +#### Components +- InputControl: Fix `Flex` wrapper usage. ([46213](https://github.com/WordPress/gutenberg/pull/46213)) +- Modal: Fix unexpected modal closing in IME Composition. ([46453](https://github.com/WordPress/gutenberg/pull/46453)) +- MaybeCategoryPanel: Avoid 403 requests for users with low permissions. ([46349](https://github.com/WordPress/gutenberg/pull/46349)) +- Rich text: Add button to clear unknown format. ([44086](https://github.com/WordPress/gutenberg/pull/44086)) + +#### Document Settings +- Fix template title in `summary` panel and requests for low privileged users. ([46304](https://github.com/WordPress/gutenberg/pull/46304)) +- Permalink: Hide edit field for users without publishing capabilities. ([46361](https://github.com/WordPress/gutenberg/pull/46361)) + +#### Patterns +- Content lock: Make filter hook namespace unique. ([46344](https://github.com/WordPress/gutenberg/pull/46344)) + +#### Layout +- Child Layout controls: Fix help text for height. ([46319](https://github.com/WordPress/gutenberg/pull/46319)) + +#### Widgets Editor +- Shortcuts: Add Ctrl+Y for redo to all editor instances on Windows. ([43392](https://github.com/WordPress/gutenberg/pull/43392)) + +#### Block API +- HTML block: Fix parsing. ([27268](https://github.com/WordPress/gutenberg/pull/27268)) + +#### Mobile +- Social Links mobile test: Wait for URL bottom sheet to appear. ([46308](https://github.com/WordPress/gutenberg/pull/46308)) + +### Performance + +#### Components +- Avoid paint on popover when hovering content. ([46201](https://github.com/WordPress/gutenberg/pull/46201)) +- CircularOption: Avoid paint on circular option hover. ([46197](https://github.com/WordPress/gutenberg/pull/46197)) +- Lodash: Replace `_.isEqual()` with `fastDeepEqual`. ([46200](https://github.com/WordPress/gutenberg/pull/46200)) +- Popover: Avoid paint on popovers when scrolling. ([46187](https://github.com/WordPress/gutenberg/pull/46187)) +- Resizable Box: Avoid paint on resizable-box handles. ([46196](https://github.com/WordPress/gutenberg/pull/46196)) +- ListView: Avoid paint on list view item hover. ([46188](https://github.com/WordPress/gutenberg/pull/46188)) + +#### Code Quality +- Lodash: Refactor `blocks` away from `_.find()`. ([46428](https://github.com/WordPress/gutenberg/pull/46428)) +- Lodash: Refactor `core-data` away from `_.find()`. ([46468](https://github.com/WordPress/gutenberg/pull/46468)) +- Lodash: Refactor `edit-site` away from `_.find()`. ([46539](https://github.com/WordPress/gutenberg/pull/46539)) +- Lodash: Refactor away from `_.orderBy()`. ([45146](https://github.com/WordPress/gutenberg/pull/45146)) +- Lodash: Refactor block library away from `_.find()`. ([46430](https://github.com/WordPress/gutenberg/pull/46430)) +- Remove usage of get_default_block_editor_settings. ([46112](https://github.com/WordPress/gutenberg/pull/46112)) + +#### Post Editor +- Lodash: Refactor editor away from `_.find()`. ([46464](https://github.com/WordPress/gutenberg/pull/46464)) +- Lodash: Refactor post editor away from `_.find()`. ([46432](https://github.com/WordPress/gutenberg/pull/46432)) + +#### Block Editor +- Avoid paint on inserter animation. ([46185](https://github.com/WordPress/gutenberg/pull/46185)) +- Improve inserter search performance. ([46153](https://github.com/WordPress/gutenberg/pull/46153)) +- Block Editor: Refactor the "order" state in the block editor reducer to use a map instead of a plain object. ([46221](https://github.com/WordPress/gutenberg/pull/46221)) +- Block Editor: Refactor the block-editor parents state to use maps instead of objects. ([46225](https://github.com/WordPress/gutenberg/pull/46225)) +- Refactor the block-editor "tree" state to use maps instead of objects. ([46229](https://github.com/WordPress/gutenberg/pull/46229)) +- Refactor the block-editor byClientId redux state to use maps instead of plain objects. ([46204](https://github.com/WordPress/gutenberg/pull/46204)) +- Fix typing performance issue for container blocks. ([46527](https://github.com/WordPress/gutenberg/pull/46527)) + +#### Testing +- E2E: Fix performance tests by making inserter search container waiting optional. ([46268](https://github.com/WordPress/gutenberg/pull/46268)) + +#### Mobile +- Columns mobile block: Avoid returning unstable innerWidths from useSelect. ([46403](https://github.com/WordPress/gutenberg/pull/46403)) + +### Experiments + +#### Block Library +- Navigation List View: Remove empty cell when there is no edit button. ([46439](https://github.com/WordPress/gutenberg/pull/46439)) + +#### Web Fonts +- WP Webfonts: Avoid duplicated font families if the font family name was defined using fallback values. ([46378](https://github.com/WordPress/gutenberg/pull/46378)) + +### Documentation +- Adds clarifications and clears up inaccuracies. ([46283](https://github.com/WordPress/gutenberg/pull/46283)) +- Adds details of how to find the .zip file. ([46305](https://github.com/WordPress/gutenberg/pull/46305)) +- Doc: Fix description and documentation for link color support. ([46405](https://github.com/WordPress/gutenberg/pull/46405)) +- Docs: Add missing useState import in BorderBoxControl documentation. ([42067](https://github.com/WordPress/gutenberg/pull/42067)) +- Docs: Add missing useState import in color picker docs. ([42069](https://github.com/WordPress/gutenberg/pull/42069)) +- Docs: Add missing useState import in confirm dialog docs. ([42071](https://github.com/WordPress/gutenberg/pull/42071)) +- Docs: Adds reminder to use Node.js v14 in Quick Start. ([46216](https://github.com/WordPress/gutenberg/pull/46216)) +- Docs: Fix missing link to `primitives` package. ([46290](https://github.com/WordPress/gutenberg/pull/46290)) +- Docs: Update reference to IE 11. ([46296](https://github.com/WordPress/gutenberg/pull/46296)) + +### Code Quality +- Block Editor: Fix `no-node-access` violations in `BlockPreview`. ([46409](https://github.com/WordPress/gutenberg/pull/46409)) +- Block Editor: Fix `no-node-access` violations in `BlockSelectionClearer`. ([46408](https://github.com/WordPress/gutenberg/pull/46408)) +- Columns mobile edit: Remove unused updateBlockSettings action bind. ([46455](https://github.com/WordPress/gutenberg/pull/46455)) +- ESLint: Fix warning in `getBlockAttribute` documentation. ([46500](https://github.com/WordPress/gutenberg/pull/46500)) +- List View: Use default parameters instead of defaultProps. ([46266](https://github.com/WordPress/gutenberg/pull/46266)) +- Removed: Remove small APIs marked to be removed in WP 6.2. ([46106](https://github.com/WordPress/gutenberg/pull/46106)) +- Site Editor: Remove invalid CSS. ([46288](https://github.com/WordPress/gutenberg/pull/46288)) + +#### Block Library +- Group Block: Remove placeholder leftovers. ([46423](https://github.com/WordPress/gutenberg/pull/46423)) +- Group: Remove unnecessary 'useCallback'. ([46418](https://github.com/WordPress/gutenberg/pull/46418)) +- Navigation Block: Add tests for Nav block uncontrolled blocks dirty state checking. ([46329](https://github.com/WordPress/gutenberg/pull/46329)) +- Navigation Block: Update attribute test for `are-blocks-dirty.js`. ([46355](https://github.com/WordPress/gutenberg/pull/46355)) +- Page List Block: Move shared "convert" description to constant. ([46368](https://github.com/WordPress/gutenberg/pull/46368)) +- Page List Block: Simplify Page List convert to links function API. ([46365](https://github.com/WordPress/gutenberg/pull/46365)) +- Query: Cleanup variation picker component. ([46424](https://github.com/WordPress/gutenberg/pull/46424)) +- RNMobile: Add an inline comment to clarify usage of 'hard' limit vs. unbounded query. ([46245](https://github.com/WordPress/gutenberg/pull/46245)) +- Shared standard Link UI component between Nav Link and Submenu blocks. ([46370](https://github.com/WordPress/gutenberg/pull/46370)) +- Template Parts: Remove unnecessary 'useCallback'. ([46420](https://github.com/WordPress/gutenberg/pull/46420)) + +#### Components +- AlignmentMatrixControl: Refactor to TypeScript. ([46162](https://github.com/WordPress/gutenberg/pull/46162)) +- Also ignore `no-node-access` for some components. ([46501](https://github.com/WordPress/gutenberg/pull/46501)) +- Fix `no-node-access` violations in `FocalPointPicker` tests. ([46312](https://github.com/WordPress/gutenberg/pull/46312)) +- Fix `no-node-access` violations in `Popover`. ([46311](https://github.com/WordPress/gutenberg/pull/46311)) +- Fix `no-node-access` violations in `Theme`. ([46310](https://github.com/WordPress/gutenberg/pull/46310)) +- Fix `no-node-access` violations in `ToolsPanel` tests. ([46313](https://github.com/WordPress/gutenberg/pull/46313)) +- withFilters: Use 'act' from React Testing Library. ([46237](https://github.com/WordPress/gutenberg/pull/46237)) + +#### Data Layer +- Data: Add ability to subscribe to one store, remove __unstableSubscribeStore. ([45513](https://github.com/WordPress/gutenberg/pull/45513)) +- ESLint: Fix warnings in the data package. ([46499](https://github.com/WordPress/gutenberg/pull/46499)) + +#### Global Styles +- Add "custom-css" as an acceptable value in the documentation for gutenberg_get_global_stylesheet. ([46493](https://github.com/WordPress/gutenberg/pull/46493)) +- PaletteEdit: Add changelog. ([46095](https://github.com/WordPress/gutenberg/pull/46095)) + +#### Block Editor +- Inserter: Update mobile tab navigation styles. ([46186](https://github.com/WordPress/gutenberg/pull/46186)) + +#### Layout +- Clarify inline comment about switching to `safecss_filter_attr`. ([46061](https://github.com/WordPress/gutenberg/pull/46061)) + +### Tools + +#### Build Tooling +- Adds Github Action to validate Gradle Wrapper. ([46247](https://github.com/WordPress/gutenberg/pull/46247)) +- Prevent api-fetch and core-data from being imported in the block editor package. ([46302](https://github.com/WordPress/gutenberg/pull/46302)) +- Serialize the map objects properly in the Redux dev tools. ([46282](https://github.com/WordPress/gutenberg/pull/46282)) + +#### Testing +- E2E: Fix flaky Block Switcher tests. ([46406](https://github.com/WordPress/gutenberg/pull/46406)) +- end-to-end tests: Add width and color test to button block. ([46452](https://github.com/WordPress/gutenberg/pull/46452)) + + +## First time contributors + +The following PRs were merged by first time contributors: + +- @corentin-gautier: Avoid paint on popover when hovering content. ([46201](https://github.com/WordPress/gutenberg/pull/46201)) +- @ingeniumed: Expose before filter hook in useSettings for injecting block settings in the editor. ([45089](https://github.com/WordPress/gutenberg/pull/45089)) +- @janusqa: Reusable block: Pluralize the message "Convert to regular blocks" depending on the number of blocks contained. ([45819](https://github.com/WordPress/gutenberg/pull/45819)) + + +## Contributors + +The following contributors merged PRs in this release: + +@aaronrobertshaw @ajlende @andrewserong @aristath @chad1008 @chintu51 @corentin-gautier @derekblank @draganescu @ellatrix @geriux @getdave @glendaviesnz @hideokamoto @ingeniumed @jameskoster @janusqa @jasmussen @jffng @jorgefilipecosta @jsnajdr @madhusudhand @MaggieCabrera @Mamaduka @matiasbenedetto @mburridge @mikachan @mirka @noisysocks @ntsekouras @oandregal @oguzkocer @ramonjd @scruffian @SiobhyB @spacedmonkey @t-hamano @talldan @tellthemachines @tyxla @WunderBart @youknowriad + + += 14.7.3 = + +## Changelog + +### Bug fixes + +- Fix typing performance issue for container blocks. ([46527](https://github.com/WordPress/gutenberg/pull/46527)) + +## Contributors + +The following contributors merged PRs in this release: + +@youknowriad + + += 14.7.2 = + + + +## Changelog + +### Bug Fixes + +- Fix fatal error when using the plugin with PHP 8 and WordPress 6.0 by checking if block_type asset properties are set. ([46488](https://github.com/WordPress/gutenberg/pull/46488)) + + +## First time contributors + +The following PRs were merged by first time contributors: + + + +## Contributors + +The following contributors merged PRs in this release: + +@noahtallen + + += 14.7.1 = + + + +## Changelog + +### Bug Fixes + +#### Layout +- Only pass parentLayout to block edit if not empty. ([46390](https://github.com/WordPress/gutenberg/pull/46390)) + +## Contributors + +The following contributors merged PRs in this release: + +@tellthemachines + + += 14.7.0 = + + + +## Changelog + +### Enhancements + +#### Style Engine +- Style engine: Trim multiple selector strings. ([45873](https://github.com/WordPress/gutenberg/pull/45873)) + +#### Block Library +- Heading Block: Add a wp-block-heading CSS class. ([42122](https://github.com/WordPress/gutenberg/pull/42122)) +- Nav Block: Clarify explanation of how 'Convert to Links' works in Page List block. ([45394](https://github.com/WordPress/gutenberg/pull/45394)) +- Nav Block: Add label field to navigation link and navigation submenu. ([45964](https://github.com/WordPress/gutenberg/pull/45964)) +- Nav Block: Add link URL to the navigation submenu inspector controls. ([45816](https://github.com/WordPress/gutenberg/pull/45816)) +- Nav Block: Fix for navigation anchor links to close modal. ([45829](https://github.com/WordPress/gutenberg/pull/45829)) +- Template Part Block: Colorize template parts and Reusable blocks. ([45473](https://github.com/WordPress/gutenberg/pull/45473)) +- List: Allow pasting pre/code. ([45016](https://github.com/WordPress/gutenberg/pull/45016)) +- Page List: Enable page list to expand in list view. ([45776](https://github.com/WordPress/gutenberg/pull/45776)) +- Page List: Add a starting page for page list block's hierarchy. ([45861](https://github.com/WordPress/gutenberg/pull/45861)) +- Page List Item: Hide edit button. ([46163](https://github.com/WordPress/gutenberg/pull/46163)) +- Site Logo: Apply width to logo container in editor. ([45821](https://github.com/WordPress/gutenberg/pull/45821)) +- Table Block: Support colspan attribute in table HTML, including when pasting. ([45981](https://github.com/WordPress/gutenberg/pull/45981)) + +#### Components +- Add themeable background color. ([45466](https://github.com/WordPress/gutenberg/pull/45466)) +- Autocomplete: Only show UI on user input. ([45904](https://github.com/WordPress/gutenberg/pull/45904)) +- Bump `DateTimePicker` deprecated prop removal version. ([46006](https://github.com/WordPress/gutenberg/pull/46006)) +- ComboboxControl: Add new opt-in prop. ([45796](https://github.com/WordPress/gutenberg/pull/45796)) +- FocalPointPicker: Add new opt-in prop. ([45958](https://github.com/WordPress/gutenberg/pull/45958)) +- Global styles: Add onChange actions to color palette items. ([45681](https://github.com/WordPress/gutenberg/pull/45681)) +- InputControl: Add `help` prop. ([45931](https://github.com/WordPress/gutenberg/pull/45931)) +- RangeControl: Remove margin override and add new opt-in prop. ([45985](https://github.com/WordPress/gutenberg/pull/45985)) +- SearchControl: Remove margin overrides and add new opt-in prop. ([46081](https://github.com/WordPress/gutenberg/pull/46081)) +- Storybook: Opt in to story store v7. ([42486](https://github.com/WordPress/gutenberg/pull/42486)) +- ToggleControl text overflows when it has a long label. ([45962](https://github.com/WordPress/gutenberg/pull/45962)) +- useControlledValue: Let TypeScript infer the return type. ([46164](https://github.com/WordPress/gutenberg/pull/46164)) + +#### Inspector Controls +- Sidebar: Add list view tab for Navigation block et al. ([45483](https://github.com/WordPress/gutenberg/pull/45483)) +- Sidebar: Only render sidebar tabs possessing items to display. ([45991](https://github.com/WordPress/gutenberg/pull/45991)) +- Sidebar: Rename appearance tab to styles. ([46027](https://github.com/WordPress/gutenberg/pull/46027)) +- Sidebar: Split block tools into menu, settings, and appearance tabs. ([45005](https://github.com/WordPress/gutenberg/pull/45005)) + +#### Design Tools +- Min Height: Add height control component with slider. ([45875](https://github.com/WordPress/gutenberg/pull/45875)) +- Spacing: Make visualiser appear on focus. ([46096](https://github.com/WordPress/gutenberg/pull/46096)) + +#### Block Editor +- [Inserter]: Replace text in `Reusable` tab with an icon. ([45851](https://github.com/WordPress/gutenberg/pull/45851)) +- [Inserter]: Update pattern explorer button css. ([45735](https://github.com/WordPress/gutenberg/pull/45735)) +- [Inserter]: Add media tab. ([44918](https://github.com/WordPress/gutenberg/pull/44918)) + +#### Patterns +- [Pattern Directory]: Add categories endpoint. ([45749](https://github.com/WordPress/gutenberg/pull/45749)) +- [Patterns]: Update pattern category descriptions. ([46005](https://github.com/WordPress/gutenberg/pull/46005)) + +#### Nested / Inner Blocks +- Mark applying block templates not persistent. ([45843](https://github.com/WordPress/gutenberg/pull/45843)) + +#### Rich Text +- Create undo level before autocorrect. ([45670](https://github.com/WordPress/gutenberg/pull/45670)) + +#### Layout +- Add Layout controls to children of Flex layout blocks. ([45364](https://github.com/WordPress/gutenberg/pull/45364)) + + +### Bug Fixes + +#### Preferences +- Disable distraction free prefference effects on small viewports. ([45591](https://github.com/WordPress/gutenberg/pull/45591)) + +#### Block Library +- List Block: Fixed a bug that List block attributes were reset in 6.1.1. ([46000](https://github.com/WordPress/gutenberg/pull/46000)) +- Gallery: Use unbound query when fetching image details. ([46143](https://github.com/WordPress/gutenberg/pull/46143)) +- Heading: Add block classname deprecation. ([46138](https://github.com/WordPress/gutenberg/pull/46138)) +- Page List: If no parent page is set, still render all children. ([45967](https://github.com/WordPress/gutenberg/pull/45967)) +- Page List: Render the children correctly in the editor. ([46165](https://github.com/WordPress/gutenberg/pull/46165)) +- Post Author: Avoid errors when the user avatars are disabled. ([45989](https://github.com/WordPress/gutenberg/pull/45989)) +- Nav Block: Navigation menu doesn't appear when hamburger clicked on. ([45773](https://github.com/WordPress/gutenberg/pull/45773)) + +#### Block Editor +- Fix broken Link Control hook. ([46113](https://github.com/WordPress/gutenberg/pull/46113)) +- Fix inserter tab panel content buttons' position. ([45800](https://github.com/WordPress/gutenberg/pull/45800)) +- Block editor: rich text: Return early if __experimentalUndo is not defined. ([46152](https://github.com/WordPress/gutenberg/pull/46152)) + +#### Global Styles +- Global Style Context: Consider global user styles ready if a theme has none. ([46073](https://github.com/WordPress/gutenberg/pull/46073)) +- Merged data should consider origin to return early. ([45969](https://github.com/WordPress/gutenberg/pull/45969)) + +#### Components +- Remove CircleIndicatorWrapper `focus-visible` outline. ([45758](https://github.com/WordPress/gutenberg/pull/45758)) +- `ColorPalette`: Show `Clear` button even when `colors` array is empty. ([46001](https://github.com/WordPress/gutenberg/pull/46001)) + +#### Site Editor +- Fix template list width. ([45888](https://github.com/WordPress/gutenberg/pull/45888)) +- Prevent edit-post from being loaded in edit-site. ([45895](https://github.com/WordPress/gutenberg/pull/45895)) + +#### CSS & Styling +- Fix the editor area height. ([45799](https://github.com/WordPress/gutenberg/pull/45799)) + +#### Full Site Editing +- Ensure post-featured-image block is in_the_loop() for BC with core and plugins, and to fix lazy-loading. ([45534](https://github.com/WordPress/gutenberg/pull/45534)) + +### Accessibility + +- Add "Testing Instructions for Keyboard" to PR template to encourage accessibility testing. ([45957](https://github.com/WordPress/gutenberg/pull/45957)) +- BlockVariationPicker: Remove Unnecessary ARIA Role. ([45916](https://github.com/WordPress/gutenberg/pull/45916)) +- Sidebar Tabs: Set default tab to first available. ([45998](https://github.com/WordPress/gutenberg/pull/45998)) +- `TabPanel`: Support manual tab activation. ([46004](https://github.com/WordPress/gutenberg/pull/46004)) +- - Constrained tabbing: Fix unstable behavior in firefox. ([42653](https://github.com/WordPress/gutenberg/pull/42653)) + +### Performance + +- Work on refactor away from Lodash to reduce build size continued" ([see 13.7](https://make.wordpress.org/core/2022/07/20/whats-new-in-gutenberg-13-7-20-july/)) + +#### Global Styles +- Add `WP_Object_Cache` to the `gutenberg_get_global_settings` method. ([45372](https://github.com/WordPress/gutenberg/pull/45372)) +- Global styles WP_Query. ([46043](https://github.com/WordPress/gutenberg/pull/46043)) +- Ignore cached `wp_theme_has_theme_json` when `WP_DEBUG` is enabled. ([45882](https://github.com/WordPress/gutenberg/pull/45882)) +- Make `theme.json` object caches non persistent. ([46150](https://github.com/WordPress/gutenberg/pull/46150)) +- Remove `test_global_styles_user_cpt_change_invalidates_cached_stylesheet`. ([45993](https://github.com/WordPress/gutenberg/pull/45993)) +- Update `gutenberg_get_global_stylesheet` to use `WP_Object_Cache`. ([45679](https://github.com/WordPress/gutenberg/pull/45679)) +- Update which origins are queried for `gutenberg_get_global_settings`. ([45971](https://github.com/WordPress/gutenberg/pull/45971)) + +#### Post Editor +- useBlockEditorSettings: Return const empty array to avoid rerenders. ([46117](https://github.com/WordPress/gutenberg/pull/46117)) + +#### Block Editor +- Update the attributes reducer to use a map instead of a regular object. ([46146](https://github.com/WordPress/gutenberg/pull/46146)) + + +### Experiments + +#### Block Library +- Nav Block: Add basic edit button UI to Nav block offcanvas editor. ([45815](https://github.com/WordPress/gutenberg/pull/45815)) +- Nav Block: Add submenu menu item to list view. ([45794](https://github.com/WordPress/gutenberg/pull/45794)) +- Nav Block: Alternative: Add inserter to Nav block offcanvas experiment. ([45947](https://github.com/WordPress/gutenberg/pull/45947)) +- Nav Block: Display inserter popover in offcanvas UI. ([46013](https://github.com/WordPress/gutenberg/pull/46013)) +- Nav Block: List View - Stop child item selecting a parent which is already selected. ([45860](https://github.com/WordPress/gutenberg/pull/45860)) +- Nav Block: Add simple back button to inspector controls. ([45852](https://github.com/WordPress/gutenberg/pull/45852)) +- Nav Block: Move color controls to support panel. ([46049](https://github.com/WordPress/gutenberg/pull/46049)) +- Nav Block: Enable easier drag and drop for navigation building. ([45906](https://github.com/WordPress/gutenberg/pull/45906)) +- Nav Block: Hide the create new menu button if the experiment is enabled. ([46019](https://github.com/WordPress/gutenberg/pull/46019)) +- Navigation List view: Fix incorect class. ([46129](https://github.com/WordPress/gutenberg/pull/46129)) +- Navigation List view: Include offcanvas specific styles. ([45963](https://github.com/WordPress/gutenberg/pull/45963)) +- Navigation List view: Scroll horizontally when table overflows. ([45966](https://github.com/WordPress/gutenberg/pull/45966)) + + +### Documentation + +- (docs) Document the special case of shipping point releases when new release branch already exists. ([46083](https://github.com/WordPress/gutenberg/pull/46083)) +- Added InspectorControls import to example. ([45872](https://github.com/WordPress/gutenberg/pull/45872)) +- Fix NavigableRegion README. ([45879](https://github.com/WordPress/gutenberg/pull/45879)) +- Fix link & code markdown. ([45708](https://github.com/WordPress/gutenberg/pull/45708)) +- Navigation: Adds a warning about duplicate code for the future. ([45844](https://github.com/WordPress/gutenberg/pull/45844)) +- Storybook: Add link to component folder on GitHub, retire Storysource. ([45727](https://github.com/WordPress/gutenberg/pull/45727)) +- Style Engine: Add first draft of contributing doc. ([45930](https://github.com/WordPress/gutenberg/pull/45930)) +- Update applying-styles-with-stylesheets.md. ([45925](https://github.com/WordPress/gutenberg/pull/45925)) + +### Code Quality + +#### Components +- Cleanup the BlockPreview component. ([45936](https://github.com/WordPress/gutenberg/pull/45936)) +- Convert the `Snackbar` component to TypeScript. ([45472](https://github.com/WordPress/gutenberg/pull/45472)) +- Fix ESLint violations in `ContextSystemProvider` tests. ([46010](https://github.com/WordPress/gutenberg/pull/46010)) +- Fix ESLint violations in `NoticeList` tests. ([46011](https://github.com/WordPress/gutenberg/pull/46011)) +- Fix `no-node-access` in `Grid` tests. ([45900](https://github.com/WordPress/gutenberg/pull/45900)) +- Fix `no-node-access` in `Sandbox` tests. ([45908](https://github.com/WordPress/gutenberg/pull/45908)) +- Fix `no-node-access` in `Text` tests. ([45898](https://github.com/WordPress/gutenberg/pull/45898)) +- Fix `no-node-access` in `Theme` tests. ([45896](https://github.com/WordPress/gutenberg/pull/45896)) +- Fix `no-node-access` violation in `ControlLabel` tests. ([46007](https://github.com/WordPress/gutenberg/pull/46007)) +- Fix `no-node-access` violations in `Card` tests. ([46158](https://github.com/WordPress/gutenberg/pull/46158)) +- Fix `no-node-access` violations in `Disabled` tests. ([46156](https://github.com/WordPress/gutenberg/pull/46156)) +- Improve `BoxControl` tests. ([45968](https://github.com/WordPress/gutenberg/pull/45968)) +- Improve `Dropdown` tests. ([45911](https://github.com/WordPress/gutenberg/pull/45911)) +- LinkedButton: Remove unnecessary span tag. ([46063](https://github.com/WordPress/gutenberg/pull/46063)) +- TextControl: Restrict `type` prop in TypeScript. ([45433](https://github.com/WordPress/gutenberg/pull/45433)) +- Tooltip: Add readme and unit tests for `shortcut` prop. ([46092](https://github.com/WordPress/gutenberg/pull/46092)) +- `NumberControl`: Refactor styles/tests/stories to TypeScript, replace `fireEvent` with `user-event`. ([45990](https://github.com/WordPress/gutenberg/pull/45990)) +- useBaseField: Convert component to TypeScript. ([45712](https://github.com/WordPress/gutenberg/pull/45712)) +- Small refactoring to the NavigableRegion component. ([45849](https://github.com/WordPress/gutenberg/pull/45849)) + +#### Block Library +- ESLint: Fix minor ESLint warning in `LinkUI`. ([46161](https://github.com/WordPress/gutenberg/pull/46161)) +- Fix ESLint warnings in tests. ([46034](https://github.com/WordPress/gutenberg/pull/46034)) +- Fix invalid attribute markup in core/home-link block. ([46089](https://github.com/WordPress/gutenberg/pull/46089)) +- Link UI: Destructure the props earlier in the component. ([46209](https://github.com/WordPress/gutenberg/pull/46209)) +- Navigation Link UI: Try to align both files. ([46205](https://github.com/WordPress/gutenberg/pull/46205)) +- Navigation Menu Selector: Share the functions needed for the NavigationMenuSelector. ([46053](https://github.com/WordPress/gutenberg/pull/46053)) +- Navigation: Extract components. ([45850](https://github.com/WordPress/gutenberg/pull/45850)) +- Navigation: Reduce duplicate code. ([45779](https://github.com/WordPress/gutenberg/pull/45779)) +- Navigation: Remove unused clientId prop. ([46020](https://github.com/WordPress/gutenberg/pull/46020)) +- Post Featured Image: Only get the post title when rendering alt text. ([45835](https://github.com/WordPress/gutenberg/pull/45835)) +- Reduce prop drilling in Block Card component. ([46052](https://github.com/WordPress/gutenberg/pull/46052)) +- Refactor link creation UI to standalone component/file. ([46031](https://github.com/WordPress/gutenberg/pull/46031)) +- Remove Nav block specific classes from Nav offcanvas Link UI. ([46182](https://github.com/WordPress/gutenberg/pull/46182)) +- Remove WrappedNavigationMenuSelector. ([46056](https://github.com/WordPress/gutenberg/pull/46056)) +- Update offcanvas back button to select parent Nav block and limited to Nav block only. ([46037](https://github.com/WordPress/gutenberg/pull/46037)) +- Don't check if constants set by `wp_initial_constants()` are defined. ([45979](https://github.com/WordPress/gutenberg/pull/45979)) + +#### Block Editor +- LinkControl unit tests: Use user.type to type into search field. ([45802](https://github.com/WordPress/gutenberg/pull/45802)) +- Simplify api for link UI abstraction to use a single prop for the value. ([46189](https://github.com/WordPress/gutenberg/pull/46189)) +- URLInput: Keep the search results label in sync with the results list. ([45806](https://github.com/WordPress/gutenberg/pull/45806)) +- Use @wordpress/escape-html escapeHTML in Link UI in preference to Lodash method. ([46184](https://github.com/WordPress/gutenberg/pull/46184)) + +#### Global Styles +- Cleaner logic in wp_theme_has_theme_json. ([45950](https://github.com/WordPress/gutenberg/pull/45950)) +- Remove usage of wp_get_theme. ([45770](https://github.com/WordPress/gutenberg/pull/45770)) + +#### Post Editor +- Update BlockCard to pass className instead of isSynced prop. ([46021](https://github.com/WordPress/gutenberg/pull/46021)) + +#### Full Site Editing +- Block editor: Separate content styles for the iframe. ([44298](https://github.com/WordPress/gutenberg/pull/44298)) + +### Tools + +#### Build Tooling +- Bump caniuse-lite version. ([46093](https://github.com/WordPress/gutenberg/pull/46093)) + +#### Testing +- Fix Quote block's unwrap end-to-end test. ([46168](https://github.com/WordPress/gutenberg/pull/46168)) +- Remove 'response.deleted' check. ([45992](https://github.com/WordPress/gutenberg/pull/45992)) +- Warning: Fix ESLint warnings in tests. ([46033](https://github.com/WordPress/gutenberg/pull/46033)) +- ESLint: Enable `testing-library/no-container` rule. ([46160](https://github.com/WordPress/gutenberg/pull/46160)) +- Element: Fix `no-node-access` in `createInterpolateElement`. ([45894](https://github.com/WordPress/gutenberg/pull/45894)) +- Block Editor: Fix ESLint warnings in `MediaUpload` tests. ([46035](https://github.com/WordPress/gutenberg/pull/46035)) +- Block Editor: Fix `no-node-access` in `RecursionProvider` tests. ([45902](https://github.com/WordPress/gutenberg/pull/45902)) +- Block Editor: Fix block alignment tests for React 18. ([45937](https://github.com/WordPress/gutenberg/pull/45937)) +- Block Editor: Wait for popover positioning in `MediaReplaceFlow` tests. ([45863](https://github.com/WordPress/gutenberg/pull/45863)) +- Compose: Fix 'no-container' violations in 'useDisabled' tests. ([45797](https://github.com/WordPress/gutenberg/pull/45797)) +- Compose: Fix ESLint violations in `withGlobalEvents` tests. ([46012](https://github.com/WordPress/gutenberg/pull/46012)) + +## First time contributors + +The following PRs were merged by first time contributors: + +- @albarin: Remove 'response.deleted' check. ([45992](https://github.com/WordPress/gutenberg/pull/45992)) +- @artemiomorales: Clarify explanation of how 'Convert to Links' works in Page List block. ([45394](https://github.com/WordPress/gutenberg/pull/45394)) +- @coreyworrell: Fix for navigation anchor links to close modal. ([45829](https://github.com/WordPress/gutenberg/pull/45829)) +- @devanshijoshi9: Components: ToggleControl text overflows when it has a long label. ([45962](https://github.com/WordPress/gutenberg/pull/45962)) +- @flexseth: Added InspectorControls import to example. ([45872](https://github.com/WordPress/gutenberg/pull/45872)) +- @hiyascout: Update applying-styles-with-stylesheets.md. ([45925](https://github.com/WordPress/gutenberg/pull/45925)) +- @marissa-makes: BlockVariationPicker: Remove Unnecessary ARIA Role. ([45916](https://github.com/WordPress/gutenberg/pull/45916)) +- @mpkelly: Support `colspan` attribute in `table` HTML, including when pasting. ([45981](https://github.com/WordPress/gutenberg/pull/45981)) +- @TobiasBg: Fix invalid attribute markup in core/home-link block. ([46089](https://github.com/WordPress/gutenberg/pull/46089)) + + +## Contributors + +The following contributors merged PRs in this release: + +@aaronrobertshaw @adamziel @afercia @ajlende @albarin @alexstine @andrewserong @artemiomorales @brookewp @chad1008 @ciampo @coreyworrell @ddryo @devanshijoshi9 @draganescu @ellatrix @felixarntz @flexseth @fullofcaffeine @geriux @getdave @glendaviesnz @gvgvgvijayan @hiyascout @jsnajdr @kienstra @MaggieCabrera @Mamaduka @marissa-makes @mikachan @mirka @mmtr @mpkelly @ntsekouras @oandregal @ocean90 @oguzkocer @ramonjd @scruffian @SiobhyB @spacedmonkey @stokesman @t-hamano @tellthemachines @TobiasBg @tyxla @walbo @youknowriad + + += 14.6.1 = + +## Changelog + +### Fixes + +#### Global Styles +- Fix the `upgrader_process_complete` hook for `wp_theme_has_theme_json`. ([45881](https://github.com/WordPress/gutenberg/pull/45881)) +- Update `wp_theme_has_theme_json` to use `WP_Object_Cache`. ([45543](https://github.com/WordPress/gutenberg/pull/45543)) + +### Bug Fixes +- Tag Processor: Prevent bugs from pre-PHP8 strspn/strcspn behavior. ([45822](https://github.com/WordPress/gutenberg/pull/45822)) + +## Contributors + +The following contributors merged PRs in this release: + +@oandregal @dmsnell + + + += 14.6.0 = + +## Changelog + +### Enhancements + +#### Block Library + +- Latest posts: Add color support. ([41874](https://github.com/WordPress/gutenberg/pull/41874)) +- Latest posts and latest comments: Add spacing support. ([45110](https://github.com/WordPress/gutenberg/pull/45110)) +- Navigation: Adds a list view. ([45546](https://github.com/WordPress/gutenberg/pull/45546)) +- Navigation: Add a new ManageMenusButton component. ([45782](https://github.com/WordPress/gutenberg/pull/45782)) +- Navigation: Reposition the navigation selector. ([45555](https://github.com/WordPress/gutenberg/pull/45555)) +- Navigation Link: Add the URL field to the Navigation Link inspector controls. ([45751](https://github.com/WordPress/gutenberg/pull/45751)) +- Author: Make the Author selector display all users instead of just 10. ([45640](https://github.com/WordPress/gutenberg/pull/45640)) +- Columns: Add transform to unwrap the contents. ([45666](https://github.com/WordPress/gutenberg/pull/45666)) +- Read More: Add aria-label and screen reader text. ([45490](https://github.com/WordPress/gutenberg/pull/45490)) +- Group: Use a variation picker in the placeholder. ([43496](https://github.com/WordPress/gutenberg/pull/43496)) + +#### Components + +- Use new theming accent color in all components. ([45289](https://github.com/WordPress/gutenberg/pull/45289)) +- CheckboxControl: Replace margin overrides with new opt-in prop. ([45434](https://github.com/WordPress/gutenberg/pull/45434)) +- FocalPointPicker: Update the design of the focal point handle. ([45053](https://github.com/WordPress/gutenberg/pull/45053)) +- FontSizePicker: Update hint text to match the design. ([44966](https://github.com/WordPress/gutenberg/pull/44966)) +- CheckboxControl: Move icons out of labels. ([45535](https://github.com/WordPress/gutenberg/pull/45535)) + +#### Block Editor + +- Converts paragraphs to headings with keyboard shortcuts. ([44681](https://github.com/WordPress/gutenberg/pull/44681)) +- Restore the empty paragraph inserter. ([45542](https://github.com/WordPress/gutenberg/pull/45542)) +- Transform: Select all blocks if the result has more than one block. ([45015](https://github.com/WordPress/gutenberg/pull/45015)) +- Content-only locked patterns: Move "Modify" to the ellipsis menu. ([45391](https://github.com/WordPress/gutenberg/pull/45391)) +- Patterns: Adjust the space in the pattern explorer list. ([45730](https://github.com/WordPress/gutenberg/pull/45730)) +- Update: Lock icon to outline. ([45645](https://github.com/WordPress/gutenberg/pull/45645)) +- Don't use capital case for 'Distraction free' strings. ([45538](https://github.com/WordPress/gutenberg/pull/45538)) +- Replace Justification/Orientation controls with ToggleGroupControl. ([45637](https://github.com/WordPress/gutenberg/pull/45637)) + +#### Site Editor + +- Replace FSE with Site Editor. ([45699](https://github.com/WordPress/gutenberg/pull/45699)) + +#### Design Tools + +- Add a minHeight block support under dimensions. ([45300](https://github.com/WordPress/gutenberg/pull/45300)) +- Hide the block toolbar when the spacing visualizer is showing. ([45131](https://github.com/WordPress/gutenberg/pull/45131)) + +#### Global Styles + +- Elements: Add a text decoration control to link elements. ([45643](https://github.com/WordPress/gutenberg/pull/45643)) +- Global styles: Convert preset font size values to CSS vars. ([44967](https://github.com/WordPress/gutenberg/pull/44967)) +- Fluid typography: Adjust font size min and max rules. ([45536](https://github.com/WordPress/gutenberg/pull/45536)) +- Try generating random color palettes. ([40988](https://github.com/WordPress/gutenberg/pull/40988)) + +#### Plugin + +- Updates tested up to version to 6.1. ([45630](https://github.com/WordPress/gutenberg/pull/45630)) + +#### Patterns + +- Pattern Directory API: Add support for pagination parameters. ([45293](https://github.com/WordPress/gutenberg/pull/45293)) +- Update bundled patterns compat directory. ([45620](https://github.com/WordPress/gutenberg/pull/45620)) + +### Bug Fixes + +#### Block Library + +- Change the order of the pseudo-states in the pseudo-selectors array. ([45559](https://github.com/WordPress/gutenberg/pull/45559)) +- Cover: Avoid content loss when the templateLock value is all or contentOnly. ([45632](https://github.com/WordPress/gutenberg/pull/45632)) +- Fix alignment of create new post link. ([45638](https://github.com/WordPress/gutenberg/pull/45638)) +- Fix navigation appender position to prevent obstructing its items. ([43530](https://github.com/WordPress/gutenberg/pull/43530)) +- Fix: Button block text alignment. ([45663](https://github.com/WordPress/gutenberg/pull/45663)) +- Query Pagination: Fix positioning of the next link in editor when the parent is selected. ([45651](https://github.com/WordPress/gutenberg/pull/45651)) +- Site Logo: Use the correct home URL setting. ([45476](https://github.com/WordPress/gutenberg/pull/45476)) +- Switch background color to text color on the block separator. ([44943](https://github.com/WordPress/gutenberg/pull/44943)) +- Table Block: Apply borders and padding on both front end and editor. ([45069](https://github.com/WordPress/gutenberg/pull/45069)) +- Table block: Fix error in margin value. ([45674](https://github.com/WordPress/gutenberg/pull/45674)) +- Template Part Block: Update block isActive method. ([45672](https://github.com/WordPress/gutenberg/pull/45672)) +- Navigation: Fix overflowing menu name in the navigation selector dropdown. ([45647](https://github.com/WordPress/gutenberg/pull/45647)) + +#### Accessibility + +- Fix focus return when closing the Post publish panel. ([45623](https://github.com/WordPress/gutenberg/pull/45623)) +- Fix navigate regions backwards for macOS Firefox and Safari. ([45019](https://github.com/WordPress/gutenberg/pull/45019)) +- Fix the Save buttons labeling and tooltip. ([43952](https://github.com/WordPress/gutenberg/pull/43952)) +- Fix the navigate regions focus style. ([45369](https://github.com/WordPress/gutenberg/pull/45369)) +- Fix: Contrast checker appears unexpectedly on some blocks. ([45639](https://github.com/WordPress/gutenberg/pull/45639)) +- Fix: Contrast checker does not update properly. ([45686](https://github.com/WordPress/gutenberg/pull/45686)) + +#### Components + +- Autocomplete: Fix unexpected block insertion during IME composition. ([45510](https://github.com/WordPress/gutenberg/pull/45510)) +- Fix ESLint warning for Dashicon. ([45795](https://github.com/WordPress/gutenberg/pull/45795)) +- FormTokenField: Fix duplicate input in IME composition. ([45607](https://github.com/WordPress/gutenberg/pull/45607)) +- Making size prop work for icon components using dash icon strings. ([45593](https://github.com/WordPress/gutenberg/pull/45593)) +- ToolsPanel: Prevent calling deselect when panel remounts. ([45673](https://github.com/WordPress/gutenberg/pull/45673)) +- Color Picker: Prevent all number fields to become 0 when one of them is an empty string. ([45649](https://github.com/WordPress/gutenberg/pull/45649)) +- ToggleGroupControl: Only show the enclosing border when `isBlock`. ([45492](https://github.com/WordPress/gutenberg/pull/45492)) +- Autocomplete: Check key events more strictly in IME composition. ([45626](https://github.com/WordPress/gutenberg/pull/45626)) + +#### CSS & Styling + +- Inherit font from theme on overlay close button. ([45635](https://github.com/WordPress/gutenberg/pull/45635)) +- Navigation: Fix font inheritance when using text menu button. ([45514](https://github.com/WordPress/gutenberg/pull/45514)) +- Remove hover style to button on dark block tools UI. ([45653](https://github.com/WordPress/gutenberg/pull/45653)) +- Remove width from block mover button focus style. ([45665](https://github.com/WordPress/gutenberg/pull/45665)) +- Site editor hover/select: Fix double border. ([45589](https://github.com/WordPress/gutenberg/pull/45589)) +- Remove duplicate output of existing classnames in layout classnames. ([45499](https://github.com/WordPress/gutenberg/pull/45499)) + +#### Post Editor + +- BlockManagerCategory: Fix styles for indeterminate. ([45564](https://github.com/WordPress/gutenberg/pull/45564)) +- Fix: Updated names from List View to Document Overview. ([45524](https://github.com/WordPress/gutenberg/pull/45524)) +- Strip HTML from Post Title when pasting multiline title containing HTML. ([35825](https://github.com/WordPress/gutenberg/pull/35825)) + +#### Site Editor + +- Decode entities in template title and description. ([45716](https://github.com/WordPress/gutenberg/pull/45716)) +- Link to homeUrl from site editor view menu. ([45475](https://github.com/WordPress/gutenberg/pull/45475)) + +#### Block Editor + +- Fix Link UI popover positioning when inspector control input is focused. ([45661](https://github.com/WordPress/gutenberg/pull/45661)) +- Paste: Fix list only paste from Google documentation. ([45498](https://github.com/WordPress/gutenberg/pull/45498)) +- Make Manage Reusable blocks match similar links. ([45641](https://github.com/WordPress/gutenberg/pull/45641))([45689](https://github.com/WordPress/gutenberg/pull/45689)) +- List View: Disable branch expansion when block editing is locked. ([45541](https://github.com/WordPress/gutenberg/pull/45541)) +- Spacing visualizer: Fix display of unexpected visualizer for certain mouse actions. ([45739](https://github.com/WordPress/gutenberg/pull/45739)) + +### Experiments + +- A list view duplicate for use in navigation list view experiment. ([45544](https://github.com/WordPress/gutenberg/pull/45544)) +- Introduce experiment for inspector based navigation editing. ([45515](https://github.com/WordPress/gutenberg/pull/45515)) + +### Documentation + +- Add missing CHANGELOG entry. ([45691](https://github.com/WordPress/gutenberg/pull/45691)) +- Change Title: How to use JavaScript with Gutenberg. ([45323](https://github.com/WordPress/gutenberg/pull/45323)) +- Docs: Update the readme for the integration test fixtures. ([45581](https://github.com/WordPress/gutenberg/pull/45581)) +- Summarize "Available commands" section and refer them it to `scripts` documentation. ([45636](https://github.com/WordPress/gutenberg/pull/45636)) +- Update applying-styles-with-stylesheets.md. ([45604](https://github.com/WordPress/gutenberg/pull/45604)) +- [create-block] Reorganized sections to provide a better learning experience of this package. ([45676](https://github.com/WordPress/gutenberg/pull/45676)) +- Change "block style variations" references to "block style". ([45650](https://github.com/WordPress/gutenberg/pull/45650)) + +### Performance + +- Lodash: Refactor away from `_.reduce()`. ([45460](https://github.com/WordPress/gutenberg/pull/45460)) +- Lodash: Refactor block editor away from `_.reduce()`. ([45455](https://github.com/WordPress/gutenberg/pull/45455)) +- Lodash: Refactor blocks away from `_.reduce()`. ([45457](https://github.com/WordPress/gutenberg/pull/45457)) +- Lodash: Refactor site editor away from `_.reduce()`. ([45459](https://github.com/WordPress/gutenberg/pull/45459)) +- Lodash: Refactor post editor away from `_.reduce()`. ([45458](https://github.com/WordPress/gutenberg/pull/45458)) +- Do not look for block variants, if not supporting block-templates. ([45362](https://github.com/WordPress/gutenberg/pull/45362)) +- List: Disable nested list drop zone so dropping list items works. ([45321](https://github.com/WordPress/gutenberg/pull/45321)) +- Use low-level cache for get_user_data_from_wp_global_styles. ([45634](https://github.com/WordPress/gutenberg/pull/45634)) +- Update: Improve performance of block template object retrieval. ([45646](https://github.com/WordPress/gutenberg/pull/45646)) + +### Code Quality + +#### Block Editor + +- Block Editor: Improve `LinkControl` tests. ([45609](https://github.com/WordPress/gutenberg/pull/45609)) +- Block Editor: Improve `ResponsiveBlockControl` tests. ([45610](https://github.com/WordPress/gutenberg/pull/45610)) +- Block Editor: Improve `ReusableBlocksTab` tests. ([45652](https://github.com/WordPress/gutenberg/pull/45652)) +- LinkControl: Suppress errors on null values. ([45742](https://github.com/WordPress/gutenberg/pull/45742)) +- Simplify ResizableEditor component. ([45578](https://github.com/WordPress/gutenberg/pull/45578)) +- Remove duplicate colon. ([45763](https://github.com/WordPress/gutenberg/pull/45763)) +- Extract the manage menus button to a shared component to reduce duplicate code. ([45769](https://github.com/WordPress/gutenberg/pull/45769)) +- Backport pseudo selector comments from core. ([45619](https://github.com/WordPress/gutenberg/pull/45619)) +- unstableSubscribeStore: Support store descriptors. ([45481](https://github.com/WordPress/gutenberg/pull/45481)) + +#### Components + +- BaseField: Remove unnecessary `.firstChild` from tests. ([45687](https://github.com/WordPress/gutenberg/pull/45687)) +- DateTime: Remove unused types. ([45615](https://github.com/WordPress/gutenberg/pull/45615)) +- Draggable: Convert component to TypeScript. ([45471](https://github.com/WordPress/gutenberg/pull/45471)) +- Fix `no-container` violations in `FormGroup` tests. ([45662](https://github.com/WordPress/gutenberg/pull/45662)) +- Fix `testing-library/no-node-access` in `TreeGrid` tests. ([45554](https://github.com/WordPress/gutenberg/pull/45554)) +- FontSizePicker: Use components instead of helper functions. ([44891](https://github.com/WordPress/gutenberg/pull/44891)) +- Improve tests for `ToggleGroupControl`. ([45627](https://github.com/WordPress/gutenberg/pull/45627)) +- MenuGroup: Convert component to TypeScript. ([45617](https://github.com/WordPress/gutenberg/pull/45617)) +- Popover: Fix exhaustive-deps warning. ([45656](https://github.com/WordPress/gutenberg/pull/45656)) +- Refactor `ItemGroup` to pass `exhaustive-deps`. ([45531](https://github.com/WordPress/gutenberg/pull/45531)) +- Refactor `useFlex` to pass `exhaustive-deps`. ([45528](https://github.com/WordPress/gutenberg/pull/45528)) +- Refactor `withNotices` to pass `exhaustive-deps`. ([45530](https://github.com/WordPress/gutenberg/pull/45530)) +- Refactor`PaletteEditListView` to ignore `exhaustive-deps`. ([45467](https://github.com/WordPress/gutenberg/pull/45467)) +- TabPanel: Fix the `exhaustive-deps` warning. ([45660](https://github.com/WordPress/gutenberg/pull/45660)) +- ToolsPanel: Fix exhaustive-deps hook warning. ([45715](https://github.com/WordPress/gutenberg/pull/45715)) +- Truncate: Remove unnecessary `.firstChild` from tests. ([45694](https://github.com/WordPress/gutenberg/pull/45694)) +- View component: Rename index.js to index.ts. ([45667](https://github.com/WordPress/gutenberg/pull/45667)) +- `ColorPalette`, `BorderBox`, `BorderBoxControl`: Polish and DRY prop types, add default values. ([45463](https://github.com/WordPress/gutenberg/pull/45463)) +- `NavigatorScreen`: Satisfy `exhaustive-deps` eslint rule. ([45648](https://github.com/WordPress/gutenberg/pull/45648)) +- Fix `useCx` story to satisfy `exhaustive-deps` eslint rule. ([45614](https://github.com/WordPress/gutenberg/pull/45614)) +- URLPopover: Use new placement prop instead of legacy position prop. ([44391](https://github.com/WordPress/gutenberg/pull/44391)) +- Tidy and minor refactor of Link UI code. ([37833](https://github.com/WordPress/gutenberg/pull/37833)) + +#### Block Library + +- Avatar: Escape the 'get_author_posts_url()'. ([45427](https://github.com/WordPress/gutenberg/pull/45427)) +- Button: Remove unnecessary 'useCallback'. ([45584](https://github.com/WordPress/gutenberg/pull/45584)) +- Make unwrapping columns slighly more efficient. ([45684](https://github.com/WordPress/gutenberg/pull/45684)) +- Simplfy handling of save of Nav block uncontrolled inner blocks. ([45517](https://github.com/WordPress/gutenberg/pull/45517)) +- Lodash: Refactor block library away from `_.reduce()`. ([45456](https://github.com/WordPress/gutenberg/pull/45456)) + +### Tools + +#### Testing + +- Components: Add `exhaustive-deps` eslint rule. ([41166](https://github.com/WordPress/gutenberg/pull/41166)) +- Fix typos in Paragraph block end-to-end tests. ([45611](https://github.com/WordPress/gutenberg/pull/45611)) +- FontSizePicker: Fix a buggy unit test. ([45529](https://github.com/WordPress/gutenberg/pull/45529)) +- Ignore warnings for `window.wp` in Playwright. ([45598](https://github.com/WordPress/gutenberg/pull/45598)) +- Migrate mentions tests to playwright. ([43064](https://github.com/WordPress/gutenberg/pull/43064)) +- Navigation Toggle unit test: Unmount synchronously to cancel popover positioning. ([45726](https://github.com/WordPress/gutenberg/pull/45726)) +- React Native unit tests: Migrate getByA11yLabel usages. ([45454](https://github.com/WordPress/gutenberg/pull/45454)) +- Unit Tests: Rewrite ReactDOM.render usages to RTL. ([45453](https://github.com/WordPress/gutenberg/pull/45453)) +- E2E: Add site and widget editor supports for ensureSidebarOpened. ([45480](https://github.com/WordPress/gutenberg/pull/45480)) + +#### Build Tooling + +- Include TS and JSX files to testing-library lint. ([45533](https://github.com/WordPress/gutenberg/pull/45533)) +- Remove use of `set-output` in workflows. ([45357](https://github.com/WordPress/gutenberg/pull/45357)) + +#### Triage + +- Configure labels for GHA Dependabot PRs. ([45516](https://github.com/WordPress/gutenberg/pull/45516)) + +## First time contributors + +The following PRs were merged by first time contributors: + +- @edanzer: Template Part Block: Update block isActive method. ([45672](https://github.com/WordPress/gutenberg/pull/45672)) +- @TimBroddin: Fix alignment of create new post link. ([45638](https://github.com/WordPress/gutenberg/pull/45638)) +- @wojtekn: Make Author block selector to display all users instead of just 10. ([45640](https://github.com/WordPress/gutenberg/pull/45640)) + + +## Contributors + +The following contributors merged PRs in this release: + +@aaronrobertshaw @afercia @andrewserong @bph @brookewp @c4rl0sbr4v0 @carolinan @chad1008 @ciampo @Copons @DaisyOlsen @desrosj @dinhtungdu @draganescu @dsas @edanzer @ellatrix @enejb @flootr @getdave @glendaviesnz @hz-tyfoon @jasmussen @javierarce @jffng @jonathanbossenger @jorgefilipecosta @jsnajdr @juanmaguitar @juhi123 @kevin940726 @Mamaduka @matiasbenedetto @mikachan @mirka @mmtr @mtias @ndiego @nielslange @noisysocks @ntsekouras @peterwilsoncc @ramonjd @ryelle @scruffian @spacedmonkey @t-hamano @TimBroddin @tyxla @vcanales @walbo @wojtekn @youknowriad @yuliyan + + + + += 14.5.4 = + +## Changelog + +### Fixes + +#### Global Styles +- Fix the `upgrader_process_complete` hook for `wp_theme_has_theme_json`. ([45881](https://github.com/WordPress/gutenberg/pull/45881)) + +## Contributors + +The following contributors merged PRs in this release: + +@oandregal + + + = 14.5.3 = diff --git a/docs/contributors/code/release.md b/docs/contributors/code/release.md index 4ce35e189fd700..492813376b2c9d 100644 --- a/docs/contributors/code/release.md +++ b/docs/contributors/code/release.md @@ -34,6 +34,10 @@ To release a release candidate (RC) version of the plugin, enter `rc`. To releas This will trigger a GitHub Actions (GHA) workflow that bumps the plugin version, builds the Gutenberg plugin .zip file, creates a release draft, and attaches the plugin .zip file to it. This part of the process typically takes a little under six minutes. You'll see that workflow appear at the top of the list, right under the blue banner. Once it's finished, it'll change its status icon from a yellow dot to a green checkmark. You can follow along in a more detailed view by clicking on the workflow. +#### Publishing the @wordpress packages to NPM + +As part of the release candidate (RC) process, all of the `@wordpress` packages are published to NPM. You may see messaging after the ["Build Gutenberg Plugin Zip" action](https://github.com/WordPress/gutenberg/actions/workflows/build-plugin-zip.yml) action has created the draft release that the ["Publish npm packages"](https://github.com/WordPress/gutenberg/actions/workflows/publish-npm-packages.yml) action requires someone with appropriate permissions to trigger the action. This is not the case as this process is automated and it will automatically run after the release notes are published. + #### View the release draft As soon as the workflow has finished, you'll find the release draft under [https://github.com/WordPress/gutenberg/releases](https://github.com/WordPress/gutenberg/releases). The draft is pre-populated with changelog entries based on previous release candidates for this version, and any changes that have since been cherry-picked to the release branch. Thus, when releasing the first stable version of a series, make sure to delete any RC version headers (that are only there for your information) and to move the more recent changes to the correct section (see below). @@ -150,6 +154,8 @@ The method for point releases is nearly identical to the main Plugin release pro The point release should only contain the _specific commits_ required. To do this you should checkout the previous _minor_ stable (i.e. non-RC) release branch (e.g. `release/12.5`) locally and then cherry pick any commits that you require into that branch. +_IMPORTANT:_ If an RC already exists for a new version, you _need_ to cherry-pick the same commits in the respective release branch, as they will not be included automatically. E.g.: If you're about to release a new point-release for 12.5 and just cherry-picked into `release/12.5`, but 12.6.0-rc.1 is already out, then you need to cherry-pick the same commits into the `release/12.6` branch, or they won't be included in subsequent releases for 12.6! + The cherry-picking process can be automated with the [`npm run cherry-pick` script](/docs/contributors/code/auto-cherry-picking.md). You must also ensure that all PRs being included are assigned to the Github Milestone on which the point release is based. Bear in mind, that when PRs are _merged_ they are automatically assigned a milestone for the next _stable_ release. Therefore you will need to go back through each PR in Github and re-assign the Milestone. diff --git a/docs/contributors/code/scripts.md b/docs/contributors/code/scripts.md index 3b36c8f4be4956..b7cabe0130d71c 100644 --- a/docs/contributors/code/scripts.md +++ b/docs/contributors/code/scripts.md @@ -31,7 +31,6 @@ The editor includes a number of packages to enable various pieces of functionali | [Is Shallow Equal](/packages/is-shallow-equal/README.md) | wp-is-shallow-equal | A function for performing a shallow comparison between two objects or arrays | | [Keycodes](/packages/keycodes/README.md) | wp-keycodes | Keycodes utilities for WordPress, used to check the key pressed in events like `onKeyDown` | | [List Reusable blocks](/packages/list-reusable-blocks/README.md) | wp-list-reusable-blocks | Package used to add import/export links to the listing page of the reusable blocks | -| [NUX](/packages/nux/README.md) | wp-nux | Components, and wp.data methods useful for onboarding a new user to the WordPress admin interface | | [Plugins](/packages/plugins/README.md) | wp-plugins | Plugins module for WordPress | | [Redux Routine](/packages/redux-routine/README.md) | wp-redux-routine | Redux middleware for generator coroutines | | [Rich Text](/packages/rich-text/README.md) | wp-rich-text | Helper functions to convert HTML or a DOM tree into a rich text value and back | diff --git a/docs/explanations/faq.md b/docs/explanations/faq.md index c56dd2e0c3c6ca..08fcc6cbdce521 100644 --- a/docs/explanations/faq.md +++ b/docs/explanations/faq.md @@ -408,9 +408,11 @@ _See:_ [Editor Styles](/docs/how-to-guides/themes/theme-support.md#editor-styles ## What browsers does Gutenberg support? -Gutenberg works in modern browsers, and Internet Explorer 11. +Gutenberg works in modern browsers. -Our [list of supported browsers can be found in the Make WordPress handbook](https://make.wordpress.org/core/handbook/best-practices/browser-support/). By “modern browsers” we generally mean the _current and past two versions_ of each major browser. +The [list of supported browsers can be found in the Make WordPress handbook](https://make.wordpress.org/core/handbook/best-practices/browser-support/). The term “modern browsers” generally refers to the _current and previous two versions_ of each major browser. + +Since WordPress 5.8, Gutenberg no longer supports any version of Internet Explorer. ## Should I be concerned that Gutenberg will make my plugin obsolete? diff --git a/docs/getting-started/create-block/README.md b/docs/getting-started/create-block/README.md index ae56e4b123f0e9..05d17f708c9204 100644 --- a/docs/getting-started/create-block/README.md +++ b/docs/getting-started/create-block/README.md @@ -18,6 +18,8 @@ From your plugins directory, to create your block run: npx @wordpress/create-block gutenpride --template @wordpress/create-block-tutorial-template ``` +> Remember that you should use Node.js v14. Other versions may result in an error in the terminal. See [Node Development Tools](https://developer.wordpress.org/block-editor/getting-started/devenv/#node-development-tools) for more info. + The [npx command](https://docs.npmjs.com/cli/v8/commands/npx) runs a command from a remote package, in this case our create-block package that will create a new directory called `gutenpride`, installs the necessary files, and builds the block plugin. If you want an interactive mode that prompts you for details, run the command without the `gutenpride` name. You now need to activate the plugin from inside wp-admin plugins page. diff --git a/docs/getting-started/create-block/attributes.md b/docs/getting-started/create-block/attributes.md index 5ec6f60c3d6a4d..02a55f380dcee1 100644 --- a/docs/getting-started/create-block/attributes.md +++ b/docs/getting-started/create-block/attributes.md @@ -23,7 +23,7 @@ Note: The text portion is equivalent to `innerText` attribute of a DOM element. ## Edit and Save -The **attributes** are passed to the `edit` and `save` functions, along with a **setAttributes** function to set the values. Additional parameters are also passed in to these functions, see [the edit/save documentation](/docs/reference-guides/block-api/block-edit-save.md) for more details. +The **attributes** are passed to both the `edit` and `save` functions. The **setAttributes** function is also passed, but only to the `edit` function. The **setAttributes** function is used to set the values. Additional parameters are also passed in to the `edit` and `save` functions, see [the edit/save documentation](/docs/reference-guides/block-api/block-edit-save.md) for more details. The `attributes` is a JavaScript object containing the values of each attribute, or default values if defined. The `setAttributes` is a function to update an attribute. diff --git a/docs/getting-started/create-block/block-anatomy.md b/docs/getting-started/create-block/block-anatomy.md index 3f43c018cea39c..83dbcf14696325 100644 --- a/docs/getting-started/create-block/block-anatomy.md +++ b/docs/getting-started/create-block/block-anatomy.md @@ -29,7 +29,7 @@ registerBlockType( metadata.name, { } ); ``` -The first parameter in the **registerBlockType** function is the block name, this should match exactly to the name registered in the PHP file. +The first parameter in the **registerBlockType** function is the block name, this should match exactly to the `name` property in the `block.json` file. By importing the metadata from `block.json` and referencing the `name` property in the first parameter we ensure that they will match, and continue to match even if the name is subsequently changed in `block.json`. The second parameter to the function is the block object. See the [block registration documentation](/docs/reference-guides/block-api/block-registration.md) for full details. @@ -37,7 +37,7 @@ The last two block object properties are **edit** and **save**, these are the ke The results of the edit function is what the editor will render to the editor page when the block is inserted. -The results of the save function is what the editor will insert into the **post_content** field when the post is saved. The post_content field is the field in the WordPress database used to store the content of the post. +The results of the save function is what the editor will insert into the **post_content** field when the post is saved. The post_content field is the field in the **wp_posts** table in the WordPress database that is used to store the content of the post. Most of the properties are set in the `src/block.json` file. diff --git a/docs/how-to-guides/block-tutorial/README.md b/docs/how-to-guides/block-tutorial/README.md index b1ff5a19119c2b..95aa4182430c07 100644 --- a/docs/how-to-guides/block-tutorial/README.md +++ b/docs/how-to-guides/block-tutorial/README.md @@ -2,7 +2,9 @@ The purpose of this tutorial is to step through the fundamentals of creating a new block type. Beginning with the simplest possible example, each new section will incrementally build upon the last to include more of the common functionality you could expect to need when implementing your own block types. -To follow along with this tutorial, you can [download the accompanying WordPress plugin](https://github.com/WordPress/gutenberg-examples) which includes all of the examples for you to try on your own site. At each step along the way, experiment by modifying the examples with your own ideas, and observe the effects they have on the block's behavior. +To follow along with this tutorial, you can download the [accompanying WordPress plugin](https://github.com/WordPress/gutenberg-examples) which includes all of the examples for you to try on your own site. At each step along the way, experiment by modifying the examples with your own ideas, and observe the effects they have on the block's behavior. + +> To find the latest version of the .zip file go to the repo's [releases page](https://github.com/WordPress/gutenberg-examples/releases) and look in the latest release under 'Assets'. Code snippets are provided in two formats "JSX" and "Plain". JSX refers to JavaScript code that uses JSX syntax which requires a build step. Plain refers to "classic" JavaScript that does not require building. You can change between them using tabs found above each code example. Using JSX, does require you to run [the JavaScript build step](/docs/how-to-guides/javascript/js-build-setup/) to compile your code to a browser compatible format. diff --git a/docs/how-to-guides/block-tutorial/applying-styles-with-stylesheets.md b/docs/how-to-guides/block-tutorial/applying-styles-with-stylesheets.md index 815adfdf80c26d..e668a6ac762388 100644 --- a/docs/how-to-guides/block-tutorial/applying-styles-with-stylesheets.md +++ b/docs/how-to-guides/block-tutorial/applying-styles-with-stylesheets.md @@ -2,7 +2,7 @@ ## Overview -A block typically inserts markup (HTML) into post content that you want to style in someway. This guides walks through a few different ways you can use CSS with the block editor and how to work with styles and stylesheets. +A block typically inserts markup (HTML) into post content that you want to style in some way. This guide walks through a few different ways you can use CSS with the block editor and how to work with styles and stylesheets. ## Before you start diff --git a/docs/how-to-guides/block-tutorial/extending-the-query-loop-block.md b/docs/how-to-guides/block-tutorial/extending-the-query-loop-block.md index 3956971645f86a..e43dcb9727088d 100644 --- a/docs/how-to-guides/block-tutorial/extending-the-query-loop-block.md +++ b/docs/how-to-guides/block-tutorial/extending-the-query-loop-block.md @@ -192,6 +192,8 @@ Notice that we have also disabled the `postType` control. When the user selects Because our plugin uses custom attributes that we need to query, we want to add our own controls to allow the users to select those instead of the ones we have just disabled from the core inspector controls. We can do this via a [React HOC](https://reactjs.org/docs/higher-order-components.html) hooked into a [block filter](https://developer.wordpress.org/block-editor/reference-guides/filters/block-filters/), like so: ```jsx +import { InspectorControls } from '@wordpress/block-editor'; + export const withBookQueryControls = ( BlockEdit ) => ( props ) => { // We only want to add these controls if it is our variation, // so here we can implement a custom logic to check for that, similiar diff --git a/docs/manifest.json b/docs/manifest.json index 8cc39af57531e4..a06df18c6c44b9 100644 --- a/docs/manifest.json +++ b/docs/manifest.json @@ -1733,12 +1733,6 @@ "markdown_source": "../packages/npm-package-json-lint-config/README.md", "parent": "packages" }, - { - "title": "@wordpress/nux", - "slug": "packages-nux", - "markdown_source": "../packages/nux/README.md", - "parent": "packages" - }, { "title": "@wordpress/plugins", "slug": "packages-plugins", @@ -1973,12 +1967,6 @@ "markdown_source": "../docs/reference-guides/data/data-core-notices.md", "parent": "data" }, - { - "title": "The NUX (New User Experience) Data", - "slug": "data-core-nux", - "markdown_source": "../docs/reference-guides/data/data-core-nux.md", - "parent": "data" - }, { "title": "Preferences", "slug": "data-core-preferences", diff --git a/docs/reference-guides/README.md b/docs/reference-guides/README.md index 33fdd9aa602414..f13c838697f2de 100644 --- a/docs/reference-guides/README.md +++ b/docs/reference-guides/README.md @@ -63,7 +63,6 @@ - [**core/editor**: The Post Editor’s Data](/docs/reference-guides/data/data-core-editor.md) - [**core/keyboard-shortcuts**: The Keyboard Shortcuts Data](/docs/reference-guides/data/data-core-keyboard-shortcuts.md) - [**core/notices**: Notices Data](/docs/reference-guides/data/data-core-notices.md) - - [**core/nux**: The NUX (New User Experience) Data](/docs/reference-guides/data/data-core-nux.md) - [**core/preferences**: Preferences](/docs/reference-guides/data/data-core-preferences.md) - [**core/reusable-blocks**: Reusable blocks](/docs/reference-guides/data/data-core-reusable-blocks.md) - [**core/rich-text**: Rich Text](/docs/reference-guides/data/data-core-rich-text.md) diff --git a/docs/reference-guides/block-api/block-metadata.md b/docs/reference-guides/block-api/block-metadata.md index 6185043353fc24..4acb0f914e0379 100644 --- a/docs/reference-guides/block-api/block-metadata.md +++ b/docs/reference-guides/block-api/block-metadata.md @@ -252,7 +252,7 @@ The `ancestor` property makes a block available inside the specified block types An icon property should be specified to make it easier to identify a block. These can be any of WordPress' Dashicons (slug serving also as a fallback in non-js contexts). -**Note:** It's also possible to override this property on the client-side with the source of the SVG element. In addition, this property can be defined with JavaScript as an object containing background and foreground colors. This colors will appear with the icon when they are applicable e.g.: in the inserter. Custom SVG icons are automatically wrapped in the [wp.primitives.SVG](/packages/packages-primitives) component to add accessibility attributes (aria-hidden, role, and focusable). +**Note:** It's also possible to override this property on the client-side with the source of the SVG element. In addition, this property can be defined with JavaScript as an object containing background and foreground colors. This colors will appear with the icon when they are applicable e.g.: in the inserter. Custom SVG icons are automatically wrapped in the [wp.primitives.SVG](/packages/primitives/README.md) component to add accessibility attributes (aria-hidden, role, and focusable). ### Description diff --git a/docs/reference-guides/block-api/block-registration.md b/docs/reference-guides/block-api/block-registration.md index 5c38bf7f79c361..97077cb7efdfaa 100644 --- a/docs/reference-guides/block-api/block-registration.md +++ b/docs/reference-guides/block-api/block-registration.md @@ -35,7 +35,7 @@ A block requires a few properties to be specified before it can be registered su - **Type:** `String` -This is the display title for your block, which can be translated with our translation functions. The title will display in the Inserter and in other areas of the editor. +This is the display title for your block, which can be translated with our translation functions. The title will display in the Inserter and in other areas of the editor. ```js // Our data object @@ -90,7 +90,7 @@ icon: 'book-alt', icon: , ``` -**Note:** Custom SVG icons are automatically wrapped in the [`wp.primitives.SVG` component](/packages/primitives/src/svg/) to add accessibility attributes (`aria-hidden`, `role`, and `focusable`). +**Note:** Custom SVG icons are automatically wrapped in the [`wp.primitives.SVG` component](/packages/primitives/README.md) to add accessibility attributes (`aria-hidden`, `role`, and `focusable`). An object can also be passed as icon, in this case, icon, as specified above, should be included in the src property. @@ -179,7 +179,7 @@ attributes: { - **Type:** `Object` -Example provides structured example data for the block. This data is used to construct a preview for the block to be shown in the Inspector Help Panel when the user mouses over the block. +Example provides structured example data for the block. This data is used to construct a preview for the block to be shown in the Inspector Help Panel when the user mouses over the block and in the Styles panel when the block is selected. The data provided in the example object should match the attributes defined. For example: diff --git a/docs/reference-guides/block-api/block-supports.md b/docs/reference-guides/block-api/block-supports.md index f3d15f118ae8fa..68afd793231eff 100644 --- a/docs/reference-guides/block-api/block-supports.md +++ b/docs/reference-guides/block-api/block-supports.md @@ -351,36 +351,25 @@ supports: { Link color presets are sourced from the `editor-color-palette` [theme support](/docs/how-to-guides/themes/theme-support.md#block-color-palettes). -When the block declares support for `color.link`, the attributes definition is extended to include two new attributes: `linkColor` and `style`: - -- `linkColor`: attribute of `string` type with no default assigned. - - When a user chooses from the list of preset link colors, the preset slug is stored in the `linkColor` attribute. - - The block can apply a default preset text color by specifying its own attribute with a default e.g.: - - ```js - attributes: { - linkColor: { - type: 'string', - default: 'some-preset-link-color-slug', - } - } - ``` +When the block declares support for `color.link`, the attributes definition is extended to include the `style` attribute: - `style`: attribute of `object` type with no default assigned. - When a custom link color is selected (i.e. using the custom color picker), the custom color value is stored in the `style.color.link` attribute. + When a link color is selected, the color value is stored in the `style.elements.link.color.text` attribute. - The block can apply a default custom link color by specifying its own attribute with a default e.g.: + The block can apply a default link color by specifying its own attribute with a default e.g.: ```js attributes: { style: { type: 'object', default: { - color: { - link: '#ff0000', + elements: { + link: { + color: { + text: '#ff0000', + } + } } } } @@ -583,7 +572,7 @@ attributes: { } ``` -A spacing property may define an array of allowable sides – 'top', 'right', 'bottom', 'left' – that can be configured. When such arbitrary sides are defined, only UI controls for those sides are displayed. +A spacing property may define an array of allowable sides – 'top', 'right', 'bottom', 'left' – that can be configured. When such arbitrary sides are defined, only UI controls for those sides are displayed. Axial sides are defined with the `vertical` and `horizontal` terms, and display a single UI control for each axial pair (for example, `vertical` controls both the top and bottom sides). A spacing property may support arbitrary individual sides **or** axial sides, but not a mix of both. diff --git a/docs/reference-guides/core-blocks.md b/docs/reference-guides/core-blocks.md index 65e3cb1f65c8fc..14f800f140aee3 100644 --- a/docs/reference-guides/core-blocks.md +++ b/docs/reference-guides/core-blocks.md @@ -284,7 +284,7 @@ Introduce new sections and organize content to help visitors (and search engines - **Name:** core/heading - **Category:** text -- **Supports:** __unstablePasteTextInline, align (full, wide), anchor, color (background, gradients, link, text), spacing (margin, padding), typography (fontSize, lineHeight), ~~className~~ +- **Supports:** __unstablePasteTextInline, align (full, wide), anchor, className, color (background, gradients, link, text), spacing (margin, padding), typography (fontSize, lineHeight) - **Attributes:** content, level, placeholder, textAlign ## Home Link @@ -428,8 +428,17 @@ Display a list of all pages. ([Source](https://github.com/WordPress/gutenberg/tr - **Name:** core/page-list - **Category:** widgets -- **Supports:** ~~html~~, ~~reusable~~ -- **Attributes:** +- **Supports:** typography (fontSize, lineHeight), ~~html~~, ~~reusable~~ +- **Attributes:** parentPageID + +## Page List Item + +Displays a page inside a list of all pages. ([Source](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/page-list-item)) + +- **Name:** core/page-list-item +- **Category:** widgets +- **Supports:** ~~html~~, ~~inserter~~, ~~lock~~, ~~reusable~~ +- **Attributes:** hasChildren, id, label, link, title ## Paragraph @@ -563,7 +572,7 @@ Contains the block elements used to render a post, like the title, date, feature - **Name:** core/post-template - **Category:** theme -- **Supports:** align, typography (fontSize, lineHeight), ~~html~~, ~~reusable~~ +- **Supports:** align, color (background, gradients, link, text), typography (fontSize, lineHeight), ~~html~~, ~~reusable~~ - **Attributes:** ## Post Terms @@ -608,7 +617,7 @@ An advanced block that allows displaying post types based on different query par - **Name:** core/query - **Category:** theme -- **Supports:** align (full, wide), color (background, gradients, link, text), ~~html~~ +- **Supports:** align (full, wide), ~~html~~ - **Attributes:** displayLayout, namespace, query, queryId, tagName ## No results diff --git a/docs/reference-guides/data/README.md b/docs/reference-guides/data/README.md index 1134c1d5ddd307..5f4d8d92d4bd49 100644 --- a/docs/reference-guides/data/README.md +++ b/docs/reference-guides/data/README.md @@ -12,7 +12,6 @@ - [**core/editor**: The Post Editor’s Data](/docs/reference-guides/data/data-core-editor.md) - [**core/keyboard-shortcuts**: The Keyboard Shortcuts Data](/docs/reference-guides/data/data-core-keyboard-shortcuts.md) - [**core/notices**: Notices Data](/docs/reference-guides/data/data-core-notices.md) -- [**core/nux**: The NUX (New User Experience) Data](/docs/reference-guides/data/data-core-nux.md) - [**core/preferences**: Preferences](/docs/reference-guides/data/data-core-preferences.md) - [**core/reusable-blocks**: Reusable blocks](/docs/reference-guides/data/data-core-reusable-blocks.md) - [**core/rich-text**: Rich Text](/docs/reference-guides/data/data-core-rich-text.md) diff --git a/docs/reference-guides/data/data-core-block-editor.md b/docs/reference-guides/data/data-core-block-editor.md index 8d7bceb5b44317..0580aa0141b2be 100644 --- a/docs/reference-guides/data/data-core-block-editor.md +++ b/docs/reference-guides/data/data-core-block-editor.md @@ -556,6 +556,18 @@ _Properties_ - _isDisabled_ `boolean`: Whether or not the user should be prevented from inserting this item. - _frecency_ `number`: Heuristic that combines frequency and recency. +### getLastInsertedBlockClientId + +Gets the client id of the last inserted block. + +_Parameters_ + +- _state_ `Object`: Global application state. + +_Returns_ + +- `string|undefined`: Client Id of the last inserted block. + ### getLastMultiSelectedBlockClientId Returns the client ID of the last block in the multi-selection set, or null @@ -801,7 +813,7 @@ _Parameters_ _Returns_ -- `?string`: Block Template Lock +- `string|false`: Block Template Lock ### hasBlockMovingClientId diff --git a/docs/reference-guides/data/data-core-edit-site.md b/docs/reference-guides/data/data-core-edit-site.md index 9ee5fea71801b9..53fd4d138383ae 100644 --- a/docs/reference-guides/data/data-core-edit-site.md +++ b/docs/reference-guides/data/data-core-edit-site.md @@ -20,8 +20,11 @@ _Returns_ ### getCurrentTemplateNavigationPanelSubMenu -Returns the current template or template part's corresponding -navigation panel's sub menu, to be used with `openNavigationPanelToMenu`. +> **Deprecated** + +### getCurrentTemplateTemplateParts + +Returns the template parts and their blocks for the current edited template. _Parameters_ @@ -29,11 +32,13 @@ _Parameters_ _Returns_ -- `string`: The current template or template part's sub menu. +- `Array`: Template parts and their blocks in an array. -### getCurrentTemplateTemplateParts +### getEditedPostContext -Returns the template parts and their blocks for the current edited template. +> **Deprecated** + +Returns the edited post's context object. _Parameters_ @@ -41,7 +46,7 @@ _Parameters_ _Returns_ -- `Array`: Template parts and their blocks in an array. +- `Object`: Page. ### getEditedPostId @@ -81,30 +86,16 @@ _Returns_ ### getHomeTemplateId -Returns the current home template ID. - -_Parameters_ - -- _state_ `Object`: Global application state. - -_Returns_ - -- `number?`: Home template ID. +> **Deprecated** ### getNavigationPanelActiveMenu -Returns the active menu in the navigation panel. - -_Parameters_ - -- _state_ `Object`: Global application state. - -_Returns_ - -- `string`: Active menu. +> **Deprecated** ### getPage +> **Deprecated** + Returns the current page object. _Parameters_ @@ -179,15 +170,7 @@ _Returns_ ### isNavigationOpened -Returns the current opened/closed state of the navigation panel. - -_Parameters_ - -- _state_ `Object`: Global application state. - -_Returns_ - -- `boolean`: True if the navigation panel should be open; false if closed. +> **Deprecated** ### isSaveViewOpened @@ -233,11 +216,9 @@ _Parameters_ ### openNavigationPanelToMenu -Opens the navigation panel and sets its active menu at the same time. +> **Deprecated** -_Parameters_ - -- _menu_ `string`: Identifies the menu to open. +Opens the navigation panel and sets its active menu at the same time. ### removeTemplate @@ -257,14 +238,21 @@ _Parameters_ - _options_ `[Object]`: - _options.allowUndo_ `[boolean]`: Whether to allow the user to undo reverting the template. Default true. -### setHomeTemplateId +### setEditedPostContext -Action that sets the home template ID to the template ID of the page resolved -from a given path. +Set's the current block editor context. _Parameters_ -- _homeTemplateId_ `number`: The template ID for the homepage. +- _context_ `Object`: The context object. + +_Returns_ + +- `number`: The resolved template ID for the page route. + +### setHomeTemplateId + +> **Deprecated** ### setIsInserterOpened @@ -290,11 +278,9 @@ _Parameters_ ### setIsNavigationPanelOpened -Sets whether the navigation panel should be open. +> **Deprecated** -_Parameters_ - -- _isOpen_ `boolean`: If true, opens the nav panel. If false, closes it. It does not toggle the state, but sets it directly. +Sets whether the navigation panel should be open. ### setIsSaveViewOpened @@ -306,11 +292,9 @@ _Parameters_ ### setNavigationPanelActiveMenu -Action that sets the active navigation panel menu. - -_Parameters_ +> **Deprecated** -- _menu_ `string`: Menu prop of active menu. +Action that sets the active navigation panel menu. _Returns_ diff --git a/docs/reference-guides/data/data-core-nux.md b/docs/reference-guides/data/data-core-nux.md deleted file mode 100644 index 4d2e8a0d98d546..00000000000000 --- a/docs/reference-guides/data/data-core-nux.md +++ /dev/null @@ -1,99 +0,0 @@ -# The NUX (New User Experience) Data - -Namespace: `core/nux`. - -## Selectors - - - -### areTipsEnabled - -Returns whether or not tips are globally enabled. - -_Parameters_ - -- _state_ `Object`: Global application state. - -_Returns_ - -- `boolean`: Whether tips are globally enabled. - -### getAssociatedGuide - -Returns an object describing the guide, if any, that the given tip is a part -of. - -_Parameters_ - -- _state_ `Object`: Global application state. -- _tipId_ `string`: The tip to query. - -_Returns_ - -- `?NUXGuideInfo`: Information about the associated guide. - -### isTipVisible - -Determines whether or not the given tip is showing. Tips are hidden if they -are disabled, have been dismissed, or are not the current tip in any -guide that they have been added to. - -_Parameters_ - -- _state_ `Object`: Global application state. -- _tipId_ `string`: The tip to query. - -_Returns_ - -- `boolean`: Whether or not the given tip is showing. - - - -## Actions - - - -### disableTips - -Returns an action object that, when dispatched, prevents all tips from -showing again. - -_Returns_ - -- `Object`: Action object. - -### dismissTip - -Returns an action object that, when dispatched, dismisses the given tip. A -dismissed tip will not show again. - -_Parameters_ - -- _id_ `string`: The tip to dismiss. - -_Returns_ - -- `Object`: Action object. - -### enableTips - -Returns an action object that, when dispatched, makes all tips show again. - -_Returns_ - -- `Object`: Action object. - -### triggerGuide - -Returns an action object that, when dispatched, presents a guide that takes -the user through a series of tips step by step. - -_Parameters_ - -- _tipIds_ `string[]`: Which tips to show in the guide. - -_Returns_ - -- `Object`: Action object. - - diff --git a/docs/reference-guides/theme-json-reference/theme-json-living.md b/docs/reference-guides/theme-json-reference/theme-json-living.md index 8f07f409028bec..c26bcae731d98c 100644 --- a/docs/reference-guides/theme-json-reference/theme-json-living.md +++ b/docs/reference-guides/theme-json-reference/theme-json-living.md @@ -119,7 +119,7 @@ Settings related to typography. | customFontSize | boolean | true | | | fontStyle | boolean | true | | | fontWeight | boolean | true | | -| fluid | boolean | | | +| fluid | undefined | false | | | letterSpacing | boolean | true | | | lineHeight | boolean | false | | | textDecoration | boolean | true | | @@ -225,6 +225,13 @@ Outline styles. | style | undefined | | | width | undefined | | +--- + +### css + +Sets custom CSS to apply styling not covered by other theme.json properties. + + --- diff --git a/docs/toc.json b/docs/toc.json index 532e6ef2d20e1d..4203f40c16cbc4 100644 --- a/docs/toc.json +++ b/docs/toc.json @@ -278,7 +278,6 @@ "docs/reference-guides/data/data-core-keyboard-shortcuts.md": [] }, { "docs/reference-guides/data/data-core-notices.md": [] }, - { "docs/reference-guides/data/data-core-nux.md": [] }, { "docs/reference-guides/data/data-core-preferences.md": [] }, diff --git a/gutenberg.php b/gutenberg.php index 9c93c6c823f621..4d4489ef4c0615 100644 --- a/gutenberg.php +++ b/gutenberg.php @@ -3,9 +3,9 @@ * Plugin Name: Gutenberg * Plugin URI: https://github.com/WordPress/gutenberg * Description: Printing since 1440. This is the development plugin for the new block editor in core. - * Requires at least: 5.9 + * Requires at least: 6.0 * Requires PHP: 5.6 - * Version: 14.6.0-rc.1 + * Version: 14.8.0-rc.1 * Author: Gutenberg Team * Text Domain: gutenberg * diff --git a/lib/block-supports/duotone.php b/lib/block-supports/duotone.php index 4a9a23d7b5af3f..b4a8397d72ece7 100644 --- a/lib/block-supports/duotone.php +++ b/lib/block-supports/duotone.php @@ -387,7 +387,7 @@ function gutenberg_get_duotone_filter_svg( $preset ) { $svg = ob_get_clean(); - if ( ! defined( 'SCRIPT_DEBUG' ) || ! SCRIPT_DEBUG ) { + if ( ! SCRIPT_DEBUG ) { // Clean up the whitespace. $svg = preg_replace( "/[\r\n\t ]+/", ' ', $svg ); $svg = str_replace( '> <', '><', $svg ); @@ -464,7 +464,7 @@ function gutenberg_render_duotone_support( $block_content, $block ) { // !important is needed because these styles render before global styles, // and they should be overriding the duotone filters set by global styles. - $filter_style = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG + $filter_style = SCRIPT_DEBUG ? $selector . " {\n\tfilter: " . $filter_property . " !important;\n}\n" : $selector . '{filter:' . $filter_property . ' !important;}'; diff --git a/lib/block-supports/layout.php b/lib/block-supports/layout.php index a50e1fb8837178..39869baa4e5167 100644 --- a/lib/block-supports/layout.php +++ b/lib/block-supports/layout.php @@ -83,7 +83,7 @@ function gutenberg_get_layout_style( $selector, $layout, $has_block_gap_support $wide_max_width_value = $wide_size ? $wide_size : $content_size; // Make sure there is a single CSS rule, and all tags are stripped for security. - // TODO: Use `safecss_filter_attr` instead - once https://core.trac.wordpress.org/ticket/46197 is patched. + // TODO: Use `safecss_filter_attr` instead when the minimum required WP version is >= 6.1. $all_max_width_value = wp_strip_all_tags( explode( ';', $all_max_width_value )[0] ); $wide_max_width_value = wp_strip_all_tags( explode( ';', $wide_max_width_value )[0] ); @@ -316,23 +316,71 @@ function gutenberg_get_classnames_from_last_tag( $html ) { * @return string Filtered block content. */ function gutenberg_render_layout_support_flag( $block_content, $block ) { - $block_type = WP_Block_Type_Registry::get_instance()->get_registered( $block['blockName'] ); - $support_layout = block_has_support( $block_type, array( '__experimentalLayout' ), false ); + $block_type = WP_Block_Type_Registry::get_instance()->get_registered( $block['blockName'] ); + $support_layout = block_has_support( $block_type, array( '__experimentalLayout' ), false ); + $has_child_layout = isset( $block['attrs']['style']['layout']['selfStretch'] ); - if ( ! $support_layout ) { + if ( ! $support_layout + && ! $has_child_layout ) { return $block_content; } - $block_gap = gutenberg_get_global_settings( array( 'spacing', 'blockGap' ) ); - $global_layout_settings = gutenberg_get_global_settings( array( 'layout' ) ); - $has_block_gap_support = isset( $block_gap ) ? null !== $block_gap : false; - $default_block_layout = _wp_array_get( $block_type->supports, array( '__experimentalLayout', 'default' ), array() ); - $used_layout = isset( $block['attrs']['layout'] ) ? $block['attrs']['layout'] : $default_block_layout; + $outer_class_names = array(); + + if ( $has_child_layout && ( 'fixed' === $block['attrs']['style']['layout']['selfStretch'] || 'fill' === $block['attrs']['style']['layout']['selfStretch'] ) ) { + + $container_content_class = wp_unique_id( 'wp-container-content-' ); - if ( isset( $used_layout['inherit'] ) && $used_layout['inherit'] ) { - if ( ! $global_layout_settings ) { - return $block_content; + $child_layout_styles = array(); + + if ( 'fixed' === $block['attrs']['style']['layout']['selfStretch'] && isset( $block['attrs']['style']['layout']['flexSize'] ) ) { + $child_layout_styles[] = array( + 'selector' => ".$container_content_class", + 'declarations' => array( + 'flex-basis' => $block['attrs']['style']['layout']['flexSize'], + 'box-sizing' => 'border-box', + ), + ); + } elseif ( 'fill' === $block['attrs']['style']['layout']['selfStretch'] ) { + $child_layout_styles[] = array( + 'selector' => ".$container_content_class", + 'declarations' => array( + 'flex-grow' => '1', + ), + ); } + + gutenberg_style_engine_get_stylesheet_from_css_rules( + $child_layout_styles, + array( + 'context' => 'block-supports', + 'prettify' => false, + ) + ); + + $outer_class_names[] = $container_content_class; + + } + + // Return early if only child layout exists. + if ( ! $support_layout && ! empty( $outer_class_names ) ) { + $content = new WP_HTML_Tag_Processor( $block_content ); + $content->next_tag(); + $content->add_class( implode( ' ', $outer_class_names ) ); + return (string) $content; + } + + $global_settings = gutenberg_get_global_settings(); + $block_gap = _wp_array_get( $global_settings, array( 'spacing', 'blockGap' ), null ); + $has_block_gap_support = isset( $block_gap ); + $global_layout_settings = _wp_array_get( $global_settings, array( 'layout' ), null ); + $root_padding_aware_alignments = _wp_array_get( $global_settings, array( 'useRootPaddingAwareAlignments' ), false ); + + $default_block_layout = _wp_array_get( $block_type->supports, array( '__experimentalLayout', 'default' ), array() ); + $used_layout = isset( $block['attrs']['layout'] ) ? $block['attrs']['layout'] : $default_block_layout; + + if ( isset( $used_layout['inherit'] ) && $used_layout['inherit'] && ! $global_layout_settings ) { + return $block_content; } $class_names = array(); @@ -346,7 +394,7 @@ function gutenberg_render_layout_support_flag( $block_content, $block ) { } if ( - gutenberg_get_global_settings( array( 'useRootPaddingAwareAlignments' ) ) && + $root_padding_aware_alignments && isset( $used_layout['type'] ) && 'constrained' === $used_layout['type'] ) { @@ -428,13 +476,26 @@ function gutenberg_render_layout_support_flag( $block_content, $block ) { } } + $content_with_outer_classnames = ''; + + if ( ! empty( $outer_class_names ) ) { + $content_with_outer_classnames = new WP_HTML_Tag_Processor( $block_content ); + $content_with_outer_classnames->next_tag(); + foreach ( $outer_class_names as $outer_class_name ) { + $content_with_outer_classnames->add_class( $outer_class_name ); + } + + $content_with_outer_classnames = (string) $content_with_outer_classnames; + } + /** * The first chunk of innerContent contains the block markup up until the inner blocks start. * We want to target the opening tag of the inner blocks wrapper, which is the last tag in that chunk. */ $inner_content_classnames = isset( $block['innerContent'][0] ) && 'string' === gettype( $block['innerContent'][0] ) ? gutenberg_get_classnames_from_last_tag( $block['innerContent'][0] ) : ''; - $content = new WP_HTML_Tag_Processor( $block_content ); + $content = $content_with_outer_classnames ? new WP_HTML_Tag_Processor( $content_with_outer_classnames ) : new WP_HTML_Tag_Processor( $block_content ); + if ( $inner_content_classnames ) { $content->next_tag( array( 'class_name' => $inner_content_classnames ) ); foreach ( $class_names as $class_name ) { @@ -442,7 +503,9 @@ function gutenberg_render_layout_support_flag( $block_content, $block ) { } } else { $content->next_tag(); - $content->add_class( implode( ' ', $class_names ) ); + foreach ( $class_names as $class_name ) { + $content->add_class( $class_name ); + } } return (string) $content; diff --git a/lib/block-supports/typography.php b/lib/block-supports/typography.php index 01d223b84281eb..809fba1a6e7aca 100644 --- a/lib/block-supports/typography.php +++ b/lib/block-supports/typography.php @@ -451,18 +451,25 @@ function gutenberg_get_typography_font_size_value( $preset, $should_use_fluid_ty // Checks if fluid font sizes are activated. $typography_settings = gutenberg_get_global_settings( array( 'typography' ) ); - $should_use_fluid_typography = isset( $typography_settings['fluid'] ) && true === $typography_settings['fluid'] ? true : $should_use_fluid_typography; + $should_use_fluid_typography + = isset( $typography_settings['fluid'] ) && + ( true === $typography_settings['fluid'] || is_array( $typography_settings['fluid'] ) ) ? + true : + $should_use_fluid_typography; if ( ! $should_use_fluid_typography ) { return $preset['size']; } + $fluid_settings = isset( $typography_settings['fluid'] ) && is_array( $typography_settings['fluid'] ) ? $typography_settings['fluid'] : array(); + // Defaults. $default_maximum_viewport_width = '1600px'; $default_minimum_viewport_width = '768px'; $default_minimum_font_size_factor = 0.75; $default_scale_factor = 1; - $default_minimum_font_size_limit = '14px'; + $has_min_font_size = isset( $fluid_settings['minFontSize'] ) && ! empty( gutenberg_get_typography_value_and_unit( $fluid_settings['minFontSize'] ) ); + $default_minimum_font_size_limit = $has_min_font_size ? $fluid_settings['minFontSize'] : '14px'; // Font sizes. $fluid_font_size_settings = isset( $preset['fluid'] ) ? $preset['fluid'] : null; diff --git a/lib/blocks.php b/lib/blocks.php index d991eac8ddbe4d..add72e77062cb3 100644 --- a/lib/blocks.php +++ b/lib/blocks.php @@ -23,7 +23,6 @@ function gutenberg_reregister_core_block_types() { 'columns', 'comments', 'group', - 'heading', 'html', 'list', 'list-item', @@ -69,6 +68,7 @@ function gutenberg_reregister_core_block_types() { 'home-link.php' => 'core/home-link', 'image.php' => 'core/image', 'gallery.php' => 'core/gallery', + 'heading.php' => 'core/heading', 'latest-comments.php' => 'core/latest-comments', 'latest-posts.php' => 'core/latest-posts', 'loginout.php' => 'core/loginout', @@ -190,7 +190,7 @@ function gutenberg_register_core_block_assets( $block_name ) { // When in production, use the plugin's version as the default asset version; // else (for development or test) default to use the current time. - $default_version = defined( 'GUTENBERG_VERSION' ) && ! ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? GUTENBERG_VERSION : time(); + $default_version = defined( 'GUTENBERG_VERSION' ) && ! SCRIPT_DEBUG ? GUTENBERG_VERSION : time(); $style_path = "build/block-library/blocks/$block_name/"; $stylesheet_url = gutenberg_url( $style_path . 'style.css' ); diff --git a/lib/compat/wordpress-6.1/class-wp-theme-json-6-1.php b/lib/class-wp-theme-json-gutenberg.php similarity index 53% rename from lib/compat/wordpress-6.1/class-wp-theme-json-6-1.php rename to lib/class-wp-theme-json-gutenberg.php index e965e0e8246775..001798e5a326ed 100644 --- a/lib/compat/wordpress-6.1/class-wp-theme-json-6-1.php +++ b/lib/class-wp-theme-json-gutenberg.php @@ -1,8 +1,9 @@ array( ':visited', ':hover', ':focus', ':active' ), - 'button' => array( ':visited', ':hover', ':focus', ':active' ), - ); + const ROOT_BLOCK_SELECTOR = 'body'; /** * The sources of data this object can represent. * + * @since 5.8.0 + * @since 6.1.0 Added 'blocks'. * @var string[] */ const VALID_ORIGINS = array( @@ -44,11 +59,140 @@ class WP_Theme_JSON_6_1 extends WP_Theme_JSON_6_0 { 'custom', ); + /** + * Presets are a set of values that serve + * to bootstrap some styles: colors, font sizes, etc. + * + * They are a unkeyed array of values such as: + * + * ```php + * array( + * array( + * 'slug' => 'unique-name-within-the-set', + * 'name' => 'Name for the UI', + * => 'value' + * ), + * ) + * ``` + * + * This contains the necessary metadata to process them: + * + * - path => Where to find the preset within the settings section. + * - prevent_override => Disables override of default presets by theme presets. + * The relationship between whether to override the defaults + * and whether the defaults are enabled is inverse: + * - If defaults are enabled => theme presets should not be overriden + * - If defaults are disabled => theme presets should be overriden + * For example, a theme sets defaultPalette to false, + * making the default palette hidden from the user. + * In that case, we want all the theme presets to be present, + * so they should override the defaults by setting this false. + * - use_default_names => whether to use the default names + * - value_key => the key that represents the value + * - value_func => optionally, instead of value_key, a function to generate + * the value that takes a preset as an argument + * (either value_key or value_func should be present) + * - css_vars => template string to use in generating the CSS Custom Property. + * Example output: "--wp--preset--duotone--blue: " will generate as many CSS Custom Properties as presets defined + * substituting the $slug for the slug's value for each preset value. + * - classes => array containing a structure with the classes to + * generate for the presets, where for each array item + * the key is the class name and the value the property name. + * The "$slug" substring will be replaced by the slug of each preset. + * For example: + * 'classes' => array( + * '.has-$slug-color' => 'color', + * '.has-$slug-background-color' => 'background-color', + * '.has-$slug-border-color' => 'border-color', + * ) + * - properties => array of CSS properties to be used by kses to + * validate the content of each preset + * by means of the remove_insecure_properties method. + * + * @since 5.8.0 + * @since 5.9.0 Added the `color.duotone` and `typography.fontFamilies` presets, + * `use_default_names` preset key, and simplified the metadata structure. + * @since 6.0.0 Replaced `override` with `prevent_override` and updated the + * `prevent_override` value for `color.duotone` to use `color.defaultDuotone`. + * @var array + */ + const PRESETS_METADATA = array( + array( + 'path' => array( 'color', 'palette' ), + 'prevent_override' => array( 'color', 'defaultPalette' ), + 'use_default_names' => false, + 'value_key' => 'color', + 'css_vars' => '--wp--preset--color--$slug', + 'classes' => array( + '.has-$slug-color' => 'color', + '.has-$slug-background-color' => 'background-color', + '.has-$slug-border-color' => 'border-color', + ), + 'properties' => array( 'color', 'background-color', 'border-color' ), + ), + array( + 'path' => array( 'color', 'gradients' ), + 'prevent_override' => array( 'color', 'defaultGradients' ), + 'use_default_names' => false, + 'value_key' => 'gradient', + 'css_vars' => '--wp--preset--gradient--$slug', + 'classes' => array( '.has-$slug-gradient-background' => 'background' ), + 'properties' => array( 'background' ), + ), + array( + 'path' => array( 'color', 'duotone' ), + 'prevent_override' => array( 'color', 'defaultDuotone' ), + 'use_default_names' => false, + 'value_func' => 'gutenberg_get_duotone_filter_property', + 'css_vars' => '--wp--preset--duotone--$slug', + 'classes' => array(), + 'properties' => array( 'filter' ), + ), + array( + 'path' => array( 'typography', 'fontSizes' ), + 'prevent_override' => false, + 'use_default_names' => true, + 'value_func' => 'gutenberg_get_typography_font_size_value', + 'css_vars' => '--wp--preset--font-size--$slug', + 'classes' => array( '.has-$slug-font-size' => 'font-size' ), + 'properties' => array( 'font-size' ), + ), + array( + 'path' => array( 'typography', 'fontFamilies' ), + 'prevent_override' => false, + 'use_default_names' => false, + 'value_key' => 'fontFamily', + 'css_vars' => '--wp--preset--font-family--$slug', + 'classes' => array( '.has-$slug-font-family' => 'font-family' ), + 'properties' => array( 'font-family' ), + ), + array( + 'path' => array( 'spacing', 'spacingSizes' ), + 'prevent_override' => false, + 'use_default_names' => true, + 'value_key' => 'size', + 'css_vars' => '--wp--preset--spacing--$slug', + 'classes' => array(), + 'properties' => array( 'padding', 'margin' ), + ), + ); + /** * Metadata for style properties. * * Each element is a direct mapping from the CSS property name to the * path to the value in theme.json & block attributes. + * + * @since 5.8.0 + * @since 5.9.0 Added the `border-*`, `font-family`, `font-style`, `font-weight`, + * `letter-spacing`, `margin-*`, `padding-*`, `--wp--style--block-gap`, + * `text-decoration`, `text-transform`, and `filter` properties, + * simplified the metadata structure. + * @since 6.1.0 Added the `border-*-color`, `border-*-width`, `border-*-style`, + * `--wp--style--root--padding-*`, and `box-shadow` properties, + * removed the `--wp--style--block-gap` property. + * @since 6.2.0 Added `min-height`. + * @var array */ const PROPERTIES_METADATA = array( 'background' => array( 'color', 'gradient' ), @@ -85,6 +229,7 @@ class WP_Theme_JSON_6_1 extends WP_Theme_JSON_6_0 { 'margin-right' => array( 'spacing', 'margin', 'right' ), 'margin-bottom' => array( 'spacing', 'margin', 'bottom' ), 'margin-left' => array( 'spacing', 'margin', 'left' ), + 'min-height' => array( 'dimensions', 'minHeight' ), 'outline-color' => array( 'outline', 'color' ), 'outline-offset' => array( 'outline', 'offset' ), 'outline-style' => array( 'outline', 'style' ), @@ -106,260 +251,136 @@ class WP_Theme_JSON_6_1 extends WP_Theme_JSON_6_0 { ); /** - * The valid elements that can be found under styles. + * Protected style properties. * - * @var string[] + * These style properties are only rendered if a setting enables it + * via a value other than `null`. + * + * Each element maps the style property to the corresponding theme.json + * setting key. + * + * @since 5.9.0 */ - const ELEMENTS = array( - 'link' => 'a:where(:not(.wp-element-button))', // The where is needed to lower the specificity. - 'heading' => 'h1, h2, h3, h4, h5, h6', - 'h1' => 'h1', - 'h2' => 'h2', - 'h3' => 'h3', - 'h4' => 'h4', - 'h5' => 'h5', - 'h6' => 'h6', - 'button' => '.wp-element-button, .wp-block-button__link', // We have the .wp-block-button__link class so that this will target older buttons that have been serialized. - 'caption' => '.wp-element-caption, .wp-block-audio figcaption, .wp-block-embed figcaption, .wp-block-gallery figcaption, .wp-block-image figcaption, .wp-block-table figcaption, .wp-block-video figcaption', // The block classes are necessary to target older content that won't use the new class names. - 'cite' => 'cite', - ); - - const __EXPERIMENTAL_ELEMENT_CLASS_NAMES = array( - 'button' => 'wp-element-button', - 'caption' => 'wp-element-caption', - ); - - // List of block support features that can have their related styles - // generated under their own feature level selector rather than the block's. - const BLOCK_SUPPORT_FEATURE_LEVEL_SELECTORS = array( - '__experimentalBorder' => 'border', - 'color' => 'color', - 'spacing' => 'spacing', - 'typography' => 'typography', + const PROTECTED_PROPERTIES = array( + 'spacing.blockGap' => array( 'spacing', 'blockGap' ), ); /** - * Constructor. + * Indirect metadata for style properties that are not directly output. * - * @since 5.8.0 + * Each element is a direct mapping from a CSS property name to the + * path to the value in theme.json & block attributes. * - * @param array $theme_json A structure that follows the theme.json schema. - * @param string $origin Optional. What source of data this object represents. - * One of 'default', 'theme', or 'custom'. Default 'theme'. + * Indirect properties are not output directly by `compute_style_properties`, + * but are used elsewhere in the processing of global styles. The indirect + * property is used to validate whether or not a style value is allowed. + * + * @since 6.2.0 + * @var array */ - public function __construct( $theme_json = array(), $origin = 'theme' ) { - if ( ! in_array( $origin, static::VALID_ORIGINS, true ) ) { - $origin = 'theme'; - } - - $this->theme_json = WP_Theme_JSON_Schema::migrate( $theme_json ); - $registry = WP_Block_Type_Registry::get_instance(); - $valid_block_names = array_keys( $registry->get_all_registered() ); - $valid_element_names = array_keys( static::ELEMENTS ); - $theme_json = static::sanitize( $this->theme_json, $valid_block_names, $valid_element_names ); - $this->theme_json = static::maybe_opt_in_into_settings( $theme_json ); - - // Internally, presets are keyed by origin. - $nodes = static::get_setting_nodes( $this->theme_json ); - foreach ( $nodes as $node ) { - foreach ( static::PRESETS_METADATA as $preset_metadata ) { - $path = array_merge( $node['path'], $preset_metadata['path'] ); - $preset = _wp_array_get( $this->theme_json, $path, null ); - if ( null !== $preset ) { - // If the preset is not already keyed by origin. - if ( isset( $preset[0] ) || empty( $preset ) ) { - _wp_array_set( $this->theme_json, $path, array( $origin => $preset ) ); - } - } - } - } - } + const INDIRECT_PROPERTIES_METADATA = array( + 'gap' => array( 'spacing', 'blockGap' ), + 'column-gap' => array( 'spacing', 'blockGap', 'left' ), + 'row-gap' => array( 'spacing', 'blockGap', 'top' ), + ); /** - * Given an element name, returns a class name. + * The top-level keys a theme.json can have. * - * @param string $element The name of the element. - * - * @return string The name of the class. - * - * @since 6.1.0 + * @since 5.8.0 As `ALLOWED_TOP_LEVEL_KEYS`. + * @since 5.9.0 Renamed from `ALLOWED_TOP_LEVEL_KEYS` to `VALID_TOP_LEVEL_KEYS`, + * added the `customTemplates` and `templateParts` values. + * @var string[] */ - public static function get_element_class_name( $element ) { - return array_key_exists( $element, static::__EXPERIMENTAL_ELEMENT_CLASS_NAMES ) ? static::__EXPERIMENTAL_ELEMENT_CLASS_NAMES[ $element ] : ''; - } + const VALID_TOP_LEVEL_KEYS = array( + 'customTemplates', + 'patterns', + 'settings', + 'styles', + 'templateParts', + 'version', + 'title', + ); /** - * Sanitizes the input according to the schemas. - * - * @since 5.8.0 - * @since 5.9.0 Added the `$valid_block_names` and `$valid_element_name` parameters. + * The valid properties under the settings key. * - * @param array $input Structure to sanitize. - * @param array $valid_block_names List of valid block names. - * @param array $valid_element_names List of valid element names. - * @return array The sanitized output. + * @since 5.8.0 As `ALLOWED_SETTINGS`. + * @since 5.9.0 Renamed from `ALLOWED_SETTINGS` to `VALID_SETTINGS`, + * added new properties for `border`, `color`, `spacing`, + * and `typography`, and renamed others according to the new schema. + * @since 6.0.0 Added `color.defaultDuotone`. + * @since 6.1.0 Added `layout.definitions` and `useRootPaddingAwareAlignments`. + * @since 6.2.0 Added `dimensions.minHeight`. + * @var array */ - protected static function sanitize( $input, $valid_block_names, $valid_element_names ) { - - $output = array(); - - if ( ! is_array( $input ) ) { - return $output; - } - - // Preserve only the top most level keys. - $output = array_intersect_key( $input, array_flip( static::VALID_TOP_LEVEL_KEYS ) ); - - // Remove any rules that are annotated as "top" in VALID_STYLES constant. - // Some styles are only meant to be available at the top-level (e.g.: blockGap), - // hence, the schema for blocks & elements should not have them. - $styles_non_top_level = static::VALID_STYLES; - foreach ( array_keys( $styles_non_top_level ) as $section ) { - if ( array_key_exists( $section, $styles_non_top_level ) && is_array( $styles_non_top_level[ $section ] ) ) { - foreach ( array_keys( $styles_non_top_level[ $section ] ) as $prop ) { - if ( 'top' === $styles_non_top_level[ $section ][ $prop ] ) { - unset( $styles_non_top_level[ $section ][ $prop ] ); - } - } - } - } - - // Build the schema based on valid block & element names. - $schema = array(); - $schema_styles_elements = array(); - - // Set allowed element pseudo selectors based on per element allow list. - // Target data structure in schema: - // e.g. - // - top level elements: `$schema['styles']['elements']['link'][':hover']`. - // - block level elements: `$schema['styles']['blocks']['core/button']['elements']['link'][':hover']`. - foreach ( $valid_element_names as $element ) { - $schema_styles_elements[ $element ] = $styles_non_top_level; - - if ( array_key_exists( $element, static::VALID_ELEMENT_PSEUDO_SELECTORS ) ) { - foreach ( static::VALID_ELEMENT_PSEUDO_SELECTORS[ $element ] as $pseudo_selector ) { - $schema_styles_elements[ $element ][ $pseudo_selector ] = $styles_non_top_level; - } - } - } - - $schema_styles_blocks = array(); - $schema_settings_blocks = array(); - foreach ( $valid_block_names as $block ) { - $schema_settings_blocks[ $block ] = static::VALID_SETTINGS; - $schema_styles_blocks[ $block ] = $styles_non_top_level; - $schema_styles_blocks[ $block ]['elements'] = $schema_styles_elements; - } - - $schema['styles'] = static::VALID_STYLES; - $schema['styles']['blocks'] = $schema_styles_blocks; - $schema['styles']['elements'] = $schema_styles_elements; - $schema['settings'] = static::VALID_SETTINGS; - $schema['settings']['blocks'] = $schema_settings_blocks; - - // Remove anything that's not present in the schema. - foreach ( array( 'styles', 'settings' ) as $subtree ) { - if ( ! isset( $input[ $subtree ] ) ) { - continue; - } - - if ( ! is_array( $input[ $subtree ] ) ) { - unset( $output[ $subtree ] ); - continue; - } - - $result = static::remove_keys_not_in_schema( $input[ $subtree ], $schema[ $subtree ] ); - - if ( empty( $result ) ) { - unset( $output[ $subtree ] ); - } else { - $output[ $subtree ] = $result; - } - } - - return $output; - } - - /** - * Removes insecure data from theme.json. - * - * @since 5.9.0 - * - * @param array $theme_json Structure to sanitize. - * @return array Sanitized structure. - */ - public static function remove_insecure_properties( $theme_json ) { - $sanitized = array(); - - $theme_json = WP_Theme_JSON_Schema::migrate( $theme_json ); - - $valid_block_names = array_keys( static::get_blocks_metadata() ); - $valid_element_names = array_keys( static::ELEMENTS ); - - $theme_json = static::sanitize( $theme_json, $valid_block_names, $valid_element_names ); - - $blocks_metadata = static::get_blocks_metadata(); - $style_nodes = static::get_style_nodes( $theme_json, $blocks_metadata ); - - foreach ( $style_nodes as $metadata ) { - $input = _wp_array_get( $theme_json, $metadata['path'], array() ); - if ( empty( $input ) ) { - continue; - } - - $output = static::remove_insecure_styles( $input ); - - // Get a reference to element name from path. - // $metadata['path'] = array('styles','elements','link');. - $current_element = $metadata['path'][ count( $metadata['path'] ) - 1 ]; - - // $output is stripped of pseudo selectors. Readd and process them - // for insecure styles here. - if ( array_key_exists( $current_element, static::VALID_ELEMENT_PSEUDO_SELECTORS ) ) { - - foreach ( static::VALID_ELEMENT_PSEUDO_SELECTORS[ $current_element ] as $pseudo_selector ) { - if ( isset( $input[ $pseudo_selector ] ) ) { - $output[ $pseudo_selector ] = static::remove_insecure_styles( $input[ $pseudo_selector ] ); - } - } - } - - if ( ! empty( $output ) ) { - _wp_array_set( $sanitized, $metadata['path'], $output ); - } - } - - $setting_nodes = static::get_setting_nodes( $theme_json ); - foreach ( $setting_nodes as $metadata ) { - $input = _wp_array_get( $theme_json, $metadata['path'], array() ); - if ( empty( $input ) ) { - continue; - } - - $output = static::remove_insecure_settings( $input ); - if ( ! empty( $output ) ) { - _wp_array_set( $sanitized, $metadata['path'], $output ); - } - } - - if ( empty( $sanitized['styles'] ) ) { - unset( $theme_json['styles'] ); - } else { - $theme_json['styles'] = $sanitized['styles']; - } - - if ( empty( $sanitized['settings'] ) ) { - unset( $theme_json['settings'] ); - } else { - $theme_json['settings'] = $sanitized['settings']; - } - - return $theme_json; - } + const VALID_SETTINGS = array( + 'appearanceTools' => null, + 'useRootPaddingAwareAlignments' => null, + 'border' => array( + 'color' => null, + 'radius' => null, + 'style' => null, + 'width' => null, + ), + 'color' => array( + 'background' => null, + 'custom' => null, + 'customDuotone' => null, + 'customGradient' => null, + 'defaultDuotone' => null, + 'defaultGradients' => null, + 'defaultPalette' => null, + 'duotone' => null, + 'gradients' => null, + 'link' => null, + 'palette' => null, + 'text' => null, + ), + 'custom' => null, + 'dimensions' => array( + 'minHeight' => null, + ), + 'layout' => array( + 'contentSize' => null, + 'definitions' => null, + 'wideSize' => null, + ), + 'spacing' => array( + 'customSpacingSize' => null, + 'spacingSizes' => null, + 'spacingScale' => null, + 'blockGap' => null, + 'margin' => null, + 'padding' => null, + 'units' => null, + ), + 'typography' => array( + 'fluid' => null, + 'customFontSize' => null, + 'dropCap' => null, + 'fontFamilies' => null, + 'fontSizes' => null, + 'fontStyle' => null, + 'fontWeight' => null, + 'letterSpacing' => null, + 'lineHeight' => null, + 'textDecoration' => null, + 'textTransform' => null, + ), + ); /** * The valid properties under the styles key. * + * @since 5.8.0 As `ALLOWED_STYLES`. + * @since 5.9.0 Renamed from `ALLOWED_STYLES` to `VALID_STYLES`, + * added new properties for `border`, `filter`, `spacing`, + * and `typography`. + * @since 6.1.0 Added new side properties for `border`, + * added new property `shadow`, + * updated `blockGap` to be allowed at any level. + * @since 6.2.0 Added `dimensions.minHeight`. * @var array */ const VALID_STYLES = array( @@ -378,7 +399,10 @@ public static function remove_insecure_properties( $theme_json ) { 'gradient' => null, 'text' => null, ), - 'shadow' => null, + 'css' => null, + 'dimensions' => array( + 'minHeight' => null, + ), 'filter' => array( 'duotone' => null, ), @@ -388,6 +412,7 @@ public static function remove_insecure_properties( $theme_json ) { 'style' => null, 'width' => null, ), + 'shadow' => null, 'spacing' => array( 'margin' => null, 'padding' => null, @@ -406,68 +431,367 @@ public static function remove_insecure_properties( $theme_json ) { ); /** - * Function that appends a sub-selector to a existing one. + * Defines which pseudo selectors are enabled for which elements. * - * Given the compounded $selector "h1, h2, h3" - * and the $to_append selector ".some-class" the result will be - * "h1.some-class, h2.some-class, h3.some-class". + * The order of the selectors should be: visited, hover, focus, active. + * This is to ensure that 'visited' has the lowest specificity + * and the other selectors can always overwrite it. * - * @since 5.8.0 - * @since 6.1.0 Added append position. + * See https://core.trac.wordpress.org/ticket/56928. + * Note: this will affect both top-level and block-level elements. * - * @param string $selector Original selector. - * @param string $to_append Selector to append. - * @param string $position A position sub-selector should be appended. Default: 'right'. - * @return string + * @since 6.1.0 */ - protected static function append_to_selector( $selector, $to_append, $position = 'right' ) { - $new_selectors = array(); - $selectors = explode( ',', $selector ); - foreach ( $selectors as $sel ) { - $new_selectors[] = 'right' === $position ? $sel . $to_append : $to_append . $sel; - } + const VALID_ELEMENT_PSEUDO_SELECTORS = array( + 'link' => array( ':visited', ':hover', ':focus', ':active' ), + 'button' => array( ':visited', ':hover', ':focus', ':active' ), + ); - return implode( ',', $new_selectors ); - } + /** + * The valid elements that can be found under styles. + * + * @since 5.8.0 + * @since 6.1.0 Added `heading`, `button`, and `caption` elements. + * @var string[] + */ + const ELEMENTS = array( + 'link' => 'a:where(:not(.wp-element-button))', // The `where` is needed to lower the specificity. + 'heading' => 'h1, h2, h3, h4, h5, h6', + 'h1' => 'h1', + 'h2' => 'h2', + 'h3' => 'h3', + 'h4' => 'h4', + 'h5' => 'h5', + 'h6' => 'h6', + // We have the .wp-block-button__link class so that this will target older buttons that have been serialized. + 'button' => '.wp-element-button, .wp-block-button__link', + // The block classes are necessary to target older content that won't use the new class names. + 'caption' => '.wp-element-caption, .wp-block-audio figcaption, .wp-block-embed figcaption, .wp-block-gallery figcaption, .wp-block-image figcaption, .wp-block-table figcaption, .wp-block-video figcaption', + 'cite' => 'cite', + ); + + const __EXPERIMENTAL_ELEMENT_CLASS_NAMES = array( + 'button' => 'wp-element-button', + 'caption' => 'wp-element-caption', + ); /** - * Returns the metadata for each block. + * List of block support features that can have their related styles + * generated under their own feature level selector rather than the block's. * - * Example: + * @since 6.1.0 + * @var string[] + */ + const BLOCK_SUPPORT_FEATURE_LEVEL_SELECTORS = array( + '__experimentalBorder' => 'border', + 'color' => 'color', + 'spacing' => 'spacing', + 'typography' => 'typography', + ); + + /** + * Returns a class name by an element name. * - * { - * 'core/paragraph': { - * 'selector': 'p', - * 'elements': { - * 'link' => 'link selector', - * 'etc' => 'element selector' - * } - * }, - * 'core/heading': { - * 'selector': 'h1', - * 'elements': {} - * }, - * 'core/image': { - * 'selector': '.wp-block-image', - * 'duotone': 'img', - * 'elements': {} - * } - * } + * @since 6.1.0 * - * @return array Block metadata. + * @param string $element The name of the element. + * @return string The name of the class. */ - protected static function get_blocks_metadata() { - if ( null !== static::$blocks_metadata ) { - return static::$blocks_metadata; + public static function get_element_class_name( $element ) { + $class_name = ''; + + // TODO: Replace array_key_exists() with isset() check once WordPress drops + // support for PHP 5.6. See https://core.trac.wordpress.org/ticket/57067. + if ( array_key_exists( $element, static::__EXPERIMENTAL_ELEMENT_CLASS_NAMES ) ) { + $class_name = static::__EXPERIMENTAL_ELEMENT_CLASS_NAMES[ $element ]; } - static::$blocks_metadata = array(); + return $class_name; + } - $registry = WP_Block_Type_Registry::get_instance(); - $blocks = $registry->get_all_registered(); - foreach ( $blocks as $block_name => $block_type ) { - if ( - isset( $block_type->supports['__experimentalSelector'] ) && + /** + * Options that settings.appearanceTools enables. + * + * @since 6.0.0 + * @var array + */ + const APPEARANCE_TOOLS_OPT_INS = array( + array( 'border', 'color' ), + array( 'border', 'radius' ), + array( 'border', 'style' ), + array( 'border', 'width' ), + array( 'color', 'link' ), + array( 'dimensions', 'minHeight' ), + array( 'spacing', 'blockGap' ), + array( 'spacing', 'margin' ), + array( 'spacing', 'padding' ), + array( 'typography', 'lineHeight' ), + ); + + /** + * The latest version of the schema in use. + * + * @since 5.8.0 + * @since 5.9.0 Changed value from 1 to 2. + * @var int + */ + const LATEST_SCHEMA = 2; + + /** + * Constructor. + * + * @since 5.8.0 + * + * @param array $theme_json A structure that follows the theme.json schema. + * @param string $origin Optional. What source of data this object represents. + * One of 'default', 'theme', or 'custom'. Default 'theme'. + */ + public function __construct( $theme_json = array(), $origin = 'theme' ) { + if ( ! in_array( $origin, static::VALID_ORIGINS, true ) ) { + $origin = 'theme'; + } + + $this->theme_json = WP_Theme_JSON_Schema::migrate( $theme_json ); + $registry = WP_Block_Type_Registry::get_instance(); + $valid_block_names = array_keys( $registry->get_all_registered() ); + $valid_element_names = array_keys( static::ELEMENTS ); + $theme_json = static::sanitize( $this->theme_json, $valid_block_names, $valid_element_names ); + $this->theme_json = static::maybe_opt_in_into_settings( $theme_json ); + + // Internally, presets are keyed by origin. + $nodes = static::get_setting_nodes( $this->theme_json ); + foreach ( $nodes as $node ) { + foreach ( static::PRESETS_METADATA as $preset_metadata ) { + $path = $node['path']; + foreach ( $preset_metadata['path'] as $subpath ) { + $path[] = $subpath; + } + $preset = _wp_array_get( $this->theme_json, $path, null ); + if ( null !== $preset ) { + // If the preset is not already keyed by origin. + if ( isset( $preset[0] ) || empty( $preset ) ) { + _wp_array_set( $this->theme_json, $path, array( $origin => $preset ) ); + } + } + } + } + } + + /** + * Enables some opt-in settings if theme declared support. + * + * @since 5.9.0 + * + * @param array $theme_json A theme.json structure to modify. + * @return array The modified theme.json structure. + */ + protected static function maybe_opt_in_into_settings( $theme_json ) { + $new_theme_json = $theme_json; + + if ( + isset( $new_theme_json['settings']['appearanceTools'] ) && + true === $new_theme_json['settings']['appearanceTools'] + ) { + static::do_opt_in_into_settings( $new_theme_json['settings'] ); + } + + if ( isset( $new_theme_json['settings']['blocks'] ) && is_array( $new_theme_json['settings']['blocks'] ) ) { + foreach ( $new_theme_json['settings']['blocks'] as &$block ) { + if ( isset( $block['appearanceTools'] ) && ( true === $block['appearanceTools'] ) ) { + static::do_opt_in_into_settings( $block ); + } + } + } + + return $new_theme_json; + } + + /** + * Enables some settings. + * + * @since 5.9.0 + * + * @param array $context The context to which the settings belong. + */ + protected static function do_opt_in_into_settings( &$context ) { + foreach ( static::APPEARANCE_TOOLS_OPT_INS as $path ) { + // Use "unset prop" as a marker instead of "null" because + // "null" can be a valid value for some props (e.g. blockGap). + if ( 'unset prop' === _wp_array_get( $context, $path, 'unset prop' ) ) { + _wp_array_set( $context, $path, true ); + } + } + + unset( $context['appearanceTools'] ); + } + + /** + * Sanitizes the input according to the schemas. + * + * @since 5.8.0 + * @since 5.9.0 Added the `$valid_block_names` and `$valid_element_name` parameters. + * + * @param array $input Structure to sanitize. + * @param array $valid_block_names List of valid block names. + * @param array $valid_element_names List of valid element names. + * @return array The sanitized output. + */ + protected static function sanitize( $input, $valid_block_names, $valid_element_names ) { + + $output = array(); + + if ( ! is_array( $input ) ) { + return $output; + } + + // Preserve only the top most level keys. + $output = array_intersect_key( $input, array_flip( static::VALID_TOP_LEVEL_KEYS ) ); + + /* + * Remove any rules that are annotated as "top" in VALID_STYLES constant. + * Some styles are only meant to be available at the top-level (e.g.: blockGap), + * hence, the schema for blocks & elements should not have them. + */ + $styles_non_top_level = static::VALID_STYLES; + foreach ( array_keys( $styles_non_top_level ) as $section ) { + // array_key_exists() needs to be used instead of isset() because the value can be null. + if ( array_key_exists( $section, $styles_non_top_level ) && is_array( $styles_non_top_level[ $section ] ) ) { + foreach ( array_keys( $styles_non_top_level[ $section ] ) as $prop ) { + if ( 'top' === $styles_non_top_level[ $section ][ $prop ] ) { + unset( $styles_non_top_level[ $section ][ $prop ] ); + } + } + } + } + + // Build the schema based on valid block & element names. + $schema = array(); + $schema_styles_elements = array(); + + /* + * Set allowed element pseudo selectors based on per element allow list. + * Target data structure in schema: + * e.g. + * - top level elements: `$schema['styles']['elements']['link'][':hover']`. + * - block level elements: `$schema['styles']['blocks']['core/button']['elements']['link'][':hover']`. + */ + foreach ( $valid_element_names as $element ) { + $schema_styles_elements[ $element ] = $styles_non_top_level; + + // TODO: Replace array_key_exists() with isset() check once WordPress drops + // support for PHP 5.6. See https://core.trac.wordpress.org/ticket/57067. + if ( array_key_exists( $element, static::VALID_ELEMENT_PSEUDO_SELECTORS ) ) { + foreach ( static::VALID_ELEMENT_PSEUDO_SELECTORS[ $element ] as $pseudo_selector ) { + $schema_styles_elements[ $element ][ $pseudo_selector ] = $styles_non_top_level; + } + } + } + + $schema_styles_blocks = array(); + $schema_settings_blocks = array(); + foreach ( $valid_block_names as $block ) { + $schema_settings_blocks[ $block ] = static::VALID_SETTINGS; + $schema_styles_blocks[ $block ] = $styles_non_top_level; + $schema_styles_blocks[ $block ]['elements'] = $schema_styles_elements; + } + + $schema['styles'] = static::VALID_STYLES; + $schema['styles']['blocks'] = $schema_styles_blocks; + $schema['styles']['elements'] = $schema_styles_elements; + $schema['settings'] = static::VALID_SETTINGS; + $schema['settings']['blocks'] = $schema_settings_blocks; + + // Remove anything that's not present in the schema. + foreach ( array( 'styles', 'settings' ) as $subtree ) { + if ( ! isset( $input[ $subtree ] ) ) { + continue; + } + + if ( ! is_array( $input[ $subtree ] ) ) { + unset( $output[ $subtree ] ); + continue; + } + + $result = static::remove_keys_not_in_schema( $input[ $subtree ], $schema[ $subtree ] ); + + if ( empty( $result ) ) { + unset( $output[ $subtree ] ); + } else { + $output[ $subtree ] = $result; + } + } + + return $output; + } + + /** + * Appends a sub-selector to an existing one. + * + * Given the compounded $selector "h1, h2, h3" + * and the $to_append selector ".some-class" the result will be + * "h1.some-class, h2.some-class, h3.some-class". + * + * @since 5.8.0 + * @since 6.1.0 Added append position. + * + * @param string $selector Original selector. + * @param string $to_append Selector to append. + * @param string $position A position sub-selector should be appended. Default 'right'. + * @return string The new selector. + */ + protected static function append_to_selector( $selector, $to_append, $position = 'right' ) { + $new_selectors = array(); + $selectors = explode( ',', $selector ); + foreach ( $selectors as $sel ) { + $new_selectors[] = 'right' === $position ? $sel . $to_append : $to_append . $sel; + } + return implode( ',', $new_selectors ); + } + + /** + * Returns the metadata for each block. + * + * Example: + * + * { + * 'core/paragraph': { + * 'selector': 'p', + * 'elements': { + * 'link' => 'link selector', + * 'etc' => 'element selector' + * } + * }, + * 'core/heading': { + * 'selector': 'h1', + * 'elements': {} + * }, + * 'core/image': { + * 'selector': '.wp-block-image', + * 'duotone': 'img', + * 'elements': {} + * } + * } + * + * @since 5.8.0 + * @since 5.9.0 Added `duotone` key with CSS selector. + * @since 6.1.0 Added `features` key with block support feature level selectors. + * + * @return array Block metadata. + */ + protected static function get_blocks_metadata() { + // NOTE: the compat/6.1 version of this method in Gutenberg did not have these changes. + $registry = WP_Block_Type_Registry::get_instance(); + $blocks = $registry->get_all_registered(); + + // Is there metadata for all currently registered blocks? + $blocks = array_diff_key( $blocks, static::$blocks_metadata ); + if ( empty( $blocks ) ) { + return static::$blocks_metadata; + } + + foreach ( $blocks as $block_name => $block_type ) { + if ( + isset( $block_type->supports['__experimentalSelector'] ) && is_string( $block_type->supports['__experimentalSelector'] ) ) { static::$blocks_metadata[ $block_name ]['selector'] = $block_type->supports['__experimentalSelector']; @@ -501,7 +825,7 @@ protected static function get_blocks_metadata() { static::$blocks_metadata[ $block_name ]['features'] = $features; } - // Assign defaults, then override those that the block sets by itself. + // Assign defaults, then overwrite those that the block sets by itself. // If the block selector is compounded, will append the element to each // individual block selector. $block_selectors = explode( ',', static::$blocks_metadata[ $block_name ]['selector'] ); @@ -517,174 +841,90 @@ protected static function get_blocks_metadata() { static::$blocks_metadata[ $block_name ]['elements'][ $el_name ] = implode( ',', $element_selector ); } } + return static::$blocks_metadata; } /** - * Builds metadata for the style nodes, which returns in the form of: + * Given a tree, removes the keys that are not present in the schema. * - * [ - * [ - * 'path' => [ 'path', 'to', 'some', 'node' ], - * 'selector' => 'CSS selector for some node', - * 'duotone' => 'CSS selector for duotone for some node' - * ], - * [ - * 'path' => ['path', 'to', 'other', 'node' ], - * 'selector' => 'CSS selector for other node', - * 'duotone' => null - * ], - * ] + * It is recursive and modifies the input in-place. * * @since 5.8.0 * - * @param array $theme_json The tree to extract style nodes from. - * @param array $selectors List of selectors per block. - * @return array + * @param array $tree Input to process. + * @param array $schema Schema to adhere to. + * @return array The modified $tree. */ - protected static function get_style_nodes( $theme_json, $selectors = array() ) { - $nodes = array(); - if ( ! isset( $theme_json['styles'] ) ) { - return $nodes; - } + protected static function remove_keys_not_in_schema( $tree, $schema ) { + $tree = array_intersect_key( $tree, $schema ); - // Top-level. - $nodes[] = array( - 'path' => array( 'styles' ), - 'selector' => static::ROOT_BLOCK_SELECTOR, - ); + foreach ( $schema as $key => $data ) { + if ( ! isset( $tree[ $key ] ) ) { + continue; + } - if ( isset( $theme_json['styles']['elements'] ) ) { - foreach ( self::ELEMENTS as $element => $selector ) { - if ( ! isset( $theme_json['styles']['elements'][ $element ] ) || ! array_key_exists( $element, static::ELEMENTS ) ) { - continue; + if ( is_array( $schema[ $key ] ) && is_array( $tree[ $key ] ) ) { + $tree[ $key ] = static::remove_keys_not_in_schema( $tree[ $key ], $schema[ $key ] ); + + if ( empty( $tree[ $key ] ) ) { + unset( $tree[ $key ] ); } + } elseif ( is_array( $schema[ $key ] ) && ! is_array( $tree[ $key ] ) ) { + unset( $tree[ $key ] ); + } + } - // Handle element defaults. - $nodes[] = array( - 'path' => array( 'styles', 'elements', $element ), - 'selector' => static::ELEMENTS[ $element ], - ); - - // Handle any pseudo selectors for the element. - if ( array_key_exists( $element, static::VALID_ELEMENT_PSEUDO_SELECTORS ) ) { - foreach ( static::VALID_ELEMENT_PSEUDO_SELECTORS[ $element ] as $pseudo_selector ) { - - if ( isset( $theme_json['styles']['elements'][ $element ][ $pseudo_selector ] ) ) { - - $nodes[] = array( - 'path' => array( 'styles', 'elements', $element ), - 'selector' => static::append_to_selector( static::ELEMENTS[ $element ], $pseudo_selector ), - ); - } - } - } - } - } - - // Blocks. - if ( ! isset( $theme_json['styles']['blocks'] ) ) { - return $nodes; - } - - $nodes = array_merge( $nodes, static::get_block_nodes( $theme_json, $selectors ) ); - - // This filter allows us to modify the output of WP_Theme_JSON so that we can do things like loading block CSS independently. - return apply_filters( 'wp_theme_json_get_style_nodes', $nodes ); + return $tree; } /** - * A public helper to get the block nodes from a theme.json file. + * Returns the existing settings for each block. * - * @return array The block nodes in theme.json. - */ - public function get_styles_block_nodes() { - return static::get_block_nodes( $this->theme_json ); - } - - /** - * An internal method to get the block nodes from a theme.json file. + * Example: * - * @param array $theme_json The theme.json converted to an array. - * @param array $selectors Optional list of selectors per block. + * { + * 'root': { + * 'color': { + * 'custom': true + * } + * }, + * 'core/paragraph': { + * 'spacing': { + * 'customPadding': true + * } + * } + * } * - * @return array The block nodes in theme.json. + * @since 5.8.0 + * + * @return array Settings per block. */ - private static function get_block_nodes( $theme_json, $selectors = array() ) { - $selectors = empty( $selectors ) ? static::get_blocks_metadata() : $selectors; - $nodes = array(); - if ( ! isset( $theme_json['styles'] ) ) { - return $nodes; - } - - // Blocks. - if ( ! isset( $theme_json['styles']['blocks'] ) ) { - return $nodes; - } - - foreach ( $theme_json['styles']['blocks'] as $name => $node ) { - $selector = null; - if ( isset( $selectors[ $name ]['selector'] ) ) { - $selector = $selectors[ $name ]['selector']; - } - - $duotone_selector = null; - if ( isset( $selectors[ $name ]['duotone'] ) ) { - $duotone_selector = $selectors[ $name ]['duotone']; - } - - $feature_selectors = null; - if ( isset( $selectors[ $name ]['features'] ) ) { - $feature_selectors = $selectors[ $name ]['features']; - } - - $nodes[] = array( - 'name' => $name, - 'path' => array( 'styles', 'blocks', $name ), - 'selector' => $selector, - 'duotone' => $duotone_selector, - 'features' => $feature_selectors, - ); - - if ( isset( $theme_json['styles']['blocks'][ $name ]['elements'] ) ) { - foreach ( $theme_json['styles']['blocks'][ $name ]['elements'] as $element => $node ) { - $nodes[] = array( - 'path' => array( 'styles', 'blocks', $name, 'elements', $element ), - 'selector' => $selectors[ $name ]['elements'][ $element ], - ); - - // Handle any pseudo selectors for the element. - if ( array_key_exists( $element, static::VALID_ELEMENT_PSEUDO_SELECTORS ) ) { - foreach ( static::VALID_ELEMENT_PSEUDO_SELECTORS[ $element ] as $pseudo_selector ) { - if ( isset( $theme_json['styles']['blocks'][ $name ]['elements'][ $element ][ $pseudo_selector ] ) ) { - - $nodes[] = array( - 'path' => array( 'styles', 'blocks', $name, 'elements', $element ), - 'selector' => static::append_to_selector( $selectors[ $name ]['elements'][ $element ], $pseudo_selector ), - ); - } - } - } - } - } + public function get_settings() { + if ( ! isset( $this->theme_json['settings'] ) ) { + return array(); + } else { + return $this->theme_json['settings']; } - - return $nodes; } /** * Returns the stylesheet that results of processing * the theme.json structure this object represents. * - * @param array $types Types of styles to load. Will load all by default. It accepts: - * 'variables': only the CSS Custom Properties for presets & custom ones. - * 'styles': only the styles section in theme.json. - * 'presets': only the classes for the presets. + * @since 5.8.0 + * @since 5.9.0 Removed the `$type` parameter`, added the `$types` and `$origins` parameters. + * + * @param array $types Types of styles to load. Will load all by default. It accepts: + * - `variables`: only the CSS Custom Properties for presets & custom ones. + * - `styles`: only the styles section in theme.json. + * - `presets`: only the classes for the presets. + * - `custom-css`: only the css from global styles.css. * @param array $origins A list of origins to include. By default it includes VALID_ORIGINS. * @param array $options An array of options for now used for internal purposes only (may change without notice). * The options currently supported are 'scope' that makes sure all style are scoped to a given selector, * and root_selector which overwrites and forces a given selector to be used on the root node. - * @return string Stylesheet. + * @return string The resulting stylesheet. */ public function get_stylesheet( $types = array( 'variables', 'styles', 'presets' ), $origins = null, $options = array() ) { if ( null === $origins ) { @@ -693,7 +933,7 @@ public function get_stylesheet( $types = array( 'variables', 'styles', 'presets' if ( is_string( $types ) ) { // Dispatch error and map old arguments to new ones. - _deprecated_argument( __FUNCTION__, '5.9' ); + _deprecated_argument( __FUNCTION__, '5.9.0' ); if ( 'block_styles' === $types ) { $types = array( 'styles', 'presets' ); } elseif ( 'css_variables' === $types ) { @@ -772,182 +1012,1306 @@ public function get_stylesheet( $types = array( 'variables', 'styles', 'presets' $stylesheet .= $this->get_preset_classes( $setting_nodes, $origins ); } + // Load the custom CSS last so it has the highest specificity. + if ( in_array( 'custom-css', $types, true ) ) { + $stylesheet .= _wp_array_get( $this->theme_json, array( 'styles', 'css' ) ); + } + return $stylesheet; } - /** - * Returns a filtered declarations array if there is a separator block with only a background - * style defined in theme.json by adding a color attribute to reflect the changes in the front. + * Returns the page templates of the active theme. * - * @param array $declarations List of declarations. + * @since 5.9.0 * - * @return array $declarations List of declarations filtered. + * @return array */ - private static function update_separator_declarations( $declarations ) { - $background_matches = array_values( - array_filter( - $declarations, - function( $declaration ) { - return 'background-color' === $declaration['name']; - } - ) - ); - if ( ! empty( $background_matches && isset( $background_matches[0]['value'] ) ) ) { - $border_color_matches = array_values( - array_filter( - $declarations, - function( $declaration ) { - return 'border-color' === $declaration['name']; - } - ) - ); - $text_color_matches = array_values( - array_filter( - $declarations, - function( $declaration ) { - return 'color' === $declaration['name']; - } - ) - ); - if ( empty( $border_color_matches ) && empty( $text_color_matches ) ) { - $declarations[] = array( - 'name' => 'color', - 'value' => $background_matches[0]['value'], + public function get_custom_templates() { + $custom_templates = array(); + if ( ! isset( $this->theme_json['customTemplates'] ) || ! is_array( $this->theme_json['customTemplates'] ) ) { + return $custom_templates; + } + + foreach ( $this->theme_json['customTemplates'] as $item ) { + if ( isset( $item['name'] ) ) { + $custom_templates[ $item['name'] ] = array( + 'title' => isset( $item['title'] ) ? $item['title'] : '', + 'postTypes' => isset( $item['postTypes'] ) ? $item['postTypes'] : array( 'page' ), ); } } - - return $declarations; + return $custom_templates; } /** - * Gets the CSS rules for a particular block from theme.json. + * Returns the template part data of active theme. * - * @param array $block_metadata Metadata about the block to get styles for. + * @since 5.9.0 * - * @return string Styles for the block. + * @return array */ - public function get_styles_for_block( $block_metadata ) { - $node = _wp_array_get( $this->theme_json, $block_metadata['path'], array() ); - $use_root_padding = isset( $this->theme_json['settings']['useRootPaddingAwareAlignments'] ) && true === $this->theme_json['settings']['useRootPaddingAwareAlignments']; - $selector = $block_metadata['selector']; - $settings = _wp_array_get( $this->theme_json, array( 'settings' ) ); - - // Process style declarations for block support features the current - // block contains selectors for. Values for a feature with a custom - // selector are filtered from the theme.json node before it is - // processed as normal. - $feature_declarations = array(); - - if ( ! empty( $block_metadata['features'] ) ) { - foreach ( $block_metadata['features'] as $feature_name => $feature_selector ) { - if ( ! empty( $node[ $feature_name ] ) ) { - // Create temporary node containing only the feature data - // to leverage existing `compute_style_properties` function. - $feature = array( $feature_name => $node[ $feature_name ] ); - // Generate the feature's declarations only. - $new_feature_declarations = static::compute_style_properties( $feature, $settings, null, $this->theme_json ); - - // Merge new declarations with any that already exist for - // the feature selector. This may occur when multiple block - // support features use the same custom selector. - if ( isset( $feature_declarations[ $feature_selector ] ) ) { - $feature_declarations[ $feature_selector ] = array_merge( $feature_declarations[ $feature_selector ], $new_feature_declarations ); - } else { - $feature_declarations[ $feature_selector ] = $new_feature_declarations; - } - - // Remove the feature from the block's node now the - // styles will be included under the feature level selector. - unset( $node[ $feature_name ] ); - } - } + public function get_template_parts() { + $template_parts = array(); + if ( ! isset( $this->theme_json['templateParts'] ) || ! is_array( $this->theme_json['templateParts'] ) ) { + return $template_parts; } - // Get a reference to element name from path. - // $block_metadata['path'] = array('styles','elements','link'); - // Make sure that $block_metadata['path'] describes an element node, like ['styles', 'element', 'link']. - // Skip non-element paths like just ['styles']. - $is_processing_element = in_array( 'elements', $block_metadata['path'], true ); - - $current_element = $is_processing_element ? $block_metadata['path'][ count( $block_metadata['path'] ) - 1 ] : null; - - $element_pseudo_allowed = array_key_exists( $current_element, static::VALID_ELEMENT_PSEUDO_SELECTORS ) ? static::VALID_ELEMENT_PSEUDO_SELECTORS[ $current_element ] : array(); - - // Check for allowed pseudo classes (e.g. ":hover") from the $selector ("a:hover"). - // This also resets the array keys. - $pseudo_matches = array_values( - array_filter( - $element_pseudo_allowed, - function( $pseudo_selector ) use ( $selector ) { - return str_contains( $selector, $pseudo_selector ); - } - ) - ); - - $pseudo_selector = isset( $pseudo_matches[0] ) ? $pseudo_matches[0] : null; - - // If the current selector is a pseudo selector that's defined in the allow list for the current - // element then compute the style properties for it. - // Otherwise just compute the styles for the default selector as normal. - if ( $pseudo_selector && isset( $node[ $pseudo_selector ] ) && array_key_exists( $current_element, static::VALID_ELEMENT_PSEUDO_SELECTORS ) && in_array( $pseudo_selector, static::VALID_ELEMENT_PSEUDO_SELECTORS[ $current_element ], true ) ) { - $declarations = static::compute_style_properties( $node[ $pseudo_selector ], $settings, null, $this->theme_json, $selector, $use_root_padding ); - } else { - $declarations = static::compute_style_properties( $node, $settings, null, $this->theme_json, $selector, $use_root_padding ); + foreach ( $this->theme_json['templateParts'] as $item ) { + if ( isset( $item['name'] ) ) { + $template_parts[ $item['name'] ] = array( + 'title' => isset( $item['title'] ) ? $item['title'] : '', + 'area' => isset( $item['area'] ) ? $item['area'] : '', + ); + } } + return $template_parts; + } + /** + * Converts each style section into a list of rulesets + * containing the block styles to be appended to the stylesheet. + * + * See glossary at https://developer.mozilla.org/en-US/docs/Web/CSS/Syntax + * + * For each section this creates a new ruleset such as: + * + * block-selector { + * style-property-one: value; + * } + * + * @since 5.8.0 As `get_block_styles()`. + * @since 5.9.0 Renamed from `get_block_styles()` to `get_block_classes()` + * and no longer returns preset classes. + * Removed the `$setting_nodes` parameter. + * @since 6.1.0 Moved most internal logic to `get_styles_for_block()`. + * + * @param array $style_nodes Nodes with styles. + * @return string The new stylesheet. + */ + protected function get_block_classes( $style_nodes ) { $block_rules = ''; - // 1. Separate the ones who use the general selector - // and the ones who use the duotone selector. - $declarations_duotone = array(); - foreach ( $declarations as $index => $declaration ) { - if ( 'filter' === $declaration['name'] ) { - unset( $declarations[ $index ] ); - $declarations_duotone[] = $declaration; + foreach ( $style_nodes as $metadata ) { + if ( null === $metadata['selector'] ) { + continue; } - } - - // Update declarations if there are separators with only background color defined. - if ( '.wp-block-separator' === $selector ) { - $declarations = static::update_separator_declarations( $declarations ); - } - - // 2. Generate and append the rules that use the general selector. - $block_rules .= static::to_ruleset( $selector, $declarations ); - - // 3. Generate and append the rules that use the duotone selector. - if ( isset( $block_metadata['duotone'] ) && ! empty( $declarations_duotone ) ) { - $selector_duotone = static::scope_selector( $block_metadata['selector'], $block_metadata['duotone'] ); - $block_rules .= static::to_ruleset( $selector_duotone, $declarations_duotone ); - } - - // 4. Generate Layout block gap styles. - if ( - static::ROOT_BLOCK_SELECTOR !== $selector && - ! empty( $block_metadata['name'] ) - ) { - $block_rules .= $this->get_layout_styles( $block_metadata ); - } - - // 5. Generate and append the feature level rulesets. - foreach ( $feature_declarations as $feature_selector => $individual_feature_declarations ) { - $block_rules .= static::to_ruleset( $feature_selector, $individual_feature_declarations ); + $block_rules .= static::get_styles_for_block( $metadata ); } return $block_rules; } /** - * Outputs the CSS for layout rules on the root. + * Gets the CSS layout rules for a particular block from theme.json layout definitions. * - * @param string $selector The root node selector. - * @param array $block_metadata The metadata for the root block. - * @return string The additional root rules CSS. + * @since 6.1.0 + * + * @param array $block_metadata Metadata about the block to get styles for. + * @return string Layout styles for the block. */ - public function get_root_layout_rules( $selector, $block_metadata ) { - $css = ''; - $settings = _wp_array_get( $this->theme_json, array( 'settings' ) ); + protected function get_layout_styles( $block_metadata ) { + $block_rules = ''; + $block_type = null; + + // Skip outputting layout styles if explicitly disabled. + if ( current_theme_supports( 'disable-layout-styles' ) ) { + return $block_rules; + } + + if ( isset( $block_metadata['name'] ) ) { + $block_type = WP_Block_Type_Registry::get_instance()->get_registered( $block_metadata['name'] ); + if ( ! block_has_support( $block_type, array( '__experimentalLayout' ), false ) ) { + return $block_rules; + } + } + + $selector = isset( $block_metadata['selector'] ) ? $block_metadata['selector'] : ''; + $has_block_gap_support = _wp_array_get( $this->theme_json, array( 'settings', 'spacing', 'blockGap' ) ) !== null; + $has_fallback_gap_support = ! $has_block_gap_support; // This setting isn't useful yet: it exists as a placeholder for a future explicit fallback gap styles support. + $node = _wp_array_get( $this->theme_json, $block_metadata['path'], array() ); + $layout_definitions = _wp_array_get( $this->theme_json, array( 'settings', 'layout', 'definitions' ), array() ); + $layout_selector_pattern = '/^[a-zA-Z0-9\-\.\ *+>:\(\)]*$/'; // Allow alphanumeric classnames, spaces, wildcard, sibling, child combinator and pseudo class selectors. + + // Gap styles will only be output if the theme has block gap support, or supports a fallback gap. + // Default layout gap styles will be skipped for themes that do not explicitly opt-in to blockGap with a `true` or `false` value. + if ( $has_block_gap_support || $has_fallback_gap_support ) { + $block_gap_value = null; + // Use a fallback gap value if block gap support is not available. + if ( ! $has_block_gap_support ) { + $block_gap_value = static::ROOT_BLOCK_SELECTOR === $selector ? '0.5em' : null; + if ( ! empty( $block_type ) ) { + $block_gap_value = _wp_array_get( $block_type->supports, array( 'spacing', 'blockGap', '__experimentalDefault' ), null ); + } + } else { + $block_gap_value = static::get_property_value( $node, array( 'spacing', 'blockGap' ) ); + } + + // Support split row / column values and concatenate to a shorthand value. + if ( is_array( $block_gap_value ) ) { + if ( isset( $block_gap_value['top'] ) && isset( $block_gap_value['left'] ) ) { + $gap_row = static::get_property_value( $node, array( 'spacing', 'blockGap', 'top' ) ); + $gap_column = static::get_property_value( $node, array( 'spacing', 'blockGap', 'left' ) ); + $block_gap_value = $gap_row === $gap_column ? $gap_row : $gap_row . ' ' . $gap_column; + } else { + // Skip outputting gap value if not all sides are provided. + $block_gap_value = null; + } + } + + // If the block should have custom gap, add the gap styles. + if ( null !== $block_gap_value && false !== $block_gap_value && '' !== $block_gap_value ) { + foreach ( $layout_definitions as $layout_definition_key => $layout_definition ) { + // Allow outputting fallback gap styles for flex layout type when block gap support isn't available. + if ( ! $has_block_gap_support && 'flex' !== $layout_definition_key ) { + continue; + } + + $class_name = sanitize_title( _wp_array_get( $layout_definition, array( 'className' ), false ) ); + $spacing_rules = _wp_array_get( $layout_definition, array( 'spacingStyles' ), array() ); + + if ( + ! empty( $class_name ) && + ! empty( $spacing_rules ) + ) { + foreach ( $spacing_rules as $spacing_rule ) { + $declarations = array(); + if ( + isset( $spacing_rule['selector'] ) && + preg_match( $layout_selector_pattern, $spacing_rule['selector'] ) && + ! empty( $spacing_rule['rules'] ) + ) { + // Iterate over each of the styling rules and substitute non-string values such as `null` with the real `blockGap` value. + foreach ( $spacing_rule['rules'] as $css_property => $css_value ) { + $current_css_value = is_string( $css_value ) ? $css_value : $block_gap_value; + if ( static::is_safe_css_declaration( $css_property, $current_css_value ) ) { + $declarations[] = array( + 'name' => $css_property, + 'value' => $current_css_value, + ); + } + } + + if ( ! $has_block_gap_support ) { + // For fallback gap styles, use lower specificity, to ensure styles do not unintentionally override theme styles. + $format = static::ROOT_BLOCK_SELECTOR === $selector ? ':where(.%2$s%3$s)' : ':where(%1$s.%2$s%3$s)'; + $layout_selector = sprintf( + $format, + $selector, + $class_name, + $spacing_rule['selector'] + ); + } else { + $format = static::ROOT_BLOCK_SELECTOR === $selector ? '%s .%s%s' : '%s.%s%s'; + $layout_selector = sprintf( + $format, + $selector, + $class_name, + $spacing_rule['selector'] + ); + } + $block_rules .= static::to_ruleset( $layout_selector, $declarations ); + } + } + } + } + } + } + + // Output base styles. + if ( + static::ROOT_BLOCK_SELECTOR === $selector + ) { + $valid_display_modes = array( 'block', 'flex', 'grid' ); + foreach ( $layout_definitions as $layout_definition ) { + $class_name = sanitize_title( _wp_array_get( $layout_definition, array( 'className' ), false ) ); + $base_style_rules = _wp_array_get( $layout_definition, array( 'baseStyles' ), array() ); + + if ( + ! empty( $class_name ) && + ! empty( $base_style_rules ) + ) { + // Output display mode. This requires special handling as `display` is not exposed in `safe_style_css_filter`. + if ( + ! empty( $layout_definition['displayMode'] ) && + is_string( $layout_definition['displayMode'] ) && + in_array( $layout_definition['displayMode'], $valid_display_modes, true ) + ) { + $layout_selector = sprintf( + '%s .%s', + $selector, + $class_name + ); + $block_rules .= static::to_ruleset( + $layout_selector, + array( + array( + 'name' => 'display', + 'value' => $layout_definition['displayMode'], + ), + ) + ); + } + + foreach ( $base_style_rules as $base_style_rule ) { + $declarations = array(); + + if ( + isset( $base_style_rule['selector'] ) && + preg_match( $layout_selector_pattern, $base_style_rule['selector'] ) && + ! empty( $base_style_rule['rules'] ) + ) { + foreach ( $base_style_rule['rules'] as $css_property => $css_value ) { + if ( static::is_safe_css_declaration( $css_property, $css_value ) ) { + $declarations[] = array( + 'name' => $css_property, + 'value' => $css_value, + ); + } + } + + $layout_selector = sprintf( + '%s .%s%s', + $selector, + $class_name, + $base_style_rule['selector'] + ); + $block_rules .= static::to_ruleset( $layout_selector, $declarations ); + } + } + } + } + } + return $block_rules; + } + + /** + * Creates new rulesets as classes for each preset value such as: + * + * .has-value-color { + * color: value; + * } + * + * .has-value-background-color { + * background-color: value; + * } + * + * .has-value-font-size { + * font-size: value; + * } + * + * .has-value-gradient-background { + * background: value; + * } + * + * p.has-value-gradient-background { + * background: value; + * } + * + * @since 5.9.0 + * + * @param array $setting_nodes Nodes with settings. + * @param array $origins List of origins to process presets from. + * @return string The new stylesheet. + */ + protected function get_preset_classes( $setting_nodes, $origins ) { + $preset_rules = ''; + + foreach ( $setting_nodes as $metadata ) { + if ( null === $metadata['selector'] ) { + continue; + } + + $selector = $metadata['selector']; + $node = _wp_array_get( $this->theme_json, $metadata['path'], array() ); + $preset_rules .= static::compute_preset_classes( $node, $selector, $origins ); + } + + return $preset_rules; + } + + /** + * Converts each styles section into a list of rulesets + * to be appended to the stylesheet. + * These rulesets contain all the css variables (custom variables and preset variables). + * + * See glossary at https://developer.mozilla.org/en-US/docs/Web/CSS/Syntax + * + * For each section this creates a new ruleset such as: + * + * block-selector { + * --wp--preset--category--slug: value; + * --wp--custom--variable: value; + * } + * + * @since 5.8.0 + * @since 5.9.0 Added the `$origins` parameter. + * + * @param array $nodes Nodes with settings. + * @param array $origins List of origins to process. + * @return string The new stylesheet. + */ + protected function get_css_variables( $nodes, $origins ) { + $stylesheet = ''; + foreach ( $nodes as $metadata ) { + if ( null === $metadata['selector'] ) { + continue; + } + + $selector = $metadata['selector']; + + $node = _wp_array_get( $this->theme_json, $metadata['path'], array() ); + $declarations = static::compute_preset_vars( $node, $origins ); + $theme_vars_declarations = static::compute_theme_vars( $node ); + foreach ( $theme_vars_declarations as $theme_vars_declaration ) { + $declarations[] = $theme_vars_declaration; + } + + $stylesheet .= static::to_ruleset( $selector, $declarations ); + } + + return $stylesheet; + } + + /** + * Given a selector and a declaration list, + * creates the corresponding ruleset. + * + * @since 5.8.0 + * + * @param string $selector CSS selector. + * @param array $declarations List of declarations. + * @return string The resulting CSS ruleset. + */ + protected static function to_ruleset( $selector, $declarations ) { + if ( empty( $declarations ) ) { + return ''; + } + + $declaration_block = array_reduce( + $declarations, + static function ( $carry, $element ) { + return $carry .= $element['name'] . ': ' . $element['value'] . ';'; }, + '' + ); + + return $selector . '{' . $declaration_block . '}'; + } + + /** + * Given a settings array, returns the generated rulesets + * for the preset classes. + * + * @since 5.8.0 + * @since 5.9.0 Added the `$origins` parameter. + * + * @param array $settings Settings to process. + * @param string $selector Selector wrapping the classes. + * @param array $origins List of origins to process. + * @return string The result of processing the presets. + */ + protected static function compute_preset_classes( $settings, $selector, $origins ) { + if ( static::ROOT_BLOCK_SELECTOR === $selector ) { + // Classes at the global level do not need any CSS prefixed, + // and we don't want to increase its specificity. + $selector = ''; + } + + $stylesheet = ''; + foreach ( static::PRESETS_METADATA as $preset_metadata ) { + $slugs = static::get_settings_slugs( $settings, $preset_metadata, $origins ); + foreach ( $preset_metadata['classes'] as $class => $property ) { + foreach ( $slugs as $slug ) { + $css_var = static::replace_slug_in_string( $preset_metadata['css_vars'], $slug ); + $class_name = static::replace_slug_in_string( $class, $slug ); + $stylesheet .= static::to_ruleset( + static::append_to_selector( $selector, $class_name ), + array( + array( + 'name' => $property, + 'value' => 'var(' . $css_var . ') !important', + ), + ) + ); + } + } + } + + return $stylesheet; + } + + /** + * Function that scopes a selector with another one. This works a bit like + * SCSS nesting except the `&` operator isn't supported. + * + * + * $scope = '.a, .b .c'; + * $selector = '> .x, .y'; + * $merged = scope_selector( $scope, $selector ); + * // $merged is '.a > .x, .a .y, .b .c > .x, .b .c .y' + * + * + * @since 5.9.0 + * + * @param string $scope Selector to scope to. + * @param string $selector Original selector. + * @return string Scoped selector. + */ + protected static function scope_selector( $scope, $selector ) { + $scopes = explode( ',', $scope ); + $selectors = explode( ',', $selector ); + + $selectors_scoped = array(); + foreach ( $scopes as $outer ) { + foreach ( $selectors as $inner ) { + $outer = trim( $outer ); + $inner = trim( $inner ); + if ( ! empty( $outer ) && ! empty( $inner ) ) { + $selectors_scoped[] = $outer . ' ' . $inner; + } elseif ( empty( $outer ) ) { + $selectors_scoped[] = $inner; + } elseif ( empty( $inner ) ) { + $selectors_scoped[] = $outer; + } + } + } + + $result = implode( ', ', $selectors_scoped ); + return $result; + } + + /** + * Gets preset values keyed by slugs based on settings and metadata. + * + * + * $settings = array( + * 'typography' => array( + * 'fontFamilies' => array( + * array( + * 'slug' => 'sansSerif', + * 'fontFamily' => '"Helvetica Neue", sans-serif', + * ), + * array( + * 'slug' => 'serif', + * 'colors' => 'Georgia, serif', + * ) + * ), + * ), + * ); + * $meta = array( + * 'path' => array( 'typography', 'fontFamilies' ), + * 'value_key' => 'fontFamily', + * ); + * $values_by_slug = get_settings_values_by_slug(); + * // $values_by_slug === array( + * // 'sans-serif' => '"Helvetica Neue", sans-serif', + * // 'serif' => 'Georgia, serif', + * // ); + * + * + * @since 5.9.0 + * + * @param array $settings Settings to process. + * @param array $preset_metadata One of the PRESETS_METADATA values. + * @param array $origins List of origins to process. + * @return array Array of presets where each key is a slug and each value is the preset value. + */ + protected static function get_settings_values_by_slug( $settings, $preset_metadata, $origins ) { + $preset_per_origin = _wp_array_get( $settings, $preset_metadata['path'], array() ); + + $result = array(); + foreach ( $origins as $origin ) { + if ( ! isset( $preset_per_origin[ $origin ] ) ) { + continue; + } + foreach ( $preset_per_origin[ $origin ] as $preset ) { + $slug = _wp_to_kebab_case( $preset['slug'] ); + + $value = ''; + if ( isset( $preset_metadata['value_key'], $preset[ $preset_metadata['value_key'] ] ) ) { + $value_key = $preset_metadata['value_key']; + $value = $preset[ $value_key ]; + } elseif ( + isset( $preset_metadata['value_func'] ) && + is_callable( $preset_metadata['value_func'] ) + ) { + $value_func = $preset_metadata['value_func']; + $value = call_user_func( $value_func, $preset ); + } else { + // If we don't have a value, then don't add it to the result. + continue; + } + + $result[ $slug ] = $value; + } + } + return $result; + } + + /** + * Similar to get_settings_values_by_slug, but doesn't compute the value. + * + * @since 5.9.0 + * + * @param array $settings Settings to process. + * @param array $preset_metadata One of the PRESETS_METADATA values. + * @param array $origins List of origins to process. + * @return array Array of presets where the key and value are both the slug. + */ + protected static function get_settings_slugs( $settings, $preset_metadata, $origins = null ) { + if ( null === $origins ) { + $origins = static::VALID_ORIGINS; + } + + $preset_per_origin = _wp_array_get( $settings, $preset_metadata['path'], array() ); + + $result = array(); + foreach ( $origins as $origin ) { + if ( ! isset( $preset_per_origin[ $origin ] ) ) { + continue; + } + foreach ( $preset_per_origin[ $origin ] as $preset ) { + $slug = _wp_to_kebab_case( $preset['slug'] ); + + // Use the array as a set so we don't get duplicates. + $result[ $slug ] = $slug; + } + } + return $result; + } + + /** + * Transforms a slug into a CSS Custom Property. + * + * @since 5.9.0 + * + * @param string $input String to replace. + * @param string $slug The slug value to use to generate the custom property. + * @return string The CSS Custom Property. Something along the lines of `--wp--preset--color--black`. + */ + protected static function replace_slug_in_string( $input, $slug ) { + return strtr( $input, array( '$slug' => $slug ) ); + } + + /** + * Given the block settings, extracts the CSS Custom Properties + * for the presets and adds them to the $declarations array + * following the format: + * + * ```php + * array( + * 'name' => 'property_name', + * 'value' => 'property_value, + * ) + * ``` + * + * @since 5.8.0 + * @since 5.9.0 Added the `$origins` parameter. + * + * @param array $settings Settings to process. + * @param array $origins List of origins to process. + * @return array The modified $declarations. + */ + protected static function compute_preset_vars( $settings, $origins ) { + $declarations = array(); + foreach ( static::PRESETS_METADATA as $preset_metadata ) { + $values_by_slug = static::get_settings_values_by_slug( $settings, $preset_metadata, $origins ); + foreach ( $values_by_slug as $slug => $value ) { + $declarations[] = array( + 'name' => static::replace_slug_in_string( $preset_metadata['css_vars'], $slug ), + 'value' => $value, + ); + } + } + + return $declarations; + } + + /** + * Given an array of settings, extracts the CSS Custom Properties + * for the custom values and adds them to the $declarations + * array following the format: + * + * ```php + * array( + * 'name' => 'property_name', + * 'value' => 'property_value, + * ) + * ``` + * + * @since 5.8.0 + * + * @param array $settings Settings to process. + * @return array The modified $declarations. + */ + protected static function compute_theme_vars( $settings ) { + $declarations = array(); + $custom_values = _wp_array_get( $settings, array( 'custom' ), array() ); + $css_vars = static::flatten_tree( $custom_values ); + foreach ( $css_vars as $key => $value ) { + $declarations[] = array( + 'name' => '--wp--custom--' . $key, + 'value' => $value, + ); + } + + return $declarations; + } + + /** + * Given a tree, it creates a flattened one + * by merging the keys and binding the leaf values + * to the new keys. + * + * It also transforms camelCase names into kebab-case + * and substitutes '/' by '-'. + * + * This is thought to be useful to generate + * CSS Custom Properties from a tree, + * although there's nothing in the implementation + * of this function that requires that format. + * + * For example, assuming the given prefix is '--wp' + * and the token is '--', for this input tree: + * + * { + * 'some/property': 'value', + * 'nestedProperty': { + * 'sub-property': 'value' + * } + * } + * + * it'll return this output: + * + * { + * '--wp--some-property': 'value', + * '--wp--nested-property--sub-property': 'value' + * } + * + * @since 5.8.0 + * + * @param array $tree Input tree to process. + * @param string $prefix Optional. Prefix to prepend to each variable. Default empty string. + * @param string $token Optional. Token to use between levels. Default '--'. + * @return array The flattened tree. + */ + protected static function flatten_tree( $tree, $prefix = '', $token = '--' ) { + $result = array(); + foreach ( $tree as $property => $value ) { + $new_key = $prefix . str_replace( + '/', + '-', + strtolower( _wp_to_kebab_case( $property ) ) + ); + + if ( is_array( $value ) ) { + $new_prefix = $new_key . $token; + $flattened_subtree = static::flatten_tree( $value, $new_prefix, $token ); + foreach ( $flattened_subtree as $subtree_key => $subtree_value ) { + $result[ $subtree_key ] = $subtree_value; + } + } else { + $result[ $new_key ] = $value; + } + } + return $result; + } + + /** + * Given a styles array, it extracts the style properties + * and adds them to the $declarations array following the format: + * + * ```php + * array( + * 'name' => 'property_name', + * 'value' => 'property_value, + * ) + * ``` + * + * @since 5.8.0 + * @since 5.9.0 Added the `$settings` and `$properties` parameters. + * @since 6.1.0 Added `$theme_json`, `$selector`, and `$use_root_padding` parameters. + * + * @param array $styles Styles to process. + * @param array $settings Theme settings. + * @param array $properties Properties metadata. + * @param array $theme_json Theme JSON array. + * @param string $selector The style block selector. + * @param boolean $use_root_padding Whether to add custom properties at root level. + * @return array Returns the modified $declarations. + */ + protected static function compute_style_properties( $styles, $settings = array(), $properties = null, $theme_json = null, $selector = null, $use_root_padding = null ) { + if ( null === $properties ) { + $properties = static::PROPERTIES_METADATA; + } + + $declarations = array(); + if ( empty( $styles ) ) { + return $declarations; + } + + $root_variable_duplicates = array(); + + foreach ( $properties as $css_property => $value_path ) { + $value = static::get_property_value( $styles, $value_path, $theme_json ); + + if ( str_starts_with( $css_property, '--wp--style--root--' ) && ( static::ROOT_BLOCK_SELECTOR !== $selector || ! $use_root_padding ) ) { + continue; + } + // Root-level padding styles don't currently support strings with CSS shorthand values. + // This may change: https://github.com/WordPress/gutenberg/issues/40132. + if ( '--wp--style--root--padding' === $css_property && is_string( $value ) ) { + continue; + } + + if ( str_starts_with( $css_property, '--wp--style--root--' ) && $use_root_padding ) { + $root_variable_duplicates[] = substr( $css_property, strlen( '--wp--style--root--' ) ); + } + + // Look up protected properties, keyed by value path. + // Skip protected properties that are explicitly set to `null`. + if ( is_array( $value_path ) ) { + $path_string = implode( '.', $value_path ); + if ( + // TODO: Replace array_key_exists() with isset() check once WordPress drops + // support for PHP 5.6. See https://core.trac.wordpress.org/ticket/57067. + array_key_exists( $path_string, static::PROTECTED_PROPERTIES ) && + _wp_array_get( $settings, static::PROTECTED_PROPERTIES[ $path_string ], null ) === null + ) { + continue; + } + } + + // Skip if empty and not "0" or value represents array of longhand values. + $has_missing_value = empty( $value ) && ! is_numeric( $value ); + if ( $has_missing_value || is_array( $value ) ) { + continue; + } + + // Calculates fluid typography rules where available. + if ( 'font-size' === $css_property ) { + /* + * wp_get_typography_font_size_value() will check + * if fluid typography has been activated and also + * whether the incoming value can be converted to a fluid value. + * Values that already have a clamp() function will not pass the test, + * and therefore the original $value will be returned. + */ + $value = wp_get_typography_font_size_value( array( 'size' => $value ) ); + } + + $declarations[] = array( + 'name' => $css_property, + 'value' => $value, + ); + } + + // If a variable value is added to the root, the corresponding property should be removed. + foreach ( $root_variable_duplicates as $duplicate ) { + $discard = array_search( $duplicate, array_column( $declarations, 'name' ), true ); + if ( is_numeric( $discard ) ) { + array_splice( $declarations, $discard, 1 ); + } + } + + return $declarations; + } + + /** + * Returns the style property for the given path. + * + * It also converts CSS Custom Property stored as + * "var:preset|color|secondary" to the form + * "--wp--preset--color--secondary". + * + * It also converts references to a path to the value + * stored at that location, e.g. + * { "ref": "style.color.background" } => "#fff". + * + * @since 5.8.0 + * @since 5.9.0 Added support for values of array type, which are returned as is. + * @since 6.1.0 Added the `$theme_json` parameter. + * + * @param array $styles Styles subtree. + * @param array $path Which property to process. + * @param array $theme_json Theme JSON array. + * @return string|array Style property value. + */ + protected static function get_property_value( $styles, $path, $theme_json = null ) { + $value = _wp_array_get( $styles, $path, '' ); + + // Gutenberg didn't have this check. + if ( '' === $value || null === $value ) { + // No need to process the value further. + return ''; + } + + /* + * This converts references to a path to the value at that path + * where the values is an array with a "ref" key, pointing to a path. + * For example: { "ref": "style.color.background" } => "#fff". + */ + if ( is_array( $value ) && isset( $value['ref'] ) ) { + $value_path = explode( '.', $value['ref'] ); + $ref_value = _wp_array_get( $theme_json, $value_path ); + // Only use the ref value if we find anything. + if ( ! empty( $ref_value ) && is_string( $ref_value ) ) { + $value = $ref_value; + } + + if ( is_array( $ref_value ) && isset( $ref_value['ref'] ) ) { + $path_string = json_encode( $path ); + $ref_value_string = json_encode( $ref_value ); + _doing_it_wrong( + 'get_property_value', + sprintf( + /* translators: 1: theme.json, 2: Value name, 3: Value path, 4: Another value name. */ + __( 'Your %1$s file uses a dynamic value (%2$s) for the path at %3$s. However, the value at %3$s is also a dynamic value (pointing to %4$s) and pointing to another dynamic value is not supported. Please update %3$s to point directly to %4$s.', 'gutenberg' ), + 'theme.json', + $ref_value_string, + $path_string, + $ref_value['ref'] + ), + '6.1.0' + ); + } + } + + if ( is_array( $value ) ) { + return $value; + } + + // Convert custom CSS properties. + $prefix = 'var:'; + $prefix_len = strlen( $prefix ); + $token_in = '|'; + $token_out = '--'; + if ( 0 === strncmp( $value, $prefix, $prefix_len ) ) { + $unwrapped_name = str_replace( + $token_in, + $token_out, + substr( $value, $prefix_len ) + ); + $value = "var(--wp--$unwrapped_name)"; + } + + return $value; + } + + /** + * Builds metadata for the setting nodes, which returns in the form of: + * + * [ + * [ + * 'path' => ['path', 'to', 'some', 'node' ], + * 'selector' => 'CSS selector for some node' + * ], + * [ + * 'path' => [ 'path', 'to', 'other', 'node' ], + * 'selector' => 'CSS selector for other node' + * ], + * ] + * + * @since 5.8.0 + * + * @param array $theme_json The tree to extract setting nodes from. + * @param array $selectors List of selectors per block. + * @return array An array of setting nodes metadata. + */ + protected static function get_setting_nodes( $theme_json, $selectors = array() ) { + $nodes = array(); + if ( ! isset( $theme_json['settings'] ) ) { + return $nodes; + } + + // Top-level. + $nodes[] = array( + 'path' => array( 'settings' ), + 'selector' => static::ROOT_BLOCK_SELECTOR, + ); + + // Calculate paths for blocks. + if ( ! isset( $theme_json['settings']['blocks'] ) ) { + return $nodes; + } + + foreach ( $theme_json['settings']['blocks'] as $name => $node ) { + $selector = null; + if ( isset( $selectors[ $name ]['selector'] ) ) { + $selector = $selectors[ $name ]['selector']; + } + + $nodes[] = array( + 'path' => array( 'settings', 'blocks', $name ), + 'selector' => $selector, + ); + } + + return $nodes; + } + + /** + * Builds metadata for the style nodes, which returns in the form of: + * + * [ + * [ + * 'path' => [ 'path', 'to', 'some', 'node' ], + * 'selector' => 'CSS selector for some node', + * 'duotone' => 'CSS selector for duotone for some node' + * ], + * [ + * 'path' => ['path', 'to', 'other', 'node' ], + * 'selector' => 'CSS selector for other node', + * 'duotone' => null + * ], + * ] + * + * @since 5.8.0 + * + * @param array $theme_json The tree to extract style nodes from. + * @param array $selectors List of selectors per block. + * @return array An array of style nodes metadata. + */ + protected static function get_style_nodes( $theme_json, $selectors = array() ) { + $nodes = array(); + if ( ! isset( $theme_json['styles'] ) ) { + return $nodes; + } + + // Top-level. + $nodes[] = array( + 'path' => array( 'styles' ), + 'selector' => static::ROOT_BLOCK_SELECTOR, + ); + + if ( isset( $theme_json['styles']['elements'] ) ) { + foreach ( self::ELEMENTS as $element => $selector ) { + if ( ! isset( $theme_json['styles']['elements'][ $element ] ) || ! array_key_exists( $element, static::ELEMENTS ) ) { + continue; + } + + // Handle element defaults. + $nodes[] = array( + 'path' => array( 'styles', 'elements', $element ), + 'selector' => static::ELEMENTS[ $element ], + ); + + // Handle any pseudo selectors for the element. + // TODO: Replace array_key_exists() with isset() check once WordPress drops + // support for PHP 5.6. See https://core.trac.wordpress.org/ticket/57067. + if ( array_key_exists( $element, static::VALID_ELEMENT_PSEUDO_SELECTORS ) ) { + foreach ( static::VALID_ELEMENT_PSEUDO_SELECTORS[ $element ] as $pseudo_selector ) { + + if ( isset( $theme_json['styles']['elements'][ $element ][ $pseudo_selector ] ) ) { + $nodes[] = array( + 'path' => array( 'styles', 'elements', $element ), + 'selector' => static::append_to_selector( static::ELEMENTS[ $element ], $pseudo_selector ), + ); + } + } + } + } + } + + // Blocks. + if ( ! isset( $theme_json['styles']['blocks'] ) ) { + return $nodes; + } + + $block_nodes = static::get_block_nodes( $theme_json, $selectors ); + foreach ( $block_nodes as $block_node ) { + $nodes[] = $block_node; + } + + /** + * Filters the list of style nodes with metadata. + * + * This allows for things like loading block CSS independently. + * + * @since 6.1.0 + * + * @param array $nodes Style nodes with metadata. + */ + return apply_filters( 'wp_theme_json_get_style_nodes', $nodes ); + } + + /** + * A public helper to get the block nodes from a theme.json file. + * + * @since 6.1.0 + * + * @return array The block nodes in theme.json. + */ + public function get_styles_block_nodes() { + return static::get_block_nodes( $this->theme_json ); + } + + /** + * Returns a filtered declarations array if there is a separator block with only a background + * style defined in theme.json by adding a color attribute to reflect the changes in the front. + * + * @since 6.1.1 + * + * @param array $declarations List of declarations. + * @return array $declarations List of declarations filtered. + */ + private static function update_separator_declarations( $declarations ) { + // Gutenberg and core implementation differed. + // https://github.com/WordPress/gutenberg/pull/44943. + $background_color = ''; + $border_color_matches = false; + $text_color_matches = false; + + foreach ( $declarations as $declaration ) { + if ( 'background-color' === $declaration['name'] && ! $background_color && isset( $declaration['value'] ) ) { + $background_color = $declaration['value']; + } elseif ( 'border-color' === $declaration['name'] ) { + $border_color_matches = true; + } elseif ( 'color' === $declaration['name'] ) { + $text_color_matches = true; + } + + if ( $background_color && $border_color_matches && $text_color_matches ) { + break; + } + } + + if ( $background_color && ! $border_color_matches && ! $text_color_matches ) { + $declarations[] = array( + 'name' => 'color', + 'value' => $background_color, + ); + } + + return $declarations; + } + + /** + * An internal method to get the block nodes from a theme.json file. + * + * @since 6.1.0 + * + * @param array $theme_json The theme.json converted to an array. + * @param array $selectors Optional list of selectors per block. + * @return array The block nodes in theme.json. + */ + private static function get_block_nodes( $theme_json, $selectors = array() ) { + $selectors = empty( $selectors ) ? static::get_blocks_metadata() : $selectors; + $nodes = array(); + if ( ! isset( $theme_json['styles'] ) ) { + return $nodes; + } + + // Blocks. + if ( ! isset( $theme_json['styles']['blocks'] ) ) { + return $nodes; + } + + foreach ( $theme_json['styles']['blocks'] as $name => $node ) { + $selector = null; + if ( isset( $selectors[ $name ]['selector'] ) ) { + $selector = $selectors[ $name ]['selector']; + } + + $duotone_selector = null; + if ( isset( $selectors[ $name ]['duotone'] ) ) { + $duotone_selector = $selectors[ $name ]['duotone']; + } + + $feature_selectors = null; + if ( isset( $selectors[ $name ]['features'] ) ) { + $feature_selectors = $selectors[ $name ]['features']; + } + + $nodes[] = array( + 'name' => $name, + 'path' => array( 'styles', 'blocks', $name ), + 'selector' => $selector, + 'duotone' => $duotone_selector, + 'features' => $feature_selectors, + ); + + if ( isset( $theme_json['styles']['blocks'][ $name ]['elements'] ) ) { + foreach ( $theme_json['styles']['blocks'][ $name ]['elements'] as $element => $node ) { + $nodes[] = array( + 'path' => array( 'styles', 'blocks', $name, 'elements', $element ), + 'selector' => $selectors[ $name ]['elements'][ $element ], + ); + + // Handle any pseudo selectors for the element. + // TODO: Replace array_key_exists() with isset() check once WordPress drops + // support for PHP 5.6. See https://core.trac.wordpress.org/ticket/57067. + if ( array_key_exists( $element, static::VALID_ELEMENT_PSEUDO_SELECTORS ) ) { + foreach ( static::VALID_ELEMENT_PSEUDO_SELECTORS[ $element ] as $pseudo_selector ) { + if ( isset( $theme_json['styles']['blocks'][ $name ]['elements'][ $element ][ $pseudo_selector ] ) ) { + $nodes[] = array( + 'path' => array( 'styles', 'blocks', $name, 'elements', $element ), + 'selector' => static::append_to_selector( $selectors[ $name ]['elements'][ $element ], $pseudo_selector ), + ); + } + } + } + } + } + } + + return $nodes; + } + + /** + * Gets the CSS rules for a particular block from theme.json. + * + * @since 6.1.0 + * + * @param array $block_metadata Metadata about the block to get styles for. + * + * @return string Styles for the block. + */ + public function get_styles_for_block( $block_metadata ) { + $node = _wp_array_get( $this->theme_json, $block_metadata['path'], array() ); + $use_root_padding = isset( $this->theme_json['settings']['useRootPaddingAwareAlignments'] ) && true === $this->theme_json['settings']['useRootPaddingAwareAlignments']; + $selector = $block_metadata['selector']; + $settings = _wp_array_get( $this->theme_json, array( 'settings' ) ); + + /* + * Process style declarations for block support features the current + * block contains selectors for. Values for a feature with a custom + * selector are filtered from the theme.json node before it is + * processed as normal. + */ + $feature_declarations = array(); + + if ( ! empty( $block_metadata['features'] ) ) { + foreach ( $block_metadata['features'] as $feature_name => $feature_selector ) { + if ( ! empty( $node[ $feature_name ] ) ) { + // Create temporary node containing only the feature data + // to leverage existing `compute_style_properties` function. + $feature = array( $feature_name => $node[ $feature_name ] ); + // Generate the feature's declarations only. + $new_feature_declarations = static::compute_style_properties( $feature, $settings, null, $this->theme_json ); + + // Merge new declarations with any that already exist for + // the feature selector. This may occur when multiple block + // support features use the same custom selector. + if ( isset( $feature_declarations[ $feature_selector ] ) ) { + foreach ( $new_feature_declarations as $new_feature_declaration ) { + $feature_declarations[ $feature_selector ][] = $new_feature_declaration; + } + } else { + $feature_declarations[ $feature_selector ] = $new_feature_declarations; + } + + // Remove the feature from the block's node now the + // styles will be included under the feature level selector. + unset( $node[ $feature_name ] ); + } + } + } + + /* + * Get a reference to element name from path. + * $block_metadata['path'] = array( 'styles','elements','link' ); + * Make sure that $block_metadata['path'] describes an element node, like [ 'styles', 'element', 'link' ]. + * Skip non-element paths like just ['styles']. + */ + $is_processing_element = in_array( 'elements', $block_metadata['path'], true ); + + $current_element = $is_processing_element ? $block_metadata['path'][ count( $block_metadata['path'] ) - 1 ] : null; + + $element_pseudo_allowed = array(); + + // TODO: Replace array_key_exists() with isset() check once WordPress drops + // support for PHP 5.6. See https://core.trac.wordpress.org/ticket/57067. + if ( array_key_exists( $current_element, static::VALID_ELEMENT_PSEUDO_SELECTORS ) ) { + $element_pseudo_allowed = static::VALID_ELEMENT_PSEUDO_SELECTORS[ $current_element ]; + } + + /* + * Check for allowed pseudo classes (e.g. ":hover") from the $selector ("a:hover"). + * This also resets the array keys. + */ + $pseudo_matches = array_values( + array_filter( + $element_pseudo_allowed, + function( $pseudo_selector ) use ( $selector ) { + return str_contains( $selector, $pseudo_selector ); + } + ) + ); + + $pseudo_selector = isset( $pseudo_matches[0] ) ? $pseudo_matches[0] : null; + + /* + * If the current selector is a pseudo selector that's defined in the allow list for the current + * element then compute the style properties for it. + * Otherwise just compute the styles for the default selector as normal. + */ + if ( $pseudo_selector && isset( $node[ $pseudo_selector ] ) && + // TODO: Replace array_key_exists() with isset() check once WordPress drops + // support for PHP 5.6. See https://core.trac.wordpress.org/ticket/57067. + array_key_exists( $current_element, static::VALID_ELEMENT_PSEUDO_SELECTORS ) + && in_array( $pseudo_selector, static::VALID_ELEMENT_PSEUDO_SELECTORS[ $current_element ], true ) + ) { + $declarations = static::compute_style_properties( $node[ $pseudo_selector ], $settings, null, $this->theme_json, $selector, $use_root_padding ); + } else { + $declarations = static::compute_style_properties( $node, $settings, null, $this->theme_json, $selector, $use_root_padding ); + } + + $block_rules = ''; + + /* + * 1. Separate the declarations that use the general selector + * from the ones using the duotone selector. + */ + $declarations_duotone = array(); + foreach ( $declarations as $index => $declaration ) { + if ( 'filter' === $declaration['name'] ) { + unset( $declarations[ $index ] ); + $declarations_duotone[] = $declaration; + } + } + + // Update declarations if there are separators with only background color defined. + if ( '.wp-block-separator' === $selector ) { + $declarations = static::update_separator_declarations( $declarations ); + } + + // 2. Generate and append the rules that use the general selector. + $block_rules .= static::to_ruleset( $selector, $declarations ); + + // 3. Generate and append the rules that use the duotone selector. + if ( isset( $block_metadata['duotone'] ) && ! empty( $declarations_duotone ) ) { + $selector_duotone = static::scope_selector( $block_metadata['selector'], $block_metadata['duotone'] ); + $block_rules .= static::to_ruleset( $selector_duotone, $declarations_duotone ); + } + + // 4. Generate Layout block gap styles. + if ( + static::ROOT_BLOCK_SELECTOR !== $selector && + ! empty( $block_metadata['name'] ) + ) { + $block_rules .= $this->get_layout_styles( $block_metadata ); + } + + // 5. Generate and append the feature level rulesets. + foreach ( $feature_declarations as $feature_selector => $individual_feature_declarations ) { + $block_rules .= static::to_ruleset( $feature_selector, $individual_feature_declarations ); + } + + return $block_rules; + } + + /** + * Outputs the CSS for layout rules on the root. + * + * @since 6.1.0 + * + * @param string $selector The root node selector. + * @param array $block_metadata The metadata for the root block. + * @return string The additional root rules CSS. + */ + public function get_root_layout_rules( $selector, $block_metadata ) { + $css = ''; + $settings = _wp_array_get( $this->theme_json, array( 'settings' ) ); $use_root_padding = isset( $this->theme_json['settings']['useRootPaddingAwareAlignments'] ) && true === $this->theme_json['settings']['useRootPaddingAwareAlignments']; /* @@ -960,413 +2324,836 @@ public function get_root_layout_rules( $selector, $block_metadata ) { */ $css .= 'body { margin: 0;'; - /* - * If there are content and wide widths in theme.json, output them - * as custom properties on the body element so all blocks can use them. - */ - if ( isset( $settings['layout']['contentSize'] ) || isset( $settings['layout']['wideSize'] ) ) { - $content_size = isset( $settings['layout']['contentSize'] ) ? $settings['layout']['contentSize'] : $settings['layout']['wideSize']; - $content_size = static::is_safe_css_declaration( 'max-width', $content_size ) ? $content_size : 'initial'; - $wide_size = isset( $settings['layout']['wideSize'] ) ? $settings['layout']['wideSize'] : $settings['layout']['contentSize']; - $wide_size = static::is_safe_css_declaration( 'max-width', $wide_size ) ? $wide_size : 'initial'; - $css .= '--wp--style--global--content-size: ' . $content_size . ';'; - $css .= '--wp--style--global--wide-size: ' . $wide_size . ';'; + /* + * If there are content and wide widths in theme.json, output them + * as custom properties on the body element so all blocks can use them. + */ + if ( isset( $settings['layout']['contentSize'] ) || isset( $settings['layout']['wideSize'] ) ) { + $content_size = isset( $settings['layout']['contentSize'] ) ? $settings['layout']['contentSize'] : $settings['layout']['wideSize']; + $content_size = static::is_safe_css_declaration( 'max-width', $content_size ) ? $content_size : 'initial'; + $wide_size = isset( $settings['layout']['wideSize'] ) ? $settings['layout']['wideSize'] : $settings['layout']['contentSize']; + $wide_size = static::is_safe_css_declaration( 'max-width', $wide_size ) ? $wide_size : 'initial'; + $css .= '--wp--style--global--content-size: ' . $content_size . ';'; + $css .= '--wp--style--global--wide-size: ' . $wide_size . ';'; + } + + $css .= '}'; + + if ( $use_root_padding ) { + // Top and bottom padding are applied to the outer block container. + $css .= '.wp-site-blocks { padding-top: var(--wp--style--root--padding-top); padding-bottom: var(--wp--style--root--padding-bottom); }'; + // Right and left padding are applied to the first container with `.has-global-padding` class. + $css .= '.has-global-padding { padding-right: var(--wp--style--root--padding-right); padding-left: var(--wp--style--root--padding-left); }'; + // Nested containers with `.has-global-padding` class do not get padding. + $css .= '.has-global-padding :where(.has-global-padding) { padding-right: 0; padding-left: 0; }'; + // Alignfull children of the container with left and right padding have negative margins so they can still be full width. + $css .= '.has-global-padding > .alignfull { margin-right: calc(var(--wp--style--root--padding-right) * -1); margin-left: calc(var(--wp--style--root--padding-left) * -1); }'; + // The above rule is negated for alignfull children of nested containers. + $css .= '.has-global-padding :where(.has-global-padding) > .alignfull { margin-right: 0; margin-left: 0; }'; + // Some of the children of alignfull blocks without content width should also get padding: text blocks and non-alignfull container blocks. + $css .= '.has-global-padding > .alignfull:where(:not(.has-global-padding)) > :where([class*="wp-block-"]:not(.alignfull):not([class*="__"]),p,h1,h2,h3,h4,h5,h6,ul,ol) { padding-right: var(--wp--style--root--padding-right); padding-left: var(--wp--style--root--padding-left); }'; + // The above rule also has to be negated for blocks inside nested `.has-global-padding` blocks. + $css .= '.has-global-padding :where(.has-global-padding) > .alignfull:where(:not(.has-global-padding)) > :where([class*="wp-block-"]:not(.alignfull):not([class*="__"]),p,h1,h2,h3,h4,h5,h6,ul,ol) { padding-right: 0; padding-left: 0; }'; + } + + $css .= '.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }'; + $css .= '.wp-site-blocks > .alignright { float: right; margin-left: 2em; }'; + $css .= '.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }'; + + $block_gap_value = _wp_array_get( $this->theme_json, array( 'styles', 'spacing', 'blockGap' ), '0.5em' ); + $has_block_gap_support = _wp_array_get( $this->theme_json, array( 'settings', 'spacing', 'blockGap' ) ) !== null; + if ( $has_block_gap_support ) { + $block_gap_value = static::get_property_value( $this->theme_json, array( 'styles', 'spacing', 'blockGap' ) ); + $css .= '.wp-site-blocks > * { margin-block-start: 0; margin-block-end: 0; }'; + $css .= ".wp-site-blocks > * + * { margin-block-start: $block_gap_value; }"; + + // For backwards compatibility, ensure the legacy block gap CSS variable is still available. + $css .= "$selector { --wp--style--block-gap: $block_gap_value; }"; + } + $css .= $this->get_layout_styles( $block_metadata ); + + return $css; + } + + /** + * For metadata values that can either be booleans or paths to booleans, gets the value. + * + * ```php + * $data = array( + * 'color' => array( + * 'defaultPalette' => true + * ) + * ); + * + * static::get_metadata_boolean( $data, false ); + * // => false + * + * static::get_metadata_boolean( $data, array( 'color', 'defaultPalette' ) ); + * // => true + * ``` + * + * @since 6.0.0 + * + * @param array $data The data to inspect. + * @param bool|array $path Boolean or path to a boolean. + * @param bool $default Default value if the referenced path is missing. + * Default false. + * @return bool Value of boolean metadata. + */ + protected static function get_metadata_boolean( $data, $path, $default = false ) { + if ( is_bool( $path ) ) { + return $path; + } + + if ( is_array( $path ) ) { + $value = _wp_array_get( $data, $path ); + if ( null !== $value ) { + return $value; + } + } + + return $default; + } + + /** + * Merges new incoming data. + * + * @since 5.8.0 + * @since 5.9.0 Duotone preset also has origins. + * + * @param WP_Theme_JSON $incoming Data to merge. + */ + public function merge( $incoming ) { + $incoming_data = $incoming->get_raw_data(); + $this->theme_json = array_replace_recursive( $this->theme_json, $incoming_data ); + + /* + * The array_replace_recursive algorithm merges at the leaf level, + * but we don't want leaf arrays to be merged, so we overwrite it. + * + * For leaf values that are sequential arrays it will use the numeric indexes for replacement. + * We rather replace the existing with the incoming value, if it exists. + * This is the case of spacing.units. + * + * For leaf values that are associative arrays it will merge them as expected. + * This is also not the behavior we want for the current associative arrays (presets). + * We rather replace the existing with the incoming value, if it exists. + * This happens, for example, when we merge data from theme.json upon existing + * theme supports or when we merge anything coming from the same source twice. + * This is the case of color.palette, color.gradients, color.duotone, + * typography.fontSizes, or typography.fontFamilies. + * + * Additionally, for some preset types, we also want to make sure the + * values they introduce don't conflict with default values. We do so + * by checking the incoming slugs for theme presets and compare them + * with the equivalent default presets: if a slug is present as a default + * we remove it from the theme presets. + */ + $nodes = static::get_setting_nodes( $incoming_data ); + $slugs_global = static::get_default_slugs( $this->theme_json, array( 'settings' ) ); + foreach ( $nodes as $node ) { + // Replace the spacing.units. + $path = $node['path']; + $path[] = 'spacing'; + $path[] = 'units'; + + $content = _wp_array_get( $incoming_data, $path, null ); + if ( isset( $content ) ) { + _wp_array_set( $this->theme_json, $path, $content ); + } + + // Replace the presets. + foreach ( static::PRESETS_METADATA as $preset ) { + $override_preset = ! static::get_metadata_boolean( $this->theme_json['settings'], $preset['prevent_override'], true ); + + foreach ( static::VALID_ORIGINS as $origin ) { + $base_path = $node['path']; + foreach ( $preset['path'] as $leaf ) { + $base_path[] = $leaf; + } + + $path = $base_path; + $path[] = $origin; + + $content = _wp_array_get( $incoming_data, $path, null ); + if ( ! isset( $content ) ) { + continue; + } + + if ( 'theme' === $origin && $preset['use_default_names'] ) { + foreach ( $content as $key => $item ) { + if ( ! isset( $item['name'] ) ) { + $name = static::get_name_from_defaults( $item['slug'], $base_path ); + if ( null !== $name ) { + $content[ $key ]['name'] = $name; + } + } + } + } + + if ( + ( 'theme' !== $origin ) || + ( 'theme' === $origin && $override_preset ) + ) { + _wp_array_set( $this->theme_json, $path, $content ); + } else { + $slugs_node = static::get_default_slugs( $this->theme_json, $node['path'] ); + $slugs = array_merge_recursive( $slugs_global, $slugs_node ); + + $slugs_for_preset = _wp_array_get( $slugs, $preset['path'], array() ); + $content = static::filter_slugs( $content, $slugs_for_preset ); + _wp_array_set( $this->theme_json, $path, $content ); + } + } + } } + } - $css .= '}'; + /** + * Converts all filter (duotone) presets into SVGs. + * + * @since 5.9.1 + * + * @param array $origins List of origins to process. + * @return string SVG filters. + */ + public function get_svg_filters( $origins ) { + $blocks_metadata = static::get_blocks_metadata(); + $setting_nodes = static::get_setting_nodes( $this->theme_json, $blocks_metadata ); - if ( $use_root_padding ) { - // Top and bottom padding are applied to the outer block container. - $css .= '.wp-site-blocks { padding-top: var(--wp--style--root--padding-top); padding-bottom: var(--wp--style--root--padding-bottom); }'; - // Right and left padding are applied to the first container with `.has-global-padding` class. - $css .= '.has-global-padding { padding-right: var(--wp--style--root--padding-right); padding-left: var(--wp--style--root--padding-left); }'; - // Nested containers with `.has-global-padding` class do not get padding. - $css .= '.has-global-padding :where(.has-global-padding) { padding-right: 0; padding-left: 0; }'; - // Alignfull children of the container with left and right padding have negative margins so they can still be full width. - $css .= '.has-global-padding > .alignfull { margin-right: calc(var(--wp--style--root--padding-right) * -1); margin-left: calc(var(--wp--style--root--padding-left) * -1); }'; - // The above rule is negated for alignfull children of nested containers. - $css .= '.has-global-padding :where(.has-global-padding) > .alignfull { margin-right: 0; margin-left: 0; }'; - // Some of the children of alignfull blocks without content width should also get padding: text blocks and non-alignfull container blocks. - $css .= '.has-global-padding > .alignfull:where(:not(.has-global-padding)) > :where([class*="wp-block-"]:not(.alignfull):not([class*="__"]),p,h1,h2,h3,h4,h5,h6,ul,ol) { padding-right: var(--wp--style--root--padding-right); padding-left: var(--wp--style--root--padding-left); }'; - // The above rule also has to be negated for blocks inside nested `.has-global-padding` blocks. - $css .= '.has-global-padding :where(.has-global-padding) > .alignfull:where(:not(.has-global-padding)) > :where([class*="wp-block-"]:not(.alignfull):not([class*="__"]),p,h1,h2,h3,h4,h5,h6,ul,ol) { padding-right: 0; padding-left: 0; }'; + $filters = ''; + foreach ( $setting_nodes as $metadata ) { + $node = _wp_array_get( $this->theme_json, $metadata['path'], array() ); + if ( empty( $node['color']['duotone'] ) ) { + continue; + } + + $duotone_presets = $node['color']['duotone']; + + foreach ( $origins as $origin ) { + if ( ! isset( $duotone_presets[ $origin ] ) ) { + continue; + } + foreach ( $duotone_presets[ $origin ] as $duotone_preset ) { + $filters .= wp_get_duotone_filter_svg( $duotone_preset ); + } + } } - $css .= '.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }'; - $css .= '.wp-site-blocks > .alignright { float: right; margin-left: 2em; }'; - $css .= '.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }'; + return $filters; + } - $block_gap_value = _wp_array_get( $this->theme_json, array( 'styles', 'spacing', 'blockGap' ), '0.5em' ); - $has_block_gap_support = _wp_array_get( $this->theme_json, array( 'settings', 'spacing', 'blockGap' ) ) !== null; - if ( $has_block_gap_support ) { - $block_gap_value = static::get_property_value( $this->theme_json, array( 'styles', 'spacing', 'blockGap' ) ); - $css .= '.wp-site-blocks > * { margin-block-start: 0; margin-block-end: 0; }'; - $css .= ".wp-site-blocks > * + * { margin-block-start: $block_gap_value; }"; + /** + * Determines whether a presets should be overridden or not. + * + * @since 5.9.0 + * @deprecated 6.0.0 Use {@see 'get_metadata_boolean'} instead. + * + * @param array $theme_json The theme.json like structure to inspect. + * @param array $path Path to inspect. + * @param bool|array $override Data to compute whether to override the preset. + * @return boolean + */ + protected static function should_override_preset( $theme_json, $path, $override ) { + _deprecated_function( __METHOD__, '6.0.0', 'get_metadata_boolean' ); - // For backwards compatibility, ensure the legacy block gap CSS variable is still available. - $css .= "$selector { --wp--style--block-gap: $block_gap_value; }"; + if ( is_bool( $override ) ) { + return $override; } - $css .= $this->get_layout_styles( $block_metadata ); - return $css; + /* + * The relationship between whether to override the defaults + * and whether the defaults are enabled is inverse: + * + * - If defaults are enabled => theme presets should not be overridden + * - If defaults are disabled => theme presets should be overridden + * + * For example, a theme sets defaultPalette to false, + * making the default palette hidden from the user. + * In that case, we want all the theme presets to be present, + * so they should override the defaults. + */ + if ( is_array( $override ) ) { + $value = _wp_array_get( $theme_json, array_merge( $path, $override ) ); + if ( isset( $value ) ) { + return ! $value; + } + + // Search the top-level key if none was found for this node. + $value = _wp_array_get( $theme_json, array_merge( array( 'settings' ), $override ) ); + if ( isset( $value ) ) { + return ! $value; + } + + return true; + } } /** - * Converts each style section into a list of rulesets - * containing the block styles to be appended to the stylesheet. + * Returns the default slugs for all the presets in an associative array + * whose keys are the preset paths and the leafs is the list of slugs. * - * See glossary at https://developer.mozilla.org/en-US/docs/Web/CSS/Syntax + * For example: * - * For each section this creates a new ruleset such as: + * array( + * 'color' => array( + * 'palette' => array( 'slug-1', 'slug-2' ), + * 'gradients' => array( 'slug-3', 'slug-4' ), + * ), + * ) * - * block-selector { - * style-property-one: value; - * } + * @since 5.9.0 * - * @param array $style_nodes Nodes with styles. - * @return string The new stylesheet. + * @param array $data A theme.json like structure. + * @param array $node_path The path to inspect. It's 'settings' by default. + * @return array */ - protected function get_block_classes( $style_nodes ) { - $block_rules = ''; + protected static function get_default_slugs( $data, $node_path ) { + $slugs = array(); - foreach ( $style_nodes as $metadata ) { - if ( null === $metadata['selector'] ) { + foreach ( static::PRESETS_METADATA as $metadata ) { + $path = $node_path; + foreach ( $metadata['path'] as $leaf ) { + $path[] = $leaf; + } + $path[] = 'default'; + + $preset = _wp_array_get( $data, $path, null ); + if ( ! isset( $preset ) ) { continue; } - $block_rules .= static::get_styles_for_block( $metadata ); + + $slugs_for_preset = array(); + foreach ( $preset as $item ) { + if ( isset( $item['slug'] ) ) { + $slugs_for_preset[] = $item['slug']; + } + } + + _wp_array_set( $slugs, $metadata['path'], $slugs_for_preset ); } - return $block_rules; + return $slugs; } /** - * Given a styles array, it extracts the style properties - * and adds them to the $declarations array following the format: + * Gets a `default`'s preset name by a provided slug. * - * ```php - * array( - * 'name' => 'property_name', - * 'value' => 'property_value, - * ) - * ``` + * @since 5.9.0 * - * @param array $styles Styles to process. - * @param array $settings Theme settings. - * @param array $properties Properties metadata. - * @param array $theme_json Theme JSON array. - * @param string $selector The style block selector. - * @param boolean $use_root_padding Whether to add custom properties at root level. - * @return array Returns the modified $declarations. + * @param string $slug The slug we want to find a match from default presets. + * @param array $base_path The path to inspect. It's 'settings' by default. + * @return string|null */ - protected static function compute_style_properties( $styles, $settings = array(), $properties = null, $theme_json = null, $selector = null, $use_root_padding = null ) { - if ( null === $properties ) { - $properties = static::PROPERTIES_METADATA; + protected function get_name_from_defaults( $slug, $base_path ) { + $path = $base_path; + $path[] = 'default'; + $default_content = _wp_array_get( $this->theme_json, $path, null ); + if ( ! $default_content ) { + return null; + } + foreach ( $default_content as $item ) { + if ( $slug === $item['slug'] ) { + return $item['name']; + } + } + return null; + } + + /** + * Removes the preset values whose slug is equal to any of given slugs. + * + * @since 5.9.0 + * + * @param array $node The node with the presets to validate. + * @param array $slugs The slugs that should not be overridden. + * @return array The new node. + */ + protected static function filter_slugs( $node, $slugs ) { + if ( empty( $slugs ) ) { + return $node; } - $declarations = array(); - if ( empty( $styles ) ) { - return $declarations; + $new_node = array(); + foreach ( $node as $value ) { + if ( isset( $value['slug'] ) && ! in_array( $value['slug'], $slugs, true ) ) { + $new_node[] = $value; + } } - $root_variable_duplicates = array(); + return $new_node; + } - foreach ( $properties as $css_property => $value_path ) { - $value = static::get_property_value( $styles, $value_path, $theme_json ); + /** + * Removes insecure data from theme.json. + * + * @since 5.9.0 + * + * @param array $theme_json Structure to sanitize. + * @return array Sanitized structure. + */ + public static function remove_insecure_properties( $theme_json ) { + $sanitized = array(); - if ( str_starts_with( $css_property, '--wp--style--root--' ) && ( static::ROOT_BLOCK_SELECTOR !== $selector || ! $use_root_padding ) ) { + $theme_json = WP_Theme_JSON_Schema::migrate( $theme_json ); + + $valid_block_names = array_keys( static::get_blocks_metadata() ); + $valid_element_names = array_keys( static::ELEMENTS ); + + $theme_json = static::sanitize( $theme_json, $valid_block_names, $valid_element_names ); + + $blocks_metadata = static::get_blocks_metadata(); + $style_nodes = static::get_style_nodes( $theme_json, $blocks_metadata ); + + foreach ( $style_nodes as $metadata ) { + $input = _wp_array_get( $theme_json, $metadata['path'], array() ); + if ( empty( $input ) ) { continue; } - // Root-level padding styles don't currently support strings with CSS shorthand values. - // This may change: https://github.com/WordPress/gutenberg/issues/40132. - if ( '--wp--style--root--padding' === $css_property && is_string( $value ) ) { + + $output = static::remove_insecure_styles( $input ); + + /* + * Get a reference to element name from path. + * $metadata['path'] = array( 'styles', 'elements', 'link' ); + */ + $current_element = $metadata['path'][ count( $metadata['path'] ) - 1 ]; + + /* + * $output is stripped of pseudo selectors. Re-add and process them + * or insecure styles here. + */ + // TODO: Replace array_key_exists() with isset() check once WordPress drops + // support for PHP 5.6. See https://core.trac.wordpress.org/ticket/57067. + if ( array_key_exists( $current_element, static::VALID_ELEMENT_PSEUDO_SELECTORS ) ) { + foreach ( static::VALID_ELEMENT_PSEUDO_SELECTORS[ $current_element ] as $pseudo_selector ) { + if ( isset( $input[ $pseudo_selector ] ) ) { + $output[ $pseudo_selector ] = static::remove_insecure_styles( $input[ $pseudo_selector ] ); + } + } + } + + if ( ! empty( $output ) ) { + _wp_array_set( $sanitized, $metadata['path'], $output ); + } + } + + $setting_nodes = static::get_setting_nodes( $theme_json ); + foreach ( $setting_nodes as $metadata ) { + $input = _wp_array_get( $theme_json, $metadata['path'], array() ); + if ( empty( $input ) ) { continue; } - if ( str_starts_with( $css_property, '--wp--style--root--' ) && $use_root_padding ) { - $root_variable_duplicates[] = substr( $css_property, strlen( '--wp--style--root--' ) ); + $output = static::remove_insecure_settings( $input ); + if ( ! empty( $output ) ) { + _wp_array_set( $sanitized, $metadata['path'], $output ); } + } - // Look up protected properties, keyed by value path. - // Skip protected properties that are explicitly set to `null`. - if ( is_array( $value_path ) ) { - $path_string = implode( '.', $value_path ); - if ( - array_key_exists( $path_string, static::PROTECTED_PROPERTIES ) && - _wp_array_get( $settings, static::PROTECTED_PROPERTIES[ $path_string ], null ) === null - ) { + if ( empty( $sanitized['styles'] ) ) { + unset( $theme_json['styles'] ); + } else { + $theme_json['styles'] = $sanitized['styles']; + } + + if ( empty( $sanitized['settings'] ) ) { + unset( $theme_json['settings'] ); + } else { + $theme_json['settings'] = $sanitized['settings']; + } + + return $theme_json; + } + + /** + * Processes a setting node and returns the same node + * without the insecure settings. + * + * @since 5.9.0 + * + * @param array $input Node to process. + * @return array + */ + protected static function remove_insecure_settings( $input ) { + $output = array(); + foreach ( static::PRESETS_METADATA as $preset_metadata ) { + foreach ( static::VALID_ORIGINS as $origin ) { + $path_with_origin = $preset_metadata['path']; + $path_with_origin[] = $origin; + $presets = _wp_array_get( $input, $path_with_origin, null ); + if ( null === $presets ) { continue; } - } - // Skip if empty and not "0" or value represents array of longhand values. - $has_missing_value = empty( $value ) && ! is_numeric( $value ); - if ( $has_missing_value || is_array( $value ) ) { - continue; - } + $escaped_preset = array(); + foreach ( $presets as $preset ) { + if ( + esc_attr( esc_html( $preset['name'] ) ) === $preset['name'] && + sanitize_html_class( $preset['slug'] ) === $preset['slug'] + ) { + $value = null; + if ( isset( $preset_metadata['value_key'], $preset[ $preset_metadata['value_key'] ] ) ) { + $value = $preset[ $preset_metadata['value_key'] ]; + } elseif ( + isset( $preset_metadata['value_func'] ) && + is_callable( $preset_metadata['value_func'] ) + ) { + $value = call_user_func( $preset_metadata['value_func'], $preset ); + } - // Calculates fluid typography rules where available. - if ( 'font-size' === $css_property ) { - /* - * gutenberg_get_typography_font_size_value() will check - * if fluid typography has been activated and also - * whether the incoming value can be converted to a fluid value. - * Values that already have a "clamp()" function will not pass the test, - * and therefore the original $value will be returned. - */ - $value = gutenberg_get_typography_font_size_value( array( 'size' => $value ) ); + $preset_is_valid = true; + foreach ( $preset_metadata['properties'] as $property ) { + if ( ! static::is_safe_css_declaration( $property, $value ) ) { + $preset_is_valid = false; + break; + } + } + + if ( $preset_is_valid ) { + $escaped_preset[] = $preset; + } + } + } + + if ( ! empty( $escaped_preset ) ) { + _wp_array_set( $output, $path_with_origin, $escaped_preset ); + } } + } + return $output; + } - $declarations[] = array( - 'name' => $css_property, - 'value' => $value, - ); + /** + * Processes a style node and returns the same node + * without the insecure styles. + * + * @since 5.9.0 + * @since 6.2.0 Allow indirect properties used outside of `compute_style_properties`. + * + * @param array $input Node to process. + * @return array + */ + protected static function remove_insecure_styles( $input ) { + $output = array(); + $declarations = static::compute_style_properties( $input ); + + foreach ( $declarations as $declaration ) { + if ( static::is_safe_css_declaration( $declaration['name'], $declaration['value'] ) ) { + $path = static::PROPERTIES_METADATA[ $declaration['name'] ]; + + // Check the value isn't an array before adding so as to not + // double up shorthand and longhand styles. + $value = _wp_array_get( $input, $path, array() ); + if ( ! is_array( $value ) ) { + _wp_array_set( $output, $path, $value ); + } + } } - // If a variable value is added to the root, the corresponding property should be removed. - foreach ( $root_variable_duplicates as $duplicate ) { - $discard = array_search( $duplicate, array_column( $declarations, 'name' ), true ); - if ( is_numeric( $discard ) ) { - array_splice( $declarations, $discard, 1 ); + // Ensure indirect properties not handled by `compute_style_properties` are allowed. + foreach ( static::INDIRECT_PROPERTIES_METADATA as $property => $path ) { + $value = _wp_array_get( $input, $path, array() ); + if ( + isset( $value ) && + ! is_array( $value ) && + static::is_safe_css_declaration( $property, $value ) + ) { + _wp_array_set( $output, $path, $value ); } } - return $declarations; + return $output; } /** - * Returns the style property for the given path. + * Checks that a declaration provided by the user is safe. * - * It also converts CSS Custom Property stored as - * "var:preset|color|secondary" to the form - * "--wp--preset--color--secondary". + * @since 5.9.0 * - * It also converts references to a path to the value - * stored at that location, e.g. - * { "ref": "style.color.background" } => "#fff". + * @param string $property_name Property name in a CSS declaration, i.e. the `color` in `color: red`. + * @param string $property_value Value in a CSS declaration, i.e. the `red` in `color: red`. + * @return bool + */ + protected static function is_safe_css_declaration( $property_name, $property_value ) { + $style_to_validate = $property_name . ': ' . $property_value; + $filtered = esc_html( safecss_filter_attr( $style_to_validate ) ); + return ! empty( trim( $filtered ) ); + } + + /** + * Returns the raw data. * - * @param array $styles Styles subtree. - * @param array $path Which property to process. - * @param array $theme_json Theme JSON array. - * @return string|array|null Style property value. + * @since 5.8.0 + * + * @return array Raw data. */ - protected static function get_property_value( $styles, $path, $theme_json = null ) { - $value = _wp_array_get( $styles, $path ); + public function get_raw_data() { + return $this->theme_json; + } - // This converts references to a path to the value at that path - // where the values is an array with a "ref" key, pointing to a path. - // For example: { "ref": "style.color.background" } => "#fff". - if ( is_array( $value ) && array_key_exists( 'ref', $value ) ) { - $value_path = explode( '.', $value['ref'] ); - $ref_value = _wp_array_get( $theme_json, $value_path ); - // Only use the ref value if we find anything. - if ( ! empty( $ref_value ) && is_string( $ref_value ) ) { - $value = $ref_value; + /** + * Transforms the given editor settings according the + * add_theme_support format to the theme.json format. + * + * @since 5.8.0 + * + * @param array $settings Existing editor settings. + * @return array Config that adheres to the theme.json schema. + */ + public static function get_from_editor_settings( $settings ) { + $theme_settings = array( + 'version' => static::LATEST_SCHEMA, + 'settings' => array(), + ); + + // Deprecated theme supports. + if ( isset( $settings['disableCustomColors'] ) ) { + if ( ! isset( $theme_settings['settings']['color'] ) ) { + $theme_settings['settings']['color'] = array(); } + $theme_settings['settings']['color']['custom'] = ! $settings['disableCustomColors']; + } - if ( is_array( $ref_value ) && array_key_exists( 'ref', $ref_value ) ) { - $path_string = json_encode( $path ); - $ref_value_string = json_encode( $ref_value ); - _doing_it_wrong( 'get_property_value', "Your theme.json file uses a dynamic value ({$ref_value_string}) for the path at {$path_string}. However, the value at {$path_string} is also a dynamic value (pointing to {$ref_value['ref']}) and pointing to another dynamic value is not supported. Please update {$path_string} to point directly to {$ref_value['ref']}.", '6.1.0' ); + if ( isset( $settings['disableCustomGradients'] ) ) { + if ( ! isset( $theme_settings['settings']['color'] ) ) { + $theme_settings['settings']['color'] = array(); } + $theme_settings['settings']['color']['customGradient'] = ! $settings['disableCustomGradients']; } - if ( ! $value || is_array( $value ) ) { - return $value; + if ( isset( $settings['disableCustomFontSizes'] ) ) { + if ( ! isset( $theme_settings['settings']['typography'] ) ) { + $theme_settings['settings']['typography'] = array(); + } + $theme_settings['settings']['typography']['customFontSize'] = ! $settings['disableCustomFontSizes']; } - // Convert custom CSS properties. - $prefix = 'var:'; - $prefix_len = strlen( $prefix ); - $token_in = '|'; - $token_out = '--'; - if ( 0 === strncmp( $value, $prefix, $prefix_len ) ) { - $unwrapped_name = str_replace( - $token_in, - $token_out, - substr( $value, $prefix_len ) - ); - $value = "var(--wp--$unwrapped_name)"; + if ( isset( $settings['enableCustomLineHeight'] ) ) { + if ( ! isset( $theme_settings['settings']['typography'] ) ) { + $theme_settings['settings']['typography'] = array(); + } + $theme_settings['settings']['typography']['lineHeight'] = $settings['enableCustomLineHeight']; } - return $value; + if ( isset( $settings['enableCustomUnits'] ) ) { + if ( ! isset( $theme_settings['settings']['spacing'] ) ) { + $theme_settings['settings']['spacing'] = array(); + } + $theme_settings['settings']['spacing']['units'] = ( true === $settings['enableCustomUnits'] ) ? + array( 'px', 'em', 'rem', 'vh', 'vw', '%' ) : + $settings['enableCustomUnits']; + } + + if ( isset( $settings['colors'] ) ) { + if ( ! isset( $theme_settings['settings']['color'] ) ) { + $theme_settings['settings']['color'] = array(); + } + $theme_settings['settings']['color']['palette'] = $settings['colors']; + } + + if ( isset( $settings['gradients'] ) ) { + if ( ! isset( $theme_settings['settings']['color'] ) ) { + $theme_settings['settings']['color'] = array(); + } + $theme_settings['settings']['color']['gradients'] = $settings['gradients']; + } + + if ( isset( $settings['fontSizes'] ) ) { + $font_sizes = $settings['fontSizes']; + // Back-compatibility for presets without units. + foreach ( $font_sizes as $key => $font_size ) { + if ( is_numeric( $font_size['size'] ) ) { + $font_sizes[ $key ]['size'] = $font_size['size'] . 'px'; + } + } + if ( ! isset( $theme_settings['settings']['typography'] ) ) { + $theme_settings['settings']['typography'] = array(); + } + $theme_settings['settings']['typography']['fontSizes'] = $font_sizes; + } + + if ( isset( $settings['enableCustomSpacing'] ) ) { + if ( ! isset( $theme_settings['settings']['spacing'] ) ) { + $theme_settings['settings']['spacing'] = array(); + } + $theme_settings['settings']['spacing']['padding'] = $settings['enableCustomSpacing']; + } + + return $theme_settings; } /** - * Presets are a set of values that serve - * to bootstrap some styles: colors, font sizes, etc. - * - * They are a unkeyed array of values such as: - * - * ```php - * array( - * array( - * 'slug' => 'unique-name-within-the-set', - * 'name' => 'Name for the UI', - * => 'value' - * ), - * ) - * ``` + * Returns the current theme's wanted patterns(slugs) to be + * registered from Pattern Directory. * - * This contains the necessary metadata to process them: + * @since 6.0.0 * - * - path => Where to find the preset within the settings section. - * - prevent_override => Disables override of default presets by theme presets. - * The relationship between whether to override the defaults - * and whether the defaults are enabled is inverse: - * - If defaults are enabled => theme presets should not be overridden - * - If defaults are disabled => theme presets should be overridden - * For example, a theme sets defaultPalette to false, - * making the default palette hidden from the user. - * In that case, we want all the theme presets to be present, - * so they should override the defaults by setting this false. - * - use_default_names => whether to use the default names - * - value_key => the key that represents the value - * - value_func => optionally, instead of value_key, a function to generate - * the value that takes a preset as an argument - * (either value_key or value_func should be present) - * - css_vars => template string to use in generating the CSS Custom Property. - * Example output: "--wp--preset--duotone--blue: " will generate as many CSS Custom Properties as presets defined - * substituting the $slug for the slug's value for each preset value. - * - classes => array containing a structure with the classes to - * generate for the presets, where for each array item - * the key is the class name and the value the property name. - * The "$slug" substring will be replaced by the slug of each preset. - * For example: - * 'classes' => array( - * '.has-$slug-color' => 'color', - * '.has-$slug-background-color' => 'background-color', - * '.has-$slug-border-color' => 'border-color', - * ) - * - properties => array of CSS properties to be used by kses to - * validate the content of each preset - * by means of the remove_insecure_properties method. + * @return string[] */ - const PRESETS_METADATA = array( - array( - 'path' => array( 'color', 'palette' ), - 'prevent_override' => array( 'color', 'defaultPalette' ), - 'use_default_names' => false, - 'value_key' => 'color', - 'css_vars' => '--wp--preset--color--$slug', - 'classes' => array( - '.has-$slug-color' => 'color', - '.has-$slug-background-color' => 'background-color', - '.has-$slug-border-color' => 'border-color', - ), - 'properties' => array( 'color', 'background-color', 'border-color' ), - ), - array( - 'path' => array( 'color', 'gradients' ), - 'prevent_override' => array( 'color', 'defaultGradients' ), - 'use_default_names' => false, - 'value_key' => 'gradient', - 'css_vars' => '--wp--preset--gradient--$slug', - 'classes' => array( '.has-$slug-gradient-background' => 'background' ), - 'properties' => array( 'background' ), - ), - array( - 'path' => array( 'color', 'duotone' ), - 'prevent_override' => array( 'color', 'defaultDuotone' ), - 'use_default_names' => false, - 'value_func' => 'gutenberg_get_duotone_filter_property', - 'css_vars' => '--wp--preset--duotone--$slug', - 'classes' => array(), - 'properties' => array( 'filter' ), - ), - array( - 'path' => array( 'typography', 'fontSizes' ), - 'prevent_override' => false, - 'use_default_names' => true, - 'value_func' => 'gutenberg_get_typography_font_size_value', - 'css_vars' => '--wp--preset--font-size--$slug', - 'classes' => array( '.has-$slug-font-size' => 'font-size' ), - 'properties' => array( 'font-size' ), - ), - array( - 'path' => array( 'typography', 'fontFamilies' ), - 'prevent_override' => false, - 'use_default_names' => false, - 'value_key' => 'fontFamily', - 'css_vars' => '--wp--preset--font-family--$slug', - 'classes' => array( '.has-$slug-font-family' => 'font-family' ), - 'properties' => array( 'font-family' ), - ), - array( - 'path' => array( 'spacing', 'spacingSizes' ), - 'prevent_override' => false, - 'use_default_names' => true, - 'value_key' => 'size', - 'css_vars' => '--wp--preset--spacing--$slug', - 'classes' => array(), - 'properties' => array( 'padding', 'margin' ), - ), - ); + public function get_patterns() { + if ( isset( $this->theme_json['patterns'] ) && is_array( $this->theme_json['patterns'] ) ) { + return $this->theme_json['patterns']; + } + return array(); + } /** - * The valid properties under the settings key. + * Returns a valid theme.json as provided by a theme. * - * @var array + * Unlike get_raw_data() this returns the presets flattened, as provided by a theme. + * This also uses appearanceTools instead of their opt-ins if all of them are true. + * + * @since 6.0.0 + * + * @return array */ - const VALID_SETTINGS = array( - 'appearanceTools' => null, - 'useRootPaddingAwareAlignments' => null, - 'border' => array( - 'color' => null, - 'radius' => null, - 'style' => null, - 'width' => null, - ), - 'color' => array( - 'background' => null, - 'custom' => null, - 'customDuotone' => null, - 'customGradient' => null, - 'defaultDuotone' => null, - 'defaultGradients' => null, - 'defaultPalette' => null, - 'duotone' => null, - 'gradients' => null, - 'link' => null, - 'palette' => null, - 'text' => null, - ), - 'custom' => null, - 'layout' => array( - 'contentSize' => null, - 'definitions' => null, - 'wideSize' => null, - ), - 'spacing' => array( - 'customSpacingSize' => null, - 'spacingSizes' => null, - 'spacingScale' => null, - 'blockGap' => null, - 'margin' => null, - 'padding' => null, - 'units' => null, - ), - 'typography' => array( - 'fluid' => null, - 'customFontSize' => null, - 'dropCap' => null, - 'fontFamilies' => null, - 'fontSizes' => null, - 'fontStyle' => null, - 'fontWeight' => null, - 'letterSpacing' => null, - 'lineHeight' => null, - 'textDecoration' => null, - 'textTransform' => null, - ), - ); + public function get_data() { + $output = $this->theme_json; + $nodes = static::get_setting_nodes( $output ); + + /** + * Flatten the theme & custom origins into a single one. + * + * For example, the following: + * + * { + * "settings": { + * "color": { + * "palette": { + * "theme": [ {} ], + * "custom": [ {} ] + * } + * } + * } + * } + * + * will be converted to: + * + * { + * "settings": { + * "color": { + * "palette": [ {} ] + * } + * } + * } + */ + foreach ( $nodes as $node ) { + foreach ( static::PRESETS_METADATA as $preset_metadata ) { + $path = $node['path']; + foreach ( $preset_metadata['path'] as $preset_metadata_path ) { + $path[] = $preset_metadata_path; + } + $preset = _wp_array_get( $output, $path, null ); + if ( null === $preset ) { + continue; + } + + $items = array(); + if ( isset( $preset['theme'] ) ) { + foreach ( $preset['theme'] as $item ) { + $slug = $item['slug']; + unset( $item['slug'] ); + $items[ $slug ] = $item; + } + } + if ( isset( $preset['custom'] ) ) { + foreach ( $preset['custom'] as $item ) { + $slug = $item['slug']; + unset( $item['slug'] ); + $items[ $slug ] = $item; + } + } + $flattened_preset = array(); + foreach ( $items as $slug => $value ) { + $flattened_preset[] = array_merge( array( 'slug' => (string) $slug ), $value ); + } + _wp_array_set( $output, $path, $flattened_preset ); + } + } + + // If all of the static::APPEARANCE_TOOLS_OPT_INS are true, + // this code unsets them and sets 'appearanceTools' instead. + foreach ( $nodes as $node ) { + $all_opt_ins_are_set = true; + foreach ( static::APPEARANCE_TOOLS_OPT_INS as $opt_in_path ) { + $full_path = $node['path']; + foreach ( $opt_in_path as $opt_in_path_item ) { + $full_path[] = $opt_in_path_item; + } + // Use "unset prop" as a marker instead of "null" because + // "null" can be a valid value for some props (e.g. blockGap). + $opt_in_value = _wp_array_get( $output, $full_path, 'unset prop' ); + if ( 'unset prop' === $opt_in_value ) { + $all_opt_ins_are_set = false; + break; + } + } + + if ( $all_opt_ins_are_set ) { + $node_path_with_appearance_tools = $node['path']; + $node_path_with_appearance_tools[] = 'appearanceTools'; + _wp_array_set( $output, $node_path_with_appearance_tools, true ); + foreach ( static::APPEARANCE_TOOLS_OPT_INS as $opt_in_path ) { + $full_path = $node['path']; + foreach ( $opt_in_path as $opt_in_path_item ) { + $full_path[] = $opt_in_path_item; + } + // Use "unset prop" as a marker instead of "null" because + // "null" can be a valid value for some props (e.g. blockGap). + $opt_in_value = _wp_array_get( $output, $full_path, 'unset prop' ); + if ( true !== $opt_in_value ) { + continue; + } + + // The following could be improved to be path independent. + // At the moment it relies on a couple of assumptions: + // + // - all opt-ins having a path of size 2. + // - there's two sources of settings: the top-level and the block-level. + if ( + ( 1 === count( $node['path'] ) ) && + ( 'settings' === $node['path'][0] ) + ) { + // Top-level settings. + unset( $output['settings'][ $opt_in_path[0] ][ $opt_in_path[1] ] ); + if ( empty( $output['settings'][ $opt_in_path[0] ] ) ) { + unset( $output['settings'][ $opt_in_path[0] ] ); + } + } elseif ( + ( 3 === count( $node['path'] ) ) && + ( 'settings' === $node['path'][0] ) && + ( 'blocks' === $node['path'][1] ) + ) { + // Block-level settings. + $block_name = $node['path'][2]; + unset( $output['settings']['blocks'][ $block_name ][ $opt_in_path[0] ][ $opt_in_path[1] ] ); + if ( empty( $output['settings']['blocks'][ $block_name ][ $opt_in_path[0] ] ) ) { + unset( $output['settings']['blocks'][ $block_name ][ $opt_in_path[0] ] ); + } + } + } + } + } + + wp_recursive_ksort( $output ); + + return $output; + } /** - * Transform the spacing scale values into an array of spacing scale presets. + * Sets the spacingSizes array based on the spacingScale values from theme.json. + * + * @since 6.1.0 + * + * @return null|void */ public function set_spacing_sizes() { $spacing_scale = _wp_array_get( $this->theme_json, array( 'settings', 'spacing', 'spacingScale' ), array() ); - if ( ! is_numeric( $spacing_scale['steps'] ) + // Gutenberg didn't have the 1st isset check. + if ( ! isset( $spacing_scale['steps'] ) + || ! is_numeric( $spacing_scale['steps'] ) || ! isset( $spacing_scale['mediumStep'] ) || ! isset( $spacing_scale['unit'] ) || ! isset( $spacing_scale['operator'] ) @@ -1410,7 +3197,7 @@ public function set_spacing_sizes() { $below_sizes[] = array( /* translators: %s: Digit to indicate multiple of sizing, eg. 2X-Small. */ - 'name' => $below_midpoint_count === $steps_mid_point - 1 ? __( 'Small', 'gutenberg' ) : sprintf( __( '%sX-Small', 'gutenberg' ), strval( $x_small_count ) ), + 'name' => $below_midpoint_count === $steps_mid_point - 1 ? __( 'Small', 'gutenberg' ) : sprintf( __( '%sX-Small', 'gutenberg' ), (string) $x_small_count ), 'slug' => (string) $slug, 'size' => round( $current_step, 2 ) . $unit, ); @@ -1420,7 +3207,7 @@ public function set_spacing_sizes() { } if ( $below_midpoint_count < $steps_mid_point - 2 ) { - ++$x_small_count; + $x_small_count++; } $slug -= 10; @@ -1447,7 +3234,7 @@ public function set_spacing_sizes() { $above_sizes[] = array( /* translators: %s: Digit to indicate multiple of sizing, eg. 2X-Large. */ - 'name' => 0 === $above_midpoint_count ? __( 'Large', 'gutenberg' ) : sprintf( __( '%sX-Large', 'gutenberg' ), strval( $x_large_count ) ), + 'name' => 0 === $above_midpoint_count ? __( 'Large', 'gutenberg' ) : sprintf( __( '%sX-Large', 'gutenberg' ), (string) $x_large_count ), 'slug' => (string) $slug, 'size' => round( $current_step, 2 ) . $unit, ); @@ -1457,244 +3244,24 @@ public function set_spacing_sizes() { } if ( $above_midpoint_count > 1 ) { - ++$x_large_count; + $x_large_count++; } $slug += 10; } - $spacing_sizes = array_merge( $below_sizes, $above_sizes ); + $spacing_sizes = $below_sizes; + foreach ( $above_sizes as $above_sizes_item ) { + $spacing_sizes[] = $above_sizes_item; + } // If there are 7 or less steps in the scale revert to numbers for labels instead of t-shirt sizes. if ( $spacing_scale['steps'] <= 7 ) { for ( $spacing_sizes_count = 0; $spacing_sizes_count < count( $spacing_sizes ); $spacing_sizes_count++ ) { - $spacing_sizes[ $spacing_sizes_count ]['name'] = strval( $spacing_sizes_count + 1 ); + $spacing_sizes[ $spacing_sizes_count ]['name'] = (string) ( $spacing_sizes_count + 1 ); } } _wp_array_set( $this->theme_json, array( 'settings', 'spacing', 'spacingSizes', 'default' ), $spacing_sizes ); } - - /** - * Get the CSS layout rules for a particular block from theme.json layout definitions. - * - * @param array $block_metadata Metadata about the block to get styles for. - * - * @return string Layout styles for the block. - */ - protected function get_layout_styles( $block_metadata ) { - $block_rules = ''; - $block_type = null; - - // Skip outputting layout styles if explicitly disabled. - if ( current_theme_supports( 'disable-layout-styles' ) ) { - return $block_rules; - } - - if ( isset( $block_metadata['name'] ) ) { - $block_type = WP_Block_Type_Registry::get_instance()->get_registered( $block_metadata['name'] ); - if ( ! block_has_support( $block_type, array( '__experimentalLayout' ), false ) ) { - return $block_rules; - } - } - - $selector = isset( $block_metadata['selector'] ) ? $block_metadata['selector'] : ''; - $has_block_gap_support = _wp_array_get( $this->theme_json, array( 'settings', 'spacing', 'blockGap' ) ) !== null; - $has_fallback_gap_support = ! $has_block_gap_support; // This setting isn't useful yet: it exists as a placeholder for a future explicit fallback gap styles support. - $node = _wp_array_get( $this->theme_json, $block_metadata['path'], array() ); - $layout_definitions = _wp_array_get( $this->theme_json, array( 'settings', 'layout', 'definitions' ), array() ); - $layout_selector_pattern = '/^[a-zA-Z0-9\-\.\ *+>:\(\)]*$/'; // Allow alphanumeric classnames, spaces, wildcard, sibling, child combinator and pseudo class selectors. - - // Gap styles will only be output if the theme has block gap support, or supports a fallback gap. - // Default layout gap styles will be skipped for themes that do not explicitly opt-in to blockGap with a `true` or `false` value. - if ( $has_block_gap_support || $has_fallback_gap_support ) { - $block_gap_value = null; - // Use a fallback gap value if block gap support is not available. - if ( ! $has_block_gap_support ) { - $block_gap_value = static::ROOT_BLOCK_SELECTOR === $selector ? '0.5em' : null; - if ( ! empty( $block_type ) ) { - $block_gap_value = _wp_array_get( $block_type->supports, array( 'spacing', 'blockGap', '__experimentalDefault' ), null ); - } - } else { - $block_gap_value = static::get_property_value( $node, array( 'spacing', 'blockGap' ) ); - } - - // Support split row / column values and concatenate to a shorthand value. - if ( is_array( $block_gap_value ) ) { - if ( isset( $block_gap_value['top'] ) && isset( $block_gap_value['left'] ) ) { - $gap_row = static::get_property_value( $node, array( 'spacing', 'blockGap', 'top' ) ); - $gap_column = static::get_property_value( $node, array( 'spacing', 'blockGap', 'left' ) ); - $block_gap_value = $gap_row === $gap_column ? $gap_row : $gap_row . ' ' . $gap_column; - } else { - // Skip outputting gap value if not all sides are provided. - $block_gap_value = null; - } - } - - // If the block should have custom gap, add the gap styles. - if ( null !== $block_gap_value && false !== $block_gap_value && '' !== $block_gap_value ) { - foreach ( $layout_definitions as $layout_definition_key => $layout_definition ) { - // Allow outputting fallback gap styles for flex layout type when block gap support isn't available. - if ( ! $has_block_gap_support && 'flex' !== $layout_definition_key ) { - continue; - } - - $class_name = sanitize_title( _wp_array_get( $layout_definition, array( 'className' ), false ) ); - $spacing_rules = _wp_array_get( $layout_definition, array( 'spacingStyles' ), array() ); - - if ( - ! empty( $class_name ) && - ! empty( $spacing_rules ) - ) { - foreach ( $spacing_rules as $spacing_rule ) { - $declarations = array(); - if ( - isset( $spacing_rule['selector'] ) && - preg_match( $layout_selector_pattern, $spacing_rule['selector'] ) && - ! empty( $spacing_rule['rules'] ) - ) { - // Iterate over each of the styling rules and substitute non-string values such as `null` with the real `blockGap` value. - foreach ( $spacing_rule['rules'] as $css_property => $css_value ) { - $current_css_value = is_string( $css_value ) ? $css_value : $block_gap_value; - if ( static::is_safe_css_declaration( $css_property, $current_css_value ) ) { - $declarations[] = array( - 'name' => $css_property, - 'value' => $current_css_value, - ); - } - } - - if ( ! $has_block_gap_support ) { - // For fallback gap styles, use lower specificity, to ensure styles do not unintentionally override theme styles. - $format = static::ROOT_BLOCK_SELECTOR === $selector ? ':where(.%2$s%3$s)' : ':where(%1$s.%2$s%3$s)'; - $layout_selector = sprintf( - $format, - $selector, - $class_name, - $spacing_rule['selector'] - ); - } else { - $format = static::ROOT_BLOCK_SELECTOR === $selector ? '%s .%s%s' : '%s.%s%s'; - $layout_selector = sprintf( - $format, - $selector, - $class_name, - $spacing_rule['selector'] - ); - } - $block_rules .= static::to_ruleset( $layout_selector, $declarations ); - } - } - } - } - } - } - - // Output base styles. - if ( - static::ROOT_BLOCK_SELECTOR === $selector - ) { - $valid_display_modes = array( 'block', 'flex', 'grid' ); - foreach ( $layout_definitions as $layout_definition ) { - $class_name = sanitize_title( _wp_array_get( $layout_definition, array( 'className' ), false ) ); - $base_style_rules = _wp_array_get( $layout_definition, array( 'baseStyles' ), array() ); - - if ( - ! empty( $class_name ) && - ! empty( $base_style_rules ) - ) { - // Output display mode. This requires special handling as `display` is not exposed in `safe_style_css_filter`. - if ( - ! empty( $layout_definition['displayMode'] ) && - is_string( $layout_definition['displayMode'] ) && - in_array( $layout_definition['displayMode'], $valid_display_modes, true ) - ) { - $layout_selector = sprintf( - '%s .%s', - $selector, - $class_name - ); - $block_rules .= static::to_ruleset( - $layout_selector, - array( - array( - 'name' => 'display', - 'value' => $layout_definition['displayMode'], - ), - ) - ); - } - - foreach ( $base_style_rules as $base_style_rule ) { - $declarations = array(); - - if ( - isset( $base_style_rule['selector'] ) && - preg_match( $layout_selector_pattern, $base_style_rule['selector'] ) && - ! empty( $base_style_rule['rules'] ) - ) { - foreach ( $base_style_rule['rules'] as $css_property => $css_value ) { - if ( static::is_safe_css_declaration( $css_property, $css_value ) ) { - $declarations[] = array( - 'name' => $css_property, - 'value' => $css_value, - ); - } - } - - $layout_selector = sprintf( - '%s .%s%s', - $selector, - $class_name, - $base_style_rule['selector'] - ); - $block_rules .= static::to_ruleset( $layout_selector, $declarations ); - } - } - } - } - } - return $block_rules; - } - - /** - * Function that scopes a selector with another one. This works a bit like - * SCSS nesting except the `&` operator isn't supported. - * - * - * $scope = '.a, .b .c'; - * $selector = '> .x, .y'; - * $merged = scope_selector( $scope, $selector ); - * // $merged is '.a > .x, .a .y, .b .c > .x, .b .c .y' - * - * - * @since 5.9.0 - * - * @param string $scope Selector to scope to. - * @param string $selector Original selector. - * @return string Scoped selector. - */ - public static function scope_selector( $scope, $selector ) { - $scopes = explode( ',', $scope ); - $selectors = explode( ',', $selector ); - - $selectors_scoped = array(); - foreach ( $scopes as $outer ) { - foreach ( $selectors as $inner ) { - $outer = trim( $outer ); - $inner = trim( $inner ); - if ( ! empty( $outer ) && ! empty( $inner ) ) { - $selectors_scoped[] = $outer . ' ' . $inner; - } elseif ( empty( $outer ) ) { - $selectors_scoped[] = $inner; - } elseif ( empty( $inner ) ) { - $selectors_scoped[] = $outer; - } - } - } - - $result = implode( ', ', $selectors_scoped ); - return $result; - } - } diff --git a/lib/client-assets.php b/lib/client-assets.php index ea199750fa10b3..b7537057b89ab7 100644 --- a/lib/client-assets.php +++ b/lib/client-assets.php @@ -193,7 +193,7 @@ function gutenberg_override_style( $styles, $handle, $src, $deps = array(), $ver function gutenberg_register_packages_scripts( $scripts ) { // When in production, use the plugin's version as the default asset version; // else (for development or test) default to use the current time. - $default_version = defined( 'GUTENBERG_VERSION' ) && ! ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? GUTENBERG_VERSION : time(); + $default_version = defined( 'GUTENBERG_VERSION' ) && ! SCRIPT_DEBUG ? GUTENBERG_VERSION : time(); foreach ( glob( gutenberg_dir_path() . 'build/*/index.min.js' ) as $path ) { // Prefix `wp-` to package directory to get script handle. @@ -249,7 +249,16 @@ function gutenberg_register_packages_scripts( $scripts ) { function gutenberg_register_packages_styles( $styles ) { // When in production, use the plugin's version as the asset version; // else (for development or test) default to use the current time. - $version = defined( 'GUTENBERG_VERSION' ) && ! ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? GUTENBERG_VERSION : time(); + $version = defined( 'GUTENBERG_VERSION' ) && ! SCRIPT_DEBUG ? GUTENBERG_VERSION : time(); + + gutenberg_override_style( + $styles, + 'wp-block-editor-content', + gutenberg_url( 'build/block-editor/content.css' ), + array(), + $version + ); + $styles->add_data( 'wp-block-editor-content', 'rtl', 'replace' ); // Editor Styles. gutenberg_override_style( @@ -265,7 +274,7 @@ function gutenberg_register_packages_styles( $styles ) { $styles, 'wp-editor', gutenberg_url( 'build/editor/style.css' ), - array( 'wp-components', 'wp-block-editor', 'wp-nux', 'wp-reusable-blocks' ), + array( 'wp-components', 'wp-block-editor', 'wp-reusable-blocks' ), $version ); $styles->add_data( 'wp-editor', 'rtl', 'replace' ); @@ -274,7 +283,7 @@ function gutenberg_register_packages_styles( $styles ) { $styles, 'wp-edit-post', gutenberg_url( 'build/edit-post/style.css' ), - array( 'wp-components', 'wp-block-editor', 'wp-editor', 'wp-edit-blocks', 'wp-block-library', 'wp-nux' ), + array( 'wp-components', 'wp-block-editor', 'wp-editor', 'wp-edit-blocks', 'wp-block-library' ), $version ); $styles->add_data( 'wp-edit-post', 'rtl', 'replace' ); @@ -315,6 +324,9 @@ function gutenberg_register_packages_styles( $styles ) { 'wp-reset-editor-styles', 'wp-block-library', 'wp-reusable-blocks', + // Until #37466, we can't specifically add them as editor styles yet, + // so we must hard-code it here as a dependency. + 'wp-block-editor-content', ); // Only load the default layout and margin styles for themes without theme.json file. @@ -355,15 +367,6 @@ function gutenberg_register_packages_styles( $styles ) { ); $styles->add_data( 'wp-edit-blocks', 'rtl', 'replace' ); - gutenberg_override_style( - $styles, - 'wp-nux', - gutenberg_url( 'build/nux/style.css' ), - array( 'wp-components' ), - $version - ); - $styles->add_data( 'wp-nux', 'rtl', 'replace' ); - gutenberg_override_style( $styles, 'wp-block-library-theme', @@ -486,7 +489,7 @@ function gutenberg_enqueue_stored_styles( $options = array() ) { $style_tag_id = 'core'; foreach ( $core_styles_keys as $style_key ) { // Adds comment if code is prettified to identify core styles sections in debugging. - $should_prettify = isset( $options['prettify'] ) ? true === $options['prettify'] : defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG; + $should_prettify = isset( $options['prettify'] ) ? true === $options['prettify'] : SCRIPT_DEBUG; if ( $should_prettify ) { $compiled_core_stylesheet .= "/**\n * Core styles: $style_key\n */\n"; } @@ -527,6 +530,37 @@ function gutenberg_enqueue_stored_styles( $options = array() ) { } } +/** + * Registers vendor JavaScript files to be used as dependencies of the editor + * and plugins. + * + * This function is called from a script during the plugin build process, so it + * should not call any WordPress PHP functions. + * + * @since 13.0 + * + * @param WP_Scripts $scripts WP_Scripts instance. + */ +function gutenberg_register_vendor_scripts( $scripts ) { + $extension = SCRIPT_DEBUG ? '.js' : '.min.js'; + + gutenberg_override_script( + $scripts, + 'react', + gutenberg_url( 'build/vendors/react' . $extension ), + // See https://github.com/pmmmwh/react-refresh-webpack-plugin/blob/main/docs/TROUBLESHOOTING.md#externalising-react. + SCRIPT_DEBUG ? array( 'wp-react-refresh-entry', 'wp-polyfill' ) : array( 'wp-polyfill' ) + ); + gutenberg_override_script( + $scripts, + 'react-dom', + gutenberg_url( 'build/vendors/react-dom' . $extension ), + array( 'react' ) + ); +} +add_action( 'wp_default_scripts', 'gutenberg_register_vendor_scripts' ); + + /* * Always remove the Core action hook while gutenberg_enqueue_stored_styles() exists to avoid styles being printed twice. * This is also because gutenberg_enqueue_stored_styles uses the Style Engine's `gutenberg_*` functions and `_Gutenberg` classes, diff --git a/lib/compat/wordpress-6.0/block-editor-settings.php b/lib/compat/wordpress-6.0/block-editor-settings.php deleted file mode 100644 index 8aae673bff334d..00000000000000 --- a/lib/compat/wordpress-6.0/block-editor-settings.php +++ /dev/null @@ -1,35 +0,0 @@ - .alignleft { float: left; margin-right: 2em; }'; - $root_styles .= '.wp-site-blocks > .alignright { float: right; margin-left: 2em; }'; - $root_styles .= '.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }'; - - if ( - ( isset( $style['__unstableType'] ) && ( 'presets' === $style['__unstableType'] ) ) || - ( isset( $style['__unstableType'] ) && ( 'theme' === $style['__unstableType'] ) && str_contains( $style['css'], $root_styles ) ) - ) { - return true; - } - - return false; -} diff --git a/lib/compat/wordpress-6.0/block-gallery.php b/lib/compat/wordpress-6.0/block-gallery.php deleted file mode 100644 index 53c3a0c2247fd0..00000000000000 --- a/lib/compat/wordpress-6.0/block-gallery.php +++ /dev/null @@ -1,57 +0,0 @@ -= 6.0. - * - * @return void. - */ -function gutenberg_check_gallery_block_v2_compatibility() { - $use_balance_tags = (int) get_option( 'use_balanceTags' ); - $v2_gallery_enabled = boolval( 1 !== $use_balance_tags || is_wp_version_compatible( '6.0' ) ) ? 'true' : 'false'; - - wp_add_inline_script( - 'wp-dom-ready', - 'wp.galleryBlockV2Enabled = ' . $v2_gallery_enabled . ';', - 'after' - ); -} -add_action( 'init', 'gutenberg_check_gallery_block_v2_compatibility' ); - -/** - * Prevent use_balanceTags being enabled on WordPress 5.8 or earlier as it breaks - * the layout of the new Gallery block. - * - * @since 12.1.0 - * @todo This should be removed when the minimum required WP version is >= 6.0. - * - * @param int $new_value The new value for use_balanceTags. - */ -function gutenberg_use_balancetags_check( $new_value ) { - global $wp_version; - - if ( 1 === (int) $new_value && version_compare( $wp_version, '6.0', '<' ) ) { - /* translators: %s: Minimum required version */ - $message = sprintf( __( 'Gutenberg requires WordPress %s or later in order to enable the “Correct invalidly nested XHTML automatically” option. Please upgrade WordPress before enabling.', 'gutenberg' ), '6.0' ); - add_settings_error( 'gutenberg_use_balancetags_check', 'gutenberg_use_balancetags_check', $message, 'error' ); - if ( class_exists( 'WP_CLI' ) ) { - WP_CLI::error( $message ); - } - return 0; - } - - return $new_value; -} -add_filter( 'pre_update_option_use_balanceTags', 'gutenberg_use_balancetags_check' ); diff --git a/lib/compat/wordpress-6.0/block-patterns-update.php b/lib/compat/wordpress-6.0/block-patterns-update.php deleted file mode 100644 index c17a65be2ae223..00000000000000 --- a/lib/compat/wordpress-6.0/block-patterns-update.php +++ /dev/null @@ -1,217 +0,0 @@ -is_registered( 'query' ) ) { - register_block_pattern_category( 'query', array( 'label' => __( 'Query', 'gutenberg' ) ) ); - } - - if ( ! $pattern_category_registry->is_registered( 'featured' ) ) { - register_block_pattern_category( 'featured', array( 'label' => __( 'Featured', 'gutenberg' ) ) ); - } - - $patterns = array( - 'query-standard-posts' => array( - 'title' => _x( 'Standard', 'Block pattern title', 'gutenberg' ), - 'blockTypes' => array( 'core/query' ), - 'categories' => array( 'query' ), - 'content' => ' -
- - - - - -
- - - -
- ', - ), - 'query-medium-posts' => array( - 'title' => _x( 'Image at left', 'Block pattern title', 'gutenberg' ), - 'blockTypes' => array( 'core/query' ), - 'categories' => array( 'query' ), - 'content' => ' -
- - -
-
- - -
-
-
- - -
- ', - ), - 'query-small-posts' => array( - 'title' => _x( 'Small image and title', 'Block pattern title', 'gutenberg' ), - 'blockTypes' => array( 'core/query' ), - 'categories' => array( 'query' ), - 'content' => ' -
- - -
-
- - -
-
- - -
- ', - ), - 'query-grid-posts' => array( - 'title' => _x( 'Grid', 'Block pattern title', 'gutenberg' ), - 'blockTypes' => array( 'core/query' ), - 'categories' => array( 'query' ), - 'content' => ' -
- - -
- -
- - -
- ', - ), - 'query-large-title-posts' => array( - 'title' => _x( 'Large title', 'Block pattern title', 'gutenberg' ), - 'blockTypes' => array( 'core/query' ), - 'categories' => array( 'query' ), - 'content' => ' -
-
- -
- - - -
-
- - - -
-
- -
-
- ', - ), - 'query-offset-posts' => array( - 'title' => _x( 'Offset', 'Block pattern title', 'gutenberg' ), - 'blockTypes' => array( 'core/query' ), - 'categories' => array( 'query' ), - 'content' => ' -
-
-
-
- - - - - - -
-
- - -
-
- - - - - - -
-
-
-
- ', - ), - // Initial block pattern to be used with block transformations with patterns. - 'social-links-shared-background-color' => array( - 'title' => _x( 'Social links with a shared background color', 'Block pattern title', 'gutenberg' ), - 'categories' => array( 'buttons' ), - 'blockTypes' => array( 'core/social-links' ), - 'viewportWidth' => 500, - 'content' => ' - - ', - ), - ); - - foreach ( $patterns as $name => $pattern ) { - $pattern_name = 'core/' . $name; - if ( ! WP_Block_Patterns_Registry::get_instance()->is_registered( $pattern_name ) ) { - register_block_pattern( $pattern_name, $pattern ); - } - } -} - -/** - * Deactivate the legacy patterns bundled with WordPress. - */ -function gutenberg_remove_core_patterns() { - $core_block_patterns = array( - 'text-two-columns', - 'two-buttons', - 'two-images', - 'text-two-columns-with-images', - 'text-three-columns-buttons', - 'large-header', - 'large-header-button', - 'three-buttons', - 'heading-paragraph', - 'quote', - 'query-standard-posts', - 'query-medium-posts', - 'query-small-posts', - 'query-grid-posts', - 'query-large-title-posts', - 'query-offset-posts', - 'social-links-shared-background-color', - ); - - foreach ( $core_block_patterns as $core_block_pattern ) { - $name = 'core/' . $core_block_pattern; - if ( WP_Block_Patterns_Registry::get_instance()->is_registered( $name ) ) { - unregister_block_pattern( $name ); - } - } -} - -add_action( - 'init', - function() { - if ( ! get_theme_support( 'core-block-patterns' ) || ! function_exists( 'unregister_block_pattern' ) ) { - return; - } - gutenberg_remove_core_patterns(); - gutenberg_register_gutenberg_patterns(); - } -); diff --git a/lib/compat/wordpress-6.0/block-patterns.php b/lib/compat/wordpress-6.0/block-patterns.php deleted file mode 100644 index c42ec73152219a..00000000000000 --- a/lib/compat/wordpress-6.0/block-patterns.php +++ /dev/null @@ -1,44 +0,0 @@ -get_patterns(); - if ( empty( $pattern_settings ) ) { - return; - } - - $request = new WP_REST_Request( 'GET', '/wp/v2/pattern-directory/patterns' ); - $request['slug'] = $pattern_settings; - $response = rest_do_request( $request ); - if ( $response->is_error() ) { - return; - } - $patterns = $response->get_data(); - $patterns_registry = WP_Block_Patterns_Registry::get_instance(); - foreach ( $patterns as $pattern ) { - $pattern_name = sanitize_title( $pattern['title'] ); - // Some patterns might be already registered as core patterns with the `core` prefix. - $is_registered = $patterns_registry->is_registered( $pattern_name ) || $patterns_registry->is_registered( "core/$pattern_name" ); - if ( ! $is_registered ) { - register_block_pattern( $pattern_name, (array) $pattern ); - } - } - } -} diff --git a/lib/compat/wordpress-6.0/block-template-utils.php b/lib/compat/wordpress-6.0/block-template-utils.php deleted file mode 100644 index 1fcd3dae83e594..00000000000000 --- a/lib/compat/wordpress-6.0/block-template-utils.php +++ /dev/null @@ -1,132 +0,0 @@ -get( 'TextDomain' ); - $filename = get_temp_dir() . $theme_name . $obscura . '.zip'; - - $zip = new ZipArchive(); - if ( true !== $zip->open( $filename, ZipArchive::CREATE | ZipArchive::OVERWRITE ) ) { - return new WP_Error( 'unable_to_create_zip', __( 'Unable to open export file (archive) for writing.', 'gutenberg' ) ); - } - - $zip->addEmptyDir( 'templates' ); - $zip->addEmptyDir( 'parts' ); - - // Get path of the theme. - $theme_path = wp_normalize_path( get_stylesheet_directory() ); - - // Create recursive directory iterator. - $theme_files = new RecursiveIteratorIterator( - new RecursiveDirectoryIterator( $theme_path ), - RecursiveIteratorIterator::LEAVES_ONLY - ); - - // Make a copy of the current theme. - foreach ( $theme_files as $file ) { - // Skip directories as they are added automatically. - if ( ! $file->isDir() ) { - // Get real and relative path for current file. - $file_path = wp_normalize_path( $file ); - $relative_path = substr( $file_path, strlen( $theme_path ) + 1 ); - - if ( ! gutenberg_is_theme_directory_ignored( $relative_path ) ) { - $zip->addFile( $file_path, $relative_path ); - } - } - } - - // Load templates into the zip file. - $templates = gutenberg_get_block_templates(); - foreach ( $templates as $template ) { - $template->content = _remove_theme_attribute_in_block_template_content( $template->content ); - - $zip->addFromString( - 'templates/' . $template->slug . '.html', - $template->content - ); - } - - // Load template parts into the zip file. - $template_parts = gutenberg_get_block_templates( array(), 'wp_template_part' ); - foreach ( $template_parts as $template_part ) { - $zip->addFromString( - 'parts/' . $template_part->slug . '.html', - $template_part->content - ); - } - - // Load theme.json into the zip file. - $tree = WP_Theme_JSON_Resolver_Gutenberg::get_theme_data( array(), array( 'with_supports' => false ) ); - // Merge with user data. - $tree->merge( WP_Theme_JSON_Resolver_Gutenberg::get_user_data() ); - - $theme_json_raw = $tree->get_data(); - // If a version is defined, add a schema. - if ( $theme_json_raw['version'] ) { - global $wp_version; - $theme_json_version = 'wp/' . substr( $wp_version, 0, 3 ); - if ( defined( 'IS_GUTENBERG_PLUGIN' ) ) { - $theme_json_version = 'trunk'; - } - $schema = array( '$schema' => 'https://schemas.wp.org/' . $theme_json_version . '/theme.json' ); - $theme_json_raw = array_merge( $schema, $theme_json_raw ); - } - - // Convert to a string. - $theme_json_encoded = wp_json_encode( $theme_json_raw, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE ); - - // Replace 4 spaces with a tab. - $theme_json_tabbed = preg_replace( '~(?:^|\G)\h{4}~m', "\t", $theme_json_encoded ); - - // Add the theme.json file to the zip. - $zip->addFromString( - 'theme.json', - $theme_json_tabbed - ); - - // Save changes to the zip file. - $zip->close(); - - return $filename; -} diff --git a/lib/compat/wordpress-6.0/blocks.php b/lib/compat/wordpress-6.0/blocks.php deleted file mode 100644 index ed83f62f76d420..00000000000000 --- a/lib/compat/wordpress-6.0/blocks.php +++ /dev/null @@ -1,194 +0,0 @@ - 'comment_date_gmt', - 'order' => 'ASC', - 'status' => 'approve', - 'no_found_rows' => false, - ); - - if ( is_user_logged_in() ) { - $comment_args['include_unapproved'] = array( get_current_user_id() ); - } else { - $unapproved_email = wp_get_unapproved_comment_author_email(); - - if ( $unapproved_email ) { - $comment_args['include_unapproved'] = array( $unapproved_email ); - } - } - - if ( ! empty( $block->context['postId'] ) ) { - $comment_args['post_id'] = (int) $block->context['postId']; - } - - if ( get_option( 'thread_comments' ) ) { - $comment_args['hierarchical'] = 'threaded'; - } else { - $comment_args['hierarchical'] = false; - } - - if ( get_option( 'page_comments' ) === '1' || get_option( 'page_comments' ) === true ) { - $per_page = get_option( 'comments_per_page' ); - $default_page = get_option( 'default_comments_page' ); - if ( $per_page > 0 ) { - $comment_args['number'] = $per_page; - - $page = (int) get_query_var( 'cpage' ); - if ( $page ) { - $comment_args['paged'] = $page; - } elseif ( 'oldest' === $default_page ) { - $comment_args['paged'] = 1; - } elseif ( 'newest' === $default_page ) { - $max_num_pages = (int) ( new WP_Comment_Query( $comment_args ) )->max_num_pages; - if ( 0 !== $max_num_pages ) { - $comment_args['paged'] = $max_num_pages; - } - } - // Set the `cpage` query var to ensure the previous and next pagination links are correct - // when inheriting the Discussion Settings. - if ( 0 === $page && isset( $comment_args['paged'] ) && $comment_args['paged'] > 0 ) { - set_query_var( 'cpage', $comment_args['paged'] ); - } - } - } - - return $comment_args; - } -} - -if ( ! function_exists( 'get_comments_pagination_arrow' ) ) { - /** - * Helper function that returns the proper pagination arrow html for - * `CommentsPaginationNext` and `CommentsPaginationPrevious` blocks based - * on the provided `paginationArrow` from `CommentsPagination` context. - * - * It's used in CommentsPaginationNext and CommentsPaginationPrevious blocks. - * - * @since 6.0.0 - * - * @param WP_Block $block Block instance. - * @param string $pagination_type Type of the arrow we will be rendering. Default 'next'. Accepts 'next' or 'previous'. - * - * @return string|null Returns the constructed WP_Query arguments. - */ - function get_comments_pagination_arrow( $block, $pagination_type = 'next' ) { - $arrow_map = array( - 'none' => '', - 'arrow' => array( - 'next' => '→', - 'previous' => '←', - ), - 'chevron' => array( - 'next' => '»', - 'previous' => '«', - ), - ); - if ( ! empty( $block->context['comments/paginationArrow'] ) && ! empty( $arrow_map[ $block->context['comments/paginationArrow'] ][ $pagination_type ] ) ) { - $arrow_attribute = $block->context['comments/paginationArrow']; - $arrow = $arrow_map[ $block->context['comments/paginationArrow'] ][ $pagination_type ]; - $arrow_classes = "wp-block-comments-pagination-$pagination_type-arrow is-arrow-$arrow_attribute"; - return "$arrow"; - } - return null; - } -} - -/** - * Workaround for getting discussion settings as block editor settings - * so any user can access to them without needing to be an admin. - * - * @param array $settings Default editor settings. - * - * @return array Filtered editor settings. - */ -function gutenberg_extend_block_editor_settings_with_discussion_settings( $settings ) { - - $settings['__experimentalDiscussionSettings'] = array( - 'commentOrder' => get_option( 'comment_order' ), - 'commentsPerPage' => get_option( 'comments_per_page' ), - 'defaultCommentsPage' => get_option( 'default_comments_page' ), - 'pageComments' => get_option( 'page_comments' ), - 'threadComments' => get_option( 'thread_comments' ), - 'threadCommentsDepth' => get_option( 'thread_comments_depth' ), - 'defaultCommentStatus' => get_option( 'default_comment_status' ), - 'avatarURL' => get_avatar_url( - '', - array( - 'size' => 96, - 'force_default' => true, - 'default' => get_option( 'avatar_default' ), - ) - ), - ); - - return $settings; -} -add_filter( 'block_editor_settings_all', 'gutenberg_extend_block_editor_settings_with_discussion_settings' ); - -/** - * Mark the `children` attr of comments as embeddable so they can be included in - * REST API responses without additional requests. - * - * @return void - */ -function gutenberg_rest_comment_set_children_as_embeddable() { - add_filter( - 'rest_prepare_comment', - function ( $response ) { - $links = $response->get_links(); - if ( isset( $links['children'] ) ) { - $href = $links['children'][0]['href']; - $response->remove_link( 'children', $href ); - $response->add_link( 'children', $href, array( 'embeddable' => true ) ); - } - return $response; - } - ); -} -add_action( 'rest_api_init', 'gutenberg_rest_comment_set_children_as_embeddable' ); - -/** - * Registers the lock block attribute for block types. - * - * Once 6.0 is the minimum supported WordPress version for the Gutenberg - * plugin, this shim can be removed - * - * Doesn't need to be backported into Core. - * - * @param array $args Array of arguments for registering a block type. - * @return array $args - */ -function gutenberg_register_lock_attribute( $args ) { - // Setup attributes if needed. - if ( ! isset( $args['attributes'] ) || ! is_array( $args['attributes'] ) ) { - $args['attributes'] = array(); - } - - if ( ! array_key_exists( 'lock', $args['attributes'] ) ) { - $args['attributes']['lock'] = array( - 'type' => 'object', - ); - } - - return $args; -} -add_filter( 'register_block_type_args', 'gutenberg_register_lock_attribute' ); diff --git a/lib/compat/wordpress-6.0/class-gutenberg-rest-edit-site-export-controller.php b/lib/compat/wordpress-6.0/class-gutenberg-rest-edit-site-export-controller.php deleted file mode 100644 index e4cffb2bff268a..00000000000000 --- a/lib/compat/wordpress-6.0/class-gutenberg-rest-edit-site-export-controller.php +++ /dev/null @@ -1,89 +0,0 @@ -namespace = 'wp-block-editor/v1'; - $this->rest_base = 'export'; - } - - /** - * Registers the necessary REST API routes. - */ - public function register_routes() { - register_rest_route( - $this->namespace, - '/' . $this->rest_base, - array( - array( - 'methods' => WP_REST_Server::READABLE, - 'callback' => array( $this, 'export' ), - 'permission_callback' => array( $this, 'permissions_check' ), - ), - ), - true // Override core route if already exists (WP 5.9). - ); - } - - /** - * Checks whether a given request has permission to export. - * - * @return WP_Error|bool True if the request has access, or WP_Error object. - */ - public function permissions_check() { - if ( current_user_can( 'edit_theme_options' ) ) { - return true; - } - - return new WP_Error( - 'rest_cannot_export_templates', - __( 'Sorry, you are not allowed to export templates and template parts.', 'gutenberg' ), - array( 'status' => rest_authorization_required_code() ) - ); - } - - /** - * Output a ZIP file with an export of the current templates - * template parts, theme.json and index.php from the site editor, - * and close the connection. - * - * @return WP_Error|void - */ - public function export() { - // Generate the export file. - $filename = gutenberg_generate_block_templates_export_file(); - - if ( is_wp_error( $filename ) ) { - $filename->add_data( array( 'status' => 500 ) ); - - return $filename; - } - - $theme_name = basename( get_stylesheet() ); - - header( 'Content-Type: application/zip' ); - header( 'Content-Disposition: attachment; filename=' . $theme_name . '.zip' ); - header( 'Content-Length: ' . filesize( $filename ) ); - flush(); - readfile( $filename ); - unlink( $filename ); - exit; - } -} diff --git a/lib/compat/wordpress-6.0/class-gutenberg-rest-pattern-directory-controller-6-0.php b/lib/compat/wordpress-6.0/class-gutenberg-rest-pattern-directory-controller-6-0.php deleted file mode 100644 index 331e6015edb0cd..00000000000000 --- a/lib/compat/wordpress-6.0/class-gutenberg-rest-pattern-directory-controller-6-0.php +++ /dev/null @@ -1,48 +0,0 @@ -= 6.0. - * - * @param array $query_args Query arguments to generate a transient key from. - * @return string Transient key. - */ - protected function get_transient_key( $query_args ) { - if ( method_exists( get_parent_class( $this ), __FUNCTION__ ) ) { - return parent::get_transient_key( $query_args ); - } - - if ( isset( $query_args['slug'] ) ) { - // This is an additional precaution because the "sort" function expects an array. - $query_args['slug'] = wp_parse_list( $query_args['slug'] ); - - // Empty arrays should not affect the transient key. - if ( empty( $query_args['slug'] ) ) { - unset( $query_args['slug'] ); - } else { - // Sort the array so that the transient key doesn't depend on the order of slugs. - sort( $query_args['slug'] ); - } - } - - return 'wp_remote_block_patterns_' . md5( serialize( $query_args ) ); - } -} diff --git a/lib/compat/wordpress-6.0/class-wp-rest-block-pattern-categories-controller.php b/lib/compat/wordpress-6.0/class-wp-rest-block-pattern-categories-controller.php deleted file mode 100644 index 0f60781dae106c..00000000000000 --- a/lib/compat/wordpress-6.0/class-wp-rest-block-pattern-categories-controller.php +++ /dev/null @@ -1,152 +0,0 @@ -namespace = 'wp/v2'; - $this->rest_base = 'block-patterns/categories'; - } - - /** - * Registers the routes for the objects of the controller. - * - * @see register_rest_route() - * - * @since 6.0.0 - */ - public function register_routes() { - register_rest_route( - $this->namespace, - '/' . $this->rest_base, - array( - array( - 'methods' => WP_REST_Server::READABLE, - 'callback' => array( $this, 'get_items' ), - 'permission_callback' => array( $this, 'get_items_permissions_check' ), - ), - 'schema' => array( $this, 'get_public_item_schema' ), - ) - ); - } - - /** - * Checks whether a given request has permission to read block patterns. - * - * @since 6.0.0 - * - * @param WP_REST_Request $request Full details about the request. - * - * @return WP_Error|bool True if the request has read access, WP_Error object otherwise. - */ - public function get_items_permissions_check( $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable - if ( current_user_can( 'edit_posts' ) ) { - return true; - } - - foreach ( get_post_types( array( 'show_in_rest' => true ), 'objects' ) as $post_type ) { - if ( current_user_can( $post_type->cap->edit_posts ) ) { - return true; - } - } - - return new WP_Error( - 'rest_cannot_view', - __( 'Sorry, you are not allowed to view the registered block pattern categories.', 'gutenberg' ), - array( 'status' => rest_authorization_required_code() ) - ); - } - - /** - * Retrieves all block pattern categories. - * - * @since 6.0.0 - * - * @param WP_REST_Request $request Full details about the request. - * - * @return WP_Error|WP_REST_Response Response object on success, or WP_Error object on failure. - */ - public function get_items( $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable - $response = array(); - $categories = WP_Block_Pattern_Categories_Registry::get_instance()->get_all_registered(); - foreach ( $categories as $category ) { - $prepared_category = $this->prepare_item_for_response( $category, $request ); - $response[] = $this->prepare_response_for_collection( $prepared_category ); - } - return rest_ensure_response( $response ); - } - - /** - * Prepare a raw block pattern category before it gets output in a REST API response. - * - * @since 6.0.0 - * - * @param object $item Raw category as registered, before any changes. - * @param WP_REST_Request $request Request object. - * @return WP_REST_Response - */ - public function prepare_item_for_response( $item, $request ) { - $fields = $this->get_fields_for_response( $request ); - $keys = array( 'name', 'label' ); - $data = array(); - foreach ( $keys as $key ) { - if ( rest_is_field_included( $key, $fields ) ) { - $data[ $key ] = $item[ $key ]; - } - } - - $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; - $data = $this->add_additional_fields_to_object( $data, $request ); - $data = $this->filter_response_by_context( $data, $context ); - return rest_ensure_response( $data ); - } - - /** - * Retrieves the block pattern category schema, conforming to JSON Schema. - * - * @since 6.0.0 - * - * @return array Item schema data. - */ - public function get_item_schema() { - $schema = array( - '$schema' => 'http://json-schema.org/draft-04/schema#', - 'title' => 'block-pattern-category', - 'type' => 'object', - 'properties' => array( - 'name' => array( - 'description' => __( 'The category name.', 'gutenberg' ), - 'type' => 'string', - 'readonly' => true, - 'context' => array( 'view', 'edit', 'embed' ), - ), - 'label' => array( - 'description' => __( 'The category label, in human readable format.', 'gutenberg' ), - 'type' => 'string', - 'readonly' => true, - 'context' => array( 'view', 'edit', 'embed' ), - ), - ), - ); - - return $this->add_additional_fields_schema( $schema ); - } -} diff --git a/lib/compat/wordpress-6.0/class-wp-theme-json-6-0.php b/lib/compat/wordpress-6.0/class-wp-theme-json-6-0.php deleted file mode 100644 index 07820ccbd1dbe2..00000000000000 --- a/lib/compat/wordpress-6.0/class-wp-theme-json-6-0.php +++ /dev/null @@ -1,680 +0,0 @@ - array( 'color', 'gradient' ), - 'background-color' => array( 'color', 'background' ), - 'border-radius' => array( 'border', 'radius' ), - 'border-top-left-radius' => array( 'border', 'radius', 'topLeft' ), - 'border-top-right-radius' => array( 'border', 'radius', 'topRight' ), - 'border-bottom-left-radius' => array( 'border', 'radius', 'bottomLeft' ), - 'border-bottom-right-radius' => array( 'border', 'radius', 'bottomRight' ), - 'border-color' => array( 'border', 'color' ), - 'border-width' => array( 'border', 'width' ), - 'border-style' => array( 'border', 'style' ), - 'border-top-color' => array( 'border', 'top', 'color' ), - 'border-top-width' => array( 'border', 'top', 'width' ), - 'border-top-style' => array( 'border', 'top', 'style' ), - 'border-right-color' => array( 'border', 'right', 'color' ), - 'border-right-width' => array( 'border', 'right', 'width' ), - 'border-right-style' => array( 'border', 'right', 'style' ), - 'border-bottom-color' => array( 'border', 'bottom', 'color' ), - 'border-bottom-width' => array( 'border', 'bottom', 'width' ), - 'border-bottom-style' => array( 'border', 'bottom', 'style' ), - 'border-left-color' => array( 'border', 'left', 'color' ), - 'border-left-width' => array( 'border', 'left', 'width' ), - 'border-left-style' => array( 'border', 'left', 'style' ), - 'color' => array( 'color', 'text' ), - 'font-family' => array( 'typography', 'fontFamily' ), - 'font-size' => array( 'typography', 'fontSize' ), - 'font-style' => array( 'typography', 'fontStyle' ), - 'font-weight' => array( 'typography', 'fontWeight' ), - 'letter-spacing' => array( 'typography', 'letterSpacing' ), - 'line-height' => array( 'typography', 'lineHeight' ), - 'margin' => array( 'spacing', 'margin' ), - 'margin-top' => array( 'spacing', 'margin', 'top' ), - 'margin-right' => array( 'spacing', 'margin', 'right' ), - 'margin-bottom' => array( 'spacing', 'margin', 'bottom' ), - 'margin-left' => array( 'spacing', 'margin', 'left' ), - 'padding' => array( 'spacing', 'padding' ), - 'padding-top' => array( 'spacing', 'padding', 'top' ), - 'padding-right' => array( 'spacing', 'padding', 'right' ), - 'padding-bottom' => array( 'spacing', 'padding', 'bottom' ), - 'padding-left' => array( 'spacing', 'padding', 'left' ), - '--wp--style--block-gap' => array( 'spacing', 'blockGap' ), - 'text-decoration' => array( 'typography', 'textDecoration' ), - 'text-transform' => array( 'typography', 'textTransform' ), - 'filter' => array( 'filter', 'duotone' ), - ); - - /** - * Presets are a set of values that serve - * to bootstrap some styles: colors, font sizes, etc. - * - * They are a unkeyed array of values such as: - * - * ```php - * array( - * array( - * 'slug' => 'unique-name-within-the-set', - * 'name' => 'Name for the UI', - * => 'value' - * ), - * ) - * ``` - * - * This contains the necessary metadata to process them: - * - * - path => Where to find the preset within the settings section. - * - prevent_override => Disables override of default presets by theme presets. - * The relationship between whether to override the defaults - * and whether the defaults are enabled is inverse: - * - If defaults are enabled => theme presets should not be overriden - * - If defaults are disabled => theme presets should be overriden - * For example, a theme sets defaultPalette to false, - * making the default palette hidden from the user. - * In that case, we want all the theme presets to be present, - * so they should override the defaults by setting this false. - * - use_default_names => whether to use the default names - * - value_key => the key that represents the value - * - value_func => optionally, instead of value_key, a function to generate - * the value that takes a preset as an argument - * (either value_key or value_func should be present) - * - css_vars => template string to use in generating the CSS Custom Property. - * Example output: "--wp--preset--duotone--blue: " will generate as many CSS Custom Properties as presets defined - * substituting the $slug for the slug's value for each preset value. - * - classes => array containing a structure with the classes to - * generate for the presets, where for each array item - * the key is the class name and the value the property name. - * The "$slug" substring will be replaced by the slug of each preset. - * For example: - * 'classes' => array( - * '.has-$slug-color' => 'color', - * '.has-$slug-background-color' => 'background-color', - * '.has-$slug-border-color' => 'border-color', - * ) - * - properties => array of CSS properties to be used by kses to - * validate the content of each preset - * by means of the remove_insecure_properties method. - */ - const PRESETS_METADATA = array( - array( - 'path' => array( 'color', 'palette' ), - 'prevent_override' => array( 'color', 'defaultPalette' ), - 'use_default_names' => false, - 'value_key' => 'color', - 'css_vars' => '--wp--preset--color--$slug', - 'classes' => array( - '.has-$slug-color' => 'color', - '.has-$slug-background-color' => 'background-color', - '.has-$slug-border-color' => 'border-color', - ), - 'properties' => array( 'color', 'background-color', 'border-color' ), - ), - array( - 'path' => array( 'color', 'gradients' ), - 'prevent_override' => array( 'color', 'defaultGradients' ), - 'use_default_names' => false, - 'value_key' => 'gradient', - 'css_vars' => '--wp--preset--gradient--$slug', - 'classes' => array( '.has-$slug-gradient-background' => 'background' ), - 'properties' => array( 'background' ), - ), - array( - 'path' => array( 'color', 'duotone' ), - 'prevent_override' => array( 'color', 'defaultDuotone' ), - 'use_default_names' => false, - 'value_func' => 'gutenberg_get_duotone_filter_property', - 'css_vars' => '--wp--preset--duotone--$slug', - 'classes' => array(), - 'properties' => array( 'filter' ), - ), - array( - 'path' => array( 'typography', 'fontSizes' ), - 'prevent_override' => false, - 'use_default_names' => true, - 'value_key' => 'size', - 'css_vars' => '--wp--preset--font-size--$slug', - 'classes' => array( '.has-$slug-font-size' => 'font-size' ), - 'properties' => array( 'font-size' ), - ), - array( - 'path' => array( 'typography', 'fontFamilies' ), - 'prevent_override' => false, - 'use_default_names' => false, - 'value_key' => 'fontFamily', - 'css_vars' => '--wp--preset--font-family--$slug', - 'classes' => array( '.has-$slug-font-family' => 'font-family' ), - 'properties' => array( 'font-family' ), - ), - ); - - /** - * The top-level keys a theme.json can have. - * - * @var string[] - */ - const VALID_TOP_LEVEL_KEYS = array( - 'customTemplates', - 'patterns', - 'settings', - 'styles', - 'templateParts', - 'version', - 'title', - ); - - const APPEARANCE_TOOLS_OPT_INS = array( - array( 'border', 'color' ), - array( 'border', 'radius' ), - array( 'border', 'style' ), - array( 'border', 'width' ), - array( 'color', 'link' ), - array( 'spacing', 'blockGap' ), - array( 'spacing', 'margin' ), - array( 'spacing', 'padding' ), - array( 'typography', 'lineHeight' ), - ); - - /** - * The valid properties under the settings key. - * - * @var array - */ - const VALID_SETTINGS = array( - 'appearanceTools' => null, - 'border' => array( - 'color' => null, - 'radius' => null, - 'style' => null, - 'width' => null, - ), - 'color' => array( - 'background' => null, - 'custom' => null, - 'customDuotone' => null, - 'customGradient' => null, - 'defaultDuotone' => null, - 'defaultGradients' => null, - 'defaultPalette' => null, - 'duotone' => null, - 'gradients' => null, - 'link' => null, - 'palette' => null, - 'text' => null, - ), - 'custom' => null, - 'layout' => array( - 'contentSize' => null, - 'wideSize' => null, - ), - 'spacing' => array( - 'blockGap' => null, - 'margin' => null, - 'padding' => null, - 'units' => null, - ), - 'typography' => array( - 'customFontSize' => null, - 'dropCap' => null, - 'fontFamilies' => null, - 'fontSizes' => null, - 'fontStyle' => null, - 'fontWeight' => null, - 'letterSpacing' => null, - 'lineHeight' => null, - 'textDecoration' => null, - 'textTransform' => null, - ), - ); - - /** - * The valid properties under the styles key. - * - * @var array - */ - const VALID_STYLES = array( - 'border' => array( - 'color' => null, - 'radius' => null, - 'style' => null, - 'width' => null, - 'top' => null, - 'right' => null, - 'bottom' => null, - 'left' => null, - ), - 'color' => array( - 'background' => null, - 'gradient' => null, - 'text' => null, - ), - 'filter' => array( - 'duotone' => null, - ), - 'spacing' => array( - 'margin' => null, - 'padding' => null, - 'blockGap' => 'top', - ), - 'typography' => array( - 'fontFamily' => null, - 'fontSize' => null, - 'fontStyle' => null, - 'fontWeight' => null, - 'letterSpacing' => null, - 'lineHeight' => null, - 'textDecoration' => null, - 'textTransform' => null, - ), - ); - - /** - * Returns the current theme's wanted patterns(slugs) to be - * registered from Pattern Directory. - * - * @return array - */ - public function get_patterns() { - if ( isset( $this->theme_json['patterns'] ) && is_array( $this->theme_json['patterns'] ) ) { - return $this->theme_json['patterns']; - } - return array(); - } - - /** - * Converts each style section into a list of rulesets - * containing the block styles to be appended to the stylesheet. - * - * See glossary at https://developer.mozilla.org/en-US/docs/Web/CSS/Syntax - * - * For each section this creates a new ruleset such as: - * - * block-selector { - * style-property-one: value; - * } - * - * @param array $style_nodes Nodes with styles. - * @return string The new stylesheet. - */ - protected function get_block_classes( $style_nodes ) { - $block_rules = ''; - - foreach ( $style_nodes as $metadata ) { - if ( null === $metadata['selector'] ) { - continue; - } - - $node = _wp_array_get( $this->theme_json, $metadata['path'], array() ); - $selector = $metadata['selector']; - $settings = _wp_array_get( $this->theme_json, array( 'settings' ) ); - $declarations = static::compute_style_properties( $node, $settings ); - - // 1. Separate the ones who use the general selector - // and the ones who use the duotone selector. - $declarations_duotone = array(); - foreach ( $declarations as $index => $declaration ) { - if ( 'filter' === $declaration['name'] ) { - unset( $declarations[ $index ] ); - $declarations_duotone[] = $declaration; - } - } - - /* - * Reset default browser margin on the root body element. - * This is set on the root selector **before** generating the ruleset - * from the `theme.json`. This is to ensure that if the `theme.json` declares - * `margin` in its `spacing` declaration for the `body` element then these - * user-generated values take precedence in the CSS cascade. - * @link https://github.com/WordPress/gutenberg/issues/36147. - */ - if ( static::ROOT_BLOCK_SELECTOR === $selector ) { - $block_rules .= 'body { margin: 0; }'; - } - - // 2. Generate the rules that use the general selector. - $block_rules .= static::to_ruleset( $selector, $declarations ); - - // 3. Generate the rules that use the duotone selector. - if ( isset( $metadata['duotone'] ) && ! empty( $declarations_duotone ) ) { - $selector_duotone = static::scope_selector( $metadata['selector'], $metadata['duotone'] ); - $block_rules .= static::to_ruleset( $selector_duotone, $declarations_duotone ); - } - - if ( static::ROOT_BLOCK_SELECTOR === $selector ) { - $block_rules .= '.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }'; - $block_rules .= '.wp-site-blocks > .alignright { float: right; margin-left: 2em; }'; - $block_rules .= '.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }'; - - $has_block_gap_support = _wp_array_get( $this->theme_json, array( 'settings', 'spacing', 'blockGap' ) ) !== null; - if ( $has_block_gap_support ) { - $block_rules .= '.wp-site-blocks > * { margin-block-start: 0; margin-block-end: 0; }'; - $block_rules .= '.wp-site-blocks > * + * { margin-block-start: var( --wp--style--block-gap ); }'; - } - } - } - - return $block_rules; - } - - /** - * Merge new incoming data. - * - * @param WP_Theme_JSON $incoming Data to merge. - */ - public function merge( $incoming ) { - $incoming_data = $incoming->get_raw_data(); - $this->theme_json = array_replace_recursive( $this->theme_json, $incoming_data ); - - /* - * The array_replace_recursive algorithm merges at the leaf level, - * but we don't want leaf arrays to be merged, so we overwrite it. - * - * For leaf values that are sequential arrays it will use the numeric indexes for replacement. - * We rather replace the existing with the incoming value, if it exists. - * This is the case of spacing.units. - * - * For leaf values that are associative arrays it will merge them as expected. - * This is also not the behavior we want for the current associative arrays (presets). - * We rather replace the existing with the incoming value, if it exists. - * This happens, for example, when we merge data from theme.json upon existing - * theme supports or when we merge anything coming from the same source twice. - * This is the case of color.palette, color.gradients, color.duotone, - * typography.fontSizes, or typography.fontFamilies. - * - * Additionally, for some preset types, we also want to make sure the - * values they introduce don't conflict with default values. We do so - * by checking the incoming slugs for theme presets and compare them - * with the equivalent default presets: if a slug is present as a default - * we remove it from the theme presets. - */ - $nodes = static::get_setting_nodes( $incoming_data ); - $slugs_global = static::get_default_slugs( $this->theme_json, array( 'settings' ) ); - foreach ( $nodes as $node ) { - $slugs_node = static::get_default_slugs( $this->theme_json, $node['path'] ); - $slugs = array_merge_recursive( $slugs_global, $slugs_node ); - - // Replace the spacing.units. - $path = array_merge( $node['path'], array( 'spacing', 'units' ) ); - $content = _wp_array_get( $incoming_data, $path, null ); - if ( isset( $content ) ) { - _wp_array_set( $this->theme_json, $path, $content ); - } - - // Replace the presets. - foreach ( static::PRESETS_METADATA as $preset ) { - $override_preset = ! static::get_metadata_boolean( $this->theme_json['settings'], $preset['prevent_override'], true ); - - foreach ( static::VALID_ORIGINS as $origin ) { - $base_path = array_merge( $node['path'], $preset['path'] ); - $path = array_merge( $base_path, array( $origin ) ); - $content = _wp_array_get( $incoming_data, $path, null ); - if ( ! isset( $content ) ) { - continue; - } - - if ( 'theme' === $origin && $preset['use_default_names'] ) { - foreach ( $content as &$item ) { - if ( ! array_key_exists( 'name', $item ) ) { - $name = static::get_name_from_defaults( $item['slug'], $base_path ); - if ( null !== $name ) { - $item['name'] = $name; - } - } - } - } - - if ( - ( 'theme' !== $origin ) || - ( 'theme' === $origin && $override_preset ) - ) { - _wp_array_set( $this->theme_json, $path, $content ); - } else { - $slugs_for_preset = _wp_array_get( $slugs, $preset['path'], array() ); - $content = static::filter_slugs( $content, $slugs_for_preset ); - _wp_array_set( $this->theme_json, $path, $content ); - } - } - } - } - } - - /** - * Converts all filter (duotone) presets into SVGs. - * - * @param array $origins List of origins to process. - * - * @return string SVG filters. - */ - public function get_svg_filters( $origins ) { - $blocks_metadata = static::get_blocks_metadata(); - $setting_nodes = static::get_setting_nodes( $this->theme_json, $blocks_metadata ); - - $filters = ''; - foreach ( $setting_nodes as $metadata ) { - $node = _wp_array_get( $this->theme_json, $metadata['path'], array() ); - if ( empty( $node['color']['duotone'] ) ) { - continue; - } - - $duotone_presets = $node['color']['duotone']; - - foreach ( $origins as $origin ) { - if ( ! isset( $duotone_presets[ $origin ] ) ) { - continue; - } - foreach ( $duotone_presets[ $origin ] as $duotone_preset ) { - $filters .= gutenberg_get_duotone_filter_svg( $duotone_preset ); - } - } - } - - return $filters; - } - - /** - * For metadata values that can either be booleans or paths to booleans, gets the value. - * - * ```php - * $data = array( - * 'color' => array( - * 'defaultPalette' => true - * ) - * ); - * - * static::get_metadata_boolean( $data, false ); - * // => false - * - * static::get_metadata_boolean( $data, array( 'color', 'defaultPalette' ) ); - * // => true - * ``` - * - * @param array $data The data to inspect. - * @param bool|array $path Boolean or path to a boolean. - * @param bool $default Default value if the referenced path is missing. - * @return boolean - */ - protected static function get_metadata_boolean( $data, $path, $default = false ) { - if ( is_bool( $path ) ) { - return $path; - } - - if ( is_array( $path ) ) { - $value = _wp_array_get( $data, $path ); - if ( null !== $value ) { - return $value; - } - } - - return $default; - } - - /** - * Returns a valid theme.json as provided by a theme. - * - * Unlike get_raw_data() this returns the presets flattened, as provided by a theme. - * This also uses appearanceTools instead of their opt-ins if all of them are true. - * - * @return string[] - */ - public function get_data() { - $output = $this->theme_json; - $nodes = static::get_setting_nodes( $output ); - - /** - * Flatten the theme & custom origins into a single one. - * - * For example, the following: - * - * { - * "settings": { - * "color": { - * "palette": { - * "theme": [ {} ], - * "custom": [ {} ] - * } - * } - * } - * } - * - * will be converted to: - * - * { - * "settings": { - * "color": { - * "palette": [ {} ] - * } - * } - * } - */ - foreach ( $nodes as $node ) { - foreach ( static::PRESETS_METADATA as $preset_metadata ) { - $path = array_merge( $node['path'], $preset_metadata['path'] ); - $preset = _wp_array_get( $output, $path, null ); - if ( null === $preset ) { - continue; - } - - $items = array(); - if ( isset( $preset['theme'] ) ) { - foreach ( $preset['theme'] as $item ) { - $slug = $item['slug']; - unset( $item['slug'] ); - $items[ $slug ] = $item; - } - } - if ( isset( $preset['custom'] ) ) { - foreach ( $preset['custom'] as $item ) { - $slug = $item['slug']; - unset( $item['slug'] ); - $items[ $slug ] = $item; - } - } - $flattened_preset = array(); - foreach ( $items as $slug => $value ) { - $flattened_preset[] = array_merge( array( 'slug' => (string) $slug ), $value ); - } - _wp_array_set( $output, $path, $flattened_preset ); - } - } - - // If all of the static::APPEARANCE_TOOLS_OPT_INS are true, - // this code unsets them and sets 'appearanceTools' instead. - foreach ( $nodes as $node ) { - $all_opt_ins_are_set = true; - foreach ( static::APPEARANCE_TOOLS_OPT_INS as $opt_in_path ) { - $full_path = array_merge( $node['path'], $opt_in_path ); - // Use "unset prop" as a marker instead of "null" because - // "null" can be a valid value for some props (e.g. blockGap). - $opt_in_value = _wp_array_get( $output, $full_path, 'unset prop' ); - if ( 'unset prop' === $opt_in_value ) { - $all_opt_ins_are_set = false; - break; - } - } - - if ( $all_opt_ins_are_set ) { - _wp_array_set( $output, array_merge( $node['path'], array( 'appearanceTools' ) ), true ); - foreach ( static::APPEARANCE_TOOLS_OPT_INS as $opt_in_path ) { - $full_path = array_merge( $node['path'], $opt_in_path ); - // Use "unset prop" as a marker instead of "null" because - // "null" can be a valid value for some props (e.g. blockGap). - $opt_in_value = _wp_array_get( $output, $full_path, 'unset prop' ); - if ( true !== $opt_in_value ) { - continue; - } - - // The following could be improved to be path independent. - // At the moment it relies on a couple of assumptions: - // - // - all opt-ins having a path of size 2. - // - there's two sources of settings: the top-level and the block-level. - if ( - ( 1 === count( $node['path'] ) ) && - ( 'settings' === $node['path'][0] ) - ) { - // Top-level settings. - unset( $output['settings'][ $opt_in_path[0] ][ $opt_in_path[1] ] ); - if ( empty( $output['settings'][ $opt_in_path[0] ] ) ) { - unset( $output['settings'][ $opt_in_path[0] ] ); - } - } elseif ( - ( 3 === count( $node['path'] ) ) && - ( 'settings' === $node['path'][0] ) && - ( 'blocks' === $node['path'][1] ) - ) { - // Block-level settings. - $block_name = $node['path'][2]; - unset( $output['settings']['blocks'][ $block_name ][ $opt_in_path[0] ][ $opt_in_path[1] ] ); - if ( empty( $output['settings']['blocks'][ $block_name ][ $opt_in_path[0] ] ) ) { - unset( $output['settings']['blocks'][ $block_name ][ $opt_in_path[0] ] ); - } - } - } - } - } - - wp_recursive_ksort( $output ); - - return $output; - } - - /** - * Enables some settings. - * - * @since 5.9.0 - * - * @param array $context The context to which the settings belong. - */ - protected static function do_opt_in_into_settings( &$context ) { - foreach ( static::APPEARANCE_TOOLS_OPT_INS as $path ) { - // Use "unset prop" as a marker instead of "null" because - // "null" can be a valid value for some props (e.g. blockGap). - if ( 'unset prop' === _wp_array_get( $context, $path, 'unset prop' ) ) { - _wp_array_set( $context, $path, true ); - } - } - - unset( $context['appearanceTools'] ); - } -} diff --git a/lib/compat/wordpress-6.0/class-wp-theme-json-resolver-6-0.php b/lib/compat/wordpress-6.0/class-wp-theme-json-resolver-6-0.php deleted file mode 100644 index 07b83049cfeaad..00000000000000 --- a/lib/compat/wordpress-6.0/class-wp-theme-json-resolver-6-0.php +++ /dev/null @@ -1,223 +0,0 @@ - true ) ); - - if ( null === static::$theme ) { - $theme_json_data = static::read_json_file( static::get_file_path_from_theme( 'theme.json' ) ); - $theme_json_data = static::translate( $theme_json_data, wp_get_theme()->get( 'TextDomain' ) ); - static::$theme = new WP_Theme_JSON_Gutenberg( $theme_json_data ); - - if ( wp_get_theme()->parent() ) { - // Get parent theme.json. - $parent_theme_json_data = static::read_json_file( static::get_file_path_from_theme( 'theme.json', true ) ); - $parent_theme_json_data = static::translate( $parent_theme_json_data, wp_get_theme()->parent()->get( 'TextDomain' ) ); - $parent_theme = new WP_Theme_JSON_Gutenberg( $parent_theme_json_data ); - - // Merge the child theme.json into the parent theme.json. - // The child theme takes precedence over the parent. - $parent_theme->merge( static::$theme ); - static::$theme = $parent_theme; - } - } - - if ( ! $options['with_supports'] ) { - return static::$theme; - } - - /* - * We want the presets and settings declared in theme.json - * to override the ones declared via theme supports. - * So we take theme supports, transform it to theme.json shape - * and merge the static::$theme upon that. - */ - $theme_support_data = WP_Theme_JSON_Gutenberg::get_from_editor_settings( get_default_block_editor_settings() ); - if ( ! wp_theme_has_theme_json() ) { - if ( ! isset( $theme_support_data['settings']['color'] ) ) { - $theme_support_data['settings']['color'] = array(); - } - - $default_palette = false; - if ( current_theme_supports( 'default-color-palette' ) ) { - $default_palette = true; - } - if ( ! isset( $theme_support_data['settings']['color']['palette'] ) ) { - // If the theme does not have any palette, we still want to show the core one. - $default_palette = true; - } - $theme_support_data['settings']['color']['defaultPalette'] = $default_palette; - - $default_gradients = false; - if ( current_theme_supports( 'default-gradient-presets' ) ) { - $default_gradients = true; - } - if ( ! isset( $theme_support_data['settings']['color']['gradients'] ) ) { - // If the theme does not have any gradients, we still want to show the core ones. - $default_gradients = true; - } - $theme_support_data['settings']['color']['defaultGradients'] = $default_gradients; - - // Classic themes without a theme.json don't support global duotone. - $theme_support_data['settings']['color']['defaultDuotone'] = false; - } - $with_theme_supports = new WP_Theme_JSON_Gutenberg( $theme_support_data ); - $with_theme_supports->merge( static::$theme ); - - return $with_theme_supports; - } - /** - * Returns the style variations defined by the theme. - * - * @return array - */ - public static function get_style_variations() { - $variations = array(); - $base_directory = get_stylesheet_directory() . '/styles'; - if ( is_dir( $base_directory ) ) { - $nested_files = new RecursiveIteratorIterator( new RecursiveDirectoryIterator( $base_directory ) ); - $nested_html_files = iterator_to_array( new RegexIterator( $nested_files, '/^.+\.json$/i', RecursiveRegexIterator::GET_MATCH ) ); - ksort( $nested_html_files ); - foreach ( $nested_html_files as $path => $file ) { - $decoded_file = wp_json_file_decode( $path, array( 'associative' => true ) ); - if ( is_array( $decoded_file ) ) { - $translated = static::translate( $decoded_file, wp_get_theme()->get( 'TextDomain' ) ); - $variation = ( new WP_Theme_JSON_Gutenberg( $translated ) )->get_raw_data(); - if ( empty( $variation['title'] ) ) { - $variation['title'] = basename( $path, '.json' ); - } - $variations[] = $variation; - } - } - } - return $variations; - } - - /** - * Returns the user's origin config. - * - * @return WP_Theme_JSON_Gutenberg Entity that holds styles for user data. - */ - public static function get_user_data() { - if ( null !== static::$user ) { - return static::$user; - } - - $config = array(); - $user_cpt = static::get_user_data_from_wp_global_styles( wp_get_theme() ); - - if ( array_key_exists( 'post_content', $user_cpt ) ) { - $decoded_data = json_decode( $user_cpt['post_content'], true ); - - $json_decoding_error = json_last_error(); - if ( JSON_ERROR_NONE !== $json_decoding_error ) { - trigger_error( 'Error when decoding a theme.json schema for user data. ' . json_last_error_msg() ); - return new WP_Theme_JSON_Gutenberg( $config, 'custom' ); - } - - // Very important to verify if the flag isGlobalStylesUserThemeJSON is true. - // If is not true the content was not escaped and is not safe. - if ( - is_array( $decoded_data ) && - isset( $decoded_data['isGlobalStylesUserThemeJSON'] ) && - $decoded_data['isGlobalStylesUserThemeJSON'] - ) { - unset( $decoded_data['isGlobalStylesUserThemeJSON'] ); - $config = $decoded_data; - } - } - static::$user = new WP_Theme_JSON_Gutenberg( $config, 'custom' ); - - return static::$user; - } - - /** - * There are three sources of data (origins) for a site: - * default, theme, and custom. The custom's has higher priority - * than the theme's, and the theme's higher than defaults's. - * - * Unlike the getters {@link get_core_data}, - * {@link get_theme_data}, and {@link get_user_data}, - * this method returns data after it has been merged - * with the previous origins. This means that if the same piece of data - * is declared in different origins (user, theme, and core), - * the last origin overrides the previous. - * - * For example, if the user has set a background color - * for the paragraph block, and the theme has done it as well, - * the user preference wins. - * - * @param string $origin Optional. To what level should we merge data. - * Valid values are 'theme' or 'custom'. - * Default is 'custom'. - * @return WP_Theme_JSON_Gutenberg - */ - public static function get_merged_data( $origin = 'custom' ) { - if ( is_array( $origin ) ) { - _deprecated_argument( __FUNCTION__, '5.9' ); - } - - $result = new WP_Theme_JSON_Gutenberg(); - $result->merge( static::get_core_data() ); - $result->merge( static::get_theme_data() ); - - if ( 'custom' === $origin ) { - $result->merge( static::get_user_data() ); - } - - return $result; - } -} diff --git a/lib/compat/wordpress-6.0/client-assets.php b/lib/compat/wordpress-6.0/client-assets.php deleted file mode 100644 index f9affb31af4b52..00000000000000 --- a/lib/compat/wordpress-6.0/client-assets.php +++ /dev/null @@ -1,117 +0,0 @@ -get_all_registered() as $block_type ) { - if ( ! empty( $block_type->style ) ) { - $style_handles[] = $block_type->style; - } - - if ( ! empty( $block_type->editor_style ) ) { - $style_handles[] = $block_type->editor_style; - } - - if ( ! empty( $block_type->script ) ) { - $script_handles[] = $block_type->script; - } - } - - $style_handles = array_unique( $style_handles ); - $done = wp_styles()->done; - - ob_start(); - - // We do not need reset styles for the iframed editor. - wp_styles()->done = array( 'wp-reset-editor-styles' ); - wp_styles()->do_items( $style_handles ); - wp_styles()->done = $done; - - $styles = ob_get_clean(); - - $script_handles = array_unique( $script_handles ); - $done = wp_scripts()->done; - - ob_start(); - - wp_scripts()->done = array(); - wp_scripts()->do_items( $script_handles ); - wp_scripts()->done = $done; - - $scripts = ob_get_clean(); - - return array( - 'styles' => $styles, - 'scripts' => $scripts, - ); -} - -add_filter( - 'block_editor_settings_all', - function( $settings ) { - // The `__unstableResolvedAssets` are generated by `_wp_get_iframed_editor_assets` for WP >= 6.0. - if ( function_exists( '_wp_get_iframed_editor_assets' ) ) { - return $settings; - } - - // In the future we can allow WP Dependency handles to be passed. - $settings['__unstableResolvedAssets'] = gutenberg_resolve_assets(); - return $settings; - } -); diff --git a/lib/compat/wordpress-6.0/edit-form-blocks.php b/lib/compat/wordpress-6.0/edit-form-blocks.php deleted file mode 100644 index b11851222b0baf..00000000000000 --- a/lib/compat/wordpress-6.0/edit-form-blocks.php +++ /dev/null @@ -1,66 +0,0 @@ - $user_path ) { - if ( is_string( $user_path ) && str_starts_with( $user_path, '/wp/v2/users/me' ) ) { - $preload_paths[ $user_index ] = '/wp/v2/users/me'; - break; - } - } - - return $preload_paths; -} -add_filter( 'block_editor_rest_api_preload_paths', 'gutenberg_optimize_preload_paths' ); - -/** - * Disables loading remote block patterns from REST while initializing the editor. - * Nowadays these loads are done in the `block-patterns/patterns` REST endpoint, and - * are undesired when initializing the block editor page, both in post and site editor. - * - * @param WP_Screen $current_screen WordPress current screen object. - */ -function gutenberg_disable_load_remote_patterns( $current_screen ) { - $is_site_editor = ( function_exists( 'gutenberg_is_edit_site_page' ) && gutenberg_is_edit_site_page( $current_screen->id ) ); - if ( $is_site_editor || $current_screen->is_block_editor() ) { - add_filter( 'should_load_remote_block_patterns', '__return_false' ); - } -} -add_action( 'current_screen', 'gutenberg_disable_load_remote_patterns' ); diff --git a/lib/compat/wordpress-6.0/functions.php b/lib/compat/wordpress-6.0/functions.php deleted file mode 100644 index 9368b211b05990..00000000000000 --- a/lib/compat/wordpress-6.0/functions.php +++ /dev/null @@ -1,27 +0,0 @@ -get_settings(); - return _wp_array_get( $settings, $path, $settings ); -} - -/** - * Function to get the styles resulting of merging core, theme, and user data. - * - * @param array $path Path to the specific style to retrieve. Optional. - * If empty, will return all styles. - * @param array $context { - * Metadata to know where to retrieve the $path from. Optional. - * - * @type string $block_name Which block to retrieve the styles from. - * If empty, it'll return the styles for the global context. - * @type string $origin Which origin to take data from. - * Valid values are 'all' (core, theme, and user) or 'base' (core and theme). - * If empty or unknown, 'all' is used. - * } - * - * @return array The styles to retrieve. - */ -function gutenberg_get_global_styles( $path = array(), $context = array() ) { - if ( ! empty( $context['block_name'] ) ) { - $path = array_merge( array( 'blocks', $context['block_name'] ), $path ); - } - $origin = 'custom'; - if ( isset( $context['origin'] ) && 'base' === $context['origin'] ) { - $origin = 'theme'; - } - $styles = WP_Theme_JSON_Resolver_Gutenberg::get_merged_data( $origin )->get_raw_data()['styles']; - return _wp_array_get( $styles, $path, $styles ); -} - -/** - * Returns a string containing the SVGs to be referenced as filters (duotone). - * - * @return string - */ -function gutenberg_get_global_styles_svg_filters() { - // Return cached value if it can be used and exists. - // It's cached by theme to make sure that theme switching clears the cache. - $transient_name = 'gutenberg_global_styles_svg_filters_' . get_stylesheet(); - $can_use_cached = ( - ( ! defined( 'WP_DEBUG' ) || ! WP_DEBUG ) && - ( ! defined( 'SCRIPT_DEBUG' ) || ! SCRIPT_DEBUG ) && - ( ! defined( 'REST_REQUEST' ) || ! REST_REQUEST ) && - ! is_admin() - ); - if ( $can_use_cached ) { - $cached = get_transient( $transient_name ); - if ( $cached ) { - return $cached; - } - } - - $supports_theme_json = wp_theme_has_theme_json(); - - $origins = array( 'default', 'theme', 'custom' ); - if ( ! $supports_theme_json ) { - $origins = array( 'default' ); - } - - $tree = WP_Theme_JSON_Resolver_Gutenberg::get_merged_data(); - $svgs = $tree->get_svg_filters( $origins ); - - if ( $can_use_cached ) { - // Cache for a minute, same as gutenberg_get_global_stylesheet. - set_transient( $transient_name, $svgs, MINUTE_IN_SECONDS ); - } - - return $svgs; -} diff --git a/lib/compat/wordpress-6.0/post-lock.php b/lib/compat/wordpress-6.0/post-lock.php deleted file mode 100644 index e36cdff3e7a221..00000000000000 --- a/lib/compat/wordpress-6.0/post-lock.php +++ /dev/null @@ -1,67 +0,0 @@ -display_name; - } - } - - return $response; -} -add_filter( 'heartbeat_received', 'gutenberg_refresh_post_lock', 20, 2 ); - -/** - * Updates post editor settings and adds avatar to the `postLock` user details. - * - * @param array $settings Default editor settings. - * @param WP_Block_Editor_Context $block_editor_context The current block editor context. - * - * @return array Filtered editor settings. - */ -function gutenberg_update_post_lock_details( $settings, $block_editor_context ) { - if ( empty( $block_editor_context->post ) ) { - return $settings; - } - - if ( ! isset( $settings['postLock']['user'] ) ) { - return $settings; - } - - $user_id = wp_check_post_lock( $block_editor_context->post->ID ); - if ( $user_id ) { - $settings['postLock']['user']['avatar'] = get_avatar_url( $user_id, array( 'size' => 128 ) ); - } - - return $settings; -} -add_filter( 'block_editor_settings_all', 'gutenberg_update_post_lock_details', 10, 2 ); diff --git a/lib/compat/wordpress-6.0/render-svg-filters.php b/lib/compat/wordpress-6.0/render-svg-filters.php deleted file mode 100644 index f13502b867fe95..00000000000000 --- a/lib/compat/wordpress-6.0/render-svg-filters.php +++ /dev/null @@ -1,38 +0,0 @@ -is_block_editor() - ) { - return; - } - - $filters = gutenberg_get_global_styles_svg_filters(); - if ( ! empty( $filters ) ) { - echo $filters; - } -} - -// Override actions introduced in 5.9.1 if they exist. -remove_action( 'wp_body_open', 'wp_global_styles_render_svg_filters' ); -remove_action( 'in_admin_header', 'wp_global_styles_render_svg_filters' ); -add_action( 'wp_body_open', 'gutenberg_global_styles_render_svg_filters' ); -add_action( 'in_admin_header', 'gutenberg_global_styles_render_svg_filters' ); diff --git a/lib/compat/wordpress-6.0/rest-api.php b/lib/compat/wordpress-6.0/rest-api.php deleted file mode 100644 index 0fa1852b18813b..00000000000000 --- a/lib/compat/wordpress-6.0/rest-api.php +++ /dev/null @@ -1,65 +0,0 @@ -register_routes(); -} -add_action( 'rest_api_init', 'gutenberg_register_global_styles_endpoints' ); - -/** - * Registers the Edit Site's Export REST API routes. - * - * @return void - */ -function gutenberg_register_edit_site_export_endpoint() { - $editor_settings = new Gutenberg_REST_Edit_Site_Export_Controller(); - $editor_settings->register_routes(); -} -add_action( 'rest_api_init', 'gutenberg_register_edit_site_export_endpoint' ); - - -/** - * Register a core site settings. - * - * Note: Needs to be backported into the `register_initial_settings` function. - */ -function gutenberg_register_site_settings() { - register_setting( - 'reading', - 'show_on_front', - array( - 'show_in_rest' => true, - 'type' => 'string', - 'description' => __( 'What to show on the front page', 'gutenberg' ), - ) - ); - - register_setting( - 'reading', - 'page_on_front', - array( - 'show_in_rest' => true, - 'type' => 'number', - 'description' => __( 'The ID of the page that should be displayed on the front page', 'gutenberg' ), - ) - ); - - register_setting( - 'reading', - 'page_for_posts', - array( - 'show_in_rest' => true, - 'type' => 'number', - 'description' => __( 'The ID of the page that should display the latest posts', 'gutenberg' ), - ) - ); -} -add_action( 'rest_api_init', 'gutenberg_register_site_settings', 10 ); diff --git a/lib/compat/wordpress-6.0/site-editor.php b/lib/compat/wordpress-6.0/site-editor.php deleted file mode 100644 index 081657d0061fe9..00000000000000 --- a/lib/compat/wordpress-6.0/site-editor.php +++ /dev/null @@ -1,95 +0,0 @@ - 'page', - 'postId' => $front_page_id, - ); - } - - $hierarchy = array( 'front-page', 'home', 'index' ); - $template = resolve_block_template( 'home', $hierarchy, '' ); - - if ( ! $template ) { - return null; - } - - return array( - 'postType' => 'wp_template', - 'postId' => $template->id, - ); -} - -/** - * Do a server-side redirection if missing `postType` and `postId` - * query args when visiting site editor. - * - * Note: This is a backward compatibility redirect for WP 5.9. - * - * @return void - */ -function gutenberg_site_editor_maybe_redirect() { - // Skip redirection for WP 6.0 and later. - if ( function_exists( '_resolve_home_block_template' ) ) { - return; - } - - // Check theme support. The action runs before checks in the Site Editor. - if ( ! wp_is_block_theme() ) { - return; - } - - if ( empty( $_GET['postType'] ) && empty( $_GET['postId'] ) ) { - $template = gutenberg_resolve_home_template(); - if ( ! empty( $_GET['styles'] ) ) { - $template['styles'] = sanitize_key( $_GET['styles'] ); - } - - $redirect_url = add_query_arg( - $template, - admin_url( 'site-editor.php' ) - ); - wp_safe_redirect( $redirect_url ); - exit; - } -} -add_action( 'load-site-editor.php', 'gutenberg_site_editor_maybe_redirect' ); - -/** - * Add home template settings for WP 5.9. - * - * @param array $settings Existing block editor settings. - * @param WP_Block_Editor_Context $context The current block editor context. - * @return array - */ -function gutenberg_site_editor_homepage_setting( $settings, $context ) { - if ( isset( $context->post ) ) { - return $settings; - } - - if ( ! isset( $settings['__unstableHomeTemplate'] ) ) { - $settings['__unstableHomeTemplate'] = gutenberg_resolve_home_template(); - } - - return $settings; -} -add_filter( 'block_editor_settings_all', 'gutenberg_site_editor_homepage_setting', 10, 2 ); diff --git a/lib/compat/wordpress-6.0/theme-i18n.json b/lib/compat/wordpress-6.0/theme-i18n.json deleted file mode 100644 index 282e520c338b42..00000000000000 --- a/lib/compat/wordpress-6.0/theme-i18n.json +++ /dev/null @@ -1,72 +0,0 @@ -{ - "title": "Style variation name", - "settings": { - "typography": { - "fontSizes": [ - { - "name": "Font size name" - } - ], - "fontFamilies": [ - { - "name": "Font family name" - } - ] - }, - "color": { - "palette": [ - { - "name": "Color name" - } - ], - "gradients": [ - { - "name": "Gradient name" - } - ], - "duotone": [ - { - "name": "Duotone name" - } - ] - }, - "blocks": { - "*": { - "typography": { - "fontSizes": [ - { - "name": "Font size name" - } - ], - "fontFamilies": [ - { - "name": "Font family name" - } - ] - }, - "color": { - "palette": [ - { - "name": "Color name" - } - ], - "gradients": [ - { - "name": "Gradient name" - } - ] - } - } - } - }, - "customTemplates": [ - { - "title": "Custom template name" - } - ], - "templateParts": [ - { - "title": "Template part name" - } - ] -} diff --git a/lib/compat/wordpress-6.0/theme.json b/lib/compat/wordpress-6.0/theme.json deleted file mode 100644 index 7691aa4a64e6a9..00000000000000 --- a/lib/compat/wordpress-6.0/theme.json +++ /dev/null @@ -1,245 +0,0 @@ -{ - "version": 2, - "settings": { - "appearanceTools": false, - "border": { - "color": false, - "radius": false, - "style": false, - "width": false - }, - "color": { - "background": true, - "custom": true, - "customDuotone": true, - "customGradient": true, - "defaultDuotone": true, - "defaultGradients": true, - "defaultPalette": true, - "duotone": [ - { - "name": "Dark grayscale", - "colors": [ "#000000", "#7f7f7f" ], - "slug": "dark-grayscale" - }, - { - "name": "Grayscale", - "colors": [ "#000000", "#ffffff" ], - "slug": "grayscale" - }, - { - "name": "Purple and yellow", - "colors": [ "#8c00b7", "#fcff41" ], - "slug": "purple-yellow" - }, - { - "name": "Blue and red", - "colors": [ "#000097", "#ff4747" ], - "slug": "blue-red" - }, - { - "name": "Midnight", - "colors": [ "#000000", "#00a5ff" ], - "slug": "midnight" - }, - { - "name": "Magenta and yellow", - "colors": [ "#c7005a", "#fff278" ], - "slug": "magenta-yellow" - }, - { - "name": "Purple and green", - "colors": [ "#a60072", "#67ff66" ], - "slug": "purple-green" - }, - { - "name": "Blue and orange", - "colors": [ "#1900d8", "#ffa96b" ], - "slug": "blue-orange" - } - ], - "gradients": [ - { - "name": "Vivid cyan blue to vivid purple", - "gradient": "linear-gradient(135deg,rgba(6,147,227,1) 0%,rgb(155,81,224) 100%)", - "slug": "vivid-cyan-blue-to-vivid-purple" - }, - { - "name": "Light green cyan to vivid green cyan", - "gradient": "linear-gradient(135deg,rgb(122,220,180) 0%,rgb(0,208,130) 100%)", - "slug": "light-green-cyan-to-vivid-green-cyan" - }, - { - "name": "Luminous vivid amber to luminous vivid orange", - "gradient": "linear-gradient(135deg,rgba(252,185,0,1) 0%,rgba(255,105,0,1) 100%)", - "slug": "luminous-vivid-amber-to-luminous-vivid-orange" - }, - { - "name": "Luminous vivid orange to vivid red", - "gradient": "linear-gradient(135deg,rgba(255,105,0,1) 0%,rgb(207,46,46) 100%)", - "slug": "luminous-vivid-orange-to-vivid-red" - }, - { - "name": "Very light gray to cyan bluish gray", - "gradient": "linear-gradient(135deg,rgb(238,238,238) 0%,rgb(169,184,195) 100%)", - "slug": "very-light-gray-to-cyan-bluish-gray" - }, - { - "name": "Cool to warm spectrum", - "gradient": "linear-gradient(135deg,rgb(74,234,220) 0%,rgb(151,120,209) 20%,rgb(207,42,186) 40%,rgb(238,44,130) 60%,rgb(251,105,98) 80%,rgb(254,248,76) 100%)", - "slug": "cool-to-warm-spectrum" - }, - { - "name": "Blush light purple", - "gradient": "linear-gradient(135deg,rgb(255,206,236) 0%,rgb(152,150,240) 100%)", - "slug": "blush-light-purple" - }, - { - "name": "Blush bordeaux", - "gradient": "linear-gradient(135deg,rgb(254,205,165) 0%,rgb(254,45,45) 50%,rgb(107,0,62) 100%)", - "slug": "blush-bordeaux" - }, - { - "name": "Luminous dusk", - "gradient": "linear-gradient(135deg,rgb(255,203,112) 0%,rgb(199,81,192) 50%,rgb(65,88,208) 100%)", - "slug": "luminous-dusk" - }, - { - "name": "Pale ocean", - "gradient": "linear-gradient(135deg,rgb(255,245,203) 0%,rgb(182,227,212) 50%,rgb(51,167,181) 100%)", - "slug": "pale-ocean" - }, - { - "name": "Electric grass", - "gradient": "linear-gradient(135deg,rgb(202,248,128) 0%,rgb(113,206,126) 100%)", - "slug": "electric-grass" - }, - { - "name": "Midnight", - "gradient": "linear-gradient(135deg,rgb(2,3,129) 0%,rgb(40,116,252) 100%)", - "slug": "midnight" - } - ], - "link": false, - "palette": [ - { - "name": "Black", - "slug": "black", - "color": "#000000" - }, - { - "name": "Cyan bluish gray", - "slug": "cyan-bluish-gray", - "color": "#abb8c3" - }, - { - "name": "White", - "slug": "white", - "color": "#ffffff" - }, - { - "name": "Pale pink", - "slug": "pale-pink", - "color": "#f78da7" - }, - { - "name": "Vivid red", - "slug": "vivid-red", - "color": "#cf2e2e" - }, - { - "name": "Luminous vivid orange", - "slug": "luminous-vivid-orange", - "color": "#ff6900" - }, - { - "name": "Luminous vivid amber", - "slug": "luminous-vivid-amber", - "color": "#fcb900" - }, - { - "name": "Light green cyan", - "slug": "light-green-cyan", - "color": "#7bdcb5" - }, - { - "name": "Vivid green cyan", - "slug": "vivid-green-cyan", - "color": "#00d084" - }, - { - "name": "Pale cyan blue", - "slug": "pale-cyan-blue", - "color": "#8ed1fc" - }, - { - "name": "Vivid cyan blue", - "slug": "vivid-cyan-blue", - "color": "#0693e3" - }, - { - "name": "Vivid purple", - "slug": "vivid-purple", - "color": "#9b51e0" - } - ], - "text": true - }, - "spacing": { - "blockGap": null, - "margin": false, - "padding": false, - "units": [ "px", "em", "rem", "vh", "vw", "%" ] - }, - "typography": { - "customFontSize": true, - "dropCap": true, - "fontSizes": [ - { - "name": "Small", - "slug": "small", - "size": "13px" - }, - { - "name": "Medium", - "slug": "medium", - "size": "20px" - }, - { - "name": "Large", - "slug": "large", - "size": "36px" - }, - { - "name": "Extra Large", - "slug": "x-large", - "size": "42px" - } - ], - "fontStyle": true, - "fontWeight": true, - "letterSpacing": true, - "lineHeight": false, - "textDecoration": true, - "textTransform": true - }, - "blocks": { - "core/button": { - "border": { - "radius": true - } - }, - "core/pullquote": { - "border": { - "color": true, - "radius": true, - "style": true, - "width": true - } - } - } - }, - "styles": { - "spacing": { "blockGap": "24px" } - } -} diff --git a/lib/compat/wordpress-6.1/block-patterns.php b/lib/compat/wordpress-6.1/block-patterns.php deleted file mode 100644 index 7860aa6a1dd661..00000000000000 --- a/lib/compat/wordpress-6.1/block-patterns.php +++ /dev/null @@ -1,177 +0,0 @@ -

- * - * If applicable, this will collect from both parent and child theme. - * - * Other settable fields include: - * - * - Description - * - Viewport Width - * - Categories (comma-separated values) - * - Keywords (comma-separated values) - * - Block Types (comma-separated values) - * - Post Types (comma-separated values) - * - Inserter (yes/no) - * - * @since 6.0.0 - * @access private - */ -function gutenberg_register_theme_block_patterns() { - $default_headers = array( - 'title' => 'Title', - 'slug' => 'Slug', - 'description' => 'Description', - 'viewportWidth' => 'Viewport Width', - 'categories' => 'Categories', - 'keywords' => 'Keywords', - 'blockTypes' => 'Block Types', - 'postTypes' => 'Post Types', - 'inserter' => 'Inserter', - ); - - /* - * Register patterns for the active theme. If the theme is a child theme, - * let it override any patterns from the parent theme that shares the same slug. - */ - $themes = array(); - $stylesheet = get_stylesheet(); - $template = get_template(); - if ( $stylesheet !== $template ) { - $themes[] = wp_get_theme( $stylesheet ); - } - $themes[] = wp_get_theme( $template ); - - foreach ( $themes as $theme ) { - $dirpath = $theme->get_stylesheet_directory() . '/patterns/'; - if ( ! is_dir( $dirpath ) || ! is_readable( $dirpath ) ) { - continue; - } - if ( file_exists( $dirpath ) ) { - $files = glob( $dirpath . '*.php' ); - if ( $files ) { - foreach ( $files as $file ) { - $pattern_data = get_file_data( $file, $default_headers ); - - if ( empty( $pattern_data['slug'] ) ) { - _doing_it_wrong( - '_register_theme_block_patterns', - sprintf( - /* translators: %s: file name. */ - __( 'Could not register file "%s" as a block pattern ("Slug" field missing)', 'gutenberg' ), - $file - ), - '6.0.0' - ); - continue; - } - - if ( ! preg_match( '/^[A-z0-9\/_-]+$/', $pattern_data['slug'] ) ) { - _doing_it_wrong( - '_register_theme_block_patterns', - sprintf( - /* translators: %1s: file name; %2s: slug value found. */ - __( 'Could not register file "%1$s" as a block pattern (invalid slug "%2$s")', 'gutenberg' ), - $file, - $pattern_data['slug'] - ), - '6.0.0' - ); - } - - if ( WP_Block_Patterns_Registry::get_instance()->is_registered( $pattern_data['slug'] ) ) { - continue; - } - - // Title is a required property. - if ( ! $pattern_data['title'] ) { - _doing_it_wrong( - '_register_theme_block_patterns', - sprintf( - /* translators: %1s: file name; %2s: slug value found. */ - __( 'Could not register file "%s" as a block pattern ("Title" field missing)', 'gutenberg' ), - $file - ), - '6.0.0' - ); - continue; - } - - // For properties of type array, parse data as comma-separated. - foreach ( array( 'categories', 'keywords', 'blockTypes', 'postTypes' ) as $property ) { - if ( ! empty( $pattern_data[ $property ] ) ) { - $pattern_data[ $property ] = array_filter( - preg_split( - '/[\s,]+/', - (string) $pattern_data[ $property ] - ) - ); - } else { - unset( $pattern_data[ $property ] ); - } - } - - // Parse properties of type int. - foreach ( array( 'viewportWidth' ) as $property ) { - if ( ! empty( $pattern_data[ $property ] ) ) { - $pattern_data[ $property ] = (int) $pattern_data[ $property ]; - } else { - unset( $pattern_data[ $property ] ); - } - } - - // Parse properties of type bool. - foreach ( array( 'inserter' ) as $property ) { - if ( ! empty( $pattern_data[ $property ] ) ) { - $pattern_data[ $property ] = in_array( - strtolower( $pattern_data[ $property ] ), - array( 'yes', 'true' ), - true - ); - } else { - unset( $pattern_data[ $property ] ); - } - } - - // Translate the pattern metadata. - $text_domain = $theme->get( 'TextDomain' ); - //phpcs:ignore WordPress.WP.I18n.NonSingularStringLiteralText, WordPress.WP.I18n.NonSingularStringLiteralContext, WordPress.WP.I18n.NonSingularStringLiteralDomain, WordPress.WP.I18n.LowLevelTranslationFunction - $pattern_data['title'] = translate_with_gettext_context( $pattern_data['title'], 'Pattern title', $text_domain ); - if ( ! empty( $pattern_data['description'] ) ) { - //phpcs:ignore WordPress.WP.I18n.NonSingularStringLiteralText, WordPress.WP.I18n.NonSingularStringLiteralContext, WordPress.WP.I18n.NonSingularStringLiteralDomain, WordPress.WP.I18n.LowLevelTranslationFunction - $pattern_data['description'] = translate_with_gettext_context( $pattern_data['description'], 'Pattern description', $text_domain ); - } - - // The actual pattern content is the output of the file. - ob_start(); - include $file; - $pattern_data['content'] = ob_get_clean(); - if ( ! $pattern_data['content'] ) { - continue; - } - - register_block_pattern( $pattern_data['slug'], $pattern_data ); - } - } - } - } -} -remove_action( 'init', '_register_theme_block_patterns' ); -add_action( 'init', 'gutenberg_register_theme_block_patterns' ); diff --git a/lib/compat/wordpress-6.1/block-template-utils.php b/lib/compat/wordpress-6.1/block-template-utils.php index 088333990a0d5f..928291b500084e 100644 --- a/lib/compat/wordpress-6.1/block-template-utils.php +++ b/lib/compat/wordpress-6.1/block-template-utils.php @@ -84,7 +84,7 @@ function gutenberg_get_block_templates( $query = array(), $template_type = 'wp_t array( 'taxonomy' => 'wp_theme', 'field' => 'name', - 'terms' => wp_get_theme()->get_stylesheet(), + 'terms' => get_stylesheet(), ), ), ); @@ -429,7 +429,7 @@ function gutenberg_build_block_template_result_from_post( $post ) { $theme = $terms[0]->name; $template_file = _get_block_template_file( $post->post_type, $post->post_name ); - $has_theme_file = wp_get_theme()->get_stylesheet() === $theme && null !== $template_file; + $has_theme_file = get_stylesheet() === $theme && null !== $template_file; $template = new WP_Block_Template(); $template->wp_id = $post->ID; diff --git a/lib/compat/wordpress-6.1/class-gutenberg-rest-block-patterns-controller.php b/lib/compat/wordpress-6.1/class-gutenberg-rest-block-patterns-controller-6-1.php similarity index 60% rename from lib/compat/wordpress-6.1/class-gutenberg-rest-block-patterns-controller.php rename to lib/compat/wordpress-6.1/class-gutenberg-rest-block-patterns-controller-6-1.php index c053a678083da8..b40f3aa2497f16 100644 --- a/lib/compat/wordpress-6.1/class-gutenberg-rest-block-patterns-controller.php +++ b/lib/compat/wordpress-6.1/class-gutenberg-rest-block-patterns-controller-6-1.php @@ -1,6 +1,6 @@ namespace = 'wp/v2'; - $this->rest_base = 'block-patterns/patterns'; - } - - /** - * Registers the routes for the objects of the controller. - * - * @since 6.0.0 - */ - public function register_routes() { - register_rest_route( - $this->namespace, - '/' . $this->rest_base, - array( - array( - 'methods' => WP_REST_Server::READABLE, - 'callback' => array( $this, 'get_items' ), - 'permission_callback' => array( $this, 'get_items_permissions_check' ), - ), - 'schema' => array( $this, 'get_public_item_schema' ), - ), - true - ); - } - - /** - * Checks whether a given request has permission to read block patterns. - * - * @since 6.0.0 - * - * @param WP_REST_Request $request Full details about the request. - * @return true|WP_Error True if the request has read access, WP_Error object otherwise. - */ - public function get_items_permissions_check( $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable - if ( current_user_can( 'edit_posts' ) ) { - return true; - } - - foreach ( get_post_types( array( 'show_in_rest' => true ), 'objects' ) as $post_type ) { - if ( current_user_can( $post_type->cap->edit_posts ) ) { - return true; - } - } - - return new WP_Error( - 'rest_cannot_view', - __( 'Sorry, you are not allowed to view the registered block patterns.', 'gutenberg' ), - array( 'status' => rest_authorization_required_code() ) - ); - } - - /** - * Retrieves all block patterns. - * - * @since 6.0.0 - * - * @param WP_REST_Request $request Full details about the request. - * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. - */ - public function get_items( $request ) { - if ( ! $this->remote_patterns_loaded ) { - // Load block patterns from w.org. - _load_remote_block_patterns(); // Patterns with the `core` keyword. - _load_remote_featured_patterns(); // Patterns in the `featured` category. - _register_remote_theme_patterns(); // Patterns requested by current theme. - - $this->remote_patterns_loaded = true; - } - - $response = array(); - $patterns = WP_Block_Patterns_Registry::get_instance()->get_all_registered(); - foreach ( $patterns as $pattern ) { - $prepared_pattern = $this->prepare_item_for_response( $pattern, $request ); - $response[] = $this->prepare_response_for_collection( $prepared_pattern ); - } - return rest_ensure_response( $response ); - } - +class Gutenberg_REST_Block_Patterns_Controller_6_1 extends WP_REST_Block_Patterns_Controller { /** * Prepare a raw block pattern before it gets output in a REST API response. * * @since 6.0.0 + * @since 6.1.0 Added `postTypes` property. * * @param array $item Raw pattern as registered, before any changes. * @param WP_REST_Request $request Request object. @@ -147,6 +55,7 @@ public function prepare_item_for_response( $item, $request ) { * Retrieves the block pattern schema, conforming to JSON Schema. * * @since 6.0.0 + * @since 6.1.0 Added `post_types` property. * * @return array Item schema data. */ diff --git a/lib/compat/wordpress-6.1/class-gutenberg-rest-templates-controller.php b/lib/compat/wordpress-6.1/class-gutenberg-rest-templates-controller.php index 879316c1c649e3..e911b0f1e4d203 100644 --- a/lib/compat/wordpress-6.1/class-gutenberg-rest-templates-controller.php +++ b/lib/compat/wordpress-6.1/class-gutenberg-rest-templates-controller.php @@ -228,7 +228,7 @@ protected function prepare_item_for_database( $request ) { $changes->post_type = $this->post_type; $changes->post_status = 'publish'; $changes->tax_input = array( - 'wp_theme' => isset( $request['theme'] ) ? $request['theme'] : wp_get_theme()->get_stylesheet(), + 'wp_theme' => isset( $request['theme'] ) ? $request['theme'] : get_stylesheet(), ); } elseif ( 'custom' !== $template->source ) { $changes->post_name = $template->slug; diff --git a/lib/compat/wordpress-6.1/class-wp-theme-json-resolver-6-1.php b/lib/compat/wordpress-6.1/class-wp-theme-json-resolver-6-1.php index 73e012f33d1c7e..1d4a83e3406fbe 100644 --- a/lib/compat/wordpress-6.1/class-wp-theme-json-resolver-6-1.php +++ b/lib/compat/wordpress-6.1/class-wp-theme-json-resolver-6-1.php @@ -15,7 +15,7 @@ * * @access private */ -class WP_Theme_JSON_Resolver_6_1 extends WP_Theme_JSON_Resolver_6_0 { +class WP_Theme_JSON_Resolver_6_1 extends WP_Theme_JSON_Resolver { /** * Container for data coming from core. diff --git a/lib/compat/wordpress-6.1/get-global-styles-and-settings.php b/lib/compat/wordpress-6.1/get-global-styles-and-settings.php index 35c540ce1c57a6..0829a09d084c70 100644 --- a/lib/compat/wordpress-6.1/get-global-styles-and-settings.php +++ b/lib/compat/wordpress-6.1/get-global-styles-and-settings.php @@ -56,86 +56,36 @@ function ( $item ) { } /** - * Returns the stylesheet resulting of merging core, theme, and user data. + * Repeated logic from `get_default_block_editor_settings` function. When implemented into core, + * remove logic from `get_default_block_editor_settings` and simple call this function instead. * - * @param array $types Types of styles to load. Optional. - * It accepts 'variables', 'styles', 'presets' as values. - * If empty, it'll load all for themes with theme.json support - * and only [ 'variables', 'presets' ] for themes without theme.json support. - * - * @return string Stylesheet. + * @return array */ -function gutenberg_get_global_stylesheet( $types = array() ) { - // Return cached value if it can be used and exists. - // It's cached by theme to make sure that theme switching clears the cache. - $can_use_cached = ( - ( empty( $types ) ) && - ( ! defined( 'WP_DEBUG' ) || ! WP_DEBUG ) && - ( ! defined( 'SCRIPT_DEBUG' ) || ! SCRIPT_DEBUG ) && - ( ! defined( 'REST_REQUEST' ) || ! REST_REQUEST ) && - ! is_admin() +function gutenberg_get_legacy_theme_supports_for_theme_json() { + $theme_settings = array( + 'disableCustomColors' => get_theme_support( 'disable-custom-colors' ), + 'disableCustomFontSizes' => get_theme_support( 'disable-custom-font-sizes' ), + 'disableCustomGradients' => get_theme_support( 'disable-custom-gradients' ), + 'enableCustomLineHeight' => get_theme_support( 'custom-line-height' ), + 'enableCustomSpacing' => get_theme_support( 'custom-spacing' ), + 'enableCustomUnits' => get_theme_support( 'custom-units' ), ); - $transient_name = 'gutenberg_global_styles_' . get_stylesheet(); - if ( $can_use_cached ) { - $cached = get_transient( $transient_name ); - if ( $cached ) { - return $cached; - } - } - $tree = WP_Theme_JSON_Resolver_Gutenberg::get_merged_data(); - $supports_theme_json = wp_theme_has_theme_json(); - if ( empty( $types ) && ! $supports_theme_json ) { - $types = array( 'variables', 'presets', 'base-layout-styles' ); - } elseif ( empty( $types ) ) { - $types = array( 'variables', 'styles', 'presets' ); - } - /* - * If variables are part of the stylesheet, - * we add them. - * - * This is so themes without a theme.json still work as before 5.9: - * they can override the default presets. - * See https://core.trac.wordpress.org/ticket/54782 - */ - $styles_variables = ''; - if ( in_array( 'variables', $types, true ) ) { - /* - * We only use the default, theme, and custom origins. - * This is because styles for blocks origin are added - * at a later phase (render cycle) so we only render the ones in use. - * @see wp_add_global_styles_for_blocks - */ - $origins = array( 'default', 'theme', 'custom' ); - $styles_variables = $tree->get_stylesheet( array( 'variables' ), $origins ); - $types = array_diff( $types, array( 'variables' ) ); + // Theme settings. + $color_palette = current( (array) get_theme_support( 'editor-color-palette' ) ); + if ( false !== $color_palette ) { + $theme_settings['colors'] = $color_palette; } - /* - * For the remaining types (presets, styles), we do consider origins: - * - * - themes without theme.json: only the classes for the presets defined by core - * - themes with theme.json: the presets and styles classes, both from core and the theme - */ - $styles_rest = ''; - if ( ! empty( $types ) ) { - /* - * We only use the default, theme, and custom origins. - * This is because styles for blocks origin are added - * at a later phase (render cycle) so we only render the ones in use. - * @see wp_add_global_styles_for_blocks - */ - $origins = array( 'default', 'theme', 'custom' ); - if ( ! $supports_theme_json ) { - $origins = array( 'default' ); - } - $styles_rest = $tree->get_stylesheet( $types, $origins ); + $font_sizes = current( (array) get_theme_support( 'editor-font-sizes' ) ); + if ( false !== $font_sizes ) { + $theme_settings['fontSizes'] = $font_sizes; } - $stylesheet = $styles_variables . $styles_rest; - if ( $can_use_cached ) { - // Cache for a minute. - // This cache doesn't need to be any longer, we only want to avoid spikes on high-traffic sites. - set_transient( $transient_name, $stylesheet, MINUTE_IN_SECONDS ); + + $gradient_presets = current( (array) get_theme_support( 'editor-gradient-presets' ) ); + if ( false !== $gradient_presets ) { + $theme_settings['gradients'] = $gradient_presets; } - return $stylesheet; + + return $theme_settings; } diff --git a/lib/compat/wordpress-6.1/rest-api.php b/lib/compat/wordpress-6.1/rest-api.php index f3391389d9e38a..5dd06d3aad855b 100644 --- a/lib/compat/wordpress-6.1/rest-api.php +++ b/lib/compat/wordpress-6.1/rest-api.php @@ -35,15 +35,6 @@ function gutenberg_update_post_types_rest_response( $response, $post_type ) { } add_filter( 'rest_prepare_post_type', 'gutenberg_update_post_types_rest_response', 10, 2 ); -/** - * Registers the block patterns REST API routes. - */ -function gutenberg_register_gutenberg_rest_block_patterns() { - $block_patterns = new Gutenberg_REST_Block_Patterns_Controller(); - $block_patterns->register_routes(); -} -add_action( 'rest_api_init', 'gutenberg_register_gutenberg_rest_block_patterns', 100 ); - /** * Exposes the site logo URL through the WordPress REST API. * diff --git a/lib/compat/wordpress-6.1/template-parts-screen.php b/lib/compat/wordpress-6.1/template-parts-screen.php index c8b5958bedcd4d..1555b6ab1e250b 100644 --- a/lib/compat/wordpress-6.1/template-parts-screen.php +++ b/lib/compat/wordpress-6.1/template-parts-screen.php @@ -118,17 +118,8 @@ static function( $classes ) { 'defaultTemplatePartAreas' => get_allowed_block_template_part_areas(), 'supportsLayout' => wp_theme_has_theme_json(), 'supportsTemplatePartsMode' => ! wp_is_block_theme() && current_theme_supports( 'block-template-parts' ), - '__unstableHomeTemplate' => gutenberg_resolve_home_template(), ); - /** - * We don't need home template resolution when block template parts are supported. - * Set the value to true to satisfy the editor initialization guard clause. - */ - if ( $custom_settings['supportsTemplatePartsMode'] ) { - $custom_settings['__unstableHomeTemplate'] = true; - } - // Add additional back-compat patterns registered by `current_screen` et al. $custom_settings['__experimentalAdditionalBlockPatterns'] = WP_Block_Patterns_Registry::get_instance()->get_all_registered( true ); $custom_settings['__experimentalAdditionalBlockPatternCategories'] = WP_Block_Pattern_Categories_Registry::get_instance()->get_all_registered( true ); @@ -143,7 +134,7 @@ static function( $classes ) { } $active_global_styles_id = WP_Theme_JSON_Resolver::get_user_global_styles_post_id(); - $active_theme = wp_get_theme()->get_stylesheet(); + $active_theme = get_stylesheet(); $preload_paths = array( array( '/wp/v2/media', 'OPTIONS' ), '/wp/v2/types?context=view', diff --git a/lib/compat/wordpress-6.2/block-editor-settings.php b/lib/compat/wordpress-6.2/block-editor-settings.php new file mode 100644 index 00000000000000..7323d34eb667ce --- /dev/null +++ b/lib/compat/wordpress-6.2/block-editor-settings.php @@ -0,0 +1,28 @@ + gutenberg_get_global_stylesheet( array( 'custom-css' ) ), + '__unstableType' => 'user', + 'isGlobalStyles' => true, + ); + } + + return $settings; +} + +add_filter( 'block_editor_settings_all', 'gutenberg_get_block_editor_settings_6_2', PHP_INT_MAX ); diff --git a/lib/compat/wordpress-6.2/block-patterns.php b/lib/compat/wordpress-6.2/block-patterns.php index a28566d441e7a8..1e529faf963210 100644 --- a/lib/compat/wordpress-6.2/block-patterns.php +++ b/lib/compat/wordpress-6.2/block-patterns.php @@ -6,9 +6,9 @@ */ /** - * Registers the block pattern categories REST API routes. + * Registers the block pattern categories. */ -function gutenberg_register_core_block_patterns_and_categories() { +function gutenberg_register_core_block_patterns_categories() { register_block_pattern_category( 'banner', array( @@ -18,54 +18,127 @@ function gutenberg_register_core_block_patterns_and_categories() { register_block_pattern_category( 'buttons', array( - 'label' => _x( 'Buttons', 'Block pattern category', 'gutenberg' ), + 'label' => _x( 'Buttons', 'Block pattern category', 'gutenberg' ), + 'description' => __( 'Patterns that contain buttons and call to actions.', 'gutenberg' ), ) ); register_block_pattern_category( 'columns', array( - 'label' => _x( 'Columns', 'Block pattern category', 'gutenberg' ), + 'label' => _x( 'Columns', 'Block pattern category', 'gutenberg' ), + 'description' => __( 'Multi-column patterns with more complex layouts.', 'gutenberg' ), ) ); register_block_pattern_category( - 'footer', + 'text', array( - 'label' => _x( 'Footers', 'Block pattern category', 'gutenberg' ), + 'label' => _x( 'Text', 'Block pattern category', 'gutenberg' ), + 'description' => __( 'Patterns containing mostly text.', 'gutenberg' ), ) ); register_block_pattern_category( - 'gallery', + 'query', array( - 'label' => _x( 'Gallery', 'Block pattern category', 'gutenberg' ), + 'label' => _x( 'Posts', 'Block pattern category', 'gutenberg' ), + 'description' => __( 'Display your latest posts in lists, grids or other layouts.', 'gutenberg' ), ) ); register_block_pattern_category( - 'header', + 'featured', array( - 'label' => _x( 'Headers', 'Block pattern category', 'gutenberg' ), + 'label' => _x( 'Featured', 'Block pattern category', 'gutenberg' ), + 'description' => __( 'A set of high quality curated patterns.', 'gutenberg' ), ) ); + + // Register new core block pattern categories. register_block_pattern_category( - 'text', + 'call-to-action', array( - 'label' => _x( 'Text', 'Block pattern category', 'gutenberg' ), + 'label' => _x( 'Call to Action', 'Block pattern category', 'gutenberg' ), + 'description' => __( 'Sections whose purpose is to trigger a specific action.', 'gutenberg' ), ) ); register_block_pattern_category( - 'query', + 'team', + array( + 'label' => _x( 'Team', 'Block pattern category', 'gutenberg' ), + 'description' => __( 'A variety of designs to display your team members.', 'gutenberg' ), + ) + ); + register_block_pattern_category( + 'testimonials', + array( + 'label' => _x( 'Testimonials', 'Block pattern category', 'gutenberg' ), + 'description' => __( 'Share reviews and feedback about your brand/business.', 'gutenberg' ), + ) + ); + register_block_pattern_category( + 'services', + array( + 'label' => _x( 'Services', 'Block pattern category', 'gutenberg' ), + 'description' => __( 'Briefly describe what your business does and how you can help.', 'gutenberg' ), + ) + ); + register_block_pattern_category( + 'contact', + array( + 'label' => _x( 'Contact', 'Block pattern category', 'gutenberg' ), + 'description' => __( 'Display your contact information.', 'gutenberg' ), + ) + ); + register_block_pattern_category( + 'about', + array( + 'label' => _x( 'About', 'Block pattern category', 'gutenberg' ), + 'description' => __( 'Introduce yourself.', 'gutenberg' ), + ) + ); + register_block_pattern_category( + 'portfolio', + array( + 'label' => _x( 'Portfolio', 'Block pattern category', 'gutenberg' ), + 'description' => __( 'Showcase your latest work.', 'gutenberg' ), + ) + ); + register_block_pattern_category( + 'gallery', + array( + 'label' => _x( 'Gallery', 'Block pattern category', 'gutenberg' ), + 'description' => __( 'Different layouts for displaying images.', 'gutenberg' ), + ) + ); + register_block_pattern_category( + 'media', + array( + 'label' => _x( 'Media', 'Block pattern category', 'gutenberg' ), + 'description' => __( 'Different layouts containing video or audio.', 'gutenberg' ), + ) + ); + register_block_pattern_category( + 'posts', array( 'label' => _x( 'Posts', 'Block pattern category', 'gutenberg' ), - 'description' => __( 'Display post summaries in lists, grids, and other layouts.', 'gutenberg' ), + 'description' => __( 'Display your latest posts in lists, grids or other layouts.', 'gutenberg' ), ) ); + // Site building pattern categories. register_block_pattern_category( - 'featured', + 'footer', array( - 'label' => _x( 'Featured', 'Block pattern category', 'gutenberg' ), + 'label' => _x( 'Footers', 'Block pattern category', 'gutenberg' ), + 'description' => __( 'A variety of footer designs displaying information and site navigation.', 'gutenberg' ), + ) + ); + register_block_pattern_category( + 'header', + array( + 'label' => _x( 'Headers', 'Block pattern category', 'gutenberg' ), + 'description' => __( 'A variety of header designs displaying your site title and navigation.', 'gutenberg' ), ) ); } -add_action( 'init', 'gutenberg_register_core_block_patterns_and_categories' ); +add_action( 'init', 'gutenberg_register_core_block_patterns_categories' ); /** * Registers Gutenberg-bundled patterns, with a focus on headers and footers @@ -111,3 +184,173 @@ function gutenberg_register_core_block_patterns() { } } add_action( 'init', 'gutenberg_register_core_block_patterns' ); + +/** + * Register any patterns that the active theme may provide under its + * `./patterns/` directory. Each pattern is defined as a PHP file and defines + * its metadata using plugin-style headers. The minimum required definition is: + * + * /** + * * Title: My Pattern + * * Slug: my-theme/my-pattern + * * + * + * The output of the PHP source corresponds to the content of the pattern, e.g.: + * + *

+ * + * If applicable, this will collect from both parent and child theme. + * + * Other settable fields include: + * + * - Description + * - Viewport Width + * - Categories (comma-separated values) + * - Keywords (comma-separated values) + * - Block Types (comma-separated values) + * - Post Types (comma-separated values) + * - Inserter (yes/no) + * + * @since 6.0.0 + * @access private + */ +function gutenberg_register_theme_block_patterns() { + $default_headers = array( + 'title' => 'Title', + 'slug' => 'Slug', + 'description' => 'Description', + 'viewportWidth' => 'Viewport Width', + 'categories' => 'Categories', + 'keywords' => 'Keywords', + 'blockTypes' => 'Block Types', + 'postTypes' => 'Post Types', + 'inserter' => 'Inserter', + ); + + /* + * Register patterns for the active theme. If the theme is a child theme, + * let it override any patterns from the parent theme that shares the same slug. + */ + $themes = array(); + $wp_theme = wp_get_theme(); + if ( $wp_theme->parent() ) { + $themes[] = $wp_theme->parent(); + } + $themes[] = $wp_theme; + + foreach ( $themes as $theme ) { + $dirpath = $theme->get_stylesheet_directory() . '/patterns/'; + if ( ! is_dir( $dirpath ) || ! is_readable( $dirpath ) ) { + continue; + } + if ( file_exists( $dirpath ) ) { + $files = glob( $dirpath . '*.php' ); + if ( $files ) { + foreach ( $files as $file ) { + $pattern_data = get_file_data( $file, $default_headers ); + + if ( empty( $pattern_data['slug'] ) ) { + _doing_it_wrong( + '_register_theme_block_patterns', + sprintf( + /* translators: %s: file name. */ + __( 'Could not register file "%s" as a block pattern ("Slug" field missing)', 'gutenberg' ), + $file + ), + '6.0.0' + ); + continue; + } + + if ( ! preg_match( '/^[A-z0-9\/_-]+$/', $pattern_data['slug'] ) ) { + _doing_it_wrong( + '_register_theme_block_patterns', + sprintf( + /* translators: %1s: file name; %2s: slug value found. */ + __( 'Could not register file "%1$s" as a block pattern (invalid slug "%2$s")', 'gutenberg' ), + $file, + $pattern_data['slug'] + ), + '6.0.0' + ); + } + + if ( WP_Block_Patterns_Registry::get_instance()->is_registered( $pattern_data['slug'] ) ) { + continue; + } + + // Title is a required property. + if ( ! $pattern_data['title'] ) { + _doing_it_wrong( + '_register_theme_block_patterns', + sprintf( + /* translators: %1s: file name; %2s: slug value found. */ + __( 'Could not register file "%s" as a block pattern ("Title" field missing)', 'gutenberg' ), + $file + ), + '6.0.0' + ); + continue; + } + + // For properties of type array, parse data as comma-separated. + foreach ( array( 'categories', 'keywords', 'blockTypes', 'postTypes' ) as $property ) { + if ( ! empty( $pattern_data[ $property ] ) ) { + $pattern_data[ $property ] = array_filter( + preg_split( + '/[\s,]+/', + (string) $pattern_data[ $property ] + ) + ); + } else { + unset( $pattern_data[ $property ] ); + } + } + + // Parse properties of type int. + foreach ( array( 'viewportWidth' ) as $property ) { + if ( ! empty( $pattern_data[ $property ] ) ) { + $pattern_data[ $property ] = (int) $pattern_data[ $property ]; + } else { + unset( $pattern_data[ $property ] ); + } + } + + // Parse properties of type bool. + foreach ( array( 'inserter' ) as $property ) { + if ( ! empty( $pattern_data[ $property ] ) ) { + $pattern_data[ $property ] = in_array( + strtolower( $pattern_data[ $property ] ), + array( 'yes', 'true' ), + true + ); + } else { + unset( $pattern_data[ $property ] ); + } + } + + // Translate the pattern metadata. + $text_domain = $theme->get( 'TextDomain' ); + //phpcs:ignore WordPress.WP.I18n.NonSingularStringLiteralText, WordPress.WP.I18n.NonSingularStringLiteralContext, WordPress.WP.I18n.NonSingularStringLiteralDomain, WordPress.WP.I18n.LowLevelTranslationFunction + $pattern_data['title'] = translate_with_gettext_context( $pattern_data['title'], 'Pattern title', $text_domain ); + if ( ! empty( $pattern_data['description'] ) ) { + //phpcs:ignore WordPress.WP.I18n.NonSingularStringLiteralText, WordPress.WP.I18n.NonSingularStringLiteralContext, WordPress.WP.I18n.NonSingularStringLiteralDomain, WordPress.WP.I18n.LowLevelTranslationFunction + $pattern_data['description'] = translate_with_gettext_context( $pattern_data['description'], 'Pattern description', $text_domain ); + } + + // The actual pattern content is the output of the file. + ob_start(); + include $file; + $pattern_data['content'] = ob_get_clean(); + if ( ! $pattern_data['content'] ) { + continue; + } + + register_block_pattern( $pattern_data['slug'], $pattern_data ); + } + } + } + } +} +remove_action( 'init', '_register_theme_block_patterns' ); +add_action( 'init', 'gutenberg_register_theme_block_patterns' ); diff --git a/lib/compat/wordpress-6.2/class-gutenberg-rest-block-patterns-controller-6-2.php b/lib/compat/wordpress-6.2/class-gutenberg-rest-block-patterns-controller-6-2.php new file mode 100644 index 00000000000000..d34160edb2af0b --- /dev/null +++ b/lib/compat/wordpress-6.2/class-gutenberg-rest-block-patterns-controller-6-2.php @@ -0,0 +1,107 @@ + 'call-to-action', + 'columns' => 'text', + 'query' => 'posts', + ); + + /** + * Registers the routes for the objects of the controller. + * + * @since 6.0.0 + */ + public function register_routes() { + register_rest_route( + $this->namespace, + '/' . $this->rest_base, + array( + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => array( $this, 'get_items' ), + 'permission_callback' => array( $this, 'get_items_permissions_check' ), + ), + 'schema' => array( $this, 'get_public_item_schema' ), + ), + true + ); + } + /** + * Retrieves all block patterns. + * + * @since 6.0.0 + * @since 6.2.0 Added migration for old core pattern categories to the new ones. + * + * @param WP_REST_Request $request Full details about the request. + * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. + */ + public function get_items( $request ) { + if ( ! $this->remote_patterns_loaded ) { + // Load block patterns from w.org. + _load_remote_block_patterns(); // Patterns with the `core` keyword. + _load_remote_featured_patterns(); // Patterns in the `featured` category. + _register_remote_theme_patterns(); // Patterns requested by current theme. + + $this->remote_patterns_loaded = true; + } + + $response = array(); + $patterns = WP_Block_Patterns_Registry::get_instance()->get_all_registered(); + foreach ( $patterns as $pattern ) { + $migrated_pattern = $this->migrate_pattern_categories( $pattern ); + $prepared_pattern = $this->prepare_item_for_response( $migrated_pattern, $request ); + $response[] = $this->prepare_response_for_collection( $prepared_pattern ); + } + return rest_ensure_response( $response ); + } + + /** + * Migrates old core pattern categories to new ones. + * + * Core pattern categories are being revamped and we need to handle the migration + * to the new ones and ensure backwards compatibility. + * + * @since 6.2.0 + * + * @param array $pattern Raw pattern as registered, before applying any changes. + * @return array Migrated pattern. + */ + protected function migrate_pattern_categories( $pattern ) { + if ( isset( $pattern['categories'] ) && is_array( $pattern['categories'] ) ) { + foreach ( $pattern['categories'] as $i => $category ) { + if ( array_key_exists( $category, static::$categories_migration ) ) { + $pattern['categories'][ $i ] = static::$categories_migration[ $category ]; + } + } + } + return $pattern; + } +} diff --git a/lib/compat/wordpress-6.0/class-gutenberg-rest-global-styles-controller.php b/lib/compat/wordpress-6.2/class-gutenberg-rest-global-styles-controller-6-2.php similarity index 54% rename from lib/compat/wordpress-6.0/class-gutenberg-rest-global-styles-controller.php rename to lib/compat/wordpress-6.2/class-gutenberg-rest-global-styles-controller-6-2.php index 2a50e9684c0938..684786ef22d762 100644 --- a/lib/compat/wordpress-6.0/class-gutenberg-rest-global-styles-controller.php +++ b/lib/compat/wordpress-6.2/class-gutenberg-rest-global-styles-controller-6-2.php @@ -9,111 +9,23 @@ /** * Base Global Styles REST API Controller. */ -class Gutenberg_REST_Global_Styles_Controller extends WP_REST_Global_Styles_Controller { +class Gutenberg_REST_Global_Styles_Controller_6_2 extends WP_REST_Global_Styles_Controller { /** * Registers the controllers routes. * * @return void */ public function register_routes() { - // List themes global styles. - register_rest_route( - $this->namespace, - '/' . $this->rest_base . '/themes/(?P[\/\s%\w\.\(\)\[\]\@_\-]+)/variations', - array( - array( - 'methods' => WP_REST_Server::READABLE, - 'callback' => array( $this, 'get_theme_items' ), - 'permission_callback' => array( $this, 'get_theme_items_permissions_check' ), - 'args' => array( - 'stylesheet' => array( - 'description' => __( 'The theme identifier', 'gutenberg' ), - 'type' => 'string', - ), - ), - ), - ) - ); - parent::register_routes(); } - /** - * Checks if a given request has access to read a single theme global styles config. - * - * @param WP_REST_Request $request Full details about the request. - * @return true|WP_Error True if the request has read access for the item, WP_Error object otherwise. - */ - public function get_theme_items_permissions_check( $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable - // Verify if the current user has edit_theme_options capability. - // This capability is required to edit/view/delete templates. - if ( ! current_user_can( 'edit_theme_options' ) ) { - return new WP_Error( - 'rest_cannot_manage_global_styles', - __( 'Sorry, you are not allowed to access the global styles on this site.', 'gutenberg' ), - array( - 'status' => rest_authorization_required_code(), - ) - ); - } - - return true; - } - - /** - * Prepares a single global styles config for update. - * - * @since 5.9.0 - * - * @param WP_REST_Request $request Request object. - * @return stdClass Changes to pass to wp_update_post. - */ - protected function prepare_item_for_database( $request ) { - $changes = new stdClass(); - $changes->ID = $request['id']; - $post = get_post( $request['id'] ); - $existing_config = array(); - if ( $post ) { - $existing_config = json_decode( $post->post_content, true ); - $json_decoding_error = json_last_error(); - if ( JSON_ERROR_NONE !== $json_decoding_error || ! isset( $existing_config['isGlobalStylesUserThemeJSON'] ) || - ! $existing_config['isGlobalStylesUserThemeJSON'] ) { - $existing_config = array(); - } - } - if ( isset( $request['styles'] ) || isset( $request['settings'] ) ) { - $config = array(); - if ( isset( $request['styles'] ) ) { - $config['styles'] = $request['styles']; - } elseif ( isset( $existing_config['styles'] ) ) { - $config['styles'] = $existing_config['styles']; - } - if ( isset( $request['settings'] ) ) { - $config['settings'] = $request['settings']; - } elseif ( isset( $existing_config['settings'] ) ) { - $config['settings'] = $existing_config['settings']; - } - $config['isGlobalStylesUserThemeJSON'] = true; - $config['version'] = WP_Theme_JSON_Gutenberg::LATEST_SCHEMA; - $changes->post_content = wp_json_encode( $config ); - } - // Post title. - if ( isset( $request['title'] ) ) { - if ( is_string( $request['title'] ) ) { - $changes->post_title = $request['title']; - } elseif ( ! empty( $request['title']['raw'] ) ) { - $changes->post_title = $request['title']['raw']; - } - } - return $changes; - } - /** * Prepare a global styles config output for response. * * @since 5.9.0 + * @since 6.2 Handling of style.css was added to WP_Theme_JSON. * - * @param WP_Post $post Global Styles post object. + * @param WP_Post $post Global Styles post object. * @param WP_REST_Request $request Request object. * @return WP_REST_Response Response object. */ @@ -124,12 +36,15 @@ public function prepare_item_for_response( $post, $request ) { // phpcs:ignore V if ( $is_global_styles_user_theme_json ) { $config = ( new WP_Theme_JSON_Gutenberg( $raw_config, 'custom' ) )->get_raw_data(); } + // Base fields for every post. $data = array(); $fields = $this->get_fields_for_response( $request ); + if ( rest_is_field_included( 'id', $fields ) ) { $data['id'] = $post->ID; } + if ( rest_is_field_included( 'title', $fields ) ) { $data['title'] = array(); } @@ -138,94 +53,152 @@ public function prepare_item_for_response( $post, $request ) { // phpcs:ignore V } if ( rest_is_field_included( 'title.rendered', $fields ) ) { add_filter( 'protected_title_format', array( $this, 'protected_title_format' ) ); + $data['title']['rendered'] = get_the_title( $post->ID ); + remove_filter( 'protected_title_format', array( $this, 'protected_title_format' ) ); } + if ( rest_is_field_included( 'settings', $fields ) ) { $data['settings'] = ! empty( $config['settings'] ) && $is_global_styles_user_theme_json ? $config['settings'] : new stdClass(); } + if ( rest_is_field_included( 'styles', $fields ) ) { $data['styles'] = ! empty( $config['styles'] ) && $is_global_styles_user_theme_json ? $config['styles'] : new stdClass(); } + $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; $data = $this->add_additional_fields_to_object( $data, $request ); $data = $this->filter_response_by_context( $data, $context ); + // Wrap the data in a response object. $response = rest_ensure_response( $data ); - $links = $this->prepare_links( $post->ID ); - $response->add_links( $links ); - if ( ! empty( $links['self']['href'] ) ) { - $actions = $this->get_available_actions(); - $self = $links['self']['href']; - foreach ( $actions as $rel ) { - $response->add_link( $rel, $self ); + + if ( rest_is_field_included( '_links', $fields ) || rest_is_field_included( '_embedded', $fields ) ) { + $links = $this->prepare_links( $post->ID ); + $response->add_links( $links ); + if ( ! empty( $links['self']['href'] ) ) { + $actions = $this->get_available_actions(); + $self = $links['self']['href']; + foreach ( $actions as $rel ) { + $response->add_link( $rel, $self ); + } } } + return $response; } /** - * Returns the given theme global styles config. + * Updates a single global style config. * * @since 5.9.0 + * @since 6.2.0 Added validation of styles.css property. * - * @param WP_REST_Request $request The request instance. - * @return WP_REST_Response|WP_Error + * @param WP_REST_Request $request Full details about the request. + * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ - public function get_theme_item( $request ) { - if ( wp_get_theme()->get_stylesheet() !== $request['stylesheet'] ) { - // This endpoint only supports the active theme for now. - return new WP_Error( - 'rest_theme_not_found', - __( 'Theme not found.', 'gutenberg' ), - array( 'status' => 404 ) - ); + public function update_item( $request ) { + $post_before = $this->get_post( $request['id'] ); + if ( is_wp_error( $post_before ) ) { + return $post_before; } - $theme = WP_Theme_JSON_Resolver_Gutenberg::get_merged_data( 'theme' ); - $data = array(); - $fields = $this->get_fields_for_response( $request ); - if ( rest_is_field_included( 'settings', $fields ) ) { - $data['settings'] = $theme->get_settings(); + + $changes = $this->prepare_item_for_database( $request ); + if ( is_wp_error( $changes ) ) { + return $changes; } - if ( rest_is_field_included( 'styles', $fields ) ) { - $raw_data = $theme->get_raw_data(); - $data['styles'] = isset( $raw_data['styles'] ) ? $raw_data['styles'] : array(); + + $result = wp_update_post( wp_slash( (array) $changes ), true, false ); + if ( is_wp_error( $result ) ) { + return $result; + } + + $post = get_post( $request['id'] ); + $fields_update = $this->update_additional_fields_for_object( $post, $request ); + if ( is_wp_error( $fields_update ) ) { + return $fields_update; } - $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; - $data = $this->add_additional_fields_to_object( $data, $request ); - $data = $this->filter_response_by_context( $data, $context ); - $response = rest_ensure_response( $data ); - $links = array( - 'self' => array( - 'href' => rest_url( sprintf( '%s/%s/themes/%s', $this->namespace, $this->rest_base, $request['stylesheet'] ) ), - ), - ); - $response->add_links( $links ); - return $response; - } + wp_after_insert_post( $post, true, $post_before ); + + $response = $this->prepare_item_for_response( $post, $request ); + + return rest_ensure_response( $response ); + } + /** + * Prepares a single global styles config for update. + * + * @since 5.9.0 + * @since 6.2.0 Added validation of styles.css property. + * + * @param WP_REST_Request $request Request object. + * @return stdClass Changes to pass to wp_update_post. + */ + protected function prepare_item_for_database( $request ) { + $changes = new stdClass(); + $changes->ID = $request['id']; + $post = get_post( $request['id'] ); + $existing_config = array(); + if ( $post ) { + $existing_config = json_decode( $post->post_content, true ); + $json_decoding_error = json_last_error(); + if ( JSON_ERROR_NONE !== $json_decoding_error || ! isset( $existing_config['isGlobalStylesUserThemeJSON'] ) || + ! $existing_config['isGlobalStylesUserThemeJSON'] ) { + $existing_config = array(); + } + } + if ( isset( $request['styles'] ) || isset( $request['settings'] ) ) { + $config = array(); + if ( isset( $request['styles'] ) ) { + $config['styles'] = $request['styles']; + if ( isset( $request['styles']['css'] ) ) { + $validate_custom_css = $this->validate_custom_css( $request['styles']['css'] ); + if ( is_wp_error( $validate_custom_css ) ) { + return $validate_custom_css; + } + } + } elseif ( isset( $existing_config['styles'] ) ) { + $config['styles'] = $existing_config['styles']; + } + if ( isset( $request['settings'] ) ) { + $config['settings'] = $request['settings']; + } elseif ( isset( $existing_config['settings'] ) ) { + $config['settings'] = $existing_config['settings']; + } + $config['isGlobalStylesUserThemeJSON'] = true; + $config['version'] = WP_Theme_JSON_Gutenberg::LATEST_SCHEMA; + $changes->post_content = wp_json_encode( $config ); + } + // Post title. + if ( isset( $request['title'] ) ) { + if ( is_string( $request['title'] ) ) { + $changes->post_title = $request['title']; + } elseif ( ! empty( $request['title']['raw'] ) ) { + $changes->post_title = $request['title']['raw']; + } + } + return $changes; + } /** - * Returns the given theme global styles variations. + * Validate style.css as valid CSS. * - * @param WP_REST_Request $request The request instance. + * Currently just checks for invalid markup. * - * @return WP_REST_Response|WP_Error + * @since 6.2.0 + * + * @param string $css CSS to validate. + * @return true|WP_Error True if the input was validated, otherwise WP_Error. */ - public function get_theme_items( $request ) { - if ( wp_get_theme()->get_stylesheet() !== $request['stylesheet'] ) { - // This endpoint only supports the active theme for now. + private function validate_custom_css( $css ) { + if ( preg_match( '# 404 ) + 'rest_custom_css_illegal_markup', + __( 'Markup is not allowed in CSS.', 'gutenberg' ), + array( 'status' => 400 ) ); } - - $variations = WP_Theme_JSON_Resolver_Gutenberg::get_style_variations(); - $response = rest_ensure_response( $variations ); - - return $response; + return true; } - } diff --git a/lib/compat/wordpress-6.2/class-gutenberg-rest-pattern-directory-controller-6-2.php b/lib/compat/wordpress-6.2/class-gutenberg-rest-pattern-directory-controller-6-2.php index ab845f3c38de15..e8e456676e9749 100644 --- a/lib/compat/wordpress-6.2/class-gutenberg-rest-pattern-directory-controller-6-2.php +++ b/lib/compat/wordpress-6.2/class-gutenberg-rest-pattern-directory-controller-6-2.php @@ -9,7 +9,7 @@ /** * Controller which provides REST endpoint for block patterns from wordpress.org/patterns. */ -class Gutenberg_REST_Pattern_Directory_Controller_6_2 extends Gutenberg_REST_Pattern_Directory_Controller_6_0 { +class Gutenberg_REST_Pattern_Directory_Controller_6_2 extends WP_REST_Pattern_Directory_Controller { /** * Registers the necessary REST API routes. * diff --git a/lib/compat/wordpress-6.2/class-wp-theme-json-6-2.php b/lib/compat/wordpress-6.2/class-wp-theme-json-6-2.php deleted file mode 100644 index 4c6a1f50612a84..00000000000000 --- a/lib/compat/wordpress-6.2/class-wp-theme-json-6-2.php +++ /dev/null @@ -1,219 +0,0 @@ - array( 'color', 'gradient' ), - 'background-color' => array( 'color', 'background' ), - 'border-radius' => array( 'border', 'radius' ), - 'border-top-left-radius' => array( 'border', 'radius', 'topLeft' ), - 'border-top-right-radius' => array( 'border', 'radius', 'topRight' ), - 'border-bottom-left-radius' => array( 'border', 'radius', 'bottomLeft' ), - 'border-bottom-right-radius' => array( 'border', 'radius', 'bottomRight' ), - 'border-color' => array( 'border', 'color' ), - 'border-width' => array( 'border', 'width' ), - 'border-style' => array( 'border', 'style' ), - 'border-top-color' => array( 'border', 'top', 'color' ), - 'border-top-width' => array( 'border', 'top', 'width' ), - 'border-top-style' => array( 'border', 'top', 'style' ), - 'border-right-color' => array( 'border', 'right', 'color' ), - 'border-right-width' => array( 'border', 'right', 'width' ), - 'border-right-style' => array( 'border', 'right', 'style' ), - 'border-bottom-color' => array( 'border', 'bottom', 'color' ), - 'border-bottom-width' => array( 'border', 'bottom', 'width' ), - 'border-bottom-style' => array( 'border', 'bottom', 'style' ), - 'border-left-color' => array( 'border', 'left', 'color' ), - 'border-left-width' => array( 'border', 'left', 'width' ), - 'border-left-style' => array( 'border', 'left', 'style' ), - 'color' => array( 'color', 'text' ), - 'font-family' => array( 'typography', 'fontFamily' ), - 'font-size' => array( 'typography', 'fontSize' ), - 'font-style' => array( 'typography', 'fontStyle' ), - 'font-weight' => array( 'typography', 'fontWeight' ), - 'letter-spacing' => array( 'typography', 'letterSpacing' ), - 'line-height' => array( 'typography', 'lineHeight' ), - 'margin' => array( 'spacing', 'margin' ), - 'margin-top' => array( 'spacing', 'margin', 'top' ), - 'margin-right' => array( 'spacing', 'margin', 'right' ), - 'margin-bottom' => array( 'spacing', 'margin', 'bottom' ), - 'margin-left' => array( 'spacing', 'margin', 'left' ), - 'min-height' => array( 'dimensions', 'minHeight' ), - 'padding' => array( 'spacing', 'padding' ), - 'padding-top' => array( 'spacing', 'padding', 'top' ), - 'padding-right' => array( 'spacing', 'padding', 'right' ), - 'padding-bottom' => array( 'spacing', 'padding', 'bottom' ), - 'padding-left' => array( 'spacing', 'padding', 'left' ), - '--wp--style--root--padding' => array( 'spacing', 'padding' ), - '--wp--style--root--padding-top' => array( 'spacing', 'padding', 'top' ), - '--wp--style--root--padding-right' => array( 'spacing', 'padding', 'right' ), - '--wp--style--root--padding-bottom' => array( 'spacing', 'padding', 'bottom' ), - '--wp--style--root--padding-left' => array( 'spacing', 'padding', 'left' ), - 'text-decoration' => array( 'typography', 'textDecoration' ), - 'text-transform' => array( 'typography', 'textTransform' ), - 'filter' => array( 'filter', 'duotone' ), - 'box-shadow' => array( 'shadow' ), - ); - - /** - * The valid properties under the settings key. - * - * @since 5.8.0 As `ALLOWED_SETTINGS`. - * @since 5.9.0 Renamed from `ALLOWED_SETTINGS` to `VALID_SETTINGS`, - * added new properties for `border`, `color`, `spacing`, - * and `typography`, and renamed others according to the new schema. - * @since 6.0.0 Added `color.defaultDuotone`. - * @since 6.1.0 Added `layout.definitions` and `useRootPaddingAwareAlignments`. - * @since 6.2.0 Added `dimensions.minHeight`. - * @var array - */ - const VALID_SETTINGS = array( - 'appearanceTools' => null, - 'useRootPaddingAwareAlignments' => null, - 'border' => array( - 'color' => null, - 'radius' => null, - 'style' => null, - 'width' => null, - ), - 'color' => array( - 'background' => null, - 'custom' => null, - 'customDuotone' => null, - 'customGradient' => null, - 'defaultDuotone' => null, - 'defaultGradients' => null, - 'defaultPalette' => null, - 'duotone' => null, - 'gradients' => null, - 'link' => null, - 'palette' => null, - 'text' => null, - ), - 'custom' => null, - 'dimensions' => array( - 'minHeight' => null, - ), - 'layout' => array( - 'contentSize' => null, - 'definitions' => null, - 'wideSize' => null, - ), - 'spacing' => array( - 'customSpacingSize' => null, - 'spacingSizes' => null, - 'spacingScale' => null, - 'blockGap' => null, - 'margin' => null, - 'padding' => null, - 'units' => null, - ), - 'typography' => array( - 'fluid' => null, - 'customFontSize' => null, - 'dropCap' => null, - 'fontFamilies' => null, - 'fontSizes' => null, - 'fontStyle' => null, - 'fontWeight' => null, - 'letterSpacing' => null, - 'lineHeight' => null, - 'textDecoration' => null, - 'textTransform' => null, - ), - ); - - /** - * The valid properties under the styles key. - * - * @since 5.8.0 As `ALLOWED_STYLES`. - * @since 5.9.0 Renamed from `ALLOWED_STYLES` to `VALID_STYLES`, - * added new properties for `border`, `filter`, `spacing`, - * and `typography`. - * @since 6.1.0 Added new side properties for `border`, - * added new property `shadow`, - * updated `blockGap` to be allowed at any level. - * @var array - */ - const VALID_STYLES = array( - 'border' => array( - 'color' => null, - 'radius' => null, - 'style' => null, - 'width' => null, - 'top' => null, - 'right' => null, - 'bottom' => null, - 'left' => null, - ), - 'color' => array( - 'background' => null, - 'gradient' => null, - 'text' => null, - ), - 'dimensions' => array( - 'minHeight' => null, - ), - 'filter' => array( - 'duotone' => null, - ), - 'shadow' => null, - 'spacing' => array( - 'margin' => null, - 'padding' => null, - 'blockGap' => null, - ), - 'typography' => array( - 'fontFamily' => null, - 'fontSize' => null, - 'fontStyle' => null, - 'fontWeight' => null, - 'letterSpacing' => null, - 'lineHeight' => null, - 'textDecoration' => null, - 'textTransform' => null, - ), - ); -} diff --git a/lib/compat/wordpress-6.2/class-wp-theme-json-resolver-6-2.php b/lib/compat/wordpress-6.2/class-wp-theme-json-resolver-6-2.php index e10710e0f4709f..110c8bac7b147c 100644 --- a/lib/compat/wordpress-6.2/class-wp-theme-json-resolver-6-2.php +++ b/lib/compat/wordpress-6.2/class-wp-theme-json-resolver-6-2.php @@ -17,6 +17,86 @@ */ class WP_Theme_JSON_Resolver_6_2 extends WP_Theme_JSON_Resolver_6_1 { + /** + * Returns the custom post type that contains the user's origin config + * for the active theme or a void array if none are found. + * + * This can also create and return a new draft custom post type. + * + * @param WP_Theme $theme The theme object. If empty, it + * defaults to the active theme. + * @param bool $create_post Optional. Whether a new custom post + * type should be created if none are + * found. Default false. + * @param array $post_status_filter Optional. Filter custom post type by + * post status. Default `array( 'publish' )`, + * so it only fetches published posts. + * @return array Custom Post Type for the user's origin config. + */ + public static function get_user_data_from_wp_global_styles( $theme, $create_post = false, $post_status_filter = array( 'publish' ) ) { + if ( ! $theme instanceof WP_Theme ) { + $theme = wp_get_theme(); + } + + /* + * Bail early if the theme does not support a theme.json. + * + * Since wp_theme_has_theme_json only supports the active + * theme, the extra condition for whether $theme is the active theme is + * present here. + */ + if ( $theme->get_stylesheet() === get_stylesheet() && ! wp_theme_has_theme_json() ) { + return array(); + } + + $user_cpt = array(); + $post_type_filter = 'wp_global_styles'; + $stylesheet = $theme->get_stylesheet(); + $args = array( + 'posts_per_page' => 1, + 'orderby' => 'date', + 'order' => 'desc', + 'post_type' => $post_type_filter, + 'post_status' => $post_status_filter, + 'ignore_sticky_posts' => true, + 'no_found_rows' => true, + 'update_post_meta_cache' => false, + 'update_post_term_cache' => false, + 'tax_query' => array( + array( + 'taxonomy' => 'wp_theme', + 'field' => 'name', + 'terms' => $stylesheet, + ), + ), + ); + + $global_style_query = new WP_Query(); + $recent_posts = $global_style_query->query( $args ); + if ( count( $recent_posts ) === 1 ) { + $user_cpt = get_object_vars( $recent_posts[0] ); + } elseif ( $create_post ) { + $cpt_post_id = wp_insert_post( + array( + 'post_content' => '{"version": ' . WP_Theme_JSON::LATEST_SCHEMA . ', "isGlobalStylesUserThemeJSON": true }', + 'post_status' => 'publish', + 'post_title' => 'Custom Styles', // Do not make string translatable, see https://core.trac.wordpress.org/ticket/54518. + 'post_type' => $post_type_filter, + 'post_name' => sprintf( 'wp-global-styles-%s', urlencode( $stylesheet ) ), + 'tax_input' => array( + 'wp_theme' => array( $stylesheet ), + ), + ), + true + ); + if ( ! is_wp_error( $cpt_post_id ) ) { + $user_cpt = get_object_vars( get_post( $cpt_post_id ) ); + } + } + + return $user_cpt; + } + /** * Determines whether the active theme has a theme.json file. * @@ -32,4 +112,119 @@ public static function theme_has_support() { return wp_theme_has_theme_json(); } + /** + * Returns the data merged from multiple origins. + * + * There are four sources of data (origins) for a site: + * + * - default => WordPress + * - blocks => each one of the blocks provides data for itself + * - theme => the active theme + * - custom => data provided by the user + * + * The custom's has higher priority than the theme's, the theme's higher than blocks', + * and block's higher than default's. + * + * Unlike the getters + * {@link https://developer.wordpress.org/reference/classes/wp_theme_json_resolver/get_core_data/ get_core_data}, + * {@link https://developer.wordpress.org/reference/classes/wp_theme_json_resolver/get_theme_data/ get_theme_data}, + * and {@link https://developer.wordpress.org/reference/classes/wp_theme_json_resolver/get_user_data/ get_user_data}, + * this method returns data after it has been merged with the previous origins. + * This means that if the same piece of data is declared in different origins + * (default, blocks, theme, custom), the last origin overrides the previous. + * + * For example, if the user has set a background color + * for the paragraph block, and the theme has done it as well, + * the user preference wins. + * + * @param string $origin Optional. To what level should we merge data:'default', 'blocks', 'theme' or 'custom'. + * 'custom' is used as default value as well as fallback value if the origin is unknown. + * + * @return WP_Theme_JSON + */ + public static function get_merged_data( $origin = 'custom' ) { + if ( is_array( $origin ) ) { + _deprecated_argument( __FUNCTION__, '5.9.0' ); + } + + $result = static::get_core_data(); + if ( 'default' === $origin ) { + $result->set_spacing_sizes(); + return $result; + } + + $result->merge( static::get_block_data() ); + if ( 'blocks' === $origin ) { + return $result; + } + + $result->merge( static::get_theme_data() ); + if ( 'theme' === $origin ) { + $result->set_spacing_sizes(); + return $result; + } + + $result->merge( static::get_user_data() ); + $result->set_spacing_sizes(); + return $result; + } + + /** + * Returns the user's origin config. + * + * @since 6.2 Added check for the WP_Theme_JSON_Gutenberg class to prevent $user + * values set in core fron overriding the new custom css values added to VALID_STYLES. + * This does not need to be backported to core as the new VALID_STYLES[css] value will + * be added to core with 6.2. + * + * @return WP_Theme_JSON_Gutenberg Entity that holds styles for user data. + */ + public static function get_user_data() { + if ( null !== static::$user && static::$user instanceof WP_Theme_JSON_Gutenberg ) { + return static::$user; + } + + $config = array(); + $user_cpt = static::get_user_data_from_wp_global_styles( wp_get_theme() ); + + if ( array_key_exists( 'post_content', $user_cpt ) ) { + $decoded_data = json_decode( $user_cpt['post_content'], true ); + + $json_decoding_error = json_last_error(); + if ( JSON_ERROR_NONE !== $json_decoding_error ) { + trigger_error( 'Error when decoding a theme.json schema for user data. ' . json_last_error_msg() ); + /** + * Filters the data provided by the user for global styles & settings. + * + * @param WP_Theme_JSON_Data_Gutenberg Class to access and update the underlying data. + */ + $theme_json = apply_filters( 'wp_theme_json_data_user', new WP_Theme_JSON_Data_Gutenberg( $config, 'custom' ) ); + $config = $theme_json->get_data(); + return new WP_Theme_JSON_Gutenberg( $config, 'custom' ); + } + + // Very important to verify if the flag isGlobalStylesUserThemeJSON is true. + // If is not true the content was not escaped and is not safe. + if ( + is_array( $decoded_data ) && + isset( $decoded_data['isGlobalStylesUserThemeJSON'] ) && + $decoded_data['isGlobalStylesUserThemeJSON'] + ) { + unset( $decoded_data['isGlobalStylesUserThemeJSON'] ); + $config = $decoded_data; + } + } + + /** + * Filters the data provided by the user for global styles & settings. + * + * @param WP_Theme_JSON_Data_Gutenberg Class to access and update the underlying data. + */ + $theme_json = apply_filters( 'wp_theme_json_data_user', new WP_Theme_JSON_Data_Gutenberg( $config, 'custom' ) ); + $config = $theme_json->get_data(); + + static::$user = new WP_Theme_JSON_Gutenberg( $config, 'custom' ); + + return static::$user; + } } diff --git a/lib/compat/wordpress-6.2/default-filters.php b/lib/compat/wordpress-6.2/default-filters.php index 860cf9a8bf412a..4698ead7f9bc30 100644 --- a/lib/compat/wordpress-6.2/default-filters.php +++ b/lib/compat/wordpress-6.2/default-filters.php @@ -17,6 +17,9 @@ * @package gutenberg */ -add_action( 'switch_theme', 'wp_theme_has_theme_json_clean_cache' ); -add_action( 'start_previewing_theme', 'wp_theme_has_theme_json_clean_cache' ); -add_action( 'upgrader_process_complete', '_wp_theme_has_theme_json_clean_cache_upon_upgrading_active_theme' ); +/** + * When backporting to core, the existing filters hooked to WP_Theme_JSON_Resolver::clean_cached_data() + * need to be removed. + */ +add_action( 'start_previewing_theme', '_gutenberg_clean_theme_json_caches' ); +add_action( 'switch_theme', '_gutenberg_clean_theme_json_caches' ); diff --git a/lib/compat/wordpress-6.2/edit-form-blocks.php b/lib/compat/wordpress-6.2/edit-form-blocks.php new file mode 100644 index 00000000000000..5031550abc1541 --- /dev/null +++ b/lib/compat/wordpress-6.2/edit-form-blocks.php @@ -0,0 +1,24 @@ +get_stylesheet( array( 'variables' ), $origins ); + $types = array_diff( $types, array( 'variables' ) ); + } + + /* + * For the remaining types (presets, styles), we do consider origins: * - * @param WP_Upgrader $upgrader Instance of WP_Upgrader class. - * @param array $options Metadata that identifies the data that is updated. + * - themes without theme.json: only the classes for the presets defined by core + * - themes with theme.json: the presets and styles classes, both from core and the theme */ - function _wp_theme_has_theme_json_clean_cache_upon_upgrading_active_theme( $upgrader, $options ) { - // The cache only needs cleaning when the active theme was updated. - if ( - 'update' === $options['action'] && - 'theme' === $options['type'] && - ( isset( $options['themes'][ get_stylesheet() ] ) || isset( $options['themes'][ get_template() ] ) ) - ) { - wp_theme_has_theme_json_clean_cache(); + $styles_rest = ''; + if ( ! empty( $types ) ) { + /* + * We only use the default, theme, and custom origins. + * This is because styles for blocks origin are added + * at a later phase (render cycle) so we only render the ones in use. + * @see wp_add_global_styles_for_blocks + */ + $origins = array( 'default', 'theme', 'custom' ); + if ( ! $supports_theme_json ) { + $origins = array( 'default' ); } + $styles_rest = $tree->get_stylesheet( $types, $origins ); } + $stylesheet = $styles_variables . $styles_rest; + if ( $can_use_cached ) { + wp_cache_set( $cache_key, $stylesheet, $cache_group ); + } + return $stylesheet; +} + +/** + * Function to get the settings resulting of merging core, theme, and user data. + * + * @param array $path Path to the specific setting to retrieve. Optional. + * If empty, will return all settings. + * @param array $context { + * Metadata to know where to retrieve the $path from. Optional. + * + * @type string $block_name Which block to retrieve the settings from. + * If empty, it'll return the settings for the global context. + * @type string $origin Which origin to take data from. + * Valid values are 'all' (core, theme, and user) or 'base' (core and theme). + * If empty or unknown, 'all' is used. + * } + * + * @return array The settings to retrieve. + */ +function gutenberg_get_global_settings( $path = array(), $context = array() ) { + if ( ! empty( $context['block_name'] ) ) { + $new_path = array( 'blocks', $context['block_name'] ); + foreach ( $path as $subpath ) { + $new_path[] = $subpath; + } + $path = $new_path; + } + + // This is the default value when no origin is provided or when it is 'all'. + $origin = 'custom'; + if ( + ! wp_theme_has_theme_json() || + ( isset( $context['origin'] ) && 'base' === $context['origin'] ) + ) { + $origin = 'theme'; + } + + $cache_group = 'theme_json'; + $cache_key = 'gutenberg_get_global_settings_' . $origin; + $settings = wp_cache_get( $cache_key, $cache_group ); + + if ( false === $settings || WP_DEBUG ) { + $settings = WP_Theme_JSON_Resolver_Gutenberg::get_merged_data( $origin )->get_settings(); + wp_cache_set( $cache_key, $settings, $cache_group ); + } + + return _wp_array_get( $settings, $path, $settings ); +} + +/** + * Private function to clean the caches used by gutenberg_get_global_settings method. + * + * @access private + */ +function _gutenberg_clean_theme_json_caches() { + wp_cache_delete( 'wp_theme_has_theme_json', 'theme_json' ); + wp_cache_delete( 'gutenberg_get_global_stylesheet', 'theme_json' ); + wp_cache_delete( 'gutenberg_get_global_settings_custom', 'theme_json' ); + wp_cache_delete( 'gutenberg_get_global_settings_theme', 'theme_json' ); + WP_Theme_JSON_Resolver_Gutenberg::clean_cached_data(); +} + +/** + * Tell the cache mechanisms not to persist theme.json data across requests. + * The data stored under this cache group: + * + * - wp_theme_has_theme_json + * - gutenberg_get_global_settings + * - gutenberg_get_global_stylesheet + * + * There is some hooks consumers can use to modify parts + * of the theme.json logic. + * See https://make.wordpress.org/core/2022/10/10/filters-for-theme-json-data/ + * + * The rationale to make this cache group non persistent is to make sure derived data + * from theme.json is always fresh from the potential modifications done via hooks + * that can use dynamic data (modify the stylesheet depending on some option, + * or settings depending on user permissions, etc.). + * + * A different alternative considered was to invalidate the cache upon certain + * events such as options add/update/delete, user meta, etc. + * It was judged not enough, hence this approach. + * See https://github.com/WordPress/gutenberg/pull/45372 + */ +function _gutenberg_add_non_persistent_theme_json_cache_group() { + wp_cache_add_non_persistent_groups( 'theme_json' ); } +add_action( 'plugins_loaded', '_gutenberg_add_non_persistent_theme_json_cache_group' ); diff --git a/lib/compat/wordpress-6.2/rest-api.php b/lib/compat/wordpress-6.2/rest-api.php index 023a79e3db95fb..12f7afda3b4d5d 100644 --- a/lib/compat/wordpress-6.2/rest-api.php +++ b/lib/compat/wordpress-6.2/rest-api.php @@ -23,6 +23,15 @@ function gutenberg_register_rest_pattern_directory() { } add_action( 'rest_api_init', 'gutenberg_register_rest_pattern_directory' ); +/** + * Registers the block patterns REST API routes. + */ +function gutenberg_register_rest_block_patterns() { + $block_patterns = new Gutenberg_REST_Block_Patterns_Controller_6_2(); + $block_patterns->register_routes(); +} +add_action( 'rest_api_init', 'gutenberg_register_rest_block_patterns' ); + /** * Add extra collection params to pattern directory requests. * @@ -83,3 +92,27 @@ function gutenberg_pattern_directory_collection_params_6_2( $query_params ) { return $query_params; } add_filter( 'rest_pattern_directory_collection_params', 'gutenberg_pattern_directory_collection_params_6_2' ); + +/** + * Registers the Global Styles REST API routes. + */ +function gutenberg_register_global_styles_endpoints() { + $editor_settings = new Gutenberg_REST_Global_Styles_Controller_6_2(); + $editor_settings->register_routes(); +} +add_action( 'rest_api_init', 'gutenberg_register_global_styles_endpoints' ); + +/** + * Updates REST API response for the sidebars and marks them as 'inactive'. + * + * Note: This can be a part of the `prepare_item_for_response` in `class-wp-rest-sidebars-controller.php`. + * + * @param WP_REST_Response $response The sidebar response object. + * @return WP_REST_Response $response Updated response object. + */ +function gutenberg_modify_rest_sidebars_response( $response ) { + $response->data['status'] = wp_is_block_theme() ? 'inactive' : 'active'; + + return $response; +} +add_filter( 'rest_prepare_sidebar', 'gutenberg_modify_rest_sidebars_response' ); diff --git a/lib/compat/wordpress-6.2/script-loader.php b/lib/compat/wordpress-6.2/script-loader.php index 9d67cbd3ef1ad8..e08bfa6e46ca8e 100644 --- a/lib/compat/wordpress-6.2/script-loader.php +++ b/lib/compat/wordpress-6.2/script-loader.php @@ -63,3 +63,82 @@ static function () use ( $style ) { $priority ); } + +/** + * Sets the content assets for the block editor. + * + * Note for core merge: see inline comment on what's been updated. + */ +function gutenberg_resolve_assets_override() { + global $pagenow; + + $script_handles = array(); + // Note for core merge: only 'wp-edit-blocks' should be in this array. + $style_handles = array( + 'wp-edit-blocks', + ); + + if ( current_theme_supports( 'wp-block-styles' ) ) { + $style_handles[] = 'wp-block-library-theme'; + } + + if ( 'widgets.php' === $pagenow || 'customize.php' === $pagenow ) { + $style_handles[] = 'wp-widgets'; + $style_handles[] = 'wp-edit-widgets'; + } + + $block_registry = WP_Block_Type_Registry::get_instance(); + + foreach ( $block_registry->get_all_registered() as $block_type ) { + // In older WordPress versions, like 6.0, these properties are not defined. + if ( isset( $block_type->style_handles ) && is_array( $block_type->style_handles ) ) { + $style_handles = array_merge( $style_handles, $block_type->style_handles ); + } + + if ( isset( $block_type->editor_style_handles ) && is_array( $block_type->editor_style_handles ) ) { + $style_handles = array_merge( $style_handles, $block_type->editor_style_handles ); + } + + if ( isset( $block_type->script_handles ) && is_array( $block_type->script_handles ) ) { + $script_handles = array_merge( $script_handles, $block_type->script_handles ); + } + } + + $style_handles = array_unique( $style_handles ); + $done = wp_styles()->done; + + ob_start(); + + // We do not need reset styles for the iframed editor. + wp_styles()->done = array( 'wp-reset-editor-styles' ); + wp_styles()->do_items( $style_handles ); + wp_styles()->done = $done; + + $styles = ob_get_clean(); + + $script_handles = array_unique( $script_handles ); + $done = wp_scripts()->done; + + ob_start(); + + wp_scripts()->done = array(); + wp_scripts()->do_items( $script_handles ); + wp_scripts()->done = $done; + + $scripts = ob_get_clean(); + + return array( + 'styles' => $styles, + 'scripts' => $scripts, + ); +} + +add_filter( + 'block_editor_settings_all', + function( $settings ) { + // We must override what core is passing now. + $settings['__unstableResolvedAssets'] = gutenberg_resolve_assets_override(); + return $settings; + }, + 100 +); diff --git a/lib/compat/wordpress-6.2/site-editor.php b/lib/compat/wordpress-6.2/site-editor.php new file mode 100644 index 00000000000000..b6246e49c6d113 --- /dev/null +++ b/lib/compat/wordpress-6.2/site-editor.php @@ -0,0 +1,24 @@ +post ) ) { + return $settings; + } + + unset( $settings['__unstableHomeTemplate'] ); + + return $settings; +} +add_filter( 'block_editor_settings_all', 'gutenberg_site_editor_unset_homepage_setting', 10, 2 ); diff --git a/lib/compat/wordpress-6.2/theme.php b/lib/compat/wordpress-6.2/theme.php new file mode 100644 index 00000000000000..79d55206449472 --- /dev/null +++ b/lib/compat/wordpress-6.2/theme.php @@ -0,0 +1,23 @@ +is_block_theme() ) { + set_theme_mod( 'wp_legacy_sidebars', $wp_registered_sidebars ); + } +} +add_action( 'switch_theme', 'gutenberg_set_legacy_sidebars', 10, 2 ); diff --git a/lib/compat/wordpress-6.2/widgets.php b/lib/compat/wordpress-6.2/widgets.php new file mode 100644 index 00000000000000..19591ae64607e3 --- /dev/null +++ b/lib/compat/wordpress-6.2/widgets.php @@ -0,0 +1,32 @@ + array( 'mobile' ), ), + '__experimentalBlockInspectorTabs' => array( + 'description' => __( 'Block inspector tab display overrides.', 'gutenberg' ), + 'type' => 'object', + 'context' => array( 'post-editor', 'site-editor', 'widgets-editor' ), + ), + + '__experimentalBlockInspectorAnimation' => array( + 'description' => __( 'Whether to enable animation when showing and hiding the block inspector.', 'gutenberg' ), + 'type' => 'object', + 'context' => array( 'site-editor' ), + ), + 'alignWide' => array( 'description' => __( 'Enable/Disable Wide/Full Alignments.', 'gutenberg' ), 'type' => 'boolean', diff --git a/lib/experimental/class-wp-theme-json-gutenberg.php b/lib/experimental/class-wp-theme-json-gutenberg.php deleted file mode 100644 index a16697dff07d07..00000000000000 --- a/lib/experimental/class-wp-theme-json-gutenberg.php +++ /dev/null @@ -1,31 +0,0 @@ -get( 'TextDomain' ) ); + if ( null === static::$theme || ! static::has_same_registered_blocks( 'theme' ) ) { + $theme_json_file = static::get_file_path_from_theme( 'theme.json' ); + $wp_theme = wp_get_theme(); + if ( '' !== $theme_json_file ) { + $theme_json_data = static::read_json_file( $theme_json_file ); + $theme_json_data = static::translate( $theme_json_data, $wp_theme->get( 'TextDomain' ) ); + } else { + $theme_json_data = array(); + } $theme_json_data = gutenberg_add_registered_webfonts_to_theme_json( $theme_json_data ); /** @@ -48,17 +54,21 @@ public static function get_theme_data( $deprecated = array(), $settings = array( $theme_json_data = $theme_json->get_data(); static::$theme = new WP_Theme_JSON_Gutenberg( $theme_json_data ); - if ( wp_get_theme()->parent() ) { + if ( $wp_theme->parent() ) { // Get parent theme.json. - $parent_theme_json_data = static::read_json_file( static::get_file_path_from_theme( 'theme.json', true ) ); - $parent_theme_json_data = static::translate( $parent_theme_json_data, wp_get_theme()->parent()->get( 'TextDomain' ) ); - $parent_theme_json_data = gutenberg_add_registered_webfonts_to_theme_json( $parent_theme_json_data ); - $parent_theme = new WP_Theme_JSON_Gutenberg( $parent_theme_json_data ); - - // Merge the child theme.json into the parent theme.json. - // The child theme takes precedence over the parent. - $parent_theme->merge( static::$theme ); - static::$theme = $parent_theme; + $parent_theme_json_file = static::get_file_path_from_theme( 'theme.json', true ); + if ( '' !== $parent_theme_json_file ) { + $parent_theme_json_data = static::read_json_file( $parent_theme_json_file ); + $parent_theme_json_data = gutenberg_add_registered_webfonts_to_theme_json( $parent_theme_json_data ); + $parent_theme = new WP_Theme_JSON_Gutenberg( $parent_theme_json_data ); + + /* + * Merge the child theme.json into the parent theme.json. + * The child theme takes precedence over the parent. + */ + $parent_theme->merge( static::$theme ); + static::$theme = $parent_theme; + } } } @@ -72,7 +82,7 @@ public static function get_theme_data( $deprecated = array(), $settings = array( * So we take theme supports, transform it to theme.json shape * and merge the static::$theme upon that. */ - $theme_support_data = WP_Theme_JSON_Gutenberg::get_from_editor_settings( get_default_block_editor_settings() ); + $theme_support_data = WP_Theme_JSON_Gutenberg::get_from_editor_settings( gutenberg_get_legacy_theme_supports_for_theme_json() ); if ( ! wp_theme_has_theme_json() ) { if ( ! isset( $theme_support_data['settings']['color'] ) ) { $theme_support_data['settings']['color'] = array(); @@ -164,47 +174,4 @@ private static function remove_JSON_comments( $array ) { return $array; } - /** - * Returns the data merged from multiple origins. - * - * There are three sources of data (origins) for a site: - * default, theme, and custom. The custom's has higher priority - * than the theme's, and the theme's higher than default's. - * - * Unlike the getters {@link get_core_data}, - * {@link get_theme_data}, and {@link get_user_data}, - * this method returns data after it has been merged - * with the previous origins. This means that if the same piece of data - * is declared in different origins (user, theme, and core), - * the last origin overrides the previous. - * - * For example, if the user has set a background color - * for the paragraph block, and the theme has done it as well, - * the user preference wins. - * - * @since 5.8.0 - * @since 5.9.0 Added user data, removed the `$settings` parameter, - * added the `$origin` parameter. - * - * @param string $origin Optional. To what level should we merge data. - * Valid values are 'theme' or 'custom'. Default 'custom'. - * @return WP_Theme_JSON - */ - public static function get_merged_data( $origin = 'custom' ) { - if ( is_array( $origin ) ) { - _deprecated_argument( __FUNCTION__, '5.9' ); - } - - $result = new WP_Theme_JSON_Gutenberg(); - $result->merge( static::get_core_data() ); - $result->merge( static::get_block_data() ); - $result->merge( static::get_theme_data() ); - if ( 'custom' === $origin ) { - $result->merge( static::get_user_data() ); - } - // Generate the default spacing sizes presets. - $result->set_spacing_sizes(); - - return $result; - } } diff --git a/lib/experimental/class-wp-webfonts.php b/lib/experimental/class-wp-webfonts.php index e40aca9e8671f8..0c0cb06a27ffb3 100644 --- a/lib/experimental/class-wp-webfonts.php +++ b/lib/experimental/class-wp-webfonts.php @@ -178,7 +178,9 @@ public function enqueue_webfont( $font_family_name ) { */ public static function get_font_slug( $to_convert ) { if ( is_array( $to_convert ) ) { - if ( isset( $to_convert['font-family'] ) ) { + if ( isset( $to_convert['slug'] ) ) { + return $to_convert['slug']; + } elseif ( isset( $to_convert['font-family'] ) ) { $to_convert = $to_convert['font-family']; } elseif ( isset( $to_convert['fontFamily'] ) ) { $to_convert = $to_convert['fontFamily']; @@ -188,6 +190,11 @@ public static function get_font_slug( $to_convert ) { } } + // If the font-family is a comma-separated list (example: "Inter, sans-serif" ), use just the first font. + if ( strpos( $to_convert, ',' ) !== false ) { + $to_convert = explode( ',', $to_convert )[0]; + } + return sanitize_title( $to_convert ); } diff --git a/lib/experimental/html/class-wp-html-span.php b/lib/experimental/html/class-wp-html-span.php new file mode 100644 index 00000000000000..39e603662b17b9 --- /dev/null +++ b/lib/experimental/html/class-wp-html-span.php @@ -0,0 +1,52 @@ +start = $start; + $this->end = $end; + } +} diff --git a/lib/experimental/html/class-wp-html-tag-processor.php b/lib/experimental/html/class-wp-html-tag-processor.php index 6e72fc38ff4118..affbb6fb27b5c1 100644 --- a/lib/experimental/html/class-wp-html-tag-processor.php +++ b/lib/experimental/html/class-wp-html-tag-processor.php @@ -180,6 +180,25 @@ * @since 6.2.0 */ class WP_HTML_Tag_Processor { + /** + * The maximum number of bookmarks allowed to exist at + * any given time. + * + * @see set_bookmark(); + * @since 6.2.0 + * @var int + */ + const MAX_BOOKMARKS = 10; + + /** + * Maximum number of times seek() can be called. + * Prevents accidental infinite loops. + * + * @see seek() + * @since 6.2.0 + * @var int + */ + const MAX_SEEK_OPS = 1000; /** * The HTML document to parse. @@ -221,6 +240,14 @@ class WP_HTML_Tag_Processor { */ private $sought_match_offset; + /** + * Whether to visit tag closers, e.g. , when walking an input document. + * + * @since 6.2.0 + * @var boolean + */ + private $stop_on_tag_closers; + /** * The updated HTML document. * @@ -276,6 +303,29 @@ class WP_HTML_Tag_Processor { */ private $tag_name_length; + /** + * Byte offset in input document where current tag token ends. + * + * Example: + * ``` + *
... + * 0 1 | + * 01234567890123456 + * --- tag name ends at 14 + * ``` + * + * @since 6.2.0 + * @var ?int + */ + private $tag_ends_at; + + /** + * Whether the current tag is an opening tag, e.g.
, or a closing tag, e.g.
. + * + * @var boolean + */ + private $is_closing_tag; + /** * Lazily-built index of attributes found within an HTML tag, keyed by the attribute name. * @@ -318,11 +368,11 @@ class WP_HTML_Tag_Processor { * * Example: * - * // Add the `WP-block-group` class, remove the `WP-group` class. - * $class_changes = [ + * // Add the `wp-block-group` class, remove the `wp-group` class. + * $classname_updates = [ * // Indexed by a comparable class name - * 'wp-block-group' => new WP_Class_Name_Operation( 'WP-block-group', WP_Class_Name_Operation::ADD ), - * 'wp-group' => new WP_Class_Name_Operation( 'WP-group', WP_Class_Name_Operation::REMOVE ) + * 'wp-block-group' => WP_HTML_Tag_Processor::ADD_CLASS, + * 'wp-group' => WP_HTML_Tag_Processor::REMOVE_CLASS * ]; * * @@ -331,6 +381,15 @@ class WP_HTML_Tag_Processor { */ 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[] + */ + private $bookmarks = array(); + const ADD_CLASS = true; const REMOVE_CLASS = false; const SKIP_CLASS = null; @@ -365,6 +424,16 @@ class WP_HTML_Tag_Processor { */ private $attribute_updates = array(); + /** + * Tracks how many times we've performed a `seek()` + * so that we can prevent accidental infinite loops. + * + * @see seek + * @since 6.2.0 + * @var int + */ + private $seek_count = 0; + /** * Constructor. * @@ -412,7 +481,16 @@ public function next_tag( $query = null ) { return false; } - $this->parse_tag_opener_attributes(); + while ( $this->parse_next_attribute() ) { + continue; + } + + $tag_ends_at = strpos( $this->html, '>', $this->parsed_bytes ); + if ( false === $tag_ends_at ) { + return false; + } + $this->tag_ends_at = $tag_ends_at; + $this->parsed_bytes = $tag_ends_at; if ( $this->matches() ) { ++$already_found; @@ -439,6 +517,123 @@ public function next_tag( $query = null ) { return true; } + + /** + * 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 thing. + * + * Example: + * ``` + *
  • One
  • Two
  • Three
+ * ^^^^ + * want to note this last item + * + * $p = new WP_HTML_Tag_Processor( $html ); + * $in_list = false; + * while ( $p->next_tag( [ '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' ); + * } + * } + * ``` + * + * Because bookmarks maintain their position they don't + * expose any internal offsets for the HTML document + * and can't be used with normal string functions. + * + * Because bookmarks allocate memory and require processing + * for every applied update they are limited and require + * a name. They should not be created inside a loop. + * + * 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. + * + * @param string $name Identifies this particular bookmark. + * @return false|void + * @throws Exception Throws on invalid bookmark name if WP_DEBUG set. + */ + public function set_bookmark( $name ) { + if ( null === $this->tag_name_starts_at ) { + return false; + } + + if ( ! array_key_exists( $name, $this->bookmarks ) && count( $this->bookmarks ) >= self::MAX_BOOKMARKS ) { + if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) { + throw new Exception( "Tried to jump to a non-existent HTML bookmark {$name}." ); + } + return false; + } + + $this->bookmarks[ $name ] = new WP_HTML_Span( + $this->tag_name_starts_at - 1, + $this->tag_ends_at + ); + + return true; + } + + + /** + * Removes a bookmark if you no longer need to use it. + * + * Releasing a bookmark frees up the small performance + * overhead they require, mainly in the form of compute + * costs when modifying the document. + * + * @param string $name Name of the bookmark to remove. + * @return bool + */ + public function release_bookmark( $name ) { + if ( ! array_key_exists( $name, $this->bookmarks ) ) { + return false; + } + + unset( $this->bookmarks[ $name ] ); + + return true; + } + + /** * Skips the contents of the title and textarea tags until an appropriate * tag closer is found. @@ -495,7 +690,9 @@ private function skip_rcdata( $tag_name ) { continue; } - $this->skip_tag_closer_attributes(); + while ( $this->parse_next_attribute() ) { + continue; + } $at = $this->parsed_bytes; if ( $at >= strlen( $this->html ) ) { return false; @@ -620,12 +817,14 @@ private function skip_script_data() { if ( $is_closing ) { $this->parsed_bytes = $at; - $this->skip_tag_closer_attributes(); - if ( $this->parsed_bytes >= $doc_length ) { return false; } + while ( $this->parse_next_attribute() ) { + continue; + } + if ( '>' === $html[ $this->parsed_bytes ] ) { ++$this->parsed_bytes; return true; @@ -656,6 +855,13 @@ private function parse_next_tag() { return false; } + if ( '/' === $this->html[ $at + 1 ] ) { + $this->is_closing_tag = true; + $at++; + } else { + $this->is_closing_tag = false; + } + /* * HTML tag names must start with [a-zA-Z] otherwise they are not tags. * For example, "<3" is rendered as text, not a tag opener. This means @@ -777,35 +983,12 @@ private function parse_next_tag() { return false; } - /** - * Parses all attributes of the current tag. - * - * @since 6.2.0 - */ - private function parse_tag_opener_attributes() { - while ( $this->parse_next_attribute() ) { - continue; - } - } - - /** - * Skips all attributes of the current tag. - * - * @since 6.2.0 - */ - private function skip_tag_closer_attributes() { - while ( $this->parse_next_attribute( 'tag-closer' ) ) { - continue; - } - } - /** * Parses the next attribute. * - * @param string $context tag-opener or tag-closer. * @since 6.2.0 */ - private function parse_next_attribute( $context = 'tag-opener' ) { + private function parse_next_attribute() { // Skip whitespace and slashes. $this->parsed_bytes += strspn( $this->html, " \t\f\r\n/", $this->parsed_bytes ); if ( $this->parsed_bytes >= strlen( $this->html ) ) { @@ -872,7 +1055,7 @@ private function parse_next_attribute( $context = 'tag-opener' ) { return false; } - if ( 'tag-opener' !== $context ) { + if ( $this->is_closing_tag ) { return true; } @@ -914,6 +1097,8 @@ private function after_tag() { $this->apply_attributes_updates(); $this->tag_name_starts_at = null; $this->tag_name_length = null; + $this->tag_ends_at = null; + $this->is_closing_tag = null; $this->attributes = array(); } @@ -1074,9 +1259,77 @@ private function apply_attributes_updates() { $this->updated_bytes = $diff->end; } + foreach ( $this->bookmarks as $bookmark ) { + /** + * As we loop through $this->attribute_updates, we keep comparing + * $bookmark->start and $bookmark->end to $diff->start. We can't + * change it and still expect the correct result, so let's accumulate + * the deltas separately and apply them all at once after the loop. + */ + $head_delta = 0; + $tail_delta = 0; + + foreach ( $this->attribute_updates as $diff ) { + $update_head = $bookmark->start >= $diff->start; + $update_tail = $bookmark->end >= $diff->start; + + if ( ! $update_head && ! $update_tail ) { + break; + } + + $delta = strlen( $diff->text ) - ( $diff->end - $diff->start ); + + if ( $update_head ) { + $head_delta += $delta; + } + + if ( $update_tail ) { + $tail_delta += $delta; + } + } + + $bookmark->start += $head_delta; + $bookmark->end += $tail_delta; + } + $this->attribute_updates = array(); } + /** + * Move the current pointer 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. + * + * @param string $bookmark_name Jump to the place in the document identified by this bookmark name. + * @return bool + * @throws Exception Throws on invalid bookmark name if WP_DEBUG set. + */ + public function seek( $bookmark_name ) { + if ( ! array_key_exists( $bookmark_name, $this->bookmarks ) ) { + if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) { + throw new Exception( 'Invalid bookmark name' ); + } + return false; + } + + if ( ++$this->seek_count > self::MAX_SEEK_OPS ) { + if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) { + throw new Exception( 'Too many calls to seek() - this can lead to performance issues.' ); + } + 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->parsed_bytes = $this->bookmarks[ $bookmark_name ]->start; + $this->updated_bytes = $this->parsed_bytes; + $this->updated_html = substr( $this->html, 0, $this->updated_bytes ); + return $this->next_tag(); + } + /** * Sort function to arrange objects with a start property in ascending order. * @@ -1159,6 +1412,25 @@ public function get_tag() { return strtoupper( $tag_name ); } + /** + * Indicates if the current tag token is a tag closer. + * + * Example: + * + * $p = new WP_HTML_Tag_Processor( '
' ); + * $p->next_tag( [ 'tag_name' => 'div', 'tag_closers' => 'visit' ] ); + * $p->is_tag_closer() === false; + * + * $p->next_tag( [ 'tag_name' => 'div', 'tag_closers' => 'visit' ] ); + * $p->is_tag_closer() === true; + *
+ * + * @return bool + */ + public function is_tag_closer() { + return $this->is_closing_tag; + } + /** * Updates or creates a new attribute on the currently matched tag with the value passed. * @@ -1175,8 +1447,8 @@ public function get_tag() { * @throws Exception When WP_DEBUG is true and the attribute name is invalid. */ public function set_attribute( $name, $value ) { - if ( null === $this->tag_name_starts_at ) { - return; + if ( $this->is_closing_tag || null === $this->tag_name_starts_at ) { + return false; } /* @@ -1216,7 +1488,7 @@ public function set_attribute( $name, $value ) { ']~Ssu', $name ) ) { - if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) { + if ( WP_DEBUG ) { throw new Exception( 'Invalid attribute name' ); } @@ -1286,8 +1558,8 @@ public function set_attribute( $name, $value ) { * @param string $name The attribute name to remove. */ public function remove_attribute( $name ) { - if ( ! isset( $this->attributes[ $name ] ) ) { - return; + if ( $this->is_closing_tag || ! isset( $this->attributes[ $name ] ) ) { + return false; } /* @@ -1316,6 +1588,10 @@ public function remove_attribute( $name ) { * @param string $class_name The class name to add. */ public function add_class( $class_name ) { + if ( $this->is_closing_tag ) { + return false; + } + if ( null !== $this->tag_name_starts_at ) { $this->classname_updates[ $class_name ] = self::ADD_CLASS; } @@ -1329,6 +1605,10 @@ public function add_class( $class_name ) { * @param string $class_name The class name to remove. */ public function remove_class( $class_name ) { + if ( $this->is_closing_tag ) { + return false; + } + if ( null !== $this->tag_name_starts_at ) { $this->classname_updates[ $class_name ] = self::REMOVE_CLASS; } @@ -1354,45 +1634,31 @@ public function __toString() { * @return string The processed HTML. */ public function get_updated_html() { - // Short-circuit if there are no updates to apply. + // Short-circuit if there are no new updates to apply. if ( ! count( $this->classname_updates ) && ! count( $this->attribute_updates ) ) { return $this->updated_html . substr( $this->html, $this->updated_bytes ); } - /* - * Parsing is in progress – let's apply the attribute updates without moving on to the next tag. - * - * In practice: - * 1. Apply the attributes updates to the original HTML - * 2. Replace the original HTML with the updated HTML - * 3. Point this tag processor to the current tag name's end in that updated HTML - */ - - // Find tag name's end in the updated markup. - $markup_updated_up_to_a_tag_name_end = $this->updated_html . substr( $this->html, $this->updated_bytes, $this->tag_name_starts_at + $this->tag_name_length - $this->updated_bytes ); - $updated_tag_name_ends_at = strlen( $markup_updated_up_to_a_tag_name_end ); - $updated_tag_name_starts_at = $updated_tag_name_ends_at - $this->tag_name_length; + // Otherwise: apply the updates, rewind before the current tag, and parse it again. + $delta_between_updated_html_end_and_current_tag_end = substr( + $this->html, + $this->updated_bytes, + $this->tag_name_starts_at + $this->tag_name_length - $this->updated_bytes + ); + $updated_html_up_to_current_tag_name_end = $this->updated_html . $delta_between_updated_html_end_and_current_tag_end; - // Apply attributes updates. - $this->updated_html = $markup_updated_up_to_a_tag_name_end; - $this->updated_bytes = $this->tag_name_starts_at + $this->tag_name_length; + // 1. Apply the attributes updates to the original HTML $this->class_name_updates_to_attributes_updates(); $this->apply_attributes_updates(); - // Replace $this->html with the updated markup. - $this->html = $this->updated_html . substr( $this->html, $this->updated_bytes ); - - // Rewind this processor to the tag name's end. - $this->tag_name_starts_at = $updated_tag_name_starts_at; - $this->parsed_bytes = $updated_tag_name_ends_at; - - // Restore the previous version of the updated_html as we are not finished with the current_tag yet. - $this->updated_html = $markup_updated_up_to_a_tag_name_end; - $this->updated_bytes = $updated_tag_name_ends_at; + // 2. Replace the original HTML with the updated HTML + $this->html = $this->updated_html . substr( $this->html, $this->updated_bytes ); + $this->updated_html = $updated_html_up_to_current_tag_name_end; + $this->updated_bytes = strlen( $this->updated_html ); - // Parse the attributes in the updated markup. - $this->attributes = array(); - $this->parse_tag_opener_attributes(); + // 3. Point this tag processor at the original tag opener and consume it + $this->parsed_bytes = strlen( $updated_html_up_to_current_tag_name_end ) - $this->tag_name_length - 2; + $this->next_tag(); return $this->html; } @@ -1407,6 +1673,7 @@ public function get_updated_html() { * * @type string|null $tag_name Which tag to find, or `null` for "any 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 ) { @@ -1418,6 +1685,7 @@ private function parse_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 ) ) { @@ -1441,6 +1709,10 @@ private function parse_query( $query ) { 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']; + } } @@ -1452,6 +1724,10 @@ private function parse_query( $query ) { * @return boolean */ private function matches() { + if ( $this->is_closing_tag && ! $this->stop_on_tag_closers ) { + return false; + } + // Do we match a case-insensitive HTML tag name? if ( null !== $this->sought_tag_name ) { /* diff --git a/lib/experimental/html/index.php b/lib/experimental/html/index.php index e7d41f8cdf4863..a31dbaf48c6b2a 100644 --- a/lib/experimental/html/index.php +++ b/lib/experimental/html/index.php @@ -7,5 +7,6 @@ // All class files necessary for the HTML Tag Processor. require_once __DIR__ . '/class-wp-html-attribute-token.php'; +require_once __DIR__ . '/class-wp-html-span.php'; require_once __DIR__ . '/class-wp-html-text-replacement.php'; require_once __DIR__ . '/class-wp-html-tag-processor.php'; diff --git a/lib/experimental/kses.php b/lib/experimental/kses.php new file mode 100644 index 00000000000000..fd7531617939fc --- /dev/null +++ b/lib/experimental/kses.php @@ -0,0 +1,66 @@ + (int) get_option( 'use_balanceTags' ) !== 1 || is_wp_version_compatible( '5.9' ), - ); - return array_merge( $settings, $experiments_settings ); -} - -add_filter( 'block_editor_settings_all', 'gutenberg_experiments_editor_settings' ); diff --git a/lib/load.php b/lib/load.php index 70dbf944535255..b700ec3f2a11d1 100644 --- a/lib/load.php +++ b/lib/load.php @@ -35,25 +35,18 @@ function gutenberg_is_experiment_enabled( $name ) { require_once __DIR__ . '/experimental/class-wp-rest-block-editor-settings-controller.php'; } - // WordPress 6.0 compat. - require_once __DIR__ . '/compat/wordpress-6.0/class-gutenberg-rest-global-styles-controller.php'; - require_once __DIR__ . '/compat/wordpress-6.0/class-gutenberg-rest-pattern-directory-controller-6-0.php'; - require_once __DIR__ . '/compat/wordpress-6.0/class-gutenberg-rest-edit-site-export-controller.php'; - if ( ! class_exists( 'WP_REST_Block_Pattern_Categories_Controller' ) ) { - require_once __DIR__ . '/compat/wordpress-6.0/class-wp-rest-block-pattern-categories-controller.php'; - } - require_once __DIR__ . '/compat/wordpress-6.0/rest-api.php'; - // WordPress 6.1 compat. - require_once __DIR__ . '/compat/wordpress-6.1/class-gutenberg-rest-block-patterns-controller.php'; + require_once __DIR__ . '/compat/wordpress-6.1/class-gutenberg-rest-block-patterns-controller-6-1.php'; require_once __DIR__ . '/compat/wordpress-6.1/class-gutenberg-rest-templates-controller.php'; require_once __DIR__ . '/compat/wordpress-6.1/rest-api.php'; // WordPress 6.2 compat. + require_once __DIR__ . '/compat/wordpress-6.2/class-gutenberg-rest-block-patterns-controller-6-2.php'; require_once __DIR__ . '/compat/wordpress-6.2/class-gutenberg-rest-block-pattern-categories-controller.php'; require_once __DIR__ . '/compat/wordpress-6.2/class-gutenberg-rest-pattern-directory-controller-6-2.php'; require_once __DIR__ . '/compat/wordpress-6.2/rest-api.php'; require_once __DIR__ . '/compat/wordpress-6.2/block-patterns.php'; + require_once __DIR__ . '/compat/wordpress-6.2/class-gutenberg-rest-global-styles-controller-6-2.php'; // Experimental. if ( ! class_exists( 'WP_Rest_Customizer_Nonces' ) ) { @@ -67,36 +60,17 @@ function gutenberg_is_experiment_enabled( $name ) { // Gutenberg plugin compat. require __DIR__ . '/compat/plugin/edit-site-routes-backwards-compat.php'; -// WordPress 6.0 compat. -require __DIR__ . '/compat/wordpress-6.0/block-gallery.php'; -require __DIR__ . '/compat/wordpress-6.0/block-editor-settings.php'; -require __DIR__ . '/compat/wordpress-6.0/get-global-styles-and-settings.php'; -require __DIR__ . '/compat/wordpress-6.0/render-svg-filters.php'; -require __DIR__ . '/compat/wordpress-6.0/post-lock.php'; -require __DIR__ . '/compat/wordpress-6.0/blocks.php'; -require __DIR__ . '/compat/wordpress-6.0/block-template-utils.php'; -require __DIR__ . '/compat/wordpress-6.0/functions.php'; -require __DIR__ . '/compat/wordpress-6.0/class-wp-theme-json-6-0.php'; -require __DIR__ . '/compat/wordpress-6.0/class-wp-theme-json-resolver-6-0.php'; -require __DIR__ . '/compat/wordpress-6.0/block-patterns.php'; -require __DIR__ . '/compat/wordpress-6.0/site-editor.php'; -require __DIR__ . '/compat/wordpress-6.0/edit-form-blocks.php'; -require __DIR__ . '/compat/wordpress-6.0/block-patterns-update.php'; -require __DIR__ . '/compat/wordpress-6.0/client-assets.php'; - // WordPress 6.1 compat. -require __DIR__ . '/compat/wordpress-6.1/blocks.php'; require __DIR__ . '/compat/wordpress-6.1/block-editor-settings.php'; +require __DIR__ . '/compat/wordpress-6.1/blocks.php'; require __DIR__ . '/compat/wordpress-6.1/persisted-preferences.php'; require __DIR__ . '/compat/wordpress-6.1/get-global-styles-and-settings.php'; require __DIR__ . '/compat/wordpress-6.1/class-wp-theme-json-data-gutenberg.php'; -require __DIR__ . '/compat/wordpress-6.1/class-wp-theme-json-6-1.php'; require __DIR__ . '/compat/wordpress-6.1/class-wp-theme-json-resolver-6-1.php'; require __DIR__ . '/compat/wordpress-6.1/block-template-utils.php'; require __DIR__ . '/compat/wordpress-6.1/wp-theme-get-post-templates.php'; require __DIR__ . '/compat/wordpress-6.1/script-loader.php'; require __DIR__ . '/compat/wordpress-6.1/date-settings.php'; -require __DIR__ . '/compat/wordpress-6.1/block-patterns.php'; require __DIR__ . '/compat/wordpress-6.1/edit-form-blocks.php'; require __DIR__ . '/compat/wordpress-6.1/template-parts-screen.php'; require __DIR__ . '/compat/wordpress-6.1/theme.php'; @@ -106,7 +80,11 @@ function gutenberg_is_experiment_enabled( $name ) { require __DIR__ . '/compat/wordpress-6.2/get-global-styles-and-settings.php'; require __DIR__ . '/compat/wordpress-6.2/default-filters.php'; require __DIR__ . '/compat/wordpress-6.2/class-wp-theme-json-resolver-6-2.php'; -require __DIR__ . '/compat/wordpress-6.2/class-wp-theme-json-6-2.php'; +require __DIR__ . '/compat/wordpress-6.2/edit-form-blocks.php'; +require __DIR__ . '/compat/wordpress-6.2/site-editor.php'; +require __DIR__ . '/compat/wordpress-6.2/block-editor-settings.php'; +require __DIR__ . '/compat/wordpress-6.2/theme.php'; +require __DIR__ . '/compat/wordpress-6.2/widgets.php'; // Experimental features. remove_action( 'plugins_loaded', '_wp_theme_json_webfonts_handler' ); // Turns off WP 6.0's stopgap handler for Webfonts API. @@ -115,7 +93,6 @@ function gutenberg_is_experiment_enabled( $name ) { if ( ! class_exists( 'WP_HTML_Tag_Processor' ) ) { require __DIR__ . '/experimental/html/index.php'; } -require __DIR__ . '/experimental/class-wp-theme-json-gutenberg.php'; require __DIR__ . '/experimental/class-wp-theme-json-resolver-gutenberg.php'; require __DIR__ . '/experimental/class-wp-webfonts.php'; require __DIR__ . '/experimental/class-wp-webfonts-provider.php'; @@ -123,8 +100,10 @@ function gutenberg_is_experiment_enabled( $name ) { require __DIR__ . '/experimental/webfonts.php'; require __DIR__ . '/experimental/navigation-theme-opt-in.php'; require __DIR__ . '/experimental/navigation-page.php'; +require __DIR__ . '/experimental/kses.php'; // Plugin specific code. +require __DIR__ . '/class-wp-theme-json-gutenberg.php'; require __DIR__ . '/blocks.php'; require __DIR__ . '/client-assets.php'; require __DIR__ . '/demo.php'; diff --git a/package-lock.json b/package-lock.json index adb96b28da9216..4cdf49e9da3232 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "gutenberg", - "version": "14.6.0-rc.1", + "version": "14.8.0-rc.1", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -94,6 +94,12 @@ "tunnel": "0.0.6" } }, + "@adobe/css-tools": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.0.1.tgz", + "integrity": "sha512-+u76oB43nOHrF4DDWRLWDCtci7f3QJoEBigemIdIeTi1ODqjx6Tad9NCVnPRwewWlKkVab5PlK8DCtPTyX7S8g==", + "dev": true + }, "@axe-core/puppeteer": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@axe-core/puppeteer/-/puppeteer-4.0.0.tgz", @@ -443,6 +449,11 @@ "@babel/types": "^7.16.0" } }, + "@babel/helper-string-parser": { + "version": "7.19.4", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz", + "integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==" + }, "@babel/helper-validator-identifier": { "version": "7.15.7", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz", @@ -525,7 +536,6 @@ "version": "7.16.0", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.16.0.tgz", "integrity": "sha512-nyYmIo7ZqKsY6P4lnVmBlxp9B3a96CscbLotlsNuktMHahkDwoPYEjXrZHU0Tj844Z9f1IthVxQln57mhkcExw==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.14.5", "@babel/helper-remap-async-to-generator": "^7.16.0", @@ -906,7 +916,6 @@ "version": "7.8.4", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.8.0" } @@ -974,7 +983,6 @@ "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.8.3" } @@ -1279,7 +1287,6 @@ "version": "7.16.0", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.16.0.tgz", "integrity": "sha512-LogN88uO+7EhxWc8WZuQ8vxdSyVGxhkh8WTC3tzlT8LccMuQdA81e9SGV6zY7kY2LjDhhDOFdQVxdGwPyBCnvg==", - "dev": true, "requires": { "@babel/helper-create-regexp-features-plugin": "^7.16.0" } @@ -1423,6 +1430,7 @@ "version": "7.16.0", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.16.0.tgz", "integrity": "sha512-JAvGxgKuwS2PihiSFaDrp94XOzzTUeDeOQlcKzVAyaPap7BnZXK/lvMDiubkPTdotPKOIZq9xWXWnggUMYiExg==", + "dev": true, "requires": { "regenerator-transform": "^0.14.2" } @@ -1785,6 +1793,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/@cnakazawa/watch/-/watch-1.0.3.tgz", "integrity": "sha512-r5160ogAvGyHsal38Kux7YYtodEKOj89RGb28ht1jh3SJb08VwRwAKKJL0bGb04Zd/3r9FL3BFIc3bBidYffCA==", + "dev": true, "requires": { "exec-sh": "^0.3.2", "minimist": "^1.2.0" @@ -2892,17 +2901,17 @@ } }, "@jest/create-cache-key-function": { - "version": "27.3.1", - "resolved": "https://registry.npmjs.org/@jest/create-cache-key-function/-/create-cache-key-function-27.3.1.tgz", - "integrity": "sha512-21lx0HRgkznc5Tc2WGiXVYQQ6Vdfohs6CkLV2FLogLRb52f6v9SiSIjTNflu23lzEmY4EalLgQLxCfhgvREV6w==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/create-cache-key-function/-/create-cache-key-function-27.5.1.tgz", + "integrity": "sha512-dmH1yW+makpTSURTy8VzdUwFnfQh1G8R+DxO2Ho2FFmBbKFEVm+3jWdvFhE2VqB/LATCTokkP0dotjyQyw5/AQ==", "requires": { - "@jest/types": "^27.2.5" + "@jest/types": "^27.5.1" }, "dependencies": { "@jest/types": { - "version": "27.4.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.4.2.tgz", - "integrity": "sha512-j35yw0PMTPpZsUoOBiuHzr1zTYoad1cVIE0ajEjcrJONxxrko/IRGKkXx3os0Nsi4Hu3+5VmDbVfq5WhG/pWAg==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", + "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", "requires": { "@types/istanbul-lib-coverage": "^2.0.0", "@types/istanbul-reports": "^3.0.0", @@ -2957,6 +2966,23 @@ } } }, + "@jest/expect-utils": { + "version": "29.2.1", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.2.1.tgz", + "integrity": "sha512-yr4aHNg5Z1CjKby5ozm7sKjgBlCOorlAoFcvrOQ/4rbZRfgZQdnmh7cth192PYIgiPZo2bBXvqdOApnAMWFJZg==", + "dev": true, + "requires": { + "jest-get-type": "^29.2.0" + }, + "dependencies": { + "jest-get-type": { + "version": "29.2.0", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.2.0.tgz", + "integrity": "sha512-uXNJlg8hKFEnDgFsrCjznB+sTxdkuqiCL6zMgA75qEbAJjJYTs9XPrvDctrEig2GDow22T/LvHgO57iJhXB/UA==", + "dev": true + } + } + }, "@jest/fake-timers": { "version": "27.4.2", "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-27.4.2.tgz", @@ -3750,14 +3776,12 @@ "@jridgewell/resolve-uri": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.0.7.tgz", - "integrity": "sha512-8cXDaBBHOr2pQ7j77Y6Vp5VDT2sIqWyWQ56TjEq4ih/a4iST3dItRe8Q9fp0rrIl9DoKhWQtUQz/YpOxLkXbNA==", - "dev": true + "integrity": "sha512-8cXDaBBHOr2pQ7j77Y6Vp5VDT2sIqWyWQ56TjEq4ih/a4iST3dItRe8Q9fp0rrIl9DoKhWQtUQz/YpOxLkXbNA==" }, "@jridgewell/set-array": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.1.tgz", - "integrity": "sha512-Ct5MqZkLGEXTVmQYbGtx9SVqD2fqwvdubdps5D3djjAkgkKwT918VNOz65pEHFaYTeWcukmJmH5SwsA9Tn2ObQ==", - "dev": true + "integrity": "sha512-Ct5MqZkLGEXTVmQYbGtx9SVqD2fqwvdubdps5D3djjAkgkKwT918VNOz65pEHFaYTeWcukmJmH5SwsA9Tn2ObQ==" }, "@jridgewell/source-map": { "version": "0.3.2", @@ -3772,14 +3796,12 @@ "@jridgewell/sourcemap-codec": { "version": "1.4.13", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.13.tgz", - "integrity": "sha512-GryiOJmNcWbovBxTfZSF71V/mXbgcV3MewDe3kIMCLyIh5e7SKAeUZs+rMnJ8jkMolZ/4/VsdBmMrw3l+VdZ3w==", - "dev": true + "integrity": "sha512-GryiOJmNcWbovBxTfZSF71V/mXbgcV3MewDe3kIMCLyIh5e7SKAeUZs+rMnJ8jkMolZ/4/VsdBmMrw3l+VdZ3w==" }, "@jridgewell/trace-mapping": { "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.13.tgz", "integrity": "sha512-o1xbKhp9qnIAoHJSWd6KlCZfqslL4valSF81H8ImioOAxluWYWOpWkpyktY2vnt4tbrX9XYaxovq6cgowaJp2w==", - "dev": true, "requires": { "@jridgewell/resolve-uri": "^3.0.3", "@jridgewell/sourcemap-codec": "^1.4.10" @@ -6936,6 +6958,99 @@ "integrity": "sha512-H1rQc1ZOHANWBvPcW+JpGwr+juXSxM8Q8YCkm3GhZd8REu1fHR3z99CErO1p9pkcfcxZnMdIZdIsXkOHY0NilA==", "dev": true }, + "@motionone/animation": { + "version": "10.14.0", + "resolved": "https://registry.npmjs.org/@motionone/animation/-/animation-10.14.0.tgz", + "integrity": "sha512-h+1sdyBP8vbxEBW5gPFDnj+m2DCqdlAuf2g6Iafb1lcMnqjsRXWlPw1AXgvUMXmreyhqmPbJqoNfIKdytampRQ==", + "requires": { + "@motionone/easing": "^10.14.0", + "@motionone/types": "^10.14.0", + "@motionone/utils": "^10.14.0", + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" + } + } + }, + "@motionone/dom": { + "version": "10.13.1", + "resolved": "https://registry.npmjs.org/@motionone/dom/-/dom-10.13.1.tgz", + "integrity": "sha512-zjfX+AGMIt/fIqd/SL1Lj93S6AiJsEA3oc5M9VkUr+Gz+juRmYN1vfvZd6MvEkSqEjwPQgcjN7rGZHrDB9APfQ==", + "requires": { + "@motionone/animation": "^10.13.1", + "@motionone/generators": "^10.13.1", + "@motionone/types": "^10.13.0", + "@motionone/utils": "^10.13.1", + "hey-listen": "^1.0.8", + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" + } + } + }, + "@motionone/easing": { + "version": "10.14.0", + "resolved": "https://registry.npmjs.org/@motionone/easing/-/easing-10.14.0.tgz", + "integrity": "sha512-2vUBdH9uWTlRbuErhcsMmt1jvMTTqvGmn9fHq8FleFDXBlHFs5jZzHJT9iw+4kR1h6a4SZQuCf72b9ji92qNYA==", + "requires": { + "@motionone/utils": "^10.14.0", + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" + } + } + }, + "@motionone/generators": { + "version": "10.14.0", + "resolved": "https://registry.npmjs.org/@motionone/generators/-/generators-10.14.0.tgz", + "integrity": "sha512-6kRHezoFfIjFN7pPpaxmkdZXD36tQNcyJe3nwVqwJ+ZfC0e3rFmszR8kp9DEVFs9QL/akWjuGPSLBI1tvz+Vjg==", + "requires": { + "@motionone/types": "^10.14.0", + "@motionone/utils": "^10.14.0", + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" + } + } + }, + "@motionone/types": { + "version": "10.14.0", + "resolved": "https://registry.npmjs.org/@motionone/types/-/types-10.14.0.tgz", + "integrity": "sha512-3bNWyYBHtVd27KncnJLhksMFQ5o2MSdk1cA/IZqsHtA9DnRM1SYgN01CTcJ8Iw8pCXF5Ocp34tyAjY7WRpOJJQ==" + }, + "@motionone/utils": { + "version": "10.14.0", + "resolved": "https://registry.npmjs.org/@motionone/utils/-/utils-10.14.0.tgz", + "integrity": "sha512-sLWBLPzRqkxmOTRzSaD3LFQXCPHvDzyHJ1a3VP9PRzBxyVd2pv51/gMOsdAcxQ9n+MIeGJnxzXBYplUHKj4jkw==", + "requires": { + "@motionone/types": "^10.14.0", + "hey-listen": "^1.0.8", + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" + } + } + }, "@mrmlnc/readdir-enhanced": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz", @@ -8132,56 +8247,21 @@ "integrity": "sha512-O/ohFq1CAQLfoNc376Z3W6gvVcCJlje5AVk0JhsI8Q40hn+NXAWCnOM1bEePfC0uDMtp0/RCK6FotUvkQ6c4Zw==" }, "@react-native-community/blur": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/@react-native-community/blur/-/blur-3.6.0.tgz", - "integrity": "sha512-GtDBhpX2pQcjl4VopOC8FktrVufrEfYRwVeMQ2WWckqKIv2BdwvlvWvj88L1WmEdBr9UNcm3rtgz+d+YXkmirA==", - "requires": { - "prop-types": "^15.5.10" - } + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@react-native-community/blur/-/blur-4.2.0.tgz", + "integrity": "sha512-StgP5zQJOCHqDRjmcKnzVkJ920S6DYBKRJfigSUnlkNQp+HzZtVtyKq0j5a7x84NtHcV7j8Uy5mz1Lx9ZKRKfA==" }, - "@react-native-community/cli": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/@react-native-community/cli/-/cli-6.3.1.tgz", - "integrity": "sha512-UQ77AkGvPzdwJt6qhYXUyDMP1v2rdCcIlrhU48FOcAhGX+N/LCL9Cp/Ic6CkiiSHJdktbgiEEJ2srprXH8nzVg==", + "@react-native-community/cli-clean": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-clean/-/cli-clean-8.0.4.tgz", + "integrity": "sha512-IwS1M1NHg6+qL8PThZYMSIMYbZ6Zbx+lIck9PLBskbosFo24M3lCOflOl++Bggjakp6mR+sRXxLMexid/GeOsQ==", "requires": { - "@react-native-community/cli-debugger-ui": "^6.0.0-rc.0", - "@react-native-community/cli-hermes": "^6.3.0", - "@react-native-community/cli-plugin-metro": "^6.2.0", - "@react-native-community/cli-server-api": "^6.2.0", - "@react-native-community/cli-tools": "^6.2.0", - "@react-native-community/cli-types": "^6.0.0", - "appdirsjs": "^1.2.4", + "@react-native-community/cli-tools": "^8.0.4", "chalk": "^4.1.2", - "command-exists": "^1.2.8", - "commander": "^2.19.0", - "cosmiconfig": "^5.1.0", - "deepmerge": "^3.2.0", - "envinfo": "^7.7.2", "execa": "^1.0.0", - "find-up": "^4.1.0", - "fs-extra": "^8.1.0", - "glob": "^7.1.3", - "graceful-fs": "^4.1.3", - "joi": "^17.2.1", - "leven": "^3.1.0", - "lodash": "^4.17.15", - "minimist": "^1.2.0", - "node-stream-zip": "^1.9.1", - "ora": "^3.4.0", - "pretty-format": "^26.6.2", - "prompts": "^2.4.0", - "semver": "^6.3.0", - "serve-static": "^1.13.1", - "strip-ansi": "^5.2.0", - "sudo-prompt": "^9.0.0", - "wcwidth": "^1.0.1" + "prompts": "^2.4.0" }, "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==" - }, "ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -8212,22 +8292,6 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, - "commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" - }, - "cosmiconfig": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz", - "integrity": "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==", - "requires": { - "import-fresh": "^2.0.0", - "is-directory": "^0.3.1", - "js-yaml": "^3.13.1", - "parse-json": "^4.0.0" - } - }, "cross-spawn": { "version": "6.0.5", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", @@ -8238,25 +8302,8 @@ "semver": "^5.5.0", "shebang-command": "^1.2.0", "which": "^1.2.9" - }, - "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" - } } }, - "deepmerge": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-3.3.0.tgz", - "integrity": "sha512-GRQOafGHwMHpjPx9iCvTgpu9NojZ49q794EEL94JVEw6VaeA8XTUyBKvAkOOjBX9oJNiV6G3P+T+tihFjo2TqA==" - }, - "envinfo": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.8.1.tgz", - "integrity": "sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw==" - }, "execa": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", @@ -8271,15 +8318,6 @@ "strip-eof": "^1.0.0" } }, - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, "get-stream": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", @@ -8288,217 +8326,139 @@ "pump": "^3.0.0" } }, - "glob": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", - "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", - "requires": { - "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" - } - }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" }, - "import-fresh": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", - "integrity": "sha1-2BNVwVYS04bGH53dOSLUMEgipUY=", + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", "requires": { - "caller-path": "^2.0.0", - "resolve-from": "^3.0.0" + "end-of-stream": "^1.1.0", + "once": "^1.3.1" } }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + }, + "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==", "requires": { - "p-locate": "^4.1.0" + "has-flag": "^4.0.0" } - }, - "ora": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/ora/-/ora-3.4.0.tgz", - "integrity": "sha512-eNwHudNbO1folBP3JsZ19v9azXWtQZjICdr3Q0TDPIaeBQ3mXLrh54wM+er0+hSp+dWKf+Z8KM58CYzEyIYxYg==", + } + } + }, + "@react-native-community/cli-config": { + "version": "8.0.6", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-config/-/cli-config-8.0.6.tgz", + "integrity": "sha512-mjVpVvdh8AviiO8xtqeX+BkjqE//NMDnISwsLWSJUfNCwTAPmdR8PGbhgP5O4hWHyJ3WkepTopl0ya7Tfi3ifw==", + "requires": { + "@react-native-community/cli-tools": "^8.0.4", + "cosmiconfig": "^5.1.0", + "deepmerge": "^3.2.0", + "glob": "^7.1.3", + "joi": "^17.2.1" + }, + "dependencies": { + "cosmiconfig": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz", + "integrity": "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==", "requires": { - "chalk": "^2.4.2", - "cli-cursor": "^2.1.0", - "cli-spinners": "^2.0.0", - "log-symbols": "^2.2.0", - "strip-ansi": "^5.2.0", - "wcwidth": "^1.0.1" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "requires": { - "has-flag": "^3.0.0" - } - } + "import-fresh": "^2.0.0", + "is-directory": "^0.3.1", + "js-yaml": "^3.13.1", + "parse-json": "^4.0.0" } }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "deepmerge": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-3.3.0.tgz", + "integrity": "sha512-GRQOafGHwMHpjPx9iCvTgpu9NojZ49q794EEL94JVEw6VaeA8XTUyBKvAkOOjBX9oJNiV6G3P+T+tihFjo2TqA==" + }, + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", "requires": { - "p-try": "^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" } }, - "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==", + "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==", "requires": { - "p-limit": "^2.2.0" + "caller-path": "^2.0.0", + "resolve-from": "^3.0.0" } }, - "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==" + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "requires": { + "brace-expansion": "^1.1.7" + } }, "parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", "requires": { "error-ex": "^1.3.1", "json-parse-better-errors": "^1.0.1" } - }, - "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==" - }, - "prompts": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", - "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", - "requires": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" - } - }, - "pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" - }, - "sisteransi": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==" - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "requires": { - "ansi-regex": "^4.1.0" - } - }, - "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==", - "requires": { - "has-flag": "^4.0.0" - } } } }, "@react-native-community/cli-debugger-ui": { - "version": "6.0.0-rc.0", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-debugger-ui/-/cli-debugger-ui-6.0.0-rc.0.tgz", - "integrity": "sha512-achYcPPoWa9D02C5tn6TBzjeY443wQTyx37urptc75JpZ7gR5YHsDyIEEWa3DDYp1va9zx/iGg+uZ/hWw07GAw==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-debugger-ui/-/cli-debugger-ui-8.0.0.tgz", + "integrity": "sha512-u2jq06GZwZ9sRERzd9FIgpW6yv4YOW4zz7Ym/B8eSzviLmy3yI/8mxJtvlGW+J8lBsfMcQoqJpqI6Rl1nZy9yQ==", "requires": { "serve-static": "^1.13.1" } }, - "@react-native-community/cli-hermes": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-hermes/-/cli-hermes-6.3.0.tgz", - "integrity": "sha512-Uhbm9bubyZLZ12vFCIfWbE/Qi3SBTbYIN/TC08EudTLhv/KbPomCQnmFsnJ7AXQFuOZJs73mBxoEAYSbRbwyVA==", + "@react-native-community/cli-doctor": { + "version": "8.0.6", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-doctor/-/cli-doctor-8.0.6.tgz", + "integrity": "sha512-ZQqyT9mJMVeFEVIwj8rbDYGCA2xXjJfsQjWk2iTRZ1CFHfhPSUuUiG8r6mJmTinAP9t+wYcbbIYzNgdSUKnDMw==", "requires": { - "@react-native-community/cli-platform-android": "^6.3.0", - "@react-native-community/cli-tools": "^6.2.0", + "@react-native-community/cli-config": "^8.0.6", + "@react-native-community/cli-platform-ios": "^8.0.6", + "@react-native-community/cli-tools": "^8.0.4", "chalk": "^4.1.2", + "command-exists": "^1.2.8", + "envinfo": "^7.7.2", + "execa": "^1.0.0", "hermes-profile-transformer": "^0.0.6", - "ip": "^1.1.5" + "ip": "^1.1.5", + "node-stream-zip": "^1.9.1", + "ora": "^5.4.1", + "prompts": "^2.4.0", + "semver": "^6.3.0", + "strip-ansi": "^5.2.0", + "sudo-prompt": "^9.0.0", + "wcwidth": "^1.0.1" }, "dependencies": { - "@react-native-community/cli-platform-android": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-android/-/cli-platform-android-6.3.0.tgz", - "integrity": "sha512-d5ufyYcvrZoHznYm5bjBXaiHIJv552t5gYtQpnUsxBhHSQ8QlaNmlLUyeSPRDfOw4ND9b0tPHqs4ufwx6vp/fQ==", - "requires": { - "@react-native-community/cli-tools": "^6.2.0", - "chalk": "^4.1.2", - "execa": "^1.0.0", - "fs-extra": "^8.1.0", - "glob": "^7.1.3", - "jetifier": "^1.6.2", - "lodash": "^4.17.15", - "logkitty": "^0.7.1", - "slash": "^3.0.0", - "xmldoc": "^1.1.2" - } + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" }, "ansi-styles": { "version": "4.3.0", @@ -8517,6 +8477,19 @@ "supports-color": "^7.1.0" } }, + "cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "requires": { + "restore-cursor": "^3.1.0" + } + }, + "cli-spinners": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.7.0.tgz", + "integrity": "sha512-qu3pN8Y3qHNgE2AFweciB1IfMnmZ/fsNTEE+NOFjmGB2F/7rLhnhzppvpCnN4FovtP26k8lHyy9ptEbNwWFLzw==" + }, "color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -8540,6 +8513,13 @@ "semver": "^5.5.0", "shebang-command": "^1.2.0", "which": "^1.2.9" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + } } }, "execa": { @@ -8564,24 +8544,50 @@ "pump": "^3.0.0" } }, - "glob": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", - "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", - "requires": { - "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" - } - }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" }, + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==" + }, + "onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "requires": { + "mimic-fn": "^2.1.0" + } + }, + "ora": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", + "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", + "requires": { + "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" + }, + "dependencies": { + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "requires": { + "ansi-regex": "^5.0.1" + } + } + } + }, "pump": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", @@ -8591,10 +8597,91 @@ "once": "^1.3.1" } }, + "restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "requires": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + } + }, "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "requires": { + "ansi-regex": "^4.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==" + } + } + }, + "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==", + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "@react-native-community/cli-hermes": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-hermes/-/cli-hermes-8.0.5.tgz", + "integrity": "sha512-Zm0wM6SfgYAEX1kfJ1QBvTayabvh79GzmjHyuSnEROVNPbl4PeCG4WFbwy489tGwOP9Qx9fMT5tRIFCD8bp6/g==", + "requires": { + "@react-native-community/cli-platform-android": "^8.0.5", + "@react-native-community/cli-tools": "^8.0.4", + "chalk": "^4.1.2", + "hermes-profile-transformer": "^0.0.6", + "ip": "^1.1.5" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "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==", + "requires": { + "color-name": "~1.1.4" + } + }, + "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==" + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" }, "supports-color": { "version": "7.2.0", @@ -8607,11 +8694,11 @@ } }, "@react-native-community/cli-platform-android": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-android/-/cli-platform-android-6.2.0.tgz", - "integrity": "sha512-QLxwClcbxVhuIGsQiIpqRnoJzRdpN2B+y/Yt2OGgDHXGbuOXulgt4D+8AhvZXrB4jyAcEUlFg/048v3RGQQudw==", + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-android/-/cli-platform-android-8.0.5.tgz", + "integrity": "sha512-z1YNE4T1lG5o9acoQR1GBvf7mq6Tzayqo/za5sHVSOJAC9SZOuVN/gg/nkBa9a8n5U7qOMFXfwhTMNqA474gXA==", "requires": { - "@react-native-community/cli-tools": "^6.2.0", + "@react-native-community/cli-tools": "^8.0.4", "chalk": "^4.1.2", "execa": "^1.0.0", "fs-extra": "^8.1.0", @@ -8619,8 +8706,7 @@ "jetifier": "^1.6.2", "lodash": "^4.17.15", "logkitty": "^0.7.1", - "slash": "^3.0.0", - "xmldoc": "^1.1.2" + "slash": "^3.0.0" }, "dependencies": { "ansi-styles": { @@ -8688,14 +8774,14 @@ } }, "glob": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", - "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", - "minimatch": "^3.0.4", + "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } @@ -8705,6 +8791,14 @@ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "requires": { + "brace-expansion": "^1.1.7" + } + }, "pump": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", @@ -8730,24 +8824,24 @@ } }, "@react-native-community/cli-platform-ios": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-ios/-/cli-platform-ios-6.2.0.tgz", - "integrity": "sha512-k15MhExxLiLDDZOeuPgvTxbp0CsoLQQpk2Du0HjZDePqqWcKJylQqMZru1o8HuQHPcEr+b71HIs5V+lKyFYpfg==", + "version": "8.0.6", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-ios/-/cli-platform-ios-8.0.6.tgz", + "integrity": "sha512-CMR6mu/LVx6JVfQRDL9uULsMirJT633bODn+IrYmrwSz250pnhON16We8eLPzxOZHyDjm7JPuSgHG3a/BPiRuQ==", "requires": { - "@react-native-community/cli-tools": "^6.2.0", + "@react-native-community/cli-tools": "^8.0.4", "chalk": "^4.1.2", + "execa": "^1.0.0", "glob": "^7.1.3", "js-yaml": "^3.13.1", "lodash": "^4.17.15", - "ora": "^3.4.0", - "plist": "^3.0.2", - "xcode": "^2.0.0" + "ora": "^5.4.1", + "plist": "^3.0.2" }, "dependencies": { "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==" + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" }, "ansi-styles": { "version": "4.3.0", @@ -8766,6 +8860,19 @@ "supports-color": "^7.1.0" } }, + "cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "requires": { + "restore-cursor": "^3.1.0" + } + }, + "cli-spinners": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.7.0.tgz", + "integrity": "sha512-qu3pN8Y3qHNgE2AFweciB1IfMnmZ/fsNTEE+NOFjmGB2F/7rLhnhzppvpCnN4FovtP26k8lHyy9ptEbNwWFLzw==" + }, "color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -8779,15 +8886,49 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "requires": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + } + }, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "requires": { + "pump": "^3.0.0" + } + }, "glob": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", - "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", - "minimatch": "^3.0.4", + "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } @@ -8797,71 +8938,72 @@ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" }, + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==" + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "requires": { + "mimic-fn": "^2.1.0" + } + }, "ora": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/ora/-/ora-3.4.0.tgz", - "integrity": "sha512-eNwHudNbO1folBP3JsZ19v9azXWtQZjICdr3Q0TDPIaeBQ3mXLrh54wM+er0+hSp+dWKf+Z8KM58CYzEyIYxYg==", + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", + "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", "requires": { - "chalk": "^2.4.2", - "cli-cursor": "^2.1.0", - "cli-spinners": "^2.0.0", - "log-symbols": "^2.2.0", - "strip-ansi": "^5.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" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "requires": { - "has-flag": "^3.0.0" - } - } } }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "requires": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + } + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + }, "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "requires": { - "ansi-regex": "^4.1.0" + "ansi-regex": "^5.0.1" } }, "supports-color": { @@ -8875,19 +9017,19 @@ } }, "@react-native-community/cli-plugin-metro": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-plugin-metro/-/cli-plugin-metro-6.2.0.tgz", - "integrity": "sha512-JfmzuFNzOr+dFTUQJo1rV0t87XAqgHRTMYXNleQVt8otOVCk1FSCgKlgqMdvQc/FCx2ZjoMWEEV/g0LrPI8Etw==", + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-plugin-metro/-/cli-plugin-metro-8.0.4.tgz", + "integrity": "sha512-UWzY1eMcEr/6262R2+d0Is5M3L/7Y/xXSDIFMoc5Rv5Wucl3hJM/TxHXmByvHpuJf6fJAfqOskyt4bZCvbI+wQ==", "requires": { - "@react-native-community/cli-server-api": "^6.2.0", - "@react-native-community/cli-tools": "^6.2.0", + "@react-native-community/cli-server-api": "^8.0.4", + "@react-native-community/cli-tools": "^8.0.4", "chalk": "^4.1.2", - "metro": "^0.66.1", - "metro-config": "^0.66.1", - "metro-core": "^0.66.1", - "metro-react-native-babel-transformer": "^0.66.1", - "metro-resolver": "^0.66.1", - "metro-runtime": "^0.66.1", + "metro": "^0.70.1", + "metro-config": "^0.70.1", + "metro-core": "^0.70.1", + "metro-react-native-babel-transformer": "^0.70.1", + "metro-resolver": "^0.70.1", + "metro-runtime": "^0.70.1", "readline": "^1.3.0" }, "dependencies": { @@ -8926,6 +9068,112 @@ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" }, + "hermes-parser": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.6.0.tgz", + "integrity": "sha512-Vf58jBZca2+QBLR9h7B7mdg8oFz2g5ILz1iVouZ5DOrOrAfBmPfJjdjDT8jrO0f+iJ4/hSRrQHqHIjSnTaLUDQ==", + "requires": { + "hermes-estree": "0.6.0" + } + }, + "metro-react-native-babel-preset": { + "version": "0.70.3", + "resolved": "https://registry.npmjs.org/metro-react-native-babel-preset/-/metro-react-native-babel-preset-0.70.3.tgz", + "integrity": "sha512-4Nxc1zEiHEu+GTdEMEsHnRgfaBkg8f/Td3+FcQ8NTSvs+xL3LBrQy6N07idWSQZHIdGFf+tTHvRfSIWLD8u8Tg==", + "requires": { + "@babel/core": "^7.14.0", + "@babel/plugin-proposal-async-generator-functions": "^7.0.0", + "@babel/plugin-proposal-class-properties": "^7.0.0", + "@babel/plugin-proposal-export-default-from": "^7.0.0", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.0.0", + "@babel/plugin-proposal-object-rest-spread": "^7.0.0", + "@babel/plugin-proposal-optional-catch-binding": "^7.0.0", + "@babel/plugin-proposal-optional-chaining": "^7.0.0", + "@babel/plugin-syntax-dynamic-import": "^7.0.0", + "@babel/plugin-syntax-export-default-from": "^7.0.0", + "@babel/plugin-syntax-flow": "^7.2.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.0.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.0.0", + "@babel/plugin-transform-exponentiation-operator": "^7.0.0", + "@babel/plugin-transform-flow-strip-types": "^7.0.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-template-literals": "^7.0.0", + "@babel/plugin-transform-typescript": "^7.5.0", + "@babel/plugin-transform-unicode-regex": "^7.0.0", + "@babel/template": "^7.0.0", + "react-refresh": "^0.4.0" + } + }, + "metro-react-native-babel-transformer": { + "version": "0.70.3", + "resolved": "https://registry.npmjs.org/metro-react-native-babel-transformer/-/metro-react-native-babel-transformer-0.70.3.tgz", + "integrity": "sha512-WKBU6S/G50j9cfmFM4k4oRYprd8u3qjleD4so1E2zbTNILg+gYla7ZFGCAvi2G0ZcqS2XuGCR375c2hF6VVvwg==", + "requires": { + "@babel/core": "^7.14.0", + "babel-preset-fbjs": "^3.4.0", + "hermes-parser": "0.6.0", + "metro-babel-transformer": "0.70.3", + "metro-react-native-babel-preset": "0.70.3", + "metro-source-map": "0.70.3", + "nullthrows": "^1.1.1" + } + }, + "metro-source-map": { + "version": "0.70.3", + "resolved": "https://registry.npmjs.org/metro-source-map/-/metro-source-map-0.70.3.tgz", + "integrity": "sha512-zsYtZGrwRbbGEFHtmMqqeCH9K9aTGNVPsurMOWCUeQA3VGyVGXPGtLMC+CdAM9jLpUyg6jw2xh0esxi+tYH7Uw==", + "requires": { + "@babel/traverse": "^7.14.0", + "@babel/types": "^7.0.0", + "invariant": "^2.2.4", + "metro-symbolicate": "0.70.3", + "nullthrows": "^1.1.1", + "ob1": "0.70.3", + "source-map": "^0.5.6", + "vlq": "^1.0.0" + } + }, + "metro-symbolicate": { + "version": "0.70.3", + "resolved": "https://registry.npmjs.org/metro-symbolicate/-/metro-symbolicate-0.70.3.tgz", + "integrity": "sha512-JTYkF1dpeDUssQ84juE1ycnhHki2ylJBBdJE1JHtfu5oC+z1ElDbBdPHq90Uvt8HbRov/ZAnxvv7Zy6asS+WCA==", + "requires": { + "invariant": "^2.2.4", + "metro-source-map": "0.70.3", + "nullthrows": "^1.1.1", + "source-map": "^0.5.6", + "through2": "^2.0.1", + "vlq": "^1.0.0" + } + }, + "ob1": { + "version": "0.70.3", + "resolved": "https://registry.npmjs.org/ob1/-/ob1-0.70.3.tgz", + "integrity": "sha512-Vy9GGhuXgDRY01QA6kdhToPd8AkLdLpX9GjH5kpqluVqTu70mgOm7tpGoJDZGaNbr9nJlJgnipqHJQRPORixIQ==" + }, + "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==" + }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -8937,47 +9185,50 @@ } }, "@react-native-community/cli-server-api": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-server-api/-/cli-server-api-6.2.0.tgz", - "integrity": "sha512-OnbnYclhoDpjge33QO5Slhfn0DsmLzzAgyrSCnb24HhSqwq7ObjMHaLpoEhpajzLG71wq5oKh0APEQjiL4Mknw==", + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-server-api/-/cli-server-api-8.0.4.tgz", + "integrity": "sha512-Orr14njx1E70CVrUA8bFdl+mrnbuXUjf1Rhhm0RxUadFpvkHuOi5dh8Bryj2MKtf8eZrpEwZ7tuQPhJEULW16A==", "requires": { - "@react-native-community/cli-debugger-ui": "^6.0.0-rc.0", - "@react-native-community/cli-tools": "^6.2.0", + "@react-native-community/cli-debugger-ui": "^8.0.0", + "@react-native-community/cli-tools": "^8.0.4", "compression": "^1.7.1", "connect": "^3.6.5", "errorhandler": "^1.5.0", - "nocache": "^2.1.0", + "nocache": "^3.0.1", "pretty-format": "^26.6.2", "serve-static": "^1.13.1", - "ws": "^1.1.0" + "ws": "^7.5.1" }, "dependencies": { "ws": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/ws/-/ws-1.1.5.tgz", - "integrity": "sha512-o3KqipXNUdS7wpQzBHSe180lBGO60SoK0yVo3CYJgb2MkobuWuBX6dhkYP5ORCLd55y+SaflMOV5fqAB53ux4w==", - "requires": { - "options": ">=0.0.5", - "ultron": "1.0.x" - } + "version": "7.5.9", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", + "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==" } } }, "@react-native-community/cli-tools": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-tools/-/cli-tools-6.2.1.tgz", - "integrity": "sha512-7RbOkZLT/3YG8CAYYM70ajRKIOgVxK/b4t9KNsPq+2uen99MGezfeglC8s1cs3vBNVVxCo0a2JbXg18bUd8eqA==", + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-tools/-/cli-tools-8.0.4.tgz", + "integrity": "sha512-ePN9lGxh6LRFiotyddEkSmuqpQhnq2iw9oiXYr4EFWpIEy0yCigTuSTiDF68+c8M9B+7bTwkRpz/rMPC4ViO5Q==", "requires": { "appdirsjs": "^1.2.4", "chalk": "^4.1.2", + "find-up": "^5.0.0", "lodash": "^4.17.15", "mime": "^2.4.1", "node-fetch": "^2.6.0", "open": "^6.2.0", + "ora": "^5.4.1", "semver": "^6.3.0", "shell-quote": "^1.7.3" }, "dependencies": { + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" + }, "ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -8995,6 +9246,19 @@ "supports-color": "^7.1.0" } }, + "cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "requires": { + "restore-cursor": "^3.1.0" + } + }, + "cli-spinners": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.7.0.tgz", + "integrity": "sha512-qu3pN8Y3qHNgE2AFweciB1IfMnmZ/fsNTEE+NOFjmGB2F/7rLhnhzppvpCnN4FovtP26k8lHyy9ptEbNwWFLzw==" + }, "color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -9008,21 +9272,105 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, + "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==", + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + } + }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" }, + "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==", + "requires": { + "p-locate": "^5.0.0" + } + }, "mime": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==" }, + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==" + }, + "onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "requires": { + "mimic-fn": "^2.1.0" + } + }, + "ora": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", + "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", + "requires": { + "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": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "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==", + "requires": { + "p-limit": "^3.0.2" + } + }, + "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==" + }, + "restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "requires": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + } + }, "semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "requires": { + "ansi-regex": "^5.0.1" + } + }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -9034,49 +9382,11 @@ } }, "@react-native-community/cli-types": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-types/-/cli-types-6.0.0.tgz", - "integrity": "sha512-K493Fk2DMJC0ZM8s8gnfseKxGasIhuDaCUDeLZcoCSFlrjKEuEs1BKKEJiev0CARhKEXKOyyp/uqYM9nWhisNw==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-types/-/cli-types-8.0.0.tgz", + "integrity": "sha512-1lZS1PEvMlFaN3Se1ksyoFWzMjk+YfKi490GgsqKJln9gvFm8tqVPdnXttI5Uf2DQf3BMse8Bk8dNH4oV6Ewow==", "requires": { - "ora": "^3.4.0" - }, - "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==" - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "ora": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/ora/-/ora-3.4.0.tgz", - "integrity": "sha512-eNwHudNbO1folBP3JsZ19v9azXWtQZjICdr3Q0TDPIaeBQ3mXLrh54wM+er0+hSp+dWKf+Z8KM58CYzEyIYxYg==", - "requires": { - "chalk": "^2.4.2", - "cli-cursor": "^2.1.0", - "cli-spinners": "^2.0.0", - "log-symbols": "^2.2.0", - "strip-ansi": "^5.2.0", - "wcwidth": "^1.0.1" - } - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "requires": { - "ansi-regex": "^4.1.0" - } - } + "joi": "^17.2.1" } }, "@react-native-community/slider": { @@ -9094,9 +9404,9 @@ "integrity": "sha512-KrwSpS1tKI70wuKl68DwJZYEvXktDHdZMG0k2AXD/rJVSlB23/X2CB2cutVR0HwNMJIal9HOUOBB2rVfa6UGtQ==" }, "@react-native/normalize-color": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@react-native/normalize-color/-/normalize-color-1.0.0.tgz", - "integrity": "sha512-xUNRvNmCl3UGCPbbHvfyFMnpvLPoOjDCcp5bT9m2k+TF/ZBklEQwhPZlkrxRx2NhgFh1X3a5uL7mJ7ZR+8G7Qg==" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@react-native/normalize-color/-/normalize-color-2.0.0.tgz", + "integrity": "sha512-Wip/xsc5lw8vsBlmY2MO/gFLp3MvuZ2baBZjDeTjjndMgM0h5sxz7AZR62RDPGgstp8Np7JzjvVqVT7tpFZqsw==" }, "@react-native/polyfills": { "version": "2.0.0", @@ -15810,16 +16120,16 @@ } }, "@testing-library/jest-dom": { - "version": "5.16.4", - "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-5.16.4.tgz", - "integrity": "sha512-Gy+IoFutbMQcky0k+bqqumXZ1cTGswLsFqmNLzNdSKkU9KGV2u9oXhukCbbJ9/LRPKiqwxEE8VpV/+YZlfkPUA==", + "version": "5.16.5", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-5.16.5.tgz", + "integrity": "sha512-N5ixQ2qKpi5OLYfwQmUb/5mSV9LneAcaUfp32pn4yCnpb8r/Yz0pXFPck21dIicKmi+ta5WRAknkZCfA8refMA==", "dev": true, "requires": { + "@adobe/css-tools": "^4.0.1", "@babel/runtime": "^7.9.2", "@types/testing-library__jest-dom": "^5.9.1", "aria-query": "^5.0.0", "chalk": "^3.0.0", - "css": "^3.0.0", "css.escape": "^1.5.1", "dom-accessibility-api": "^0.5.6", "lodash": "^4.17.15", @@ -15836,16 +16146,13 @@ } }, "aria-query": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.0.0.tgz", - "integrity": "sha512-V+SM7AbUwJ+EBnB8+DXs0hPZHO0W6pqBcc0dW90OwtVG02PswOu/teuARoLQjdDOH+t9pJgGnW5/Qmouf3gPJg==", - "dev": true - }, - "atob": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", - "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", - "dev": true + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.1.1.tgz", + "integrity": "sha512-4cPQjOYM2mqq7mZG8CSxkUvL2Yv/x29VhGq5LKehTsxRnoVQps1YGt9NyjcNQsznEsD4rr8a6zGxqeNTqJWjpA==", + "dev": true, + "requires": { + "deep-equal": "^2.0.5" + } }, "chalk": { "version": "3.0.0", @@ -15872,15 +16179,37 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "css": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/css/-/css-3.0.0.tgz", - "integrity": "sha512-DG9pFfwOrzc+hawpmqX/dHYHJG+Bsdb0klhyi1sDneOgGOXy9wQIC8hzyVp1e4NRYDBdxcylvywPkkXCHAzTyQ==", + "deep-equal": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.0.5.tgz", + "integrity": "sha512-nPiRgmbAtm1a3JsnLCf6/SLfXcjyN5v8L1TXzdCmHrXJ4hx+gW/w1YCcn7z8gJtSiDArZCgYtbao3QqLm/N1Sw==", "dev": true, "requires": { - "inherits": "^2.0.4", - "source-map": "^0.6.1", - "source-map-resolve": "^0.6.0" + "call-bind": "^1.0.0", + "es-get-iterator": "^1.1.1", + "get-intrinsic": "^1.0.1", + "is-arguments": "^1.0.4", + "is-date-object": "^1.0.2", + "is-regex": "^1.1.1", + "isarray": "^2.0.5", + "object-is": "^1.1.4", + "object-keys": "^1.1.1", + "object.assign": "^4.1.2", + "regexp.prototype.flags": "^1.3.0", + "side-channel": "^1.0.3", + "which-boxed-primitive": "^1.0.1", + "which-collection": "^1.0.1", + "which-typed-array": "^1.1.2" + } + }, + "define-properties": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", + "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==", + "dev": true, + "requires": { + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" } }, "has-flag": { @@ -15889,18 +16218,79 @@ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, + "has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true + }, "indent-string": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", "dev": true }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dev": true, + "requires": { + "has-tostringtag": "^1.0.0" + } + }, + "is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "dependencies": { + "call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + } + } + } + }, + "isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", "dev": true }, + "object.assign": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", + "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + }, + "dependencies": { + "call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + } + } + } + }, "redent": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", @@ -15911,22 +16301,6 @@ "strip-indent": "^3.0.0" } }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "source-map-resolve": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.6.0.tgz", - "integrity": "sha512-KXBr9d/fO/bWo97NXsPIAW1bFSBOuCnjbNTBMO7N59hsv5i9yzRDfcYwwt0l04+VqnKC+EwzvJZIP/qkuMgR/w==", - "dev": true, - "requires": { - "atob": "^2.1.2", - "decode-uri-component": "^0.2.0" - } - }, "strip-indent": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", @@ -15948,29 +16322,49 @@ } }, "@testing-library/react": { - "version": "12.1.5", - "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-12.1.5.tgz", - "integrity": "sha512-OfTXCJUFgjd/digLUuPxa0+/3ZxsQmE7ub9kcbW/wi96Bh3o/p5vrETcBGfP17NWPGqeYYl5LTRpwyGoMC4ysg==", + "version": "13.4.0", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-13.4.0.tgz", + "integrity": "sha512-sXOGON+WNTh3MLE9rve97ftaZukN3oNf2KjDy7YTx6hcTO2uuLHuCGynMDhFwGw/jYf4OJ2Qk0i4i79qMNNkyw==", "dev": true, "requires": { "@babel/runtime": "^7.12.5", - "@testing-library/dom": "^8.0.0", - "@types/react-dom": "<18.0.0" + "@testing-library/dom": "^8.5.0", + "@types/react-dom": "^18.0.0" + }, + "dependencies": { + "@types/react-dom": { + "version": "18.0.6", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.6.tgz", + "integrity": "sha512-/5OFZgfIPSwy+YuIBP/FgJnQnsxhZhjjrnxudMddeblOouIodEQ75X14Rr4wGSG/bknL+Omy9iWlLo1u/9GzAA==", + "dev": true, + "requires": { + "@types/react": "*" + } + } } }, "@testing-library/react-native": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/@testing-library/react-native/-/react-native-9.1.0.tgz", - "integrity": "sha512-YBCSOIMYlh8gI0VG7ExRe80hNpfhC+i7j0cvpwiopUYtbpft8bMJXO35A4zEk7BkiWXEq6bYZ7VDJR3muSLhyQ==", + "version": "11.3.0", + "resolved": "https://registry.npmjs.org/@testing-library/react-native/-/react-native-11.3.0.tgz", + "integrity": "sha512-wCbH7TJ2uVwtkQPRQTZbM3Y/mzwK5zxwkOPKfR2UdVdz/RtCC2UVyDnJMaBNwG/cNle5tc92sQDfp3Gn2oyftg==", "dev": true, "requires": { - "pretty-format": "^27.0.0" + "pretty-format": "^29.0.3" }, "dependencies": { - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "@jest/schemas": { + "version": "29.0.0", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.0.0.tgz", + "integrity": "sha512-3Ab5HgYIIAnS0HjqJHQYZS+zXc4tUmTmBH3z83ajI6afXp8X3ZtdLX+nXx+I7LNkJD7uN9LAVhgnjDgZa2z0kA==", + "dev": true, + "requires": { + "@sinclair/typebox": "^0.24.1" + } + }, + "@sinclair/typebox": { + "version": "0.24.50", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.50.tgz", + "integrity": "sha512-k8ETQOOQDg5FtK7y9KJWpsGLik+QlPmIi8zzl/dGUgshV2QitprkFlCR/AemjWOTyKn9UwSSGRTzLVotvgCjYQ==", "dev": true }, "ansi-styles": { @@ -15980,28 +16374,28 @@ "dev": true }, "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==", + "version": "29.2.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.2.1.tgz", + "integrity": "sha512-Y41Sa4aLCtKAXvwuIpTvcFBkyeYp2gdFWzXGA+ZNES3VwURIB165XO/z7CjETwzCCS53MjW/rLMyyqEnTtaOfA==", "dev": true, "requires": { - "ansi-regex": "^5.0.1", + "@jest/schemas": "^29.0.0", "ansi-styles": "^5.0.0", - "react-is": "^17.0.1" + "react-is": "^18.0.0" } }, "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==", + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", "dev": true } } }, "@testing-library/user-event": { - "version": "14.2.0", - "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.2.0.tgz", - "integrity": "sha512-+hIlG4nJS6ivZrKnOP7OGsDu9Fxmryj9vCl8x0ZINtTJcCHs2zLsYif5GzuRiBF2ck5GZG2aQr7Msg+EHlnYVQ==", + "version": "14.4.3", + "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.4.3.tgz", + "integrity": "sha512-kCUc5MEwaEMakkO5x7aoD+DLi02ehmEM2QCGWvNqAS1dV/fAvORWEjnjsEIvml59M7Y5kCkWN6fCCyPOe8OL6Q==", "dev": true }, "@tootallnate/once": { @@ -16230,43 +16624,216 @@ } }, "@types/jest": { - "version": "28.1.1", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-28.1.1.tgz", - "integrity": "sha512-C2p7yqleUKtCkVjlOur9BWVA4HgUQmEj/HWCt5WzZ5mLXrWnyIfl0wGuArc+kBXsy0ZZfLp+7dywB4HtSVYGVA==", + "version": "29.2.0", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.2.0.tgz", + "integrity": "sha512-KO7bPV21d65PKwv3LLsD8Jn3E05pjNjRZvkm+YTacWhVmykAb07wW6IkZUmQAltwQafNcDUEUrMO2h3jeBSisg==", "dev": true, "requires": { - "jest-matcher-utils": "^27.0.0", - "pretty-format": "^27.0.0" + "expect": "^29.0.0", + "pretty-format": "^29.0.0" }, "dependencies": { - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "@jest/schemas": { + "version": "29.0.0", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.0.0.tgz", + "integrity": "sha512-3Ab5HgYIIAnS0HjqJHQYZS+zXc4tUmTmBH3z83ajI6afXp8X3ZtdLX+nXx+I7LNkJD7uN9LAVhgnjDgZa2z0kA==", + "dev": true, + "requires": { + "@sinclair/typebox": "^0.24.1" + } + }, + "@jest/types": { + "version": "29.2.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.2.1.tgz", + "integrity": "sha512-O/QNDQODLnINEPAI0cl9U6zUIDXEWXt6IC1o2N2QENuos7hlGUIthlKyV4p6ki3TvXFX071blj8HUhgLGquPjw==", + "dev": true, + "requires": { + "@jest/schemas": "^29.0.0", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + } + }, + "@sinclair/typebox": { + "version": "0.24.50", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.50.tgz", + "integrity": "sha512-k8ETQOOQDg5FtK7y9KJWpsGLik+QlPmIi8zzl/dGUgshV2QitprkFlCR/AemjWOTyKn9UwSSGRTzLVotvgCjYQ==", "dev": true }, + "@types/yargs": { + "version": "17.0.13", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.13.tgz", + "integrity": "sha512-9sWaruZk2JGxIQU+IhI1fhPYRcQ0UuTNuKuCW9bR5fp7qi2Llf7WDzNa17Cy7TKnh3cdxDOiyTu6gaLS0eDatg==", + "dev": true, + "requires": { + "@types/yargs-parser": "*" + } + }, "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 }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "ci-info": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.5.0.tgz", + "integrity": "sha512-yH4RezKOGlOhxkmhbeNuC4eYZKAUsEaGtBuBzDDP1eFUKiccDWzBABxBfOx31IDwDIXMTxWuwAxUGModvkbuVw==", + "dev": true + }, + "diff-sequences": { + "version": "29.2.0", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.2.0.tgz", + "integrity": "sha512-413SY5JpYeSBZxmenGEmCVQ8mCgtFJF0w9PROdaS6z987XC2Pd2GOKqOITLtMftmyFZqgtCOb/QA7/Z3ZXfzIw==", + "dev": true + }, + "expect": { + "version": "29.2.1", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.2.1.tgz", + "integrity": "sha512-BJtA754Fba0YWRWHgjKUMTA3ltWarKgITXHQnbZ2mTxTXC4yMQlR0FI7HkB3fJYkhWBf4qjNiqvg3LDtXCcVRQ==", + "dev": true, + "requires": { + "@jest/expect-utils": "^29.2.1", + "jest-get-type": "^29.2.0", + "jest-matcher-utils": "^29.2.1", + "jest-message-util": "^29.2.1", + "jest-util": "^29.2.1" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", + "dev": true + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "jest-diff": { + "version": "29.2.1", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.2.1.tgz", + "integrity": "sha512-gfh/SMNlQmP3MOUgdzxPOd4XETDJifADpT937fN1iUGz+9DgOu2eUPHH25JDkLVcLwwqxv3GzVyK4VBUr9fjfA==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "diff-sequences": "^29.2.0", + "jest-get-type": "^29.2.0", + "pretty-format": "^29.2.1" + } + }, + "jest-get-type": { + "version": "29.2.0", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.2.0.tgz", + "integrity": "sha512-uXNJlg8hKFEnDgFsrCjznB+sTxdkuqiCL6zMgA75qEbAJjJYTs9XPrvDctrEig2GDow22T/LvHgO57iJhXB/UA==", + "dev": true + }, + "jest-matcher-utils": { + "version": "29.2.1", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.2.1.tgz", + "integrity": "sha512-hUTBh7H/Mnb6GTpihbLh8uF5rjAMdekfW/oZNXUMAXi7bbmym2HiRpzgqf/zzkjgejMrVAkPdVSQj+32enlUww==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "jest-diff": "^29.2.1", + "jest-get-type": "^29.2.0", + "pretty-format": "^29.2.1" + } + }, + "jest-message-util": { + "version": "29.2.1", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.2.1.tgz", + "integrity": "sha512-Dx5nEjw9V8C1/Yj10S/8ivA8F439VS8vTq1L7hEgwHFn9ovSKNpYW/kwNh7UglaEgXO42XxzKJB+2x0nSglFVw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.2.1", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.2.1", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + } + }, + "jest-util": { + "version": "29.2.1", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.2.1.tgz", + "integrity": "sha512-P5VWDj25r7kj7kl4pN2rG/RN2c1TLfYYYZYULnS/35nFDjBai+hBeo3MDrYZS7p6IoY3YHZnt2vq4L6mKnLk0g==", + "dev": true, + "requires": { + "@jest/types": "^29.2.1", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + } + }, + "micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "requires": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + } + }, + "picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true + }, "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==", + "version": "29.2.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.2.1.tgz", + "integrity": "sha512-Y41Sa4aLCtKAXvwuIpTvcFBkyeYp2gdFWzXGA+ZNES3VwURIB165XO/z7CjETwzCCS53MjW/rLMyyqEnTtaOfA==", "dev": true, "requires": { - "ansi-regex": "^5.0.1", + "@jest/schemas": "^29.0.0", "ansi-styles": "^5.0.0", - "react-is": "^17.0.1" + "react-is": "^18.0.0" } }, "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==", + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", "dev": true + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } } } }, @@ -16439,20 +17006,13 @@ } }, "@types/react": { - "version": "17.0.37", - "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.37.tgz", - "integrity": "sha512-2FS1oTqBGcH/s0E+CjrCCR9+JMpsu9b69RTFO+40ua43ZqP5MmQ4iUde/dMjWR909KxZwmOQIFq6AV6NjEG5xg==", + "version": "18.0.21", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.21.tgz", + "integrity": "sha512-7QUCOxvFgnD5Jk8ZKlUAhVcRj7GuJRjnjjiY/IUBWKgOlnvDvTMLD4RTF7NPyVmbRhNrbomZiOepg7M/2Kj1mA==", "requires": { "@types/prop-types": "*", "@types/scheduler": "*", "csstype": "^3.0.2" - }, - "dependencies": { - "csstype": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.10.tgz", - "integrity": "sha512-2u44ZG2OcNUO9HDp/Jl8C07x6pU/eTR3ncV91SiK3dhG9TWvRVsCoJw14Ckx5DgWkzGA3waZWO3d7pgqpUI/XA==" - } } }, "@types/react-dates": { @@ -16475,9 +17035,9 @@ } }, "@types/react-dom": { - "version": "17.0.11", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.11.tgz", - "integrity": "sha512-f96K3k+24RaLGVu/Y2Ng3e1EbZ8/cVJvypZWd7cy0ofCBaf2lcM46xNhycMZ2xGwbBjRql7hOlZ+e2WlJ5MH3Q==", + "version": "18.0.6", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.6.tgz", + "integrity": "sha512-/5OFZgfIPSwy+YuIBP/FgJnQnsxhZhjjrnxudMddeblOouIodEQ75X14Rr4wGSG/bknL+Omy9iWlLo1u/9GzAA==", "requires": { "@types/react": "*" } @@ -16557,9 +17117,9 @@ "dev": true }, "@types/testing-library__jest-dom": { - "version": "5.14.3", - "resolved": "https://registry.npmjs.org/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.14.3.tgz", - "integrity": "sha512-oKZe+Mf4ioWlMuzVBaXQ9WDnEm1+umLx0InILg+yvZVBBDmzV5KfZyLrCvadtWcx8+916jLmHafcmqqffl+iIw==", + "version": "5.14.5", + "resolved": "https://registry.npmjs.org/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.14.5.tgz", + "integrity": "sha512-SBwbxYoyPIvxHbeHxTZX2Pe/74F/tX2/D3mMvzabdeJ25bBojfW0TyB8BHrbq/9zaaKICJZjLP+8r6AeZMFCuQ==", "dev": true, "requires": { "@types/jest": "*" @@ -16588,15 +17148,6 @@ "integrity": "sha512-FvUupuM3rlRsRtCN+fDudtmytGO6iHJuuRKS1Ss0pG5z8oX0diNEw94UEL7hgDbpN94rgaK5R7sWm6RrSkZuAQ==", "dev": true }, - "@types/unzipper": { - "version": "0.10.5", - "resolved": "https://registry.npmjs.org/@types/unzipper/-/unzipper-0.10.5.tgz", - "integrity": "sha512-NrLJb29AdnBARpg9S/4ktfPEisbJ0AvaaAr3j7Q1tg8AgcEUsq2HqbNzvgLRoWyRtjzeLEv7vuL39u1mrNIyNA==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, "@types/uuid": { "version": "8.3.1", "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.1.tgz", @@ -17498,6 +18049,7 @@ "@wordpress/deprecated": "file:packages/deprecated", "@wordpress/dom": "file:packages/dom", "@wordpress/element": "file:packages/element", + "@wordpress/escape-html": "file:packages/escape-html", "@wordpress/hooks": "file:packages/hooks", "@wordpress/html-entities": "file:packages/html-entities", "@wordpress/i18n": "file:packages/i18n", @@ -17518,6 +18070,7 @@ "colord": "^2.7.0", "diff": "^4.0.2", "dom-scroll-into-view": "^1.2.1", + "fast-deep-equal": "^3.1.3", "inherits": "^2.0.3", "lodash": "^4.17.21", "react-autosize-textarea": "^7.1.0", @@ -17545,6 +18098,7 @@ "@wordpress/deprecated": "file:packages/deprecated", "@wordpress/dom": "file:packages/dom", "@wordpress/element": "file:packages/element", + "@wordpress/escape-html": "file:packages/escape-html", "@wordpress/hooks": "file:packages/hooks", "@wordpress/html-entities": "file:packages/html-entities", "@wordpress/i18n": "file:packages/i18n", @@ -17562,6 +18116,7 @@ "colord": "^2.7.0", "escape-html": "^1.0.3", "fast-average-color": "^9.1.1", + "fast-deep-equal": "^3.1.3", "lodash": "^4.17.21", "memize": "^1.1.0", "micromodal": "^0.4.10", @@ -17600,6 +18155,7 @@ "@wordpress/shortcode": "file:packages/shortcode", "change-case": "^4.1.2", "colord": "^2.7.0", + "fast-deep-equal": "^3.1.3", "hpq": "^1.3.0", "is-plain-object": "^5.0.0", "lodash": "^4.17.21", @@ -17648,7 +18204,8 @@ "date-fns": "^2.28.0", "dom-scroll-into-view": "^1.2.1", "downshift": "^6.0.15", - "framer-motion": "^6.2.8", + "fast-deep-equal": "^3.1.3", + "framer-motion": "^7.6.1", "gradient-parser": "^0.1.5", "highlight-words-core": "^1.2.2", "lodash": "^4.17.21", @@ -17702,6 +18259,7 @@ "@wordpress/url": "file:packages/url", "change-case": "^4.1.2", "equivalent-key-map": "^0.2.2", + "fast-deep-equal": "^3.1.3", "lodash": "^4.17.21", "memize": "^1.1.0", "rememo": "^4.0.0", @@ -17755,6 +18313,7 @@ "@wordpress/preferences": "file:packages/preferences", "@wordpress/widgets": "file:packages/widgets", "classnames": "^2.3.1", + "fast-deep-equal": "^3.1.3", "lodash": "^4.17.21" } }, @@ -17938,6 +18497,7 @@ "@wordpress/url": "file:packages/url", "@wordpress/viewport": "file:packages/viewport", "@wordpress/warning": "file:packages/warning", + "@wordpress/widgets": "file:packages/widgets", "classnames": "^2.3.1", "lodash": "^4.17.21", "memize": "^1.1.0", @@ -17975,9 +18535,11 @@ "@wordpress/style-engine": "file:packages/style-engine", "@wordpress/url": "file:packages/url", "@wordpress/viewport": "file:packages/viewport", + "@wordpress/widgets": "file:packages/widgets", "classnames": "^2.3.1", "colord": "^2.9.2", "downloadjs": "^1.4.7", + "fast-deep-equal": "^3.1.3", "history": "^5.1.0", "lodash": "^4.17.21", "react-autosize-textarea": "^7.1.0", @@ -18074,13 +18636,25 @@ "version": "file:packages/element", "requires": { "@babel/runtime": "^7.16.0", - "@types/react": "^17.0.37", - "@types/react-dom": "^17.0.11", + "@types/react": "^18.0.21", + "@types/react-dom": "^18.0.6", "@wordpress/escape-html": "file:packages/escape-html", "change-case": "^4.1.2", "is-plain-object": "^5.0.0", - "react": "^17.0.2", - "react-dom": "^17.0.2" + "react": "^18.2.0", + "react-dom": "^18.2.0" + }, + "dependencies": { + "@types/react": { + "version": "18.0.21", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.21.tgz", + "integrity": "sha512-7QUCOxvFgnD5Jk8ZKlUAhVcRj7GuJRjnjjiY/IUBWKgOlnvDvTMLD4RTF7NPyVmbRhNrbomZiOepg7M/2Kj1mA==", + "requires": { + "@types/prop-types": "*", + "@types/scheduler": "*", + "csstype": "^3.0.2" + } + } } }, "@wordpress/env": { @@ -18406,20 +18980,6 @@ "version": "file:packages/npm-package-json-lint-config", "dev": true }, - "@wordpress/nux": { - "version": "file:packages/nux", - "requires": { - "@babel/runtime": "^7.16.0", - "@wordpress/components": "file:packages/components", - "@wordpress/compose": "file:packages/compose", - "@wordpress/data": "file:packages/data", - "@wordpress/deprecated": "file:packages/deprecated", - "@wordpress/element": "file:packages/element", - "@wordpress/i18n": "file:packages/i18n", - "@wordpress/icons": "file:packages/icons", - "rememo": "^4.0.0" - } - }, "@wordpress/plugins": { "version": "file:packages/plugins", "requires": { @@ -18519,7 +19079,7 @@ "requires": { "@babel/runtime": "^7.16.0", "@react-native-clipboard/clipboard": "1.9.0", - "@react-native-community/blur": "3.6.0", + "@react-native-community/blur": "4.2.0", "@react-native-community/slider": "https://raw.githubusercontent.com/wordpress-mobile/react-native-slider/v3.0.2-wp-3/react-native-community-slider-3.0.2-wp-3.tgz", "@react-native-masked-view/masked-view": "0.2.6", "@react-navigation/core": "5.12.0", @@ -18542,26 +19102,26 @@ "fast-average-color": "^9.1.1", "gettext-parser": "^1.3.1", "jed": "^1.1.1", - "jsdom-jscore-rn": "git+https://github.com/iamcco/jsdom-jscore-rn.git#a562f3d57c27c13e5bfc8cf82d496e69a3ba2800", + "jsdom-jscore-rn": "0.1.8", "node-fetch": "^2.6.0", - "react-native": "0.66.2", + "react-native": "0.69.4", "react-native-fast-image": "8.5.11", "react-native-gesture-handler": "https://raw.githubusercontent.com/wordpress-mobile/react-native-gesture-handler/2.3.2-wp-2/react-native-gesture-handler-2.3.2-wp-2.tgz", "react-native-get-random-values": "1.4.0", - "react-native-hr": "git+https://github.com/Riglerr/react-native-hr.git#2d01a5cf77212d100e8b99e0310cce5234f977b3", + "react-native-hr": "https://raw.githubusercontent.com/wordpress-mobile/react-native-hr/1.1.3-wp-1/react-native-hr-1.1.3.tgz", "react-native-hsv-color-picker": "https://raw.githubusercontent.com/wordpress-mobile/react-native-hsv-color-picker/v1.0.1-wp-3/react-native-hsv-color-picker-1.0.1-wp-3.tgz", "react-native-keyboard-aware-scroll-view": "https://raw.githubusercontent.com/wordpress-mobile/react-native-keyboard-aware-scroll-view/v0.8.8-wp-1/react-native-keyboard-aware-scroll-view-0.8.8-wp-1.tgz", "react-native-linear-gradient": "https://raw.githubusercontent.com/wordpress-mobile/react-native-linear-gradient/v2.5.6-wp-3/react-native-linear-gradient-2.5.6-wp-3.tgz", "react-native-modal": "^11.10.0", "react-native-prompt-android": "https://raw.githubusercontent.com/wordpress-mobile/react-native-prompt-android/v1.0.0-wp-3/react-native-prompt-android-1.0.0-wp-3.tgz", - "react-native-reanimated": "https://raw.githubusercontent.com/wordpress-mobile/react-native-reanimated/2.4.1-wp-4/react-native-reanimated-2.4.1-wp-4.tgz", + "react-native-reanimated": "https://raw.githubusercontent.com/wordpress-mobile/react-native-reanimated/2.9.1-wp-2/react-native-reanimated-2.9.1-wp-2.tgz", "react-native-safe-area": "^0.5.0", "react-native-safe-area-context": "3.2.0", "react-native-sass-transformer": "^1.1.1", "react-native-screens": "2.9.0", "react-native-svg": "9.13.6", "react-native-url-polyfill": "^1.1.2", - "react-native-video": "https://raw.githubusercontent.com/wordpress-mobile/react-native-video/5.2.0-wp-4/react-native-video-5.2.0-wp-4.tgz", + "react-native-video": "https://raw.githubusercontent.com/wordpress-mobile/react-native-video/5.2.0-wp-5/react-native-video-5.2.0-wp-5.tgz", "react-native-webview": "11.6.2" } }, @@ -18584,8 +19144,7 @@ "requires": { "@actions/core": "^1.8.0", "@actions/github": "^5.0.1", - "jest-message-util": "^28.0.2", - "unzipper": "^0.10.11" + "jest-message-util": "^28.0.2" }, "dependencies": { "@actions/core": { @@ -19001,6 +19560,7 @@ "@wordpress/element": "file:packages/element", "@wordpress/i18n": "file:packages/i18n", "@wordpress/url": "file:packages/url", + "fast-deep-equal": "^3.1.3", "lodash": "^4.17.21" } }, @@ -19448,9 +20008,9 @@ }, "dependencies": { "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==" + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==" }, "slice-ansi": { "version": "2.1.0", @@ -19519,6 +20079,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "dev": true, "requires": { "micromatch": "^3.1.4", "normalize-path": "^2.1.1" @@ -19536,9 +20097,9 @@ "integrity": "sha512-91IFKeKk7FjfmezPKkwtaRvSpnUc4gDwPAjA1YZ9Gn0q0PPeW+vbeUsZuyDwjI7+QTHhcLen2v25fi/AmhvbJA==" }, "appdirsjs": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/appdirsjs/-/appdirsjs-1.2.6.tgz", - "integrity": "sha512-D8wJNkqMCeQs3kLasatELsddox/Xqkhp+J07iXGyL54fVN7oc+nmNfYzGuCs1IEP6uBw+TfpuO3JKwc+lECy4w==" + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/appdirsjs/-/appdirsjs-1.2.7.tgz", + "integrity": "sha512-Quji6+8kLBC3NnBeo14nPDq0+2jUs5s3/xEye+udFHumHhRk4M7aAMXp/PBJqkKYGuuyR9M/6Dq7d2AViiGmhw==" }, "appium": { "version": "1.22.3", @@ -26172,7 +26733,7 @@ "array-find-index": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", - "integrity": "sha512-M1HQyIXcBGtVywBt8WVdim+lrNaK7VHp99Qt5pSNziXznKHViIBbXWtfRTpEFpF/c4FdfxNAsCCwPp5phBYJtw==", + "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=", "dev": true, "optional": true }, @@ -27280,6 +27841,11 @@ "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==" }, + "async": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", + "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==" + }, "async-each": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz", @@ -27373,6 +27939,12 @@ "resolved": "https://registry.npmjs.org/autosize/-/autosize-4.0.2.tgz", "integrity": "sha512-jnSyH2d+qdfPGpWlcuhGiHmqBJ6g3X+8T+iRwFrHPLVcdoGJE/x6Qicm6aDHfTsbgZKxyV8UU/YB2p4cjKDRRA==" }, + "available-typed-arrays": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", + "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", + "dev": true + }, "aws-sign2": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", @@ -28294,7 +28866,9 @@ "big-integer": { "version": "1.6.48", "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.48.tgz", - "integrity": "sha512-j51egjPa7/i+RdiRuJbPdJ2FIUYYPhvYLjzoYbcMMm62ooO6F94fETG4MTs46zPAF9Brs04OajboA/qTGuz78w==" + "integrity": "sha512-j51egjPa7/i+RdiRuJbPdJ2FIUYYPhvYLjzoYbcMMm62ooO6F94fETG4MTs46zPAF9Brs04OajboA/qTGuz78w==", + "dev": true, + "optional": true }, "big.js": { "version": "5.2.2", @@ -28340,16 +28914,6 @@ } } }, - "binary": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/binary/-/binary-0.3.0.tgz", - "integrity": "sha1-n2BVO8XOjDOG87VTz/R0Yq3sqnk=", - "dev": true, - "requires": { - "buffers": "~0.1.1", - "chainsaw": "~0.1.0" - } - }, "binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", @@ -28370,7 +28934,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", - "dev": true, "requires": { "buffer": "^5.5.0", "inherits": "^2.0.4", @@ -28380,14 +28943,12 @@ "base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" }, "buffer": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "dev": true, "requires": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" @@ -28396,20 +28957,17 @@ "ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" }, "inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "readable-stream": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, "requires": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -28628,14 +29186,6 @@ } } }, - "bplist-creator": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/bplist-creator/-/bplist-creator-0.1.0.tgz", - "integrity": "sha512-sXaHZicyEEmY86WyueLTQesbeoH/mquvarJaQNbjuOQO+7gbFcDEWqKmcWA4cOTLzFlfgvkiVxolk1k5bBIpmg==", - "requires": { - "stream-buffers": "2.2.x" - } - }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -28854,24 +29404,12 @@ "integrity": "sha512-4/rOEg86jivtPTeOUUT61jJO1Ya1TrR/OkqCSZDyq84WJh3LuuiphBYJN+fm5xufIk4XAFcEwte/8WzC8If/1g==", "dev": true }, - "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 - }, "buffer-xor": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=", "dev": true }, - "buffers": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/buffers/-/buffers-0.1.1.tgz", - "integrity": "sha1-skV5w77U1tOWru5tmorn9Ugqt7s=", - "dev": true - }, "builtin-modules": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", @@ -29183,8 +29721,7 @@ "camelcase": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" }, "camelcase-css": { "version": "2.0.1", @@ -29195,7 +29732,7 @@ "camelcase-keys": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", - "integrity": "sha512-bA/Z/DERHKqoEOrp+qeGKw1QlvEQkGZSc0XaY6VnTxZr+Kv1G5zFwttpjv8qxZ/sBPT4nthwZaAcsAZTJlSKXQ==", + "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=", "dev": true, "optional": true, "requires": { @@ -29206,7 +29743,7 @@ "camelcase": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", - "integrity": "sha512-DLIsRzJVBQu72meAKPkWQOLcujdXT32hwdfnkI1frSiSRMK1MofjKHf+MEx0SB6fjEFXL8fBDv1dKymBlOp4Qw==", + "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=", "dev": true, "optional": true } @@ -29230,9 +29767,9 @@ } }, "caniuse-lite": { - "version": "1.0.30001352", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001352.tgz", - "integrity": "sha512-GUgH8w6YergqPQDGWhJGt8GDRnY0L/iJVQcU3eJ46GYf52R8tk0Wxp0PymuFVZboJYXGiCqwozAYZNRjVj6IcA==" + "version": "1.0.30001434", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001434.tgz", + "integrity": "sha512-aOBHrLmTQw//WFa2rcF1If9fa3ypkC1wzqqiKHgfdrXTWcU8C4gKVZT77eQAPWN1APys3+uQ0Df07rKauXGEYA==" }, "capital-case": { "version": "1.0.4", @@ -29248,6 +29785,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/capture-exit/-/capture-exit-2.0.0.tgz", "integrity": "sha512-PiT/hQmTonHhl/HFGN+Lx3JJUznrVYJ3+AQsnthneZbvW7x+f08Tk7yLJTLEOUvBTbduLeeBkxEaYXUOUrRq6g==", + "dev": true, "requires": { "rsvp": "^4.8.4" } @@ -29270,23 +29808,6 @@ "integrity": "sha512-Jt9tIBkRc9POUof7QA/VwWd+58fKkEEfI+/t1/eOlxKM7ZhrczNzMFefge7Ai+39y1pR/pP6cI19guHy3FSLmw==", "dev": true }, - "chainsaw": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/chainsaw/-/chainsaw-0.1.0.tgz", - "integrity": "sha1-XqtQsor+WAdNDVgpE4iCi15fvJg=", - "dev": true, - "requires": { - "traverse": ">=0.3.0 <0.4" - }, - "dependencies": { - "traverse": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.3.9.tgz", - "integrity": "sha1-cXuPIgzAu3tE5AUUwisui7xw2Lk=", - "dev": true - } - } - }, "chalk": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", @@ -29726,6 +30247,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", + "dev": true, "requires": { "restore-cursor": "^2.0.0" } @@ -29733,7 +30255,8 @@ "cli-spinners": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.2.0.tgz", - "integrity": "sha512-tgU3fKwzYjiLEQgPMD9Jt+JjHVL9kW93FiIMX/l7rivvOD4/LL0Mf7gda3+4U2KJBloybwgj5KEoQgGRioMiKQ==" + "integrity": "sha512-tgU3fKwzYjiLEQgPMD9Jt+JjHVL9kW93FiIMX/l7rivvOD4/LL0Mf7gda3+4U2KJBloybwgj5KEoQgGRioMiKQ==", + "dev": true }, "cli-table3": { "version": "0.6.2", @@ -31483,7 +32006,7 @@ "css.escape": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", - "integrity": "sha1-QuJ9T6BK4y+TGktNQZH6nN3ul8s=", + "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", "dev": true }, "cssesc": { @@ -31595,7 +32118,7 @@ "currently-unhandled": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", - "integrity": "sha512-/fITjgjGU50vjQ4FH6eUoYu+iUoUKIXws2hL15JJpIR+BbTxaXQsMuuyjtNh2WqsSBS5nsaZHFsFecyw5CCAng==", + "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=", "dev": true, "optional": true, "requires": { @@ -31691,9 +32214,9 @@ "dev": true }, "dayjs": { - "version": "1.10.7", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.10.7.tgz", - "integrity": "sha512-P6twpd70BcPK34K26uJ1KT3wlhpuOAPoMwJzpsIWUxHZ7wpmbdZL/hQqBDfz7hGurYSa5PhzdhDHtt319hL3ig==" + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.6.tgz", + "integrity": "sha512-zZbY5giJAinCG+7AGaw0wIhNZ6J8AhWuSXKvuc1KAyMiRsvGQWqh4L+MomvhdAYjN+lqvVCMq1I41e3YHvXkyQ==" }, "debug": { "version": "3.1.0", @@ -32135,7 +32658,18 @@ "depd": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", + "dev": true + }, + "deprecated-react-native-prop-types": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/deprecated-react-native-prop-types/-/deprecated-react-native-prop-types-2.3.0.tgz", + "integrity": "sha512-pWD0voFtNYxrVqvBMYf5gq3NA2GCpfodS1yNynTPc93AYA/KEMGeWDqqeUB6R2Z9ZofVhks2aeJXiuQqKNpesA==", + "requires": { + "@react-native/normalize-color": "*", + "invariant": "*", + "prop-types": "*" + } }, "deprecation": { "version": "2.0.0", @@ -32156,7 +32690,8 @@ "destroy": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", - "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=", + "dev": true }, "detab": { "version": "2.0.4", @@ -32523,9 +33058,9 @@ } }, "domutils": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", - "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz", + "integrity": "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==", "requires": { "dom-serializer": "0", "domelementtype": "1" @@ -32598,15 +33133,6 @@ "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", "dev": true }, - "duplexer2": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", - "integrity": "sha1-ixLauHjA1p4+eJEFFmKjL8a93ME=", - "dev": true, - "requires": { - "readable-stream": "^2.0.2" - } - }, "duplexify": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.6.0.tgz", @@ -32784,8 +33310,7 @@ "envinfo": { "version": "7.8.1", "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.8.1.tgz", - "integrity": "sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw==", - "dev": true + "integrity": "sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw==" }, "equivalent-key-map": { "version": "0.2.2", @@ -34452,7 +34977,8 @@ "exec-sh": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/exec-sh/-/exec-sh-0.3.2.tgz", - "integrity": "sha512-9sLAvzhI5nc8TpuQUh4ahMdCrWT00wPWz7j47/emR5+2qEfoZP5zzUXvx+vdx+H6ohhnsYC31iX04QLYJK8zTg==" + "integrity": "sha512-9sLAvzhI5nc8TpuQUh4ahMdCrWT00wPWz7j47/emR5+2qEfoZP5zzUXvx+vdx+H6ohhnsYC31iX04QLYJK8zTg==", + "dev": true }, "execa": { "version": "4.0.2", @@ -35173,8 +35699,7 @@ "fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, "fast-diff": { "version": "1.2.0", @@ -35771,6 +36296,15 @@ "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", "dev": true }, + "for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dev": true, + "requires": { + "is-callable": "^1.1.3" + } + }, "for-in": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", @@ -36028,24 +36562,39 @@ } }, "framer-motion": { - "version": "6.2.8", - "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-6.2.8.tgz", - "integrity": "sha512-4PtBWFJ6NqR350zYVt9AsFDtISTqsdqna79FvSYPfYDXuuqFmiKtZdkTnYPslnsOMedTW0pEvaQ7eqjD+sA+HA==", + "version": "7.6.15", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-7.6.15.tgz", + "integrity": "sha512-z3LPTWWq3zlmtxtZyCHWyucNRHg8iXXeLo9cFQwCIgOT8mp3gWFzU+KrGQ9h3QJFx79G9MVqAcuIN6dVhhuFQQ==", "requires": { "@emotion/is-prop-valid": "^0.8.2", - "framesync": "6.0.1", + "@motionone/dom": "10.13.1", + "framesync": "6.1.2", "hey-listen": "^1.0.8", - "popmotion": "11.0.3", - "style-value-types": "5.0.0", - "tslib": "^2.1.0" + "popmotion": "11.0.5", + "style-value-types": "5.1.2", + "tslib": "2.4.0" + }, + "dependencies": { + "tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" + } } }, "framesync": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/framesync/-/framesync-6.0.1.tgz", - "integrity": "sha512-fUY88kXvGiIItgNC7wcTOl0SNRCVXMKSWW2Yzfmn7EKNc+MpCzcz9DhdHcdjbrtN3c6R4H5dTY2jiCpPdysEjA==", + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/framesync/-/framesync-6.1.2.tgz", + "integrity": "sha512-jBTqhX6KaQVDyus8muwZbBeGGP0XgujBRbQ7gM7BRdS3CadCZIHiawyzYLnafYcvZIh5j8WE7cxZKFn7dXhu9g==", "requires": { - "tslib": "^2.1.0" + "tslib": "2.4.0" + }, + "dependencies": { + "tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" + } } }, "fresh": { @@ -36086,9 +36635,9 @@ }, "dependencies": { "graceful-fs": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", - "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==" + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==" } } }, @@ -36147,43 +36696,6 @@ "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", "optional": true }, - "fstream": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz", - "integrity": "sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "inherits": "~2.0.0", - "mkdirp": ">=0.5 0", - "rimraf": "2" - }, - "dependencies": { - "glob": { - "version": "7.1.7", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", - "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", - "dev": true, - "requires": { - "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" - } - }, - "rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - } - } - }, "function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", @@ -36365,6 +36877,12 @@ "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", "dev": true }, + "functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true + }, "gauge": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.4.tgz", @@ -36518,7 +37036,7 @@ "get-stdin": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", - "integrity": "sha512-F5aQMywwJ2n85s4hJPTT9RPxGmubonuB10MNYo17/xph174n2MIR33HRguhzVag10O/npM7SPk73LMZNP+FaWw==", + "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=", "dev": true, "optional": true }, @@ -36749,7 +37267,7 @@ "glob-to-regexp": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz", - "integrity": "sha512-Iozmtbqv0noj0uDDqoL0zNq0VBEfK2YFoMAZoxJe4cwphvLR+JskfF30QhXHOR4m3KrE6NLRYw+U9MRXvifyig==", + "integrity": "sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs=", "dev": true }, "global": { @@ -37385,14 +37903,23 @@ } }, "hermes-engine": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/hermes-engine/-/hermes-engine-0.9.0.tgz", - "integrity": "sha512-r7U+Y4P2Qg/igFVZN+DpT7JFfXUn1MM4dFne8aW+cCrF6RRymof+VqrUHs1kl07j8h8V2CNesU19RKgWbr3qPw==" + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/hermes-engine/-/hermes-engine-0.11.0.tgz", + "integrity": "sha512-7aMUlZja2IyLYAcZ69NBnwJAR5ZOYlSllj0oMpx08a8HzxHOys0eKCzfphrf6D0vX1JGO1QQvVsQKe6TkYherw==" + }, + "hermes-estree": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.6.0.tgz", + "integrity": "sha512-2YTGzJCkhdmT6VuNprWjXnvTvw/3iPNw804oc7yknvQpNKo+vJGZmtvLLCghOZf0OwzKaNAzeIMp71zQbNl09w==" }, "hermes-parser": { - "version": "0.4.7", - "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.4.7.tgz", - "integrity": "sha512-jc+zCtXbtwTiXoMAoXOHepxAaGVFIp89wwE9qcdwnMd/uGVEtPoY8FaFSsx0ThPvyKirdR2EsIIDVrpbSXz1Ag==" + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.6.0.tgz", + "integrity": "sha512-Vf58jBZca2+QBLR9h7B7mdg8oFz2g5ILz1iVouZ5DOrOrAfBmPfJjdjDT8jrO0f+iJ4/hSRrQHqHIjSnTaLUDQ==", + "dev": true, + "requires": { + "hermes-estree": "0.6.0" + } }, "hermes-profile-transformer": { "version": "0.0.6", @@ -37403,9 +37930,9 @@ }, "dependencies": { "source-map": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", - "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==" + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==" } } }, @@ -37648,22 +38175,32 @@ "dev": true }, "http-errors": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", - "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", "requires": { - "depd": "~1.1.2", + "depd": "2.0.0", "inherits": "2.0.4", "setprototypeof": "1.2.0", - "statuses": ">= 1.5.0 < 2", + "statuses": "2.0.1", "toidentifier": "1.0.1" }, "dependencies": { + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" + }, "inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, + "statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" + }, "toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", @@ -38549,6 +39086,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", + "dev": true, "requires": { "ci-info": "^2.0.0" } @@ -38704,8 +39242,7 @@ "is-interactive": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", - "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", - "dev": true + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==" }, "is-ip": { "version": "3.1.0", @@ -38903,6 +39440,231 @@ "text-extensions": "^1.0.0" } }, + "is-typed-array": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.9.tgz", + "integrity": "sha512-kfrlnTTn8pZkfpJMUgYD7YZ3qzeJgWUn8XfVYBARc4wnmNOmLbmuuaAs3q5fvB0UJOn6yHAKaGTPM7d6ezoD/A==", + "dev": true, + "requires": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "es-abstract": "^1.20.0", + "for-each": "^0.3.3", + "has-tostringtag": "^1.0.0" + }, + "dependencies": { + "call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + } + }, + "define-properties": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", + "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==", + "dev": true, + "requires": { + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + } + }, + "es-abstract": { + "version": "1.20.4", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.20.4.tgz", + "integrity": "sha512-0UtvRN79eMe2L+UNEF1BwRe364sj/DXhQ/k5FmivgoSdpM90b8Jc0mDzKMGo7QS0BVbOP/bTwBKNnDc9rNzaPA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "function.prototype.name": "^1.1.5", + "get-intrinsic": "^1.1.3", + "get-symbol-description": "^1.0.0", + "has": "^1.0.3", + "has-property-descriptors": "^1.0.0", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.3", + "is-callable": "^1.2.7", + "is-negative-zero": "^2.0.2", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "is-string": "^1.0.7", + "is-weakref": "^1.0.2", + "object-inspect": "^1.12.2", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.4.3", + "safe-regex-test": "^1.0.0", + "string.prototype.trimend": "^1.0.5", + "string.prototype.trimstart": "^1.0.5", + "unbox-primitive": "^1.0.2" + }, + "dependencies": { + "get-intrinsic": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz", + "integrity": "sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.3" + } + } + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "function.prototype.name": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", + "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.0", + "functions-have-names": "^1.2.2" + } + }, + "has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "dev": true + }, + "has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true + }, + "is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true + }, + "is-negative-zero": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", + "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", + "dev": true + }, + "is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + } + }, + "is-shared-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", + "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2" + } + }, + "is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dev": true, + "requires": { + "has-tostringtag": "^1.0.0" + } + }, + "is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dev": true, + "requires": { + "has-symbols": "^1.0.2" + } + }, + "is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.2" + } + }, + "object-inspect": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", + "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==", + "dev": true + }, + "object.assign": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", + "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + } + }, + "regexp.prototype.flags": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", + "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "functions-have-names": "^1.2.2" + } + }, + "string.prototype.trimstart": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.5.tgz", + "integrity": "sha512-THx16TJCGlsN0o6dl2o6ncWUsdgnLRSA23rRE5pyGBw/mLr3Ej/R2LaqCtgP8VNMGZsvMWnf9ooZPyY2bHvUFg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.19.5" + } + }, + "unbox-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" + } + } + } + }, "is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", @@ -38912,16 +39674,21 @@ "is-unicode-supported": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", - "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", - "dev": true + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==" }, "is-utf8": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", - "integrity": "sha512-rMYPYvCzsXywIsldgLaSoPlw5PfoB/ssr7hY4pLfcodrA5M/eArza1a9VmTiNIBNMjOGr1Ow9mTyU2o69U6U9Q==", + "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", "dev": true, "optional": true }, + "is-weakmap": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz", + "integrity": "sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==", + "dev": true + }, "is-weakref": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.1.tgz", @@ -38931,6 +39698,45 @@ "call-bind": "^1.0.0" } }, + "is-weakset": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.2.tgz", + "integrity": "sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + }, + "dependencies": { + "call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + } + }, + "get-intrinsic": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz", + "integrity": "sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.3" + } + }, + "has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true + } + } + }, "is-whitespace-character": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-whitespace-character/-/is-whitespace-character-1.0.4.tgz", @@ -40078,13 +40884,13 @@ "jest-get-type": { "version": "26.3.0", "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-26.3.0.tgz", - "integrity": "sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig==", - "dev": true + "integrity": "sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig==" }, "jest-haste-map": { "version": "26.6.2", "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-26.6.2.tgz", "integrity": "sha512-easWIJXIw71B2RdR8kgqpjQrbMRWQBgiBwXYEhtGUTaX+doCjBheluShdDMeR8IMfJiTqH4+zfhtg29apJf/8w==", + "dev": true, "requires": { "@jest/types": "^26.6.2", "@types/graceful-fs": "^4.1.2", @@ -40106,6 +40912,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "dev": true, "requires": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" @@ -40115,6 +40922,7 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, "requires": { "fill-range": "^7.0.1" } @@ -40123,6 +40931,7 @@ "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, "requires": { "to-regex-range": "^5.0.1" } @@ -40130,17 +40939,20 @@ "graceful-fs": { "version": "4.2.8", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz", - "integrity": "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==" + "integrity": "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==", + "dev": true }, "is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true }, "micromatch": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", + "dev": true, "requires": { "braces": "^3.0.1", "picomatch": "^2.2.3" @@ -40149,19 +40961,22 @@ "picomatch": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", - "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==" + "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", + "dev": true } } }, "normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true }, "to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, "requires": { "is-number": "^7.0.0" } @@ -40682,7 +41497,8 @@ "jest-regex-util": { "version": "26.0.0", "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-26.0.0.tgz", - "integrity": "sha512-Gv3ZIs/nA48/Zvjrl34bf+oD76JHiGDUxNOVgUjh3j890sblXryjY4rss71fPtD/njchl6PSE2hIhvyWa1eT0A==" + "integrity": "sha512-Gv3ZIs/nA48/Zvjrl34bf+oD76JHiGDUxNOVgUjh3j890sblXryjY4rss71fPtD/njchl6PSE2hIhvyWa1eT0A==", + "dev": true }, "jest-resolve": { "version": "27.4.5", @@ -41617,6 +42433,7 @@ "version": "26.6.2", "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-26.6.2.tgz", "integrity": "sha512-S5wqyz0DXnNJPd/xfIzZ5Xnp1HrJWBczg8mMfMpN78OJ5eDxXyf+Ygld9wX1DnUWbIbhM1YDY95NjR4CBXkb2g==", + "dev": true, "requires": { "@types/node": "*", "graceful-fs": "^4.2.4" @@ -41625,7 +42442,8 @@ "graceful-fs": { "version": "4.2.8", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz", - "integrity": "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==" + "integrity": "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==", + "dev": true } } }, @@ -41962,6 +42780,7 @@ "version": "26.6.2", "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-26.6.2.tgz", "integrity": "sha512-MDW0fKfsn0OI7MS7Euz6h8HNDXVQ0gaM9uW6RjfDmd1DAFcaxX9OqIakHIqhbnmF08Cf2DLDG+ulq8YQQ0Lp0Q==", + "dev": true, "requires": { "@jest/types": "^26.6.2", "@types/node": "*", @@ -41975,6 +42794,7 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, "requires": { "fill-range": "^7.0.1" } @@ -41983,6 +42803,7 @@ "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, "requires": { "to-regex-range": "^5.0.1" } @@ -41990,17 +42811,20 @@ "graceful-fs": { "version": "4.2.8", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz", - "integrity": "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==" + "integrity": "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==", + "dev": true }, "is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true }, "micromatch": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", + "dev": true, "requires": { "braces": "^3.0.1", "picomatch": "^2.2.3" @@ -42009,12 +42833,14 @@ "picomatch": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", - "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==" + "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", + "dev": true }, "to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, "requires": { "is-number": "^7.0.0" } @@ -42266,6 +43092,7 @@ "version": "26.6.2", "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.6.2.tgz", "integrity": "sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==", + "dev": true, "requires": { "@types/node": "*", "merge-stream": "^2.0.0", @@ -42275,12 +43102,14 @@ "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true }, "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, "requires": { "has-flag": "^4.0.0" } @@ -42347,40 +43176,115 @@ "integrity": "sha512-KmxeBlRjwoqCnBBKGsihFtvsBHyUFlBxJPK4FzeYcIuBfdjv6jFys44JITAgSTbQD+vIdwMEfyZklsuQX0yI1Q==" }, "jscodeshift": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/jscodeshift/-/jscodeshift-0.11.0.tgz", - "integrity": "sha512-SdRK2C7jjs4k/kT2mwtO07KJN9RnjxtKn03d9JVj6c3j9WwaLcFYsICYDnLAzY0hp+wG2nxl+Cm2jWLiNVYb8g==", - "requires": { - "@babel/core": "^7.1.6", - "@babel/parser": "^7.1.6", - "@babel/plugin-proposal-class-properties": "^7.1.0", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.1.0", - "@babel/plugin-proposal-optional-chaining": "^7.1.0", - "@babel/plugin-transform-modules-commonjs": "^7.1.0", - "@babel/preset-flow": "^7.0.0", - "@babel/preset-typescript": "^7.1.0", - "@babel/register": "^7.0.0", + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/jscodeshift/-/jscodeshift-0.13.1.tgz", + "integrity": "sha512-lGyiEbGOvmMRKgWk4vf+lUrCWO/8YR8sUR3FKF1Cq5fovjZDlIcw3Hu5ppLHAnEXshVffvaM0eyuY/AbOeYpnQ==", + "requires": { + "@babel/core": "^7.13.16", + "@babel/parser": "^7.13.16", + "@babel/plugin-proposal-class-properties": "^7.13.0", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.13.8", + "@babel/plugin-proposal-optional-chaining": "^7.13.12", + "@babel/plugin-transform-modules-commonjs": "^7.13.8", + "@babel/preset-flow": "^7.13.13", + "@babel/preset-typescript": "^7.13.0", + "@babel/register": "^7.13.16", "babel-core": "^7.0.0-bridge.0", - "colors": "^1.1.2", + "chalk": "^4.1.2", "flow-parser": "0.*", "graceful-fs": "^4.2.4", "micromatch": "^3.1.10", "neo-async": "^2.5.0", "node-dir": "^0.1.17", - "recast": "^0.20.3", - "temp": "^0.8.1", + "recast": "^0.20.4", + "temp": "^0.8.4", "write-file-atomic": "^2.3.0" }, "dependencies": { - "colors": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", - "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==" + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "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==", + "requires": { + "color-name": "~1.1.4" + } + }, + "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==" + }, + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "requires": { + "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" + } }, "graceful-fs": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz", - "integrity": "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==" + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==" + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "requires": { + "glob": "^7.1.3" + } + }, + "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==", + "requires": { + "has-flag": "^4.0.0" + } + }, + "temp": { + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/temp/-/temp-0.8.4.tgz", + "integrity": "sha512-s0ZZzd0BzYv5tLSptZooSjK8oj6C+c19p7Vqta9+6NPOf7r+fxq0cJe6/oN4LTC79sy5NY8ucOJNgwsKCSbfqg==", + "requires": { + "rimraf": "~2.6.2" + } } } }, @@ -42554,8 +43458,9 @@ } }, "jsdom-jscore-rn": { - "version": "git+https://github.com/iamcco/jsdom-jscore-rn.git#a562f3d57c27c13e5bfc8cf82d496e69a3ba2800", - "from": "git+https://github.com/iamcco/jsdom-jscore-rn.git#a562f3d57c27c13e5bfc8cf82d496e69a3ba2800", + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/jsdom-jscore-rn/-/jsdom-jscore-rn-0.1.8.tgz", + "integrity": "sha512-Sm0BCCQL3RRmJwztZa9B4QIOxgFOiu7vpsJjLsjG55HlRzOgUdmTOHrhV4SZkjXef5sBF+E8qKnrhsBUO7Gxjg==", "requires": { "htmlparser2-without-node-native": "^3.9.2", "querystring": "^0.2.0" @@ -43490,12 +44395,6 @@ } } }, - "listenercount": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/listenercount/-/listenercount-1.0.1.tgz", - "integrity": "sha1-hMinKrWcRyUyFIDJdeZQg0LnCTc=", - "dev": true - }, "listr": { "version": "0.14.3", "resolved": "https://registry.npmjs.org/listr/-/listr-0.14.3.tgz", @@ -43638,7 +44537,7 @@ "load-json-file": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", - "integrity": "sha512-cy7ZdNRXdablkXYNI049pthVeXFurRyb9+hA/dZzerZ0pGTx42z+y+ssxBaVV2l70t1muq5IdKhn4UtcoGUY9A==", + "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", "dev": true, "optional": true, "requires": { @@ -43652,7 +44551,7 @@ "parse-json": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", - "integrity": "sha512-QR/GGaKCkhwk1ePQNYDRKYZ3mwU9ypsKhB0XyFnLQdomyEqk3e8wpW3V5Jp88zbxK4n5ST1nqo+g9juTpownhQ==", + "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", "dev": true, "optional": true, "requires": { @@ -43781,23 +44680,12 @@ "dev": true }, "log-symbols": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz", - "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", "requires": { - "chalk": "^2.0.1" - }, - "dependencies": { - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - } + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" } }, "log-update": { @@ -43838,11 +44726,6 @@ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" }, - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" - }, "emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -43960,7 +44843,7 @@ "loud-rejection": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz", - "integrity": "sha512-RPNliZOFkqFumDhvYqOaNY4Uz9oJM2K9tC6JWsJJsNdhuONW4LQHRBpb0qf4pJApVffI5N39SwzWZJuEhfd7eQ==", + "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=", "dev": true, "optional": true, "requires": { @@ -44005,7 +44888,7 @@ "lz-string": { "version": "1.4.4", "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.4.4.tgz", - "integrity": "sha1-wNjq82BZ9wV5bh40SBHPTEmNOiY=", + "integrity": "sha512-0ckx7ZHRPqb0oUm8zNr+90mtf9DQB60H1wMCjBtfi62Kl3a7JbHob6gA2bC+xRvZoOL+1hzUK8jeuEIQE8svEQ==", "dev": true }, "macos-release": { @@ -44210,7 +45093,7 @@ "map-obj": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", - "integrity": "sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==", + "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", "dev": true, "optional": true }, @@ -44556,8 +45439,7 @@ "memoize-one": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz", - "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==", - "dev": true + "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==" }, "memoizerific": { "version": "1.11.3", @@ -44803,9 +45685,9 @@ "dev": true }, "metro": { - "version": "0.66.2", - "resolved": "https://registry.npmjs.org/metro/-/metro-0.66.2.tgz", - "integrity": "sha512-uNsISfcQ3iKKSHoN5Q+LAh0l3jeeg7ZcNZ/4BAHGsk02erA0OP+l2m+b5qYVoPptHz9Oc3KyG5oGJoTu41pWjg==", + "version": "0.70.3", + "resolved": "https://registry.npmjs.org/metro/-/metro-0.70.3.tgz", + "integrity": "sha512-uEWS7xg8oTetQDABYNtsyeUjdLhH3KAvLFpaFFoJqUpOk2A3iygszdqmjobFl6W4zrvKDJS+XxdMR1roYvUhTw==", "requires": { "@babel/code-frame": "^7.0.0", "@babel/core": "^7.14.0", @@ -44816,7 +45698,7 @@ "@babel/types": "^7.0.0", "absolute-path": "^0.0.0", "accepts": "^1.3.7", - "async": "^2.4.0", + "async": "^3.2.2", "chalk": "^4.0.0", "ci-info": "^2.0.0", "connect": "^3.6.5", @@ -44824,31 +45706,29 @@ "denodeify": "^1.2.1", "error-stack-parser": "^2.0.6", "fs-extra": "^1.0.0", - "graceful-fs": "^4.1.3", - "hermes-parser": "0.4.7", + "graceful-fs": "^4.2.4", + "hermes-parser": "0.6.0", "image-size": "^0.6.0", "invariant": "^2.2.4", - "jest-haste-map": "^26.5.2", - "jest-worker": "^26.0.0", + "jest-haste-map": "^27.3.1", + "jest-worker": "^27.2.0", "lodash.throttle": "^4.1.1", - "metro-babel-register": "0.66.2", - "metro-babel-transformer": "0.66.2", - "metro-cache": "0.66.2", - "metro-cache-key": "0.66.2", - "metro-config": "0.66.2", - "metro-core": "0.66.2", - "metro-hermes-compiler": "0.66.2", - "metro-inspector-proxy": "0.66.2", - "metro-minify-uglify": "0.66.2", - "metro-react-native-babel-preset": "0.66.2", - "metro-resolver": "0.66.2", - "metro-runtime": "0.66.2", - "metro-source-map": "0.66.2", - "metro-symbolicate": "0.66.2", - "metro-transform-plugins": "0.66.2", - "metro-transform-worker": "0.66.2", + "metro-babel-transformer": "0.70.3", + "metro-cache": "0.70.3", + "metro-cache-key": "0.70.3", + "metro-config": "0.70.3", + "metro-core": "0.70.3", + "metro-hermes-compiler": "0.70.3", + "metro-inspector-proxy": "0.70.3", + "metro-minify-uglify": "0.70.3", + "metro-react-native-babel-preset": "0.70.3", + "metro-resolver": "0.70.3", + "metro-runtime": "0.70.3", + "metro-source-map": "0.70.3", + "metro-symbolicate": "0.70.3", + "metro-transform-plugins": "0.70.3", + "metro-transform-worker": "0.70.3", "mime-types": "^2.1.27", - "mkdirp": "^0.5.1", "node-fetch": "^2.2.0", "nullthrows": "^1.1.1", "rimraf": "^2.5.4", @@ -44857,27 +45737,51 @@ "strip-ansi": "^6.0.0", "temp": "0.8.3", "throat": "^5.0.0", - "ws": "^1.1.5", + "ws": "^7.5.1", "yargs": "^15.3.1" }, "dependencies": { + "@jest/types": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", + "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0" + } + }, + "@types/yargs": { + "version": "16.0.4", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", + "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", + "requires": { + "@types/yargs-parser": "*" + } + }, "ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" }, - "async": { - "version": "2.6.4", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", - "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", + "anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", "requires": { - "lodash": "^4.17.14" + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" } }, - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "requires": { + "fill-range": "^7.0.1" + } }, "debug": { "version": "2.6.9", @@ -44892,6 +45796,14 @@ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "requires": { + "to-regex-range": "^5.0.1" + } + }, "find-up": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", @@ -44904,7 +45816,7 @@ "fs-extra": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-1.0.0.tgz", - "integrity": "sha1-zTzl9+fLYUWIP8rjGR6Yd/hYeVA=", + "integrity": "sha512-VerQV6vEKuhDWD2HGOybV6v5I73syoc/cXAbKlgTC7M/oFVEtklWlp9QH2Ijw3IaWDOQcMkldSPa7zXy79Z/UQ==", "requires": { "graceful-fs": "^4.1.2", "jsonfile": "^2.1.0", @@ -44912,27 +45824,119 @@ } }, "glob": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", - "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", - "minimatch": "^3.0.4", + "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, + "graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==" + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "hermes-parser": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.6.0.tgz", + "integrity": "sha512-Vf58jBZca2+QBLR9h7B7mdg8oFz2g5ILz1iVouZ5DOrOrAfBmPfJjdjDT8jrO0f+iJ4/hSRrQHqHIjSnTaLUDQ==", + "requires": { + "hermes-estree": "0.6.0" + } + }, "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==" }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" + }, + "jest-haste-map": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-27.5.1.tgz", + "integrity": "sha512-7GgkZ4Fw4NFbMSDSpZwXeBiIbx+t/46nJ2QitkOjvwPYyZmqttu2TDSimMHP1EkPOi4xUZAN1doE5Vd25H4Jng==", + "requires": { + "@jest/types": "^27.5.1", + "@types/graceful-fs": "^4.1.2", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "fsevents": "^2.3.2", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^27.5.1", + "jest-serializer": "^27.5.1", + "jest-util": "^27.5.1", + "jest-worker": "^27.5.1", + "micromatch": "^4.0.4", + "walker": "^1.0.7" + } + }, + "jest-regex-util": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-27.5.1.tgz", + "integrity": "sha512-4bfKq2zie+x16okqDXjXn9ql2B0dScQu+vcwe4TvFVhkVyuWLqpZrZtXxLLWoXYgn0E87I6r6GRYHF7wFZBUvg==" + }, + "jest-serializer": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-27.5.1.tgz", + "integrity": "sha512-jZCyo6iIxO1aqUxpuBlwTDMkzOAJS4a3eYz3YzgxxVQFwLeSA7Jfq5cbqCY+JLvTDrWirgusI/0KwxKMgrdf7w==", + "requires": { + "@types/node": "*", + "graceful-fs": "^4.2.9" + } + }, + "jest-util": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz", + "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", + "requires": { + "@jest/types": "^27.5.1", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "dependencies": { + "ci-info": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.6.2.tgz", + "integrity": "sha512-lVZdhvbEudris15CLytp2u6Y0p5EKfztae9Fqa189MfNmln9F33XuH69v5fvNfiRN5/0eAUz2yJL3mo+nhaRKg==" + }, + "picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==" + } + } + }, + "jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "requires": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + } + }, "jsonfile": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz", - "integrity": "sha1-NzaitCi4e72gzIO1P6PWM6NcKug=", + "integrity": "sha512-PKllAqbgLgxHaj8TElYymKCAgrASebJrWpTnEkOaTowt23VKXXN0sUeriJ+eh7y6ufb/CC5ap11pz71/cM0hUw==", "requires": { "graceful-fs": "^4.1.6" } @@ -44945,23 +45949,13 @@ "p-locate": "^4.1.0" } }, - "metro-babel-transformer": { - "version": "0.66.2", - "resolved": "https://registry.npmjs.org/metro-babel-transformer/-/metro-babel-transformer-0.66.2.tgz", - "integrity": "sha512-aJ/7fc/Xkofw8Fqa51OTDhBzBz26mmpIWrXAZcPdQ8MSTt883EWncxeCEjasc79NJ89BRi7sOkkaWZo2sXlKvw==", - "requires": { - "@babel/core": "^7.14.0", - "hermes-parser": "0.4.7", - "metro-source-map": "0.66.2", - "nullthrows": "^1.1.1" - } - }, "metro-react-native-babel-preset": { - "version": "0.66.2", - "resolved": "https://registry.npmjs.org/metro-react-native-babel-preset/-/metro-react-native-babel-preset-0.66.2.tgz", - "integrity": "sha512-H/nLBAz0MgfDloSe1FjyH4EnbokHFdncyERvLPXDACY3ROVRCeUyFNo70ywRGXW2NMbrV4H7KUyU4zkfWhC2HQ==", + "version": "0.70.3", + "resolved": "https://registry.npmjs.org/metro-react-native-babel-preset/-/metro-react-native-babel-preset-0.70.3.tgz", + "integrity": "sha512-4Nxc1zEiHEu+GTdEMEsHnRgfaBkg8f/Td3+FcQ8NTSvs+xL3LBrQy6N07idWSQZHIdGFf+tTHvRfSIWLD8u8Tg==", "requires": { "@babel/core": "^7.14.0", + "@babel/plugin-proposal-async-generator-functions": "^7.0.0", "@babel/plugin-proposal-class-properties": "^7.0.0", "@babel/plugin-proposal-export-default-from": "^7.0.0", "@babel/plugin-proposal-nullish-coalescing-operator": "^7.0.0", @@ -44981,17 +45975,15 @@ "@babel/plugin-transform-destructuring": "^7.0.0", "@babel/plugin-transform-exponentiation-operator": "^7.0.0", "@babel/plugin-transform-flow-strip-types": "^7.0.0", - "@babel/plugin-transform-for-of": "^7.0.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-object-assign": "^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-regenerator": "^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", @@ -45004,46 +45996,80 @@ } }, "metro-source-map": { - "version": "0.66.2", - "resolved": "https://registry.npmjs.org/metro-source-map/-/metro-source-map-0.66.2.tgz", - "integrity": "sha512-038tFmB7vSh73VQcDWIbr5O1m+WXWyYafDaOy+1A/2K308YP0oj33gbEgDnZsLZDwcJ+xt1x6KUEBIzlX4YGeQ==", + "version": "0.70.3", + "resolved": "https://registry.npmjs.org/metro-source-map/-/metro-source-map-0.70.3.tgz", + "integrity": "sha512-zsYtZGrwRbbGEFHtmMqqeCH9K9aTGNVPsurMOWCUeQA3VGyVGXPGtLMC+CdAM9jLpUyg6jw2xh0esxi+tYH7Uw==", "requires": { "@babel/traverse": "^7.14.0", "@babel/types": "^7.0.0", "invariant": "^2.2.4", - "metro-symbolicate": "0.66.2", + "metro-symbolicate": "0.70.3", "nullthrows": "^1.1.1", - "ob1": "0.66.2", + "ob1": "0.70.3", "source-map": "^0.5.6", "vlq": "^1.0.0" } }, "metro-symbolicate": { - "version": "0.66.2", - "resolved": "https://registry.npmjs.org/metro-symbolicate/-/metro-symbolicate-0.66.2.tgz", - "integrity": "sha512-u+DeQHyAFXVD7mVP+GST/894WHJ3i/U8oEJFnT7U3P52ZuLgX8n4tMNxhqZU12RcLR6etF8143aP0Ktx1gFLEQ==", + "version": "0.70.3", + "resolved": "https://registry.npmjs.org/metro-symbolicate/-/metro-symbolicate-0.70.3.tgz", + "integrity": "sha512-JTYkF1dpeDUssQ84juE1ycnhHki2ylJBBdJE1JHtfu5oC+z1ElDbBdPHq90Uvt8HbRov/ZAnxvv7Zy6asS+WCA==", "requires": { "invariant": "^2.2.4", - "metro-source-map": "0.66.2", + "metro-source-map": "0.70.3", "nullthrows": "^1.1.1", "source-map": "^0.5.6", "through2": "^2.0.1", "vlq": "^1.0.0" } }, + "micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "requires": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "dependencies": { + "picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==" + } + } + }, "mime-db": { - "version": "1.51.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz", - "integrity": "sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==" + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" }, "mime-types": { - "version": "2.1.34", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.34.tgz", - "integrity": "sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A==", + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "requires": { - "mime-db": "1.51.0" + "mime-db": "1.52.0" + } + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "requires": { + "brace-expansion": "^1.1.7" } }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" + }, + "ob1": { + "version": "0.70.3", + "resolved": "https://registry.npmjs.org/ob1/-/ob1-0.70.3.tgz", + "integrity": "sha512-Vy9GGhuXgDRY01QA6kdhToPd8AkLdLpX9GjH5kpqluVqTu70mgOm7tpGoJDZGaNbr9nJlJgnipqHJQRPORixIQ==" + }, "p-limit": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", @@ -45101,20 +46127,27 @@ "ansi-regex": "^5.0.1" } }, - "strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=" + "supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "requires": { + "has-flag": "^4.0.0" + } }, - "ws": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/ws/-/ws-1.1.5.tgz", - "integrity": "sha512-o3KqipXNUdS7wpQzBHSe180lBGO60SoK0yVo3CYJgb2MkobuWuBX6dhkYP5ORCLd55y+SaflMOV5fqAB53ux4w==", + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "requires": { - "options": ">=0.0.5", - "ultron": "1.0.x" + "is-number": "^7.0.0" } }, + "ws": { + "version": "7.5.9", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", + "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==" + }, "yargs": { "version": "15.4.1", "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", @@ -45144,55 +46177,90 @@ } } }, - "metro-babel-register": { - "version": "0.66.2", - "resolved": "https://registry.npmjs.org/metro-babel-register/-/metro-babel-register-0.66.2.tgz", - "integrity": "sha512-3F+vsVubUPJYKfVMeol8/7pd8CC287Rw92QYzJD8LEmI980xcgwMUEVBZ0UIAUwlLgiJG/f4Mwhuji2EeBXrPg==", - "requires": { - "@babel/core": "^7.14.0", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.0.0", - "@babel/plugin-proposal-optional-chaining": "^7.0.0", - "@babel/plugin-syntax-class-properties": "^7.0.0", - "@babel/plugin-transform-flow-strip-types": "^7.0.0", - "@babel/plugin-transform-modules-commonjs": "^7.0.0", - "@babel/register": "^7.0.0", - "escape-string-regexp": "^1.0.5" - } - }, "metro-babel-transformer": { - "version": "0.66.2", - "resolved": "https://registry.npmjs.org/metro-babel-transformer/-/metro-babel-transformer-0.66.2.tgz", - "integrity": "sha512-aJ/7fc/Xkofw8Fqa51OTDhBzBz26mmpIWrXAZcPdQ8MSTt883EWncxeCEjasc79NJ89BRi7sOkkaWZo2sXlKvw==", + "version": "0.70.3", + "resolved": "https://registry.npmjs.org/metro-babel-transformer/-/metro-babel-transformer-0.70.3.tgz", + "integrity": "sha512-bWhZRMn+mIOR/s3BDpFevWScz9sV8FGktVfMlF1eJBLoX24itHDbXvTktKBYi38PWIKcHedh6THSFpJogfuwNA==", "requires": { "@babel/core": "^7.14.0", - "hermes-parser": "0.4.7", - "metro-source-map": "0.66.2", + "hermes-parser": "0.6.0", + "metro-source-map": "0.70.3", "nullthrows": "^1.1.1" + }, + "dependencies": { + "hermes-parser": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.6.0.tgz", + "integrity": "sha512-Vf58jBZca2+QBLR9h7B7mdg8oFz2g5ILz1iVouZ5DOrOrAfBmPfJjdjDT8jrO0f+iJ4/hSRrQHqHIjSnTaLUDQ==", + "requires": { + "hermes-estree": "0.6.0" + } + }, + "metro-source-map": { + "version": "0.70.3", + "resolved": "https://registry.npmjs.org/metro-source-map/-/metro-source-map-0.70.3.tgz", + "integrity": "sha512-zsYtZGrwRbbGEFHtmMqqeCH9K9aTGNVPsurMOWCUeQA3VGyVGXPGtLMC+CdAM9jLpUyg6jw2xh0esxi+tYH7Uw==", + "requires": { + "@babel/traverse": "^7.14.0", + "@babel/types": "^7.0.0", + "invariant": "^2.2.4", + "metro-symbolicate": "0.70.3", + "nullthrows": "^1.1.1", + "ob1": "0.70.3", + "source-map": "^0.5.6", + "vlq": "^1.0.0" + } + }, + "metro-symbolicate": { + "version": "0.70.3", + "resolved": "https://registry.npmjs.org/metro-symbolicate/-/metro-symbolicate-0.70.3.tgz", + "integrity": "sha512-JTYkF1dpeDUssQ84juE1ycnhHki2ylJBBdJE1JHtfu5oC+z1ElDbBdPHq90Uvt8HbRov/ZAnxvv7Zy6asS+WCA==", + "requires": { + "invariant": "^2.2.4", + "metro-source-map": "0.70.3", + "nullthrows": "^1.1.1", + "source-map": "^0.5.6", + "through2": "^2.0.1", + "vlq": "^1.0.0" + } + }, + "ob1": { + "version": "0.70.3", + "resolved": "https://registry.npmjs.org/ob1/-/ob1-0.70.3.tgz", + "integrity": "sha512-Vy9GGhuXgDRY01QA6kdhToPd8AkLdLpX9GjH5kpqluVqTu70mgOm7tpGoJDZGaNbr9nJlJgnipqHJQRPORixIQ==" + } } }, "metro-cache": { - "version": "0.66.2", - "resolved": "https://registry.npmjs.org/metro-cache/-/metro-cache-0.66.2.tgz", - "integrity": "sha512-5QCYJtJOHoBSbL3H4/Fpl36oA697C3oYHqsce+Hk/dh2qtODUGpS3gOBhvP1B8iB+H8jJMyR75lZq129LJEsIQ==", + "version": "0.70.3", + "resolved": "https://registry.npmjs.org/metro-cache/-/metro-cache-0.70.3.tgz", + "integrity": "sha512-iCix/+z812fUqa6KlOxaTkY6LQQDoXIe/VljXkGIvpygSCmYyhjQpfQVZEVVPezFmUBYXNdabdQ6cYx6JX3yMg==", "requires": { - "metro-core": "0.66.2", - "mkdirp": "^0.5.1", + "metro-core": "0.70.3", "rimraf": "^2.5.4" }, "dependencies": { "glob": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", - "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", - "minimatch": "^3.0.4", + "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "requires": { + "brace-expansion": "^1.1.7" + } + }, "rimraf": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", @@ -45204,47 +46272,27 @@ } }, "metro-cache-key": { - "version": "0.66.2", - "resolved": "https://registry.npmjs.org/metro-cache-key/-/metro-cache-key-0.66.2.tgz", - "integrity": "sha512-WtkNmRt41qOpHh1MkNA4nLiQ/m7iGL90ysSKD+fcLqlUnOBKJptPQm0ZUv8Kfqk18ddWX2KmsSbq+Sf3I6XohQ==" + "version": "0.70.3", + "resolved": "https://registry.npmjs.org/metro-cache-key/-/metro-cache-key-0.70.3.tgz", + "integrity": "sha512-0zpw+IcpM3hmGd5sKMdxNv3sbOIUYnMUvx1/yaM6vNRReSPmOLX0bP8fYf3CGgk8NEreZ1OHbVsuw7bdKt40Mw==" }, "metro-config": { - "version": "0.66.2", - "resolved": "https://registry.npmjs.org/metro-config/-/metro-config-0.66.2.tgz", - "integrity": "sha512-0C+PrKKIBNNzLZUKN/8ZDJS2U5FLMOTXDWbvBHIdqb6YXz8WplXR2+xlSlaSCCi5b+GR7cWFWUNeKA4GQS1/AQ==", + "version": "0.70.3", + "resolved": "https://registry.npmjs.org/metro-config/-/metro-config-0.70.3.tgz", + "integrity": "sha512-SSCDjSTygoCgzoj61DdrBeJzZDRwQxUEfcgc6t6coxWSExXNR4mOngz0q4SAam49Bmjq9J2Jft6qUKnUTPrRgA==", "requires": { "cosmiconfig": "^5.0.5", "jest-validate": "^26.5.2", - "metro": "0.66.2", - "metro-cache": "0.66.2", - "metro-core": "0.66.2", - "metro-runtime": "0.66.2" + "metro": "0.70.3", + "metro-cache": "0.70.3", + "metro-core": "0.70.3", + "metro-runtime": "0.70.3" }, "dependencies": { - "@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - } - }, - "@types/istanbul-reports": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", - "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", - "requires": { - "@types/istanbul-lib-report": "*" - } - }, "camelcase": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.1.tgz", - "integrity": "sha512-tVI4q5jjFV5CavAU8DXfza/TJcZutVKo/5Foskmsqcm0MsL91moHvwiGNnqaa2o6PF/7yT5ikDRcVcl8Rj6LCA==" + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==" }, "cosmiconfig": { "version": "5.2.1", @@ -45260,17 +46308,12 @@ "import-fresh": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", - "integrity": "sha1-2BNVwVYS04bGH53dOSLUMEgipUY=", + "integrity": "sha512-eZ5H8rcgYazHbKC3PG4ClHNykCSxtAhxSSEM+2mb+7evD2CKF5V7c0dNum7AdpDh0ZdICwZY9sRSn8f+KH96sg==", "requires": { "caller-path": "^2.0.0", "resolve-from": "^3.0.0" } }, - "jest-get-type": { - "version": "26.3.0", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-26.3.0.tgz", - "integrity": "sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig==" - }, "jest-validate": { "version": "26.6.2", "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-26.6.2.tgz", @@ -45287,7 +46330,7 @@ "parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", "requires": { "error-ex": "^1.3.1", "json-parse-better-errors": "^1.0.1" @@ -45296,28 +46339,196 @@ } }, "metro-core": { - "version": "0.66.2", - "resolved": "https://registry.npmjs.org/metro-core/-/metro-core-0.66.2.tgz", - "integrity": "sha512-JieLZkef/516yxXYvQxWnf3OWw5rcgWRy76K8JV/wr/i8LGVGulPAXlIi445/QZzXVydzRVASKAEVqyxM5F4mA==", + "version": "0.70.3", + "resolved": "https://registry.npmjs.org/metro-core/-/metro-core-0.70.3.tgz", + "integrity": "sha512-NzfHB/w5R7yLaOeU1tzPTbBzCRsYSvpKJkLMP0yudszKZzIAZqNdjoEJ9GZ688Wi0ynZxcU0BxukXh4my80ZBw==", "requires": { - "jest-haste-map": "^26.5.2", + "jest-haste-map": "^27.3.1", "lodash.throttle": "^4.1.1", - "metro-resolver": "0.66.2" + "metro-resolver": "0.70.3" + }, + "dependencies": { + "@jest/types": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", + "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0" + } + }, + "@types/yargs": { + "version": "16.0.4", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", + "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", + "requires": { + "@types/yargs-parser": "*" + } + }, + "anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "requires": { + "fill-range": "^7.0.1" + } + }, + "ci-info": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.6.2.tgz", + "integrity": "sha512-lVZdhvbEudris15CLytp2u6Y0p5EKfztae9Fqa189MfNmln9F33XuH69v5fvNfiRN5/0eAUz2yJL3mo+nhaRKg==" + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==" + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" + }, + "jest-haste-map": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-27.5.1.tgz", + "integrity": "sha512-7GgkZ4Fw4NFbMSDSpZwXeBiIbx+t/46nJ2QitkOjvwPYyZmqttu2TDSimMHP1EkPOi4xUZAN1doE5Vd25H4Jng==", + "requires": { + "@jest/types": "^27.5.1", + "@types/graceful-fs": "^4.1.2", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "fsevents": "^2.3.2", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^27.5.1", + "jest-serializer": "^27.5.1", + "jest-util": "^27.5.1", + "jest-worker": "^27.5.1", + "micromatch": "^4.0.4", + "walker": "^1.0.7" + } + }, + "jest-regex-util": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-27.5.1.tgz", + "integrity": "sha512-4bfKq2zie+x16okqDXjXn9ql2B0dScQu+vcwe4TvFVhkVyuWLqpZrZtXxLLWoXYgn0E87I6r6GRYHF7wFZBUvg==" + }, + "jest-serializer": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-27.5.1.tgz", + "integrity": "sha512-jZCyo6iIxO1aqUxpuBlwTDMkzOAJS4a3eYz3YzgxxVQFwLeSA7Jfq5cbqCY+JLvTDrWirgusI/0KwxKMgrdf7w==", + "requires": { + "@types/node": "*", + "graceful-fs": "^4.2.9" + } + }, + "jest-util": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz", + "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", + "requires": { + "@jest/types": "^27.5.1", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "dependencies": { + "picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==" + } + } + }, + "jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "requires": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + } + }, + "micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "requires": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "dependencies": { + "picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==" + } + } + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" + }, + "supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "requires": { + "has-flag": "^4.0.0" + } + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "requires": { + "is-number": "^7.0.0" + } + } } }, "metro-hermes-compiler": { - "version": "0.66.2", - "resolved": "https://registry.npmjs.org/metro-hermes-compiler/-/metro-hermes-compiler-0.66.2.tgz", - "integrity": "sha512-nCVL1g9uR6vrw5+X1wjwZruRyMkndnzGRMqjqoljf+nGEqBTD607CR7elXw4fMWn/EM+1y0Vdq5altUu9LdgCA==" + "version": "0.70.3", + "resolved": "https://registry.npmjs.org/metro-hermes-compiler/-/metro-hermes-compiler-0.70.3.tgz", + "integrity": "sha512-W6WttLi4E72JL/NyteQ84uxYOFMibe0PUr9aBKuJxxfCq6QRnJKOVcNY0NLW0He2tneXGk+8ZsNz8c0flEvYqg==" }, "metro-inspector-proxy": { - "version": "0.66.2", - "resolved": "https://registry.npmjs.org/metro-inspector-proxy/-/metro-inspector-proxy-0.66.2.tgz", - "integrity": "sha512-gnLc9121eznwP0iiA9tCBW8qZjwIsCgwHWMF1g1Qaki9le9tzeJv3dK4/lFNGxyfSaLO7vahQEhsEYsiRnTROg==", + "version": "0.70.3", + "resolved": "https://registry.npmjs.org/metro-inspector-proxy/-/metro-inspector-proxy-0.70.3.tgz", + "integrity": "sha512-qQoNdPGrmyoJSWYkxSDpTaAI8xyqVdNDVVj9KRm1PG8niSuYmrCCFGLLFsMvkVYwsCWUGHoGBx0UoAzVp14ejw==", "requires": { "connect": "^3.6.5", "debug": "^2.2.0", - "ws": "^1.1.5", + "ws": "^7.5.1", "yargs": "^15.3.1" }, "dependencies": { @@ -45326,11 +46537,6 @@ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" }, - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" - }, "debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -45411,13 +46617,9 @@ } }, "ws": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/ws/-/ws-1.1.5.tgz", - "integrity": "sha512-o3KqipXNUdS7wpQzBHSe180lBGO60SoK0yVo3CYJgb2MkobuWuBX6dhkYP5ORCLd55y+SaflMOV5fqAB53ux4w==", - "requires": { - "options": ">=0.0.5", - "ultron": "1.0.x" - } + "version": "7.5.9", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", + "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==" }, "yargs": { "version": "15.4.1", @@ -45449,41 +46651,21 @@ } }, "metro-minify-uglify": { - "version": "0.66.2", - "resolved": "https://registry.npmjs.org/metro-minify-uglify/-/metro-minify-uglify-0.66.2.tgz", - "integrity": "sha512-7TUK+L5CmB5x1PVnFbgmjzHW4CUadq9H5jgp0HfFoWT1skXAyEsx0DHkKDXwnot0khnNhBOEfl62ctQOnE110Q==", + "version": "0.70.3", + "resolved": "https://registry.npmjs.org/metro-minify-uglify/-/metro-minify-uglify-0.70.3.tgz", + "integrity": "sha512-oHyjV9WDqOlDE1FPtvs6tIjjeY/oP1PNUPYL1wqyYtqvjN+zzAOrcbsAAL1sv+WARaeiMsWkF2bwtNo+Hghoog==", "requires": { "uglify-es": "^3.1.9" - }, - "dependencies": { - "commander": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.13.0.tgz", - "integrity": "sha512-MVuS359B+YzaWqjCL/c+22gfryv+mCBPHAv3zyVI2GN8EY6IRP8VwtasXn8jyyhvvq84R4ImN1OKRtcbIasjYA==" - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - }, - "uglify-es": { - "version": "3.3.9", - "resolved": "https://registry.npmjs.org/uglify-es/-/uglify-es-3.3.9.tgz", - "integrity": "sha512-r+MU0rfv4L/0eeW3xZrd16t4NZfK8Ld4SWVglYBb7ez5uXFWHuVRs6xCTrf1yirs9a4j4Y27nn7SRfO6v67XsQ==", - "requires": { - "commander": "~2.13.0", - "source-map": "~0.6.1" - } - } } }, "metro-react-native-babel-preset": { - "version": "0.66.2", - "resolved": "https://registry.npmjs.org/metro-react-native-babel-preset/-/metro-react-native-babel-preset-0.66.2.tgz", - "integrity": "sha512-H/nLBAz0MgfDloSe1FjyH4EnbokHFdncyERvLPXDACY3ROVRCeUyFNo70ywRGXW2NMbrV4H7KUyU4zkfWhC2HQ==", + "version": "0.70.3", + "resolved": "https://registry.npmjs.org/metro-react-native-babel-preset/-/metro-react-native-babel-preset-0.70.3.tgz", + "integrity": "sha512-4Nxc1zEiHEu+GTdEMEsHnRgfaBkg8f/Td3+FcQ8NTSvs+xL3LBrQy6N07idWSQZHIdGFf+tTHvRfSIWLD8u8Tg==", "dev": true, "requires": { "@babel/core": "^7.14.0", + "@babel/plugin-proposal-async-generator-functions": "^7.0.0", "@babel/plugin-proposal-class-properties": "^7.0.0", "@babel/plugin-proposal-export-default-from": "^7.0.0", "@babel/plugin-proposal-nullish-coalescing-operator": "^7.0.0", @@ -45503,17 +46685,15 @@ "@babel/plugin-transform-destructuring": "^7.0.0", "@babel/plugin-transform-exponentiation-operator": "^7.0.0", "@babel/plugin-transform-flow-strip-types": "^7.0.0", - "@babel/plugin-transform-for-of": "^7.0.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-object-assign": "^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-regenerator": "^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", @@ -45534,134 +46714,60 @@ } }, "metro-react-native-babel-transformer": { - "version": "0.66.2", - "resolved": "https://registry.npmjs.org/metro-react-native-babel-transformer/-/metro-react-native-babel-transformer-0.66.2.tgz", - "integrity": "sha512-z1ab7ihIT0pJrwgi9q2IH+LcW/xUWMQ0hH+Mrk7wbKQB0RnJdXFoxphrfoVHBHMUu+TBPetUcEkKawkK1e7Cng==", + "version": "0.70.3", + "resolved": "https://registry.npmjs.org/metro-react-native-babel-transformer/-/metro-react-native-babel-transformer-0.70.3.tgz", + "integrity": "sha512-WKBU6S/G50j9cfmFM4k4oRYprd8u3qjleD4so1E2zbTNILg+gYla7ZFGCAvi2G0ZcqS2XuGCR375c2hF6VVvwg==", + "dev": true, "requires": { "@babel/core": "^7.14.0", "babel-preset-fbjs": "^3.4.0", - "hermes-parser": "0.4.7", - "metro-babel-transformer": "0.66.2", - "metro-react-native-babel-preset": "0.66.2", - "metro-source-map": "0.66.2", + "hermes-parser": "0.6.0", + "metro-babel-transformer": "0.70.3", + "metro-react-native-babel-preset": "0.70.3", + "metro-source-map": "0.70.3", "nullthrows": "^1.1.1" - }, - "dependencies": { - "metro-babel-transformer": { - "version": "0.66.2", - "resolved": "https://registry.npmjs.org/metro-babel-transformer/-/metro-babel-transformer-0.66.2.tgz", - "integrity": "sha512-aJ/7fc/Xkofw8Fqa51OTDhBzBz26mmpIWrXAZcPdQ8MSTt883EWncxeCEjasc79NJ89BRi7sOkkaWZo2sXlKvw==", - "requires": { - "@babel/core": "^7.14.0", - "hermes-parser": "0.4.7", - "metro-source-map": "0.66.2", - "nullthrows": "^1.1.1" - } - }, - "metro-react-native-babel-preset": { - "version": "0.66.2", - "resolved": "https://registry.npmjs.org/metro-react-native-babel-preset/-/metro-react-native-babel-preset-0.66.2.tgz", - "integrity": "sha512-H/nLBAz0MgfDloSe1FjyH4EnbokHFdncyERvLPXDACY3ROVRCeUyFNo70ywRGXW2NMbrV4H7KUyU4zkfWhC2HQ==", - "requires": { - "@babel/core": "^7.14.0", - "@babel/plugin-proposal-class-properties": "^7.0.0", - "@babel/plugin-proposal-export-default-from": "^7.0.0", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.0.0", - "@babel/plugin-proposal-object-rest-spread": "^7.0.0", - "@babel/plugin-proposal-optional-catch-binding": "^7.0.0", - "@babel/plugin-proposal-optional-chaining": "^7.0.0", - "@babel/plugin-syntax-dynamic-import": "^7.0.0", - "@babel/plugin-syntax-export-default-from": "^7.0.0", - "@babel/plugin-syntax-flow": "^7.2.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.0.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.0.0", - "@babel/plugin-transform-exponentiation-operator": "^7.0.0", - "@babel/plugin-transform-flow-strip-types": "^7.0.0", - "@babel/plugin-transform-for-of": "^7.0.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-object-assign": "^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-regenerator": "^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-template-literals": "^7.0.0", - "@babel/plugin-transform-typescript": "^7.5.0", - "@babel/plugin-transform-unicode-regex": "^7.0.0", - "@babel/template": "^7.0.0", - "react-refresh": "^0.4.0" - } - }, - "metro-source-map": { - "version": "0.66.2", - "resolved": "https://registry.npmjs.org/metro-source-map/-/metro-source-map-0.66.2.tgz", - "integrity": "sha512-038tFmB7vSh73VQcDWIbr5O1m+WXWyYafDaOy+1A/2K308YP0oj33gbEgDnZsLZDwcJ+xt1x6KUEBIzlX4YGeQ==", - "requires": { - "@babel/traverse": "^7.14.0", - "@babel/types": "^7.0.0", - "invariant": "^2.2.4", - "metro-symbolicate": "0.66.2", - "nullthrows": "^1.1.1", - "ob1": "0.66.2", - "source-map": "^0.5.6", - "vlq": "^1.0.0" - } - }, - "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==" - } } }, "metro-resolver": { - "version": "0.66.2", - "resolved": "https://registry.npmjs.org/metro-resolver/-/metro-resolver-0.66.2.tgz", - "integrity": "sha512-pXQAJR/xauRf4kWFj2/hN5a77B4jLl0Fom5I3PHp6Arw/KxSBp0cnguXpGLwNQ6zQC0nxKCoYGL9gQpzMnN7Hw==", + "version": "0.70.3", + "resolved": "https://registry.npmjs.org/metro-resolver/-/metro-resolver-0.70.3.tgz", + "integrity": "sha512-5Pc5S/Gs4RlLbziuIWtvtFd9GRoILlaRC8RZDVq5JZWcWHywKy/PjNmOBNhpyvtRlzpJfy/ssIfLhu8zINt1Mw==", "requires": { "absolute-path": "^0.0.0" } }, "metro-runtime": { - "version": "0.66.2", - "resolved": "https://registry.npmjs.org/metro-runtime/-/metro-runtime-0.66.2.tgz", - "integrity": "sha512-vFhKBk2ot9FS4b+2v0OTa/guCF/QDAOJubY0CNg7PzCS5+w4y3IvZIcPX4SSS1t8pYEZBLvtdtTDarlDl81xmg==" + "version": "0.70.3", + "resolved": "https://registry.npmjs.org/metro-runtime/-/metro-runtime-0.70.3.tgz", + "integrity": "sha512-22xU7UdXZacniTIDZgN2EYtmfau2pPyh97Dcs+cWrLcJYgfMKjWBtesnDcUAQy3PHekDYvBdJZkoQUeskYTM+w==", + "requires": { + "@babel/runtime": "^7.0.0" + } }, "metro-source-map": { - "version": "0.66.2", - "resolved": "https://registry.npmjs.org/metro-source-map/-/metro-source-map-0.66.2.tgz", - "integrity": "sha512-038tFmB7vSh73VQcDWIbr5O1m+WXWyYafDaOy+1A/2K308YP0oj33gbEgDnZsLZDwcJ+xt1x6KUEBIzlX4YGeQ==", + "version": "0.70.3", + "resolved": "https://registry.npmjs.org/metro-source-map/-/metro-source-map-0.70.3.tgz", + "integrity": "sha512-zsYtZGrwRbbGEFHtmMqqeCH9K9aTGNVPsurMOWCUeQA3VGyVGXPGtLMC+CdAM9jLpUyg6jw2xh0esxi+tYH7Uw==", + "dev": true, "requires": { "@babel/traverse": "^7.14.0", "@babel/types": "^7.0.0", "invariant": "^2.2.4", - "metro-symbolicate": "0.66.2", + "metro-symbolicate": "0.70.3", "nullthrows": "^1.1.1", - "ob1": "0.66.2", + "ob1": "0.70.3", "source-map": "^0.5.6", "vlq": "^1.0.0" } }, "metro-symbolicate": { - "version": "0.66.2", - "resolved": "https://registry.npmjs.org/metro-symbolicate/-/metro-symbolicate-0.66.2.tgz", - "integrity": "sha512-u+DeQHyAFXVD7mVP+GST/894WHJ3i/U8oEJFnT7U3P52ZuLgX8n4tMNxhqZU12RcLR6etF8143aP0Ktx1gFLEQ==", + "version": "0.70.3", + "resolved": "https://registry.npmjs.org/metro-symbolicate/-/metro-symbolicate-0.70.3.tgz", + "integrity": "sha512-JTYkF1dpeDUssQ84juE1ycnhHki2ylJBBdJE1JHtfu5oC+z1ElDbBdPHq90Uvt8HbRov/ZAnxvv7Zy6asS+WCA==", + "dev": true, "requires": { "invariant": "^2.2.4", - "metro-source-map": "0.66.2", + "metro-source-map": "0.70.3", "nullthrows": "^1.1.1", "source-map": "^0.5.6", "through2": "^2.0.1", @@ -45669,9 +46775,9 @@ } }, "metro-transform-plugins": { - "version": "0.66.2", - "resolved": "https://registry.npmjs.org/metro-transform-plugins/-/metro-transform-plugins-0.66.2.tgz", - "integrity": "sha512-KTvqplh0ut7oDKovvDG6yzXM02R6X+9b2oVG+qYq8Zd3aCGTi51ASx4ThCNkAHyEvCuJdYg9fxXTL+j+wvhB5w==", + "version": "0.70.3", + "resolved": "https://registry.npmjs.org/metro-transform-plugins/-/metro-transform-plugins-0.70.3.tgz", + "integrity": "sha512-dQRIJoTkWZN2IVS2KzgS1hs7ZdHDX3fS3esfifPkqFAEwHiLctCf0EsPgIknp0AjMLvmGWfSLJigdRB/dc0ASw==", "requires": { "@babel/core": "^7.14.0", "@babel/generator": "^7.14.0", @@ -45681,23 +46787,58 @@ } }, "metro-transform-worker": { - "version": "0.66.2", - "resolved": "https://registry.npmjs.org/metro-transform-worker/-/metro-transform-worker-0.66.2.tgz", - "integrity": "sha512-dO4PtYOMGB7Vzte8aIzX39xytODhmbJrBYPu+zYzlDjyefJZT7BkZ0LkPIThtyJi96xWcGqi9JBSo0CeRupAHw==", + "version": "0.70.3", + "resolved": "https://registry.npmjs.org/metro-transform-worker/-/metro-transform-worker-0.70.3.tgz", + "integrity": "sha512-MtVVsnHhhBOp9GRLCdAb2mD1dTCsIzT4+m34KMRdBDCEbDIb90YafT5prpU8qbj5uKd0o2FOQdrJ5iy5zQilHw==", "requires": { "@babel/core": "^7.14.0", "@babel/generator": "^7.14.0", "@babel/parser": "^7.14.0", "@babel/types": "^7.0.0", "babel-preset-fbjs": "^3.4.0", - "metro": "0.66.2", - "metro-babel-transformer": "0.66.2", - "metro-cache": "0.66.2", - "metro-cache-key": "0.66.2", - "metro-hermes-compiler": "0.66.2", - "metro-source-map": "0.66.2", - "metro-transform-plugins": "0.66.2", + "metro": "0.70.3", + "metro-babel-transformer": "0.70.3", + "metro-cache": "0.70.3", + "metro-cache-key": "0.70.3", + "metro-hermes-compiler": "0.70.3", + "metro-source-map": "0.70.3", + "metro-transform-plugins": "0.70.3", "nullthrows": "^1.1.1" + }, + "dependencies": { + "metro-source-map": { + "version": "0.70.3", + "resolved": "https://registry.npmjs.org/metro-source-map/-/metro-source-map-0.70.3.tgz", + "integrity": "sha512-zsYtZGrwRbbGEFHtmMqqeCH9K9aTGNVPsurMOWCUeQA3VGyVGXPGtLMC+CdAM9jLpUyg6jw2xh0esxi+tYH7Uw==", + "requires": { + "@babel/traverse": "^7.14.0", + "@babel/types": "^7.0.0", + "invariant": "^2.2.4", + "metro-symbolicate": "0.70.3", + "nullthrows": "^1.1.1", + "ob1": "0.70.3", + "source-map": "^0.5.6", + "vlq": "^1.0.0" + } + }, + "metro-symbolicate": { + "version": "0.70.3", + "resolved": "https://registry.npmjs.org/metro-symbolicate/-/metro-symbolicate-0.70.3.tgz", + "integrity": "sha512-JTYkF1dpeDUssQ84juE1ycnhHki2ylJBBdJE1JHtfu5oC+z1ElDbBdPHq90Uvt8HbRov/ZAnxvv7Zy6asS+WCA==", + "requires": { + "invariant": "^2.2.4", + "metro-source-map": "0.70.3", + "nullthrows": "^1.1.1", + "source-map": "^0.5.6", + "through2": "^2.0.1", + "vlq": "^1.0.0" + } + }, + "ob1": { + "version": "0.70.3", + "resolved": "https://registry.npmjs.org/ob1/-/ob1-0.70.3.tgz", + "integrity": "sha512-Vy9GGhuXgDRY01QA6kdhToPd8AkLdLpX9GjH5kpqluVqTu70mgOm7tpGoJDZGaNbr9nJlJgnipqHJQRPORixIQ==" + } } }, "microevent.ts": { @@ -45773,7 +46914,8 @@ "mimic-fn": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", - "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==" + "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", + "dev": true }, "mimic-response": { "version": "1.0.1", @@ -46194,11 +47336,6 @@ } } }, - "mockdate": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/mockdate/-/mockdate-3.0.5.tgz", - "integrity": "sha512-iniQP4rj1FhBdBYS/+eQv7j1tadJ9lJtdzgOpvsOHng/GbcDh2Fhdeq+ZRldrPYdXvCyfFUmFeEwEGXZB5I/AQ==" - }, "modify-values": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/modify-values/-/modify-values-1.0.1.tgz", @@ -46389,9 +47526,9 @@ } }, "nocache": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/nocache/-/nocache-2.1.0.tgz", - "integrity": "sha512-0L9FvHG3nfnnmaEQPjT9xhfN4ISk0A8/2j4M37Np4mcDesJjHgEUfgPhdCyZuFI954tjokaIj/A3NdpFNdEh4Q==" + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/nocache/-/nocache-3.0.4.tgz", + "integrity": "sha512-WDD0bdg9mbq6F4mRxEYcPWwfA1vxd0mrvKOyxI7Xj/atfRHVeutzuWByG//jfm4uPzp0y4Kj051EORCBSQMycw==" }, "nock": { "version": "12.0.3", @@ -46604,6 +47741,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "dev": true, "requires": { "remove-trailing-separator": "^1.0.1" } @@ -47667,9 +48805,10 @@ "dev": true }, "ob1": { - "version": "0.66.2", - "resolved": "https://registry.npmjs.org/ob1/-/ob1-0.66.2.tgz", - "integrity": "sha512-RFewnL/RjE0qQBOuM+2bbY96zmJPIge/aDtsiDbLSb+MOiK8CReAhBHDgL+zrA3F1hQk00lMWpUwYcep750plA==" + "version": "0.70.3", + "resolved": "https://registry.npmjs.org/ob1/-/ob1-0.70.3.tgz", + "integrity": "sha512-Vy9GGhuXgDRY01QA6kdhToPd8AkLdLpX9GjH5kpqluVqTu70mgOm7tpGoJDZGaNbr9nJlJgnipqHJQRPORixIQ==", + "dev": true }, "object-assign": { "version": "4.1.1", @@ -48546,6 +49685,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", + "dev": true, "requires": { "mimic-fn": "^1.0.0" } @@ -48578,11 +49718,6 @@ "wordwrap": "~1.0.0" } }, - "options": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/options/-/options-0.0.6.tgz", - "integrity": "sha1-7CLTEoBrtT5zF3Pnza788cZDEo8=" - }, "ora": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/ora/-/ora-4.0.2.tgz", @@ -49504,11 +50639,6 @@ "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" - }, - "xmlbuilder": { - "version": "15.1.1", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz", - "integrity": "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==" } } }, @@ -49551,14 +50681,21 @@ } }, "popmotion": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/popmotion/-/popmotion-11.0.3.tgz", - "integrity": "sha512-Y55FLdj3UxkR7Vl3s7Qr4e9m0onSnP8W7d/xQLsoJM40vs6UKHFdygs6SWryasTZYqugMjm3BepCF4CWXDiHgA==", + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/popmotion/-/popmotion-11.0.5.tgz", + "integrity": "sha512-la8gPM1WYeFznb/JqF4GiTkRRPZsfaj2+kCxqQgr2MJylMmIKUwBfWW8Wa5fml/8gmtlD5yI01MP1QCZPWmppA==", "requires": { - "framesync": "6.0.1", + "framesync": "6.1.2", "hey-listen": "^1.0.8", - "style-value-types": "5.0.0", - "tslib": "^2.1.0" + "style-value-types": "5.1.2", + "tslib": "2.4.0" + }, + "dependencies": { + "tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" + } } }, "portfinder": { @@ -50314,7 +51451,8 @@ "private": { "version": "0.1.8", "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz", - "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==" + "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==", + "dev": true }, "proc-log": { "version": "2.0.1", @@ -50339,6 +51477,14 @@ "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", "dev": true }, + "promise": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/promise/-/promise-8.3.0.tgz", + "integrity": "sha512-rZPNPKTOYVNEEKFaq1HqTgOwZD+4/YHS5ukLzQCypkj+OkYx7iv0mA91lJlpPPZ8vMau3IIGj5Qlwrx+8iiSmg==", + "requires": { + "asap": "~2.0.6" + } + }, "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", @@ -50850,7 +51996,6 @@ "version": "2.4.2", "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", - "dev": true, "requires": { "kleur": "^3.0.3", "sisteransi": "^1.0.5" @@ -51355,12 +52500,11 @@ } }, "react": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz", - "integrity": "sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==", + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", + "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", "requires": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1" + "loose-envify": "^1.1.0" } }, "react-autosize-textarea": { @@ -51379,9 +52523,9 @@ "integrity": "sha512-nkP1w1LGe8PzhtOQO10xSDTsMWAYVj/Wm5c7ORk7CBngjCE7hHsknFRtosT5qMYkwWs8wSiU0sBwZ8dyzRbNEQ==" }, "react-devtools-core": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/react-devtools-core/-/react-devtools-core-4.21.0.tgz", - "integrity": "sha512-clGWwJHV5MHwTwYyKc+7FZHwzdbzrD2/AoZSkicUcr6YLc3Za9a9FaLhccWDHfjQ+ron9yzNhDT6Tv+FiPkD3g==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/react-devtools-core/-/react-devtools-core-4.24.0.tgz", + "integrity": "sha512-Rw7FzYOOzcfyUPaAm9P3g0tFdGqGq2LLiAI+wjYcp6CsF3DeeMrRS3HZAho4s273C29G/DJhx0e8BpRE/QZNGg==", "requires": { "shell-quote": "^1.6.1", "ws": "^7" @@ -51438,13 +52582,12 @@ "dev": true }, "react-dom": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz", - "integrity": "sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==", + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", + "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", "requires": { "loose-envify": "^1.1.0", - "object-assign": "^4.1.1", - "scheduler": "^0.20.2" + "scheduler": "^0.23.0" } }, "react-easy-crop": { @@ -51494,11 +52637,6 @@ "integrity": "sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA==", "dev": true }, - "react-freeze": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/react-freeze/-/react-freeze-1.0.3.tgz", - "integrity": "sha512-ZnXwLQnGzrDpHBHiC56TXFXvmolPeMjTn1UOm610M4EXGzbEDR7oOIyS2ZiItgbs6eZc4oU/a0hpk8PrcKvv5g==" - }, "react-input-autosize": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/react-input-autosize/-/react-input-autosize-3.0.0.tgz", @@ -51531,49 +52669,304 @@ "dev": true }, "react-native": { - "version": "0.66.2", - "resolved": "https://registry.npmjs.org/react-native/-/react-native-0.66.2.tgz", - "integrity": "sha512-d5Kp23kqAH0EmcyxyfjAr4rhVVzyK/J0cZ/JuopX16CBD4Nkad65KcJi1pyOpbhpP9L1aUUs+mRyK5kg0uLqJQ==", + "version": "0.69.4", + "resolved": "https://registry.npmjs.org/react-native/-/react-native-0.69.4.tgz", + "integrity": "sha512-rqNMialM/T4pHRKWqTIpOxA65B/9kUjtnepxwJqvsdCeMP9Q2YdSx4VASFR9RoEFYcPRU41yGf6EKrChNfns3g==", "requires": { "@jest/create-cache-key-function": "^27.0.1", - "@react-native-community/cli": "^6.0.0", - "@react-native-community/cli-platform-android": "^6.0.0", - "@react-native-community/cli-platform-ios": "^6.0.0", + "@react-native-community/cli": "^8.0.4", + "@react-native-community/cli-platform-android": "^8.0.4", + "@react-native-community/cli-platform-ios": "^8.0.4", "@react-native/assets": "1.0.0", - "@react-native/normalize-color": "1.0.0", + "@react-native/normalize-color": "2.0.0", "@react-native/polyfills": "2.0.0", "abort-controller": "^3.0.0", "anser": "^1.4.9", "base64-js": "^1.1.2", "event-target-shim": "^5.0.1", - "hermes-engine": "~0.9.0", + "hermes-engine": "~0.11.0", "invariant": "^2.2.4", "jsc-android": "^250230.2.1", - "metro-babel-register": "0.66.2", - "metro-react-native-babel-transformer": "0.66.2", - "metro-runtime": "0.66.2", - "metro-source-map": "0.66.2", + "memoize-one": "^5.0.0", + "metro-react-native-babel-transformer": "0.70.3", + "metro-runtime": "0.70.3", + "metro-source-map": "0.70.3", + "mkdirp": "^0.5.1", "nullthrows": "^1.1.1", "pretty-format": "^26.5.2", "promise": "^8.0.3", - "prop-types": "^15.7.2", - "react-devtools-core": "^4.13.0", - "react-native-codegen": "^0.0.7", + "react-devtools-core": "4.24.0", + "react-native-codegen": "^0.69.1", + "react-native-gradle-plugin": "^0.0.7", "react-refresh": "^0.4.0", + "react-shallow-renderer": "16.15.0", "regenerator-runtime": "^0.13.2", - "scheduler": "^0.20.2", + "scheduler": "^0.21.0", "stacktrace-parser": "^0.1.3", - "use-subscription": "^1.0.0", + "use-sync-external-store": "^1.0.0", "whatwg-fetch": "^3.0.0", "ws": "^6.1.4" }, "dependencies": { - "promise": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/promise/-/promise-8.1.0.tgz", - "integrity": "sha512-W04AqnILOL/sPRXziNicCjSNRruLAuIHEOVBazepu0545DDNGYHz7ar9ZgZ1fMU8/MA4mVxp5rkBWRi6OXIy3Q==", + "@react-native-community/cli": { + "version": "8.0.6", + "resolved": "https://registry.npmjs.org/@react-native-community/cli/-/cli-8.0.6.tgz", + "integrity": "sha512-E36hU/if3quQCfJHGWVkpsCnwtByRCwORuAX0r6yr1ebKktpKeEO49zY9PAu/Z1gfyxCtgluXY0HfRxjKRFXTg==", + "requires": { + "@react-native-community/cli-clean": "^8.0.4", + "@react-native-community/cli-config": "^8.0.6", + "@react-native-community/cli-debugger-ui": "^8.0.0", + "@react-native-community/cli-doctor": "^8.0.6", + "@react-native-community/cli-hermes": "^8.0.5", + "@react-native-community/cli-plugin-metro": "^8.0.4", + "@react-native-community/cli-server-api": "^8.0.4", + "@react-native-community/cli-tools": "^8.0.4", + "@react-native-community/cli-types": "^8.0.0", + "chalk": "^4.1.2", + "commander": "^2.19.0", + "execa": "^1.0.0", + "find-up": "^4.1.0", + "fs-extra": "^8.1.0", + "graceful-fs": "^4.1.3", + "leven": "^3.1.0", + "lodash": "^4.17.15", + "minimist": "^1.2.0", + "prompts": "^2.4.0", + "semver": "^6.3.0" + } + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "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==", + "requires": { + "color-name": "~1.1.4" + } + }, + "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==" + }, + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" + }, + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + } + } + }, + "execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "requires": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + } + }, + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "requires": { + "pump": "^3.0.0" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "hermes-parser": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.6.0.tgz", + "integrity": "sha512-Vf58jBZca2+QBLR9h7B7mdg8oFz2g5ILz1iVouZ5DOrOrAfBmPfJjdjDT8jrO0f+iJ4/hSRrQHqHIjSnTaLUDQ==", + "requires": { + "hermes-estree": "0.6.0" + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "requires": { + "p-locate": "^4.1.0" + } + }, + "metro-react-native-babel-preset": { + "version": "0.70.3", + "resolved": "https://registry.npmjs.org/metro-react-native-babel-preset/-/metro-react-native-babel-preset-0.70.3.tgz", + "integrity": "sha512-4Nxc1zEiHEu+GTdEMEsHnRgfaBkg8f/Td3+FcQ8NTSvs+xL3LBrQy6N07idWSQZHIdGFf+tTHvRfSIWLD8u8Tg==", + "requires": { + "@babel/core": "^7.14.0", + "@babel/plugin-proposal-async-generator-functions": "^7.0.0", + "@babel/plugin-proposal-class-properties": "^7.0.0", + "@babel/plugin-proposal-export-default-from": "^7.0.0", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.0.0", + "@babel/plugin-proposal-object-rest-spread": "^7.0.0", + "@babel/plugin-proposal-optional-catch-binding": "^7.0.0", + "@babel/plugin-proposal-optional-chaining": "^7.0.0", + "@babel/plugin-syntax-dynamic-import": "^7.0.0", + "@babel/plugin-syntax-export-default-from": "^7.0.0", + "@babel/plugin-syntax-flow": "^7.2.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.0.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.0.0", + "@babel/plugin-transform-exponentiation-operator": "^7.0.0", + "@babel/plugin-transform-flow-strip-types": "^7.0.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-template-literals": "^7.0.0", + "@babel/plugin-transform-typescript": "^7.5.0", + "@babel/plugin-transform-unicode-regex": "^7.0.0", + "@babel/template": "^7.0.0", + "react-refresh": "^0.4.0" + } + }, + "metro-react-native-babel-transformer": { + "version": "0.70.3", + "resolved": "https://registry.npmjs.org/metro-react-native-babel-transformer/-/metro-react-native-babel-transformer-0.70.3.tgz", + "integrity": "sha512-WKBU6S/G50j9cfmFM4k4oRYprd8u3qjleD4so1E2zbTNILg+gYla7ZFGCAvi2G0ZcqS2XuGCR375c2hF6VVvwg==", + "requires": { + "@babel/core": "^7.14.0", + "babel-preset-fbjs": "^3.4.0", + "hermes-parser": "0.6.0", + "metro-babel-transformer": "0.70.3", + "metro-react-native-babel-preset": "0.70.3", + "metro-source-map": "0.70.3", + "nullthrows": "^1.1.1" + } + }, + "metro-source-map": { + "version": "0.70.3", + "resolved": "https://registry.npmjs.org/metro-source-map/-/metro-source-map-0.70.3.tgz", + "integrity": "sha512-zsYtZGrwRbbGEFHtmMqqeCH9K9aTGNVPsurMOWCUeQA3VGyVGXPGtLMC+CdAM9jLpUyg6jw2xh0esxi+tYH7Uw==", + "requires": { + "@babel/traverse": "^7.14.0", + "@babel/types": "^7.0.0", + "invariant": "^2.2.4", + "metro-symbolicate": "0.70.3", + "nullthrows": "^1.1.1", + "ob1": "0.70.3", + "source-map": "^0.5.6", + "vlq": "^1.0.0" + } + }, + "metro-symbolicate": { + "version": "0.70.3", + "resolved": "https://registry.npmjs.org/metro-symbolicate/-/metro-symbolicate-0.70.3.tgz", + "integrity": "sha512-JTYkF1dpeDUssQ84juE1ycnhHki2ylJBBdJE1JHtfu5oC+z1ElDbBdPHq90Uvt8HbRov/ZAnxvv7Zy6asS+WCA==", + "requires": { + "invariant": "^2.2.4", + "metro-source-map": "0.70.3", + "nullthrows": "^1.1.1", + "source-map": "^0.5.6", + "through2": "^2.0.1", + "vlq": "^1.0.0" + } + }, + "ob1": { + "version": "0.70.3", + "resolved": "https://registry.npmjs.org/ob1/-/ob1-0.70.3.tgz", + "integrity": "sha512-Vy9GGhuXgDRY01QA6kdhToPd8AkLdLpX9GjH5kpqluVqTu70mgOm7tpGoJDZGaNbr9nJlJgnipqHJQRPORixIQ==" + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", "requires": { - "asap": "~2.0.6" + "p-try": "^2.0.0" + } + }, + "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==", + "requires": { + "p-limit": "^2.2.0" + } + }, + "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==" + }, + "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==" + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" } }, "react-refresh": { @@ -51581,6 +52974,27 @@ "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.4.3.tgz", "integrity": "sha512-Hwln1VNuGl/6bVwnd0Xdn1e84gT/8T9aYNL+HAKDArLCS7LWjwr7StE30IEYbIkx0Vi3vs+coQxe+SQDbGbbpA==" }, + "scheduler": { + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.21.0.tgz", + "integrity": "sha512-1r87x5fz9MXqswA2ERLo0EbOAU74DpIUO090gIasYTqlVoJeMcl+Z1Rg7WHz+qtPujhS/hGIt9kxZOYBV3faRQ==", + "requires": { + "loose-envify": "^1.1.0" + } + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + }, + "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==", + "requires": { + "has-flag": "^4.0.0" + } + }, "ws": { "version": "6.2.2", "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.2.tgz", @@ -51600,12 +53014,13 @@ } }, "react-native-codegen": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/react-native-codegen/-/react-native-codegen-0.0.7.tgz", - "integrity": "sha512-dwNgR8zJ3ALr480QnAmpTiqvFo+rDtq6V5oCggKhYFlRjzOmVSFn3YD41u8ltvKS5G2nQ8gCs2vReFFnRGLYng==", + "version": "0.69.2", + "resolved": "https://registry.npmjs.org/react-native-codegen/-/react-native-codegen-0.69.2.tgz", + "integrity": "sha512-yPcgMHD4mqLbckqnWjFBaxomDnBREfRjDi2G/WxNyPBQLD+PXUEmZTkDx6QoOXN+Bl2SkpnNOSsLE2+/RUHoPw==", "requires": { + "@babel/parser": "^7.14.0", "flow-parser": "^0.121.0", - "jscodeshift": "^0.11.0", + "jscodeshift": "^0.13.1", "nullthrows": "^1.1.1" } }, @@ -51633,9 +53048,14 @@ "fast-base64-decode": "^1.0.0" } }, + "react-native-gradle-plugin": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/react-native-gradle-plugin/-/react-native-gradle-plugin-0.0.7.tgz", + "integrity": "sha512-+4JpbIx42zGTONhBTIXSyfyHICHC29VTvhkkoUOJAh/XHPEixpuBduYgf6Y4y9wsN1ARlQhBBoptTvXvAFQf5g==" + }, "react-native-hr": { - "version": "git+https://github.com/Riglerr/react-native-hr.git#2d01a5cf77212d100e8b99e0310cce5234f977b3", - "from": "git+https://github.com/Riglerr/react-native-hr.git#2d01a5cf77212d100e8b99e0310cce5234f977b3" + "version": "https://raw.githubusercontent.com/wordpress-mobile/react-native-hr/1.1.3-wp-1/react-native-hr-1.1.3.tgz", + "integrity": "sha512-iwKdUpLc7lGN0+yiVfKxvvouMEXJeR4sRmfXFKXNnWOaBRdcf412zPa5vK4yEGPh5s38GDR29jYMM/RRw5U4nw==" }, "react-native-hsv-color-picker": { "version": "https://raw.githubusercontent.com/wordpress-mobile/react-native-hsv-color-picker/v1.0.1-wp-3/react-native-hsv-color-picker-1.0.1-wp-3.tgz", @@ -51676,26 +53096,258 @@ "integrity": "sha512-JyNcFTqyQh1YVOTMamH29/wzspXH1+AlDbxiuW07gkhdw0BspCdq2P0Vf6JzPdiDxhv/v1+ix4t6RjAKGWulGQ==" }, "react-native-reanimated": { - "version": "https://raw.githubusercontent.com/wordpress-mobile/react-native-reanimated/2.4.1-wp-4/react-native-reanimated-2.4.1-wp-4.tgz", - "integrity": "sha512-kPA7eM0Rn96ZzKId4QPaBnJ8UwC7NwT/CrruszF7fY+Xb1DGAY3vP7ZJu1ZsNbsxdK2ERNWZ9bnocXop4QV7cg==", + "version": "https://raw.githubusercontent.com/wordpress-mobile/react-native-reanimated/2.9.1-wp-2/react-native-reanimated-2.9.1-wp-2.tgz", + "integrity": "sha512-Lb3OyTv8mnC8Flzlz29CWQCLds7at6wnTy1R46lNT0Rv6CLArer9KWOicYO0iKBHBifKHNXQFz4WRZY40sFFGQ==", "requires": { - "@babel/plugin-transform-object-assign": "^7.10.4", + "@babel/plugin-proposal-export-namespace-from": "^7.17.12", + "@babel/plugin-transform-object-assign": "^7.16.7", + "@babel/preset-typescript": "^7.16.7", "@types/invariant": "^2.2.35", "invariant": "^2.2.4", "lodash.isequal": "^4.5.0", - "mockdate": "^3.0.2", - "react-native-screens": "^3.4.0", + "setimmediate": "^1.0.5", "string-hash-64": "^1.0.3" }, "dependencies": { - "react-native-screens": { - "version": "3.18.1", - "resolved": "https://registry.npmjs.org/react-native-screens/-/react-native-screens-3.18.1.tgz", - "integrity": "sha512-GtEC1AbvpvtKDJldavuONF/hXW1aEZO7qz8SioyHrV9L9/nDiy+iTviMFmeEoix9KwmEHHxoYxc0xiDrFxKHyA==", + "@babel/code-frame": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", + "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", + "requires": { + "@babel/highlight": "^7.18.6" + } + }, + "@babel/generator": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.20.5.tgz", + "integrity": "sha512-jl7JY2Ykn9S0yj4DQP82sYvPU+T3g0HFcWTqDLqiuA9tGRNIj9VfbtXGAYTTkyNEnQk1jkMGOdYka8aG/lulCA==", + "requires": { + "@babel/types": "^7.20.5", + "@jridgewell/gen-mapping": "^0.3.2", + "jsesc": "^2.5.1" + } + }, + "@babel/helper-annotate-as-pure": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz", + "integrity": "sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA==", + "requires": { + "@babel/types": "^7.18.6" + } + }, + "@babel/helper-create-class-features-plugin": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.20.5.tgz", + "integrity": "sha512-3RCdA/EmEaikrhayahwToF0fpweU/8o2p8vhc1c/1kftHOdTKuC65kik/TLc+qfbS8JKw4qqJbne4ovICDhmww==", + "requires": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-function-name": "^7.19.0", + "@babel/helper-member-expression-to-functions": "^7.18.9", + "@babel/helper-optimise-call-expression": "^7.18.6", + "@babel/helper-replace-supers": "^7.19.1", + "@babel/helper-split-export-declaration": "^7.18.6" + } + }, + "@babel/helper-environment-visitor": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz", + "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==" + }, + "@babel/helper-function-name": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz", + "integrity": "sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w==", + "requires": { + "@babel/template": "^7.18.10", + "@babel/types": "^7.19.0" + } + }, + "@babel/helper-hoist-variables": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", + "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", + "requires": { + "@babel/types": "^7.18.6" + } + }, + "@babel/helper-member-expression-to-functions": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.18.9.tgz", + "integrity": "sha512-RxifAh2ZoVU67PyKIO4AMi1wTenGfMR/O/ae0CCRqwgBAt5v7xjdtRw7UoSbsreKrQn5t7r89eruK/9JjYHuDg==", + "requires": { + "@babel/types": "^7.18.9" + } + }, + "@babel/helper-optimise-call-expression": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.18.6.tgz", + "integrity": "sha512-HP59oD9/fEHQkdcbgFCnbmgH5vIQTJbxh2yf+CdM89/glUNnuzr87Q8GIjGEnOktTROemO0Pe0iPAYbqZuOUiA==", + "requires": { + "@babel/types": "^7.18.6" + } + }, + "@babel/helper-plugin-utils": { + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.20.2.tgz", + "integrity": "sha512-8RvlJG2mj4huQ4pZ+rU9lqKi9ZKiRmuvGuM2HlWmkmgOhbs6zEAw6IEiJ5cQqGbDzGZOhwuOQNtZMi/ENLjZoQ==" + }, + "@babel/helper-replace-supers": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.19.1.tgz", + "integrity": "sha512-T7ahH7wV0Hfs46SFh5Jz3s0B6+o8g3c+7TMxu7xKfmHikg7EAZ3I2Qk9LFhjxXq8sL7UkP5JflezNwoZa8WvWw==", + "requires": { + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-member-expression-to-functions": "^7.18.9", + "@babel/helper-optimise-call-expression": "^7.18.6", + "@babel/traverse": "^7.19.1", + "@babel/types": "^7.19.0" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", + "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", + "requires": { + "@babel/types": "^7.18.6" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", + "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==" + }, + "@babel/helper-validator-option": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz", + "integrity": "sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw==" + }, + "@babel/highlight": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", + "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "requires": { + "@babel/helper-validator-identifier": "^7.18.6", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.5.tgz", + "integrity": "sha512-r27t/cy/m9uKLXQNWWebeCUHgnAZq0CpG1OwKRxzJMP1vpSU4bSIK2hq+/cp0bQxetkXx38n09rNu8jVkcK/zA==" + }, + "@babel/plugin-proposal-export-namespace-from": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.18.9.tgz", + "integrity": "sha512-k1NtHyOMvlDDFeb9G5PhUXuGj8m/wiwojgQVEhJ/fsVsMCpLyOP4h0uGEjYJKrRI+EVPlb5Jk+Gt9P97lOGwtA==", + "requires": { + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3" + } + }, + "@babel/plugin-syntax-typescript": { + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.20.0.tgz", + "integrity": "sha512-rd9TkG+u1CExzS4SM1BlMEhMXwFLKVjOAFFCDx9PbX5ycJWDoWMcwdJH9RhkPu1dOgn5TrxLot/Gx6lWFuAUNQ==", + "requires": { + "@babel/helper-plugin-utils": "^7.19.0" + } + }, + "@babel/plugin-transform-typescript": { + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.20.2.tgz", + "integrity": "sha512-jvS+ngBfrnTUBfOQq8NfGnSbF9BrqlR6hjJ2yVxMkmO5nL/cdifNbI30EfjRlN4g5wYWNnMPyj5Sa6R1pbLeag==", + "requires": { + "@babel/helper-create-class-features-plugin": "^7.20.2", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/plugin-syntax-typescript": "^7.20.0" + } + }, + "@babel/preset-typescript": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.18.6.tgz", + "integrity": "sha512-s9ik86kXBAnD760aybBucdpnLsAt0jK1xqJn2juOn9lkOvSHV60os5hxoVJsPzMQxvnUJFAlkont2DvvaYEBtQ==", + "requires": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/helper-validator-option": "^7.18.6", + "@babel/plugin-transform-typescript": "^7.18.6" + } + }, + "@babel/template": { + "version": "7.18.10", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz", + "integrity": "sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA==", + "requires": { + "@babel/code-frame": "^7.18.6", + "@babel/parser": "^7.18.10", + "@babel/types": "^7.18.10" + } + }, + "@babel/traverse": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.20.5.tgz", + "integrity": "sha512-WM5ZNN3JITQIq9tFZaw1ojLU3WgWdtkxnhM1AegMS+PvHjkM5IXjmYEGY7yukz5XS4sJyEf2VzWjI8uAavhxBQ==", + "requires": { + "@babel/code-frame": "^7.18.6", + "@babel/generator": "^7.20.5", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-function-name": "^7.19.0", + "@babel/helper-hoist-variables": "^7.18.6", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/parser": "^7.20.5", + "@babel/types": "^7.20.5", + "debug": "^4.1.0", + "globals": "^11.1.0" + } + }, + "@babel/types": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.20.5.tgz", + "integrity": "sha512-c9fst/h2/dcF7H+MJKZ2T0KjEQ8hY/BNnDk/H3XY8C4Aw/eWQXWn/lWntHF9ooUBnGmEvbfGrTgLWc+um0YDUg==", + "requires": { + "@babel/helper-string-parser": "^7.19.4", + "@babel/helper-validator-identifier": "^7.19.1", + "to-fast-properties": "^2.0.0" + } + }, + "@jridgewell/gen-mapping": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", + "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", + "requires": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "requires": { - "react-freeze": "^1.0.0", - "warn-once": "^0.1.0" + "ms": "2.1.2" } + }, + "globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==" + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" } } }, @@ -51811,9 +53463,10 @@ } }, "react-native-video": { - "version": "https://raw.githubusercontent.com/wordpress-mobile/react-native-video/5.2.0-wp-4/react-native-video-5.2.0-wp-4.tgz", - "integrity": "sha512-Y/kCq6hGDOMkT4VLuGtSgJaCF5QWUIipYvqTpgLaPXF33tT9evqTtKXt6OjSFyuaoxXv9rrxaNoN70Fai2A91Q==", + "version": "https://raw.githubusercontent.com/wordpress-mobile/react-native-video/5.2.0-wp-5/react-native-video-5.2.0-wp-5.tgz", + "integrity": "sha512-kUU86AmVQ73tQsTyi93Uqa4ti23bHcBIKzgU2wY6lyPTH2hqceUFOdRolRh7AnYhcBjJRGfNK8R8TfUBecsdKA==", "requires": { + "deprecated-react-native-prop-types": "^2.2.0", "prop-types": "^15.7.2" } }, @@ -51929,20 +53582,18 @@ } }, "react-shallow-renderer": { - "version": "16.14.1", - "resolved": "https://registry.npmjs.org/react-shallow-renderer/-/react-shallow-renderer-16.14.1.tgz", - "integrity": "sha512-rkIMcQi01/+kxiTE9D3fdS959U1g7gs+/rborw++42m1O9FAQiNI/UNRZExVUoAOprn4umcXf+pFRou8i4zuBg==", - "dev": true, + "version": "16.15.0", + "resolved": "https://registry.npmjs.org/react-shallow-renderer/-/react-shallow-renderer-16.15.0.tgz", + "integrity": "sha512-oScf2FqQ9LFVQgA73vr86xl2NaOIX73rh+YFqcOp68CWj56tSfgtGKrEbyhCj0rSijyG9M1CYprTh39fBi5hzA==", "requires": { "object-assign": "^4.1.1", - "react-is": "^16.12.0 || ^17.0.0" + "react-is": "^16.12.0 || ^17.0.0 || ^18.0.0" }, "dependencies": { "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 + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" } } }, @@ -51972,21 +53623,20 @@ } }, "react-test-renderer": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-17.0.2.tgz", - "integrity": "sha512-yaQ9cB89c17PUb0x6UfWRs7kQCorVdHlutU1boVPEsB8IDZH6n9tHxMacc3y0JoXOJUsZb/t/Mb8FUWMKaM7iQ==", + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-18.2.0.tgz", + "integrity": "sha512-JWD+aQ0lh2gvh4NM3bBM42Kx+XybOxCpgYK7F8ugAlpaTSnWsX+39Z4XkOykGZAHrjwwTZT3x3KxswVWxHPUqA==", "dev": true, "requires": { - "object-assign": "^4.1.1", - "react-is": "^17.0.2", - "react-shallow-renderer": "^16.13.1", - "scheduler": "^0.20.2" + "react-is": "^18.2.0", + "react-shallow-renderer": "^16.15.0", + "scheduler": "^0.23.0" }, "dependencies": { "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==", + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", "dev": true } } @@ -52354,7 +54004,7 @@ "redent": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz", - "integrity": "sha512-qtW5hKzGQZqKoh6JNSD+4lfitfPKGz42e6QwiRmPM5mmKtR0N41AbJRYu0xJi7nhOJ4WDgRkKvAk6tw4WIwR4g==", + "integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=", "dev": true, "optional": true, "requires": { @@ -52365,7 +54015,7 @@ "indent-string": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz", - "integrity": "sha512-aqwDFWSgSgfRaEwao5lg5KEcVd/2a+D1rvoG7NdilmYz0NwRk6StWpWdz/Hpk34MKPpx7s8XxUqimfcQK6gGlg==", + "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=", "dev": true, "optional": true, "requires": { @@ -52431,6 +54081,7 @@ "version": "0.14.4", "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.14.4.tgz", "integrity": "sha512-EaJaKPBI9GvKpvUz2mz4fhx7WPgvwRLY9v3hlNHWmAuJHI13T4nwKnNvm5RWJzEdnI5g5UwtOww+S8IdoUC2bw==", + "dev": true, "requires": { "@babel/runtime": "^7.8.4", "private": "^0.1.8" @@ -53018,7 +54669,8 @@ "remove-trailing-separator": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", - "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=" + "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", + "dev": true }, "renderkid": { "version": "3.0.0", @@ -53146,7 +54798,7 @@ "repeating": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", - "integrity": "sha512-ZqtSMuVybkISo2OWvqvm7iHSWngvdaW3IpsT9/uP8v4gMi591LY6h35wdOfvQdWCKFWZWm2Y1Opp4kV7vQKT6A==", + "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", "dev": true, "optional": true, "requires": { @@ -53383,6 +55035,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", + "dev": true, "requires": { "onetime": "^2.0.0", "signal-exit": "^3.0.2" @@ -53455,7 +55108,8 @@ "rsvp": { "version": "4.8.4", "resolved": "https://registry.npmjs.org/rsvp/-/rsvp-4.8.4.tgz", - "integrity": "sha512-6FomvYPfs+Jy9TfXmBpBuMWNH94SgCsZmJKcanySzgNNP6LjWxBvyLTa9KaMfDDM5oxRfrKDB0r/qeRsLwnBfA==" + "integrity": "sha512-6FomvYPfs+Jy9TfXmBpBuMWNH94SgCsZmJKcanySzgNNP6LjWxBvyLTa9KaMfDDM5oxRfrKDB0r/qeRsLwnBfA==", + "dev": true }, "rtlcss": { "version": "4.0.0", @@ -53557,6 +55211,56 @@ "ret": "~0.1.10" } }, + "safe-regex-test": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", + "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "is-regex": "^1.1.4" + }, + "dependencies": { + "call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + } + }, + "get-intrinsic": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz", + "integrity": "sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.3" + } + }, + "has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true + }, + "is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + } + } + } + }, "safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", @@ -53566,6 +55270,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/sane/-/sane-4.1.0.tgz", "integrity": "sha512-hhbzAgTIX8O7SHfp2c8/kREfEn4qO/9q8C9beyY6+tvZ87EpoZ3i1RIEvp27YBswnNbY9mWd6paKVmKbAgLfZA==", + "dev": true, "requires": { "@cnakazawa/watch": "^1.0.3", "anymatch": "^2.0.0", @@ -53582,6 +55287,7 @@ "version": "6.0.5", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, "requires": { "nice-try": "^1.0.4", "path-key": "^2.0.1", @@ -53593,7 +55299,8 @@ "semver": { "version": "5.7.0", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", - "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==" + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", + "dev": true } } }, @@ -53601,6 +55308,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "dev": true, "requires": { "cross-spawn": "^6.0.0", "get-stream": "^4.0.0", @@ -53615,6 +55323,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, "requires": { "pump": "^3.0.0" } @@ -53623,6 +55332,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, "requires": { "end-of-stream": "^1.1.0", "once": "^1.3.1" @@ -53766,11 +55476,6 @@ "neo-async": "^2.6.2" } }, - "sax": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" - }, "saxes": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", @@ -53781,12 +55486,11 @@ } }, "scheduler": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.20.2.tgz", - "integrity": "sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==", + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", + "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", "requires": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1" + "loose-envify": "^1.1.0" } }, "schema-utils": { @@ -53867,23 +55571,23 @@ "dev": true }, "send": { - "version": "0.17.2", - "resolved": "https://registry.npmjs.org/send/-/send-0.17.2.tgz", - "integrity": "sha512-UJYB6wFSJE3G00nEivR5rgWp8c2xXvJ3OPWPhmuteU0IKj8nKbG3DrjiOmLwpnHGYWAVwA69zmTm++YG0Hmwww==", + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", "requires": { "debug": "2.6.9", - "depd": "~1.1.2", - "destroy": "~1.0.4", + "depd": "2.0.0", + "destroy": "1.2.0", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "etag": "~1.8.1", "fresh": "0.5.2", - "http-errors": "1.8.1", + "http-errors": "2.0.0", "mime": "1.6.0", "ms": "2.1.3", - "on-finished": "~2.3.0", + "on-finished": "2.4.1", "range-parser": "~1.2.1", - "statuses": "~1.5.0" + "statuses": "2.0.1" }, "dependencies": { "debug": { @@ -53897,10 +55601,20 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" } } }, + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" + }, + "destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==" + }, "mime": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", @@ -53910,6 +55624,19 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "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==", + "requires": { + "ee-first": "1.1.1" + } + }, + "statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" } } }, @@ -53940,7 +55667,7 @@ "serve-favicon": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/serve-favicon/-/serve-favicon-2.5.0.tgz", - "integrity": "sha512-FMW2RvqNr03x+C0WxTyu6sOv21oOjkq5j8tjquWccwa6ScNyGFOGJVpuS1NmTVGBAHS07xnSKotgf2ehQmf9iA==", + "integrity": "sha1-k10kDN/g9YBTB/3+ln2IlCosvPA=", "dev": true, "requires": { "etag": "~1.8.1", @@ -54009,14 +55736,14 @@ } }, "serve-static": { - "version": "1.14.2", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.2.tgz", - "integrity": "sha512-+TMNA9AFxUEGuC0z2mevogSnn9MXKb4fa7ngeRMJaaGv8vTwnIEkKi+QGvPt33HSnf8pRS+WGM0EbMtCJLKMBQ==", + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", "requires": { "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "parseurl": "~1.3.3", - "send": "0.17.2" + "send": "0.18.0" } }, "set-blocking": { @@ -54056,8 +55783,7 @@ "setimmediate": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", - "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=", - "dev": true + "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=" }, "setprototypeof": { "version": "1.2.0", @@ -54288,26 +56014,6 @@ "resolved": "https://registry.npmjs.org/simple-html-tokenizer/-/simple-html-tokenizer-0.5.7.tgz", "integrity": "sha512-APW9iYbkJ5cijjX4Ljhf3VG8SwYPUJT5gZrwci/wieMabQxWFiV5VwsrP5c6GMRvXKEQMGkAB1d9dvW66dTqpg==" }, - "simple-plist": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/simple-plist/-/simple-plist-1.3.1.tgz", - "integrity": "sha512-iMSw5i0XseMnrhtIzRb7XpQEXepa9xhWxGUojHBL43SIpQuDQkh3Wpy67ZbDzZVr6EKxvwVChnVpdl8hEVLDiw==", - "requires": { - "bplist-creator": "0.1.0", - "bplist-parser": "0.3.1", - "plist": "^3.0.5" - }, - "dependencies": { - "bplist-parser": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/bplist-parser/-/bplist-parser-0.3.1.tgz", - "integrity": "sha512-PyJxiNtA5T2PlLIeBot4lbp7rj4OadzjnMZD/G5zuBNt8ei/yCU7+wW0h2bag9vr8c+/WuRWmSxbqAl9hL1rBA==", - "requires": { - "big-integer": "1.6.x" - } - } - } - }, "simple-swizzle": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", @@ -54345,8 +56051,7 @@ "sisteransi": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", - "dev": true + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==" }, "slash": { "version": "3.0.0", @@ -55273,7 +56978,8 @@ "statuses": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", + "dev": true }, "store2": { "version": "2.12.0", @@ -55297,11 +57003,6 @@ "readable-stream": "^2.0.2" } }, - "stream-buffers": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/stream-buffers/-/stream-buffers-2.2.0.tgz", - "integrity": "sha1-kdX1Ew0c75bc+n9yaUUYh0HQnuQ=" - }, "stream-each": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/stream-each/-/stream-each-1.2.2.tgz", @@ -55474,17 +57175,432 @@ "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", "dev": true }, - "get-intrinsic": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", - "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", - "dev": true, - "requires": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1" - } - }, + "get-intrinsic": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", + "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1" + } + }, + "has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "dev": true + }, + "has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true + }, + "is-callable": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.4.tgz", + "integrity": "sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w==", + "dev": true + }, + "is-negative-zero": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", + "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", + "dev": true + }, + "is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + } + }, + "is-shared-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", + "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2" + } + }, + "is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dev": true, + "requires": { + "has-tostringtag": "^1.0.0" + } + }, + "is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dev": true, + "requires": { + "has-symbols": "^1.0.2" + } + }, + "is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.2" + } + }, + "object-inspect": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", + "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==", + "dev": true + }, + "object.assign": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", + "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "has-symbols": "^1.0.1", + "object-keys": "^1.1.1" + } + }, + "regexp.prototype.flags": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", + "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "functions-have-names": "^1.2.2" + } + }, + "string.prototype.trimend": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.5.tgz", + "integrity": "sha512-I7RGvmjV4pJ7O3kdf+LXFpVfdNOxtCW/2C8f6jNiW4+PQchwxkCDzlk1/7p+Wl4bqFIZeF47qAHXLuHHWKAxog==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.19.5" + } + }, + "string.prototype.trimstart": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.5.tgz", + "integrity": "sha512-THx16TJCGlsN0o6dl2o6ncWUsdgnLRSA23rRE5pyGBw/mLr3Ej/R2LaqCtgP8VNMGZsvMWnf9ooZPyY2bHvUFg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.19.5" + } + }, + "unbox-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" + } + } + } + }, + "string.prototype.padend": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/string.prototype.padend/-/string.prototype.padend-3.1.3.tgz", + "integrity": "sha512-jNIIeokznm8SD/TZISQsZKYu7RJyheFNt84DUPrh482GC8RVp2MKqm2O5oBRdGxbDQoXrhhWtPIWQOiy20svUg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.1" + }, + "dependencies": { + "call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + } + }, + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "dev": true, + "requires": { + "object-keys": "^1.0.12" + } + }, + "es-abstract": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.19.1.tgz", + "integrity": "sha512-2vJ6tjA/UfqLm2MPs7jxVybLoB8i1t1Jd9R3kISld20sIxPcTbLuggQOUxeWeAvIUkduv/CfMjuh4WmiXr2v9w==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "get-intrinsic": "^1.1.1", + "get-symbol-description": "^1.0.0", + "has": "^1.0.3", + "has-symbols": "^1.0.2", + "internal-slot": "^1.0.3", + "is-callable": "^1.2.4", + "is-negative-zero": "^2.0.1", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.1", + "is-string": "^1.0.7", + "is-weakref": "^1.0.1", + "object-inspect": "^1.11.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.2", + "string.prototype.trimend": "^1.0.4", + "string.prototype.trimstart": "^1.0.4", + "unbox-primitive": "^1.0.1" + }, + "dependencies": { + "get-intrinsic": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", + "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1" + } + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true + } + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "has-symbols": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", + "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==", + "dev": true + }, + "is-callable": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.4.tgz", + "integrity": "sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w==", + "dev": true + }, + "is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + } + }, + "is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dev": true, + "requires": { + "has-tostringtag": "^1.0.0" + } + }, + "is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dev": true, + "requires": { + "has-symbols": "^1.0.2" + } + }, + "object-inspect": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.11.1.tgz", + "integrity": "sha512-If7BjFlpkzzBeV1cqgT3OSWT3azyoxDGajR+iGnFBfVV2EWyDyWaZZW2ERDjUaY2QM8i5jI3Sj7mhsM4DDAqWA==", + "dev": true + }, + "object.assign": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", + "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "has-symbols": "^1.0.1", + "object-keys": "^1.1.1" + }, + "dependencies": { + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true + } + } + }, + "string.prototype.trimend": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz", + "integrity": "sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + } + }, + "string.prototype.trimstart": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz", + "integrity": "sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + } + } + } + }, + "string.prototype.padstart": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/string.prototype.padstart/-/string.prototype.padstart-3.1.3.tgz", + "integrity": "sha512-NZydyOMtYxpTjGqp0VN5PYUF/tsU15yDMZnUdj16qRUIUiMJkHHSDElYyQFrMu+/WloTpA7MQSiADhBicDfaoA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.1" + }, + "dependencies": { + "call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + } + }, + "define-properties": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", + "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==", + "dev": true, + "requires": { + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + } + }, + "es-abstract": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.20.1.tgz", + "integrity": "sha512-WEm2oBhfoI2sImeM4OF2zE2V3BYdSF+KnSi9Sidz51fQHd7+JuF8Xgcj9/0o+OWeIeIS/MiuNnlruQrJf16GQA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "function.prototype.name": "^1.1.5", + "get-intrinsic": "^1.1.1", + "get-symbol-description": "^1.0.0", + "has": "^1.0.3", + "has-property-descriptors": "^1.0.0", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.3", + "is-callable": "^1.2.4", + "is-negative-zero": "^2.0.2", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "is-string": "^1.0.7", + "is-weakref": "^1.0.2", + "object-inspect": "^1.12.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.2", + "regexp.prototype.flags": "^1.4.3", + "string.prototype.trimend": "^1.0.5", + "string.prototype.trimstart": "^1.0.5", + "unbox-primitive": "^1.0.2" + }, + "dependencies": { + "get-intrinsic": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", + "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1" + } + } + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "function.prototype.name": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", + "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.0", + "functions-have-names": "^1.2.2" + } + }, + "functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true + }, "has-bigints": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", @@ -55620,191 +57736,15 @@ } } }, - "string.prototype.padend": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/string.prototype.padend/-/string.prototype.padend-3.1.3.tgz", - "integrity": "sha512-jNIIeokznm8SD/TZISQsZKYu7RJyheFNt84DUPrh482GC8RVp2MKqm2O5oBRdGxbDQoXrhhWtPIWQOiy20svUg==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.1" - }, - "dependencies": { - "call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "dev": true, - "requires": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" - } - }, - "define-properties": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", - "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", - "dev": true, - "requires": { - "object-keys": "^1.0.12" - } - }, - "es-abstract": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.19.1.tgz", - "integrity": "sha512-2vJ6tjA/UfqLm2MPs7jxVybLoB8i1t1Jd9R3kISld20sIxPcTbLuggQOUxeWeAvIUkduv/CfMjuh4WmiXr2v9w==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "get-intrinsic": "^1.1.1", - "get-symbol-description": "^1.0.0", - "has": "^1.0.3", - "has-symbols": "^1.0.2", - "internal-slot": "^1.0.3", - "is-callable": "^1.2.4", - "is-negative-zero": "^2.0.1", - "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.1", - "is-string": "^1.0.7", - "is-weakref": "^1.0.1", - "object-inspect": "^1.11.0", - "object-keys": "^1.1.1", - "object.assign": "^4.1.2", - "string.prototype.trimend": "^1.0.4", - "string.prototype.trimstart": "^1.0.4", - "unbox-primitive": "^1.0.1" - }, - "dependencies": { - "get-intrinsic": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", - "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", - "dev": true, - "requires": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1" - } - }, - "object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true - } - } - }, - "es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "dev": true, - "requires": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - } - }, - "has-symbols": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", - "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==", - "dev": true - }, - "is-callable": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.4.tgz", - "integrity": "sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w==", - "dev": true - }, - "is-regex": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", - "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - } - }, - "is-string": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", - "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", - "dev": true, - "requires": { - "has-tostringtag": "^1.0.0" - } - }, - "is-symbol": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", - "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", - "dev": true, - "requires": { - "has-symbols": "^1.0.2" - } - }, - "object-inspect": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.11.1.tgz", - "integrity": "sha512-If7BjFlpkzzBeV1cqgT3OSWT3azyoxDGajR+iGnFBfVV2EWyDyWaZZW2ERDjUaY2QM8i5jI3Sj7mhsM4DDAqWA==", - "dev": true - }, - "object.assign": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", - "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", - "dev": true, - "requires": { - "call-bind": "^1.0.0", - "define-properties": "^1.1.3", - "has-symbols": "^1.0.1", - "object-keys": "^1.1.1" - }, - "dependencies": { - "object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true - } - } - }, - "string.prototype.trimend": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz", - "integrity": "sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" - } - }, - "string.prototype.trimstart": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz", - "integrity": "sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" - } - } - } - }, - "string.prototype.padstart": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/string.prototype.padstart/-/string.prototype.padstart-3.1.3.tgz", - "integrity": "sha512-NZydyOMtYxpTjGqp0VN5PYUF/tsU15yDMZnUdj16qRUIUiMJkHHSDElYyQFrMu+/WloTpA7MQSiADhBicDfaoA==", + "string.prototype.trimend": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.5.tgz", + "integrity": "sha512-I7RGvmjV4pJ7O3kdf+LXFpVfdNOxtCW/2C8f6jNiW4+PQchwxkCDzlk1/7p+Wl4bqFIZeF47qAHXLuHHWKAxog==", "dev": true, "requires": { "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.1" + "define-properties": "^1.1.4", + "es-abstract": "^1.19.5" }, "dependencies": { "call-bind": { @@ -55828,45 +57768,46 @@ } }, "es-abstract": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.20.1.tgz", - "integrity": "sha512-WEm2oBhfoI2sImeM4OF2zE2V3BYdSF+KnSi9Sidz51fQHd7+JuF8Xgcj9/0o+OWeIeIS/MiuNnlruQrJf16GQA==", + "version": "1.20.4", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.20.4.tgz", + "integrity": "sha512-0UtvRN79eMe2L+UNEF1BwRe364sj/DXhQ/k5FmivgoSdpM90b8Jc0mDzKMGo7QS0BVbOP/bTwBKNnDc9rNzaPA==", "dev": true, "requires": { "call-bind": "^1.0.2", "es-to-primitive": "^1.2.1", "function-bind": "^1.1.1", "function.prototype.name": "^1.1.5", - "get-intrinsic": "^1.1.1", + "get-intrinsic": "^1.1.3", "get-symbol-description": "^1.0.0", "has": "^1.0.3", "has-property-descriptors": "^1.0.0", "has-symbols": "^1.0.3", "internal-slot": "^1.0.3", - "is-callable": "^1.2.4", + "is-callable": "^1.2.7", "is-negative-zero": "^2.0.2", "is-regex": "^1.1.4", "is-shared-array-buffer": "^1.0.2", "is-string": "^1.0.7", "is-weakref": "^1.0.2", - "object-inspect": "^1.12.0", + "object-inspect": "^1.12.2", "object-keys": "^1.1.1", - "object.assign": "^4.1.2", + "object.assign": "^4.1.4", "regexp.prototype.flags": "^1.4.3", + "safe-regex-test": "^1.0.0", "string.prototype.trimend": "^1.0.5", "string.prototype.trimstart": "^1.0.5", "unbox-primitive": "^1.0.2" }, "dependencies": { "get-intrinsic": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", - "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz", + "integrity": "sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==", "dev": true, "requires": { "function-bind": "^1.1.1", "has": "^1.0.3", - "has-symbols": "^1.0.1" + "has-symbols": "^1.0.3" } } } @@ -55894,12 +57835,6 @@ "functions-have-names": "^1.2.2" } }, - "functions-have-names": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", - "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", - "dev": true - }, "has-bigints": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", @@ -55913,9 +57848,9 @@ "dev": true }, "is-callable": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.4.tgz", - "integrity": "sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w==", + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", "dev": true }, "is-negative-zero": { @@ -55977,14 +57912,14 @@ "dev": true }, "object.assign": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", - "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", + "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", "dev": true, "requires": { - "call-bind": "^1.0.0", - "define-properties": "^1.1.3", - "has-symbols": "^1.0.1", + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "has-symbols": "^1.0.3", "object-keys": "^1.1.1" } }, @@ -55999,17 +57934,6 @@ "functions-have-names": "^1.2.2" } }, - "string.prototype.trimend": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.5.tgz", - "integrity": "sha512-I7RGvmjV4pJ7O3kdf+LXFpVfdNOxtCW/2C8f6jNiW4+PQchwxkCDzlk1/7p+Wl4bqFIZeF47qAHXLuHHWKAxog==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.19.5" - } - }, "string.prototype.trimstart": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.5.tgz", @@ -56099,7 +58023,7 @@ "strip-bom": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", - "integrity": "sha512-kwrX1y7czp1E69n2ajbG65mIo9dqvJ+8aBQXOGVxqwvNbsXdFM6Lq37dLAY3mknUwru8CfcCbfOLL/gMo+fi3g==", + "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", "dev": true, "optional": true, "requires": { @@ -56120,7 +58044,7 @@ "strip-indent": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz", - "integrity": "sha512-I5iQq6aFMM62fBEAIB/hXzwJD6EEZ0xEGCX2t7oXqaKPIRgt4WruAQ285BISgdkP+HLGWyeGmNJcpIwFeRYRUA==", + "integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=", "dev": true, "optional": true, "requires": { @@ -56175,12 +58099,19 @@ } }, "style-value-types": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/style-value-types/-/style-value-types-5.0.0.tgz", - "integrity": "sha512-08yq36Ikn4kx4YU6RD7jWEv27v4V+PUsOGa4n/as8Et3CuODMJQ00ENeAVXAeydX4Z2j1XHZF1K2sX4mGl18fA==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/style-value-types/-/style-value-types-5.1.2.tgz", + "integrity": "sha512-Vs9fNreYF9j6W2VvuDTP7kepALi7sk0xtk2Tu8Yxi9UoajJdEVpNpCov0HsLTqXvNGKX+Uv09pkozVITi1jf3Q==", "requires": { "hey-listen": "^1.0.8", - "tslib": "^2.1.0" + "tslib": "2.4.0" + }, + "dependencies": { + "tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" + } } }, "stylehacks": { @@ -57867,7 +59798,7 @@ "trim-newlines": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz", - "integrity": "sha512-Nm4cF79FhSTzrLKGDMi3I4utBtFv8qKy4sq1enftf2gMdpqI8oVQTAfySkTz5r49giVzDj88SVZXP4CeYQwjaw==", + "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=", "dev": true, "optional": true }, @@ -58069,17 +60000,33 @@ "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==", "dev": true }, + "uglify-es": { + "version": "3.3.9", + "resolved": "https://registry.npmjs.org/uglify-es/-/uglify-es-3.3.9.tgz", + "integrity": "sha512-r+MU0rfv4L/0eeW3xZrd16t4NZfK8Ld4SWVglYBb7ez5uXFWHuVRs6xCTrf1yirs9a4j4Y27nn7SRfO6v67XsQ==", + "requires": { + "commander": "~2.13.0", + "source-map": "~0.6.1" + }, + "dependencies": { + "commander": { + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.13.0.tgz", + "integrity": "sha512-MVuS359B+YzaWqjCL/c+22gfryv+mCBPHAv3zyVI2GN8EY6IRP8VwtasXn8jyyhvvq84R4ImN1OKRtcbIasjYA==" + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + } + } + }, "uglify-js": { "version": "3.13.7", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.13.7.tgz", "integrity": "sha512-1Psi2MmnZJbnEsgJJIlfnd7tFlJfitusmR7zDI8lXlFI0ACD4/Rm/xdrU8bh6zF0i74aiVoBtkRiFulkrmh3AA==", "dev": true }, - "ultron": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.0.2.tgz", - "integrity": "sha1-rOEWq1V80Zc4ak6I9GhTeMiy5Po=" - }, "unbox-primitive": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz", @@ -58380,45 +60327,13 @@ "untildify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/untildify/-/untildify-2.1.0.tgz", - "integrity": "sha512-sJjbDp2GodvkB0FZZcn7k6afVisqX5BZD7Yq3xp4nN2O15BBK0cLm3Vwn2vQaF7UDS0UUsrQMkkplmDI5fskig==", + "integrity": "sha1-F+soB5h/dpUunASF/DEdBqgmouA=", "dev": true, "optional": true, "requires": { "os-homedir": "^1.0.0" } }, - "unzipper": { - "version": "0.10.11", - "resolved": "https://registry.npmjs.org/unzipper/-/unzipper-0.10.11.tgz", - "integrity": "sha512-+BrAq2oFqWod5IESRjL3S8baohbevGcVA+teAIOYWM3pDVdseogqbzhhvvmiyQrUNKFUnDMtELW3X8ykbyDCJw==", - "dev": true, - "requires": { - "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" - }, - "dependencies": { - "bluebird": { - "version": "3.4.7", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.4.7.tgz", - "integrity": "sha1-9y12C+Cbf3bQjtj66Ysomo0F+rM=", - "dev": true - }, - "graceful-fs": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz", - "integrity": "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==", - "dev": true - } - } - }, "upath": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", @@ -58656,7 +60571,7 @@ "utila": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/utila/-/utila-0.4.0.tgz", - "integrity": "sha512-Z0DbgELS9/L/75wZbro8xAnT50pBVFQZ+hUEueGDU5FN51YSCYM+jdxsfCiHjwNP/4LCDD0i/graKpeBnOXKRA==", + "integrity": "sha1-ihagXURWV6Oupe7MWxKk+lN5dyw=", "dev": true }, "utility-types": { @@ -58677,7 +60592,7 @@ "uuid-browser": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/uuid-browser/-/uuid-browser-3.1.0.tgz", - "integrity": "sha512-dsNgbLaTrd6l3MMxTtouOCFw4CBFc/3a+GgYA2YyrJvyQ1u6q4pcu3ktLoUZ/VN/Aw9WsauazbgsgdfVWgAKQg==", + "integrity": "sha1-DwWkCu90+eWVHiDvv0SxGHHlZBA=", "dev": true }, "v8-compile-cache": { @@ -58895,11 +60810,6 @@ "makeerror": "1.0.x" } }, - "warn-once": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/warn-once/-/warn-once-0.1.1.tgz", - "integrity": "sha512-VkQZJbO8zVImzYFteBXvBOZEl1qL175WH8VmZcxF2fZAoudNhNDvHi+doCaAEdU2l2vtcIwa2zn0QK5+I1HQ3Q==" - }, "warning": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", @@ -60220,11 +62130,249 @@ } } }, + "which-collection": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.1.tgz", + "integrity": "sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==", + "dev": true, + "requires": { + "is-map": "^2.0.1", + "is-set": "^2.0.1", + "is-weakmap": "^2.0.1", + "is-weakset": "^2.0.1" + } + }, "which-module": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=" }, + "which-typed-array": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.8.tgz", + "integrity": "sha512-Jn4e5PItbcAHyLoRDwvPj1ypu27DJbtdYXUa5zsinrUx77Uvfb0cXwwnGMTn7cjUfhhqgVQnVJCwF+7cgU7tpw==", + "dev": true, + "requires": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "es-abstract": "^1.20.0", + "for-each": "^0.3.3", + "has-tostringtag": "^1.0.0", + "is-typed-array": "^1.1.9" + }, + "dependencies": { + "call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + } + }, + "define-properties": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", + "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==", + "dev": true, + "requires": { + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + } + }, + "es-abstract": { + "version": "1.20.4", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.20.4.tgz", + "integrity": "sha512-0UtvRN79eMe2L+UNEF1BwRe364sj/DXhQ/k5FmivgoSdpM90b8Jc0mDzKMGo7QS0BVbOP/bTwBKNnDc9rNzaPA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "function.prototype.name": "^1.1.5", + "get-intrinsic": "^1.1.3", + "get-symbol-description": "^1.0.0", + "has": "^1.0.3", + "has-property-descriptors": "^1.0.0", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.3", + "is-callable": "^1.2.7", + "is-negative-zero": "^2.0.2", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "is-string": "^1.0.7", + "is-weakref": "^1.0.2", + "object-inspect": "^1.12.2", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.4.3", + "safe-regex-test": "^1.0.0", + "string.prototype.trimend": "^1.0.5", + "string.prototype.trimstart": "^1.0.5", + "unbox-primitive": "^1.0.2" + }, + "dependencies": { + "get-intrinsic": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz", + "integrity": "sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.3" + } + } + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "function.prototype.name": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", + "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.0", + "functions-have-names": "^1.2.2" + } + }, + "has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "dev": true + }, + "has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true + }, + "is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true + }, + "is-negative-zero": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", + "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", + "dev": true + }, + "is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + } + }, + "is-shared-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", + "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2" + } + }, + "is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dev": true, + "requires": { + "has-tostringtag": "^1.0.0" + } + }, + "is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dev": true, + "requires": { + "has-symbols": "^1.0.2" + } + }, + "is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.2" + } + }, + "object-inspect": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", + "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==", + "dev": true + }, + "object.assign": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", + "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + } + }, + "regexp.prototype.flags": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", + "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "functions-have-names": "^1.2.2" + } + }, + "string.prototype.trimstart": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.5.tgz", + "integrity": "sha512-THx16TJCGlsN0o6dl2o6ncWUsdgnLRSA23rRE5pyGBw/mLr3Ej/R2LaqCtgP8VNMGZsvMWnf9ooZPyY2bHvUFg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.19.5" + } + }, + "unbox-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" + } + } + } + }, "wicg-inert": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/wicg-inert/-/wicg-inert-3.1.2.tgz", @@ -60540,7 +62688,7 @@ "x-default-browser": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/x-default-browser/-/x-default-browser-0.4.0.tgz", - "integrity": "sha512-7LKo7RtWfoFN/rHx1UELv/2zHGMx8MkZKDq1xENmOCTkfIqZJ0zZ26NEJX8czhnPXVcqS0ARjjfJB+eJ0/5Cvw==", + "integrity": "sha1-cM8NqF2nwKtcsPFaiX8jIqa91IE=", "dev": true, "requires": { "default-browser-id": "^1.0.4" @@ -60552,22 +62700,6 @@ "integrity": "sha1-R0tQhlrzpJqcRlfwWs0UVFj3fYI=", "dev": true }, - "xcode": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/xcode/-/xcode-2.1.0.tgz", - "integrity": "sha512-uCrmPITrqTEzhn0TtT57fJaNaw8YJs1aCzs+P/QqxsDbvPZSv7XMPPwXrKvHtD6pLjBM/NaVwraWJm8q83Y4iQ==", - "requires": { - "simple-plist": "^1.0.0", - "uuid": "^3.3.2" - }, - "dependencies": { - "uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" - } - } - }, "xml": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/xml/-/xml-1.0.1.tgz", @@ -60580,20 +62712,17 @@ "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==", "dev": true }, + "xmlbuilder": { + "version": "15.1.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz", + "integrity": "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==" + }, "xmlchars": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", "dev": true }, - "xmldoc": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/xmldoc/-/xmldoc-1.1.2.tgz", - "integrity": "sha512-ruPC/fyPNck2BD1dpz0AZZyrEwMOrWTO5lDdIXS91rs3wtm4j+T8Rp2o+zoOYkkAxJTZRPOSnOGei1egoRmKMQ==", - "requires": { - "sax": "^1.2.1" - } - }, "xtend": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", @@ -60743,8 +62872,7 @@ "yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==" }, "zip-stream": { "version": "2.1.3", diff --git a/package.json b/package.json index 0168806a72ec37..8c87dcaae93a7d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gutenberg", - "version": "14.6.0-rc.1", + "version": "14.8.0-rc.1", "private": true, "description": "A new WordPress editor experience.", "author": "The WordPress Contributors", @@ -63,7 +63,6 @@ "@wordpress/list-reusable-blocks": "file:packages/list-reusable-blocks", "@wordpress/media-utils": "file:packages/media-utils", "@wordpress/notices": "file:packages/notices", - "@wordpress/nux": "file:packages/nux", "@wordpress/plugins": "file:packages/plugins", "@wordpress/preferences": "file:packages/preferences", "@wordpress/preferences-persistence": "file:packages/preferences-persistence", @@ -112,10 +111,10 @@ "@storybook/builder-webpack5": "6.5.7", "@storybook/manager-webpack5": "6.5.7", "@storybook/react": "6.5.7", - "@testing-library/jest-dom": "5.16.4", - "@testing-library/react": "12.1.5", - "@testing-library/react-native": "9.1.0", - "@testing-library/user-event": "14.2.0", + "@testing-library/jest-dom": "5.16.5", + "@testing-library/react": "13.4.0", + "@testing-library/react-native": "11.3.0", + "@testing-library/user-event": "14.4.3", "@types/classnames": "2.3.1", "@types/eslint": "7.28.0", "@types/estree": "0.0.50", @@ -126,11 +125,12 @@ "@types/npm-package-arg": "6.1.1", "@types/prettier": "2.4.4", "@types/qs": "6.9.7", + "@types/react": "18.0.21", "@types/react-dates": "21.8.3", + "@types/react-dom": "18.0.6", "@types/requestidlecallback": "0.3.4", "@types/semver": "7.3.8", "@types/sprintf-js": "1.1.2", - "@types/unzipper": "0.10.5", "@types/uuid": "8.3.1", "@wordpress/babel-plugin-import-jsx-pragma": "file:packages/babel-plugin-import-jsx-pragma", "@wordpress/babel-plugin-makepot": "file:packages/babel-plugin-makepot", @@ -204,8 +204,8 @@ "lint-staged": "10.0.1", "lodash": "4.17.21", "make-dir": "3.0.0", - "metro-react-native-babel-preset": "0.66.2", - "metro-react-native-babel-transformer": "0.66.2", + "metro-react-native-babel-preset": "0.70.3", + "metro-react-native-babel-transformer": "0.70.3", "mkdirp": "0.5.1", "nock": "12.0.3", "node-fetch": "2.6.1", @@ -216,12 +216,12 @@ "postcss-loader": "6.2.1", "prettier": "npm:wp-prettier@2.6.2", "progress": "2.0.3", - "react": "17.0.2", - "react-dom": "17.0.2", - "react-native": "0.66.2", + "react": "18.2.0", + "react-dom": "18.2.0", + "react-native": "0.69.4", "react-native-url-polyfill": "1.1.2", "react-refresh": "0.10.0", - "react-test-renderer": "17.0.2", + "react-test-renderer": "18.2.0", "redux": "4.1.2", "resize-observer-polyfill": "1.5.1", "rimraf": "3.0.2", @@ -238,7 +238,6 @@ "terser-webpack-plugin": "5.1.4", "typescript": "4.4.2", "uglify-js": "3.13.7", - "unzipper": "0.10.11", "uuid": "8.3.0", "wd": "1.12.1", "webpack": "5.65.0", diff --git a/packages/annotations/package.json b/packages/annotations/package.json index 5d7021f0104112..8a60fcc93422c4 100644 --- a/packages/annotations/package.json +++ b/packages/annotations/package.json @@ -33,9 +33,6 @@ "rememo": "^4.0.0", "uuid": "^8.3.0" }, - "peerDependencies": { - "react": "^17.0.0" - }, "publishConfig": { "access": "public" } diff --git a/packages/babel-plugin-makepot/index.js b/packages/babel-plugin-makepot/index.js index 3dc1b3ed26651d..6bd5891a604389 100644 --- a/packages/babel-plugin-makepot/index.js +++ b/packages/babel-plugin-makepot/index.js @@ -33,7 +33,7 @@ */ const { po } = require( 'gettext-parser' ); -const { pick, isEqual, merge, isEmpty } = require( 'lodash' ); +const { merge, isEmpty } = require( 'lodash' ); const { relative, sep } = require( 'path' ); const { writeFileSync } = require( 'fs' ); @@ -182,10 +182,7 @@ function isValidTranslationKey( key ) { * @return {boolean} Whether valid translation keys match. */ function isSameTranslation( a, b ) { - return isEqual( - pick( a, VALID_TRANSLATION_KEYS ), - pick( b, VALID_TRANSLATION_KEYS ) - ); + return VALID_TRANSLATION_KEYS.every( ( key ) => a[ key ] === b[ key ] ); } /** diff --git a/packages/base-styles/CHANGELOG.md b/packages/base-styles/CHANGELOG.md index 85fc4cb684b63f..a532f63be70066 100644 --- a/packages/base-styles/CHANGELOG.md +++ b/packages/base-styles/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### Enhancements + +- Lighten the border color in the `input-style__neutral` mixin ([#46252](https://github.com/WordPress/gutenberg/pull/46252)). + ## 4.13.0 (2022-11-16) ## 4.12.0 (2022-11-02) diff --git a/packages/base-styles/_colors.scss b/packages/base-styles/_colors.scss index 03f6bfbdd566d4..2ce58b64e43b8c 100644 --- a/packages/base-styles/_colors.scss +++ b/packages/base-styles/_colors.scss @@ -1,3 +1,5 @@ +@import "./functions"; + /** * Colors */ diff --git a/packages/base-styles/_default-custom-properties.scss b/packages/base-styles/_default-custom-properties.scss index 9f2979ea039bbb..52dfeb3899d772 100644 --- a/packages/base-styles/_default-custom-properties.scss +++ b/packages/base-styles/_default-custom-properties.scss @@ -4,4 +4,6 @@ // It also provides default CSS variables for npm package consumers. :root { @include admin-scheme(#007cba); + --wp-block-synced-color: #7a00df; + --wp-block-synced-color--rgb: #{hex-to-rgb(#7a00df)}; } diff --git a/packages/base-styles/_mixins.scss b/packages/base-styles/_mixins.scss index 50af0938bccdb8..9eee5820eba811 100644 --- a/packages/base-styles/_mixins.scss +++ b/packages/base-styles/_mixins.scss @@ -131,7 +131,7 @@ box-shadow: 0 0 0 transparent; transition: box-shadow 0.1s linear; border-radius: $radius-block-ui; - border: $border-width solid $gray-700; + border: $border-width solid $gray-600; @include reduce-motion("transition"); } @@ -550,3 +550,36 @@ } /* stylelint-enable function-comma-space-after */ } + +@mixin custom-scrollbars-on-hover() { + visibility: hidden; + + $handle-color: #757575; + $track-color: #1e1e1e; + + // WebKit + &::-webkit-scrollbar { + width: 12px; + height: 12px; + } + &::-webkit-scrollbar-track { + background-color: $track-color; + } + &::-webkit-scrollbar-thumb { + background-color: $handle-color; + border-radius: 8px; + border: 3px solid transparent; + background-clip: padding-box; + } + + // Firefox 109+ and Chrome 111+ + scrollbar-color: $handle-color $track-color; // Syntax, "dark", "light", or "#handle-color #track-color" + scrollbar-width: thin; + scrollbar-gutter: stable; + + &:hover, + &:focus, + & > * { + visibility: visible; + } +} diff --git a/packages/base-styles/_variables.scss b/packages/base-styles/_variables.scss index cc922ba5a035a2..c4b0edd50e61b8 100644 --- a/packages/base-styles/_variables.scss +++ b/packages/base-styles/_variables.scss @@ -57,15 +57,15 @@ $admin-sidebar-width-big: 190px; $admin-sidebar-width-collapsed: 36px; $modal-min-width: 360px; $spinner-size: 16px; +$canvas-padding: $grid-unit-30; /** * Shadows. */ -$shadow-popover: 0 2px 6px rgba($black, 0.05); -$shadow-modal: 0 10px 10px rgba($black, 0.25); - +$shadow-popover: 0 0.7px 1px rgba($black, 0.1), 0 1.2px 1.7px -0.2px rgba($black, 0.1), 0 2.3px 3.3px -0.5px rgba($black, 0.1); +$shadow-modal: 0 0.7px 1px rgba($black, 0.15), 0 2.7px 3.8px -0.2px rgba($black, 0.15), 0 5.5px 7.8px -0.3px rgba($black, 0.15), 0.1px 11.5px 16.4px -0.5px rgba($black, 0.15); /** * Editor widths. diff --git a/packages/base-styles/_z-index.scss b/packages/base-styles/_z-index.scss index 14305a93611977..5c9af46fec6e6f 100644 --- a/packages/base-styles/_z-index.scss +++ b/packages/base-styles/_z-index.scss @@ -7,15 +7,13 @@ $z-layers: ( ".block-editor-block-switcher__arrow": 1, ".block-editor-block-list__block {core/image aligned wide or fullwide}": 20, ".block-library-classic__toolbar": 31, // When scrolled to top this toolbar needs to sit over block-editor-block-toolbar - ".block-editor-block-list__layout .reusable-block-indicator": 1, ".block-editor-block-list__block-selection-button": 22, ".components-form-toggle__input": 1, ".edit-post-text-editor__toolbar": 1, ".edit-site-code-editor__toolbar": 1, - ".edit-post-sidebar__panel-tab.is-active": 1, // These next three share a stacking context - ".block-library-template-part__selection-search": 1, // higher sticky element + ".block-library-template-part__selection-search": 2, // higher sticky element // These next two share a stacking context ".interface-complementary-area .components-panel" : 0, // lower scrolling content @@ -109,14 +107,6 @@ $z-layers: ( // Show interface skeleton footer above interface skeleton drawer ".interface-interface-skeleton__footer": 90, - // Show the navigation toggle above the skeleton header - ".edit-site-navigation-toggle": 31, - // Show the navigation link above the skeleton header - ".edit-site-navigation-link": 31, - - // Show the FSE template previews above the editor and any open block toolbars - ".edit-site-navigation-panel__preview": 32, - // Above the block list and the header. ".block-editor-block-popover": 31, @@ -152,10 +142,10 @@ $z-layers: ( ".skip-to-selected-block": 100000, ".interface-interface-skeleton__actions": 100000, - // Show NUX tips above popovers, wp-admin menus, submenus, and sidebar: - ".nux-dot-tip": 1000001, + // The focus styles of the region navigation containers should be above their content. + ".is-focusing-regions {region} :focus::after": 1000000, - // Show tooltips above NUX tips, wp-admin menus, submenus, and sidebar: + // Show tooltips above wp-admin menus, submenus, and sidebar: ".components-tooltip": 1000002, // Keep template popover underneath 'Create custom template' modal overlay. @@ -187,8 +177,6 @@ $z-layers: ( // Appear under the topbar. ".customize-widgets__block-toolbar": 7, - - ".is-focusing-regions [role='region']:focus .interface-navigable-region__stacker": -1, ); @function z-index( $key ) { diff --git a/packages/block-directory/CHANGELOG.md b/packages/block-directory/CHANGELOG.md index d65f7d56149a6f..f7f7566370060c 100644 --- a/packages/block-directory/CHANGELOG.md +++ b/packages/block-directory/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### Breaking Changes + +- Updated dependencies to require React 18 ([45235](https://github.com/WordPress/gutenberg/pull/45235)) + ## 3.20.0 (2022-11-16) ## 3.19.0 (2022-11-02) diff --git a/packages/block-directory/package.json b/packages/block-directory/package.json index 1083e6281f7aba..0055a4846d7b6f 100644 --- a/packages/block-directory/package.json +++ b/packages/block-directory/package.json @@ -47,8 +47,8 @@ "change-case": "^4.1.2" }, "peerDependencies": { - "react": "^17.0.0", - "react-dom": "^17.0.0" + "react": "^18.0.0", + "react-dom": "^18.0.0" }, "publishConfig": { "access": "public" diff --git a/packages/block-directory/src/plugins/installed-blocks-pre-publish-panel/index.js b/packages/block-directory/src/plugins/installed-blocks-pre-publish-panel/index.js index a51b68fcaffef2..f0d46b7d1e1dec 100644 --- a/packages/block-directory/src/plugins/installed-blocks-pre-publish-panel/index.js +++ b/packages/block-directory/src/plugins/installed-blocks-pre-publish-panel/index.js @@ -2,7 +2,6 @@ * WordPress dependencies */ import { _n, sprintf } from '@wordpress/i18n'; -import { PluginPrePublishPanel } from '@wordpress/edit-post'; import { useSelect } from '@wordpress/data'; import { blockDefault } from '@wordpress/icons'; @@ -12,6 +11,10 @@ import { blockDefault } from '@wordpress/icons'; import CompactList from '../../components/compact-list'; import { store as blockDirectoryStore } from '../../store'; +// We shouldn't import the edit-post package directly +// because it would include the wp-edit-post in all pages loading the block-directory script. +const { PluginPrePublishPanel } = window?.wp?.editPost ?? {}; + export default function InstalledBlocksPrePublishPanel() { const newBlockTypes = useSelect( ( select ) => select( blockDirectoryStore ).getNewBlockTypes(), diff --git a/packages/block-editor/CHANGELOG.md b/packages/block-editor/CHANGELOG.md index 4d37bf5737599c..34cd726c55fdf5 100644 --- a/packages/block-editor/CHANGELOG.md +++ b/packages/block-editor/CHANGELOG.md @@ -2,6 +2,19 @@ ## Unreleased +### Breaking Changes + +- Updated dependencies to require React 18 ([45235](https://github.com/WordPress/gutenberg/pull/45235)) + +### Enhancements + +- `URLInput`: the `renderSuggestions` callback prop now receives `currentInputValue` as a new parameter ([45806](https://github.com/WordPress/gutenberg/pull/45806)). +- Fluid typography: add configurable fluid typography settings for minimum font size to theme.json ([#42489](https://github.com/WordPress/gutenberg/pull/42489)). + +### Bug Fix + +- `SpacingSizesControl`: Change ARIA role from `region` to `group` to avoid unwanted ARIA landmark regions ([#46530](https://github.com/WordPress/gutenberg/pull/46530)). + ## 10.5.0 (2022-11-16) ### Enhancement diff --git a/packages/block-editor/README.md b/packages/block-editor/README.md index 5760f1584c5db8..5a861eaa1acad7 100644 --- a/packages/block-editor/README.md +++ b/packages/block-editor/README.md @@ -428,6 +428,7 @@ _Parameters_ - _args.minimumFontSize_ `?string`: Minimum font size for any clamp() calculation. Optional. - _args.scaleFactor_ `?number`: A scale factor to determine how fast a font scales within boundaries. Optional. - _args.minimumFontSizeFactor_ `?number`: How much to scale defaultFontSize by to derive minimumFontSize. Optional. +- _args.minimumFontSizeLimit_ `?string`: The smallest a calculated font size may be. Optional. _Returns_ @@ -520,7 +521,7 @@ attributes. _Parameters_ - _attributes_ `Object`: Block attributes. -- _isFluidFontSizeActive_ `boolean`: Whether the function should try to convert font sizes to fluid values. +- _fluidTypographySettings_ `Object|boolean`: If boolean, whether the function should try to convert font sizes to fluid values, otherwise an object containing theme fluid typography settings. _Returns_ diff --git a/packages/block-editor/package.json b/packages/block-editor/package.json index e90f7aaf4619f4..dce5352d5812a6 100644 --- a/packages/block-editor/package.json +++ b/packages/block-editor/package.json @@ -44,6 +44,7 @@ "@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", @@ -64,6 +65,7 @@ "colord": "^2.7.0", "diff": "^4.0.2", "dom-scroll-into-view": "^1.2.1", + "fast-deep-equal": "^3.1.3", "inherits": "^2.0.3", "lodash": "^4.17.21", "react-autosize-textarea": "^7.1.0", @@ -73,8 +75,8 @@ "traverse": "^0.6.6" }, "peerDependencies": { - "react": "^17.0.0", - "react-dom": "^17.0.0" + "react": "^18.0.0", + "react-dom": "^18.0.0" }, "publishConfig": { "access": "public" diff --git a/packages/block-editor/src/autocompleters/block.js b/packages/block-editor/src/autocompleters/block.js index e5bcd2e78dbbf0..9c1747fe867b62 100644 --- a/packages/block-editor/src/autocompleters/block.js +++ b/packages/block-editor/src/autocompleters/block.js @@ -1,8 +1,3 @@ -/** - * External dependencies - */ -import { orderBy } from 'lodash'; - /** * WordPress dependencies */ @@ -20,6 +15,7 @@ import { searchBlockItems } from '../components/inserter/search-items'; import useBlockTypesState from '../components/inserter/hooks/use-block-types-state'; import BlockIcon from '../components/block-icon'; import { store as blockEditorStore } from '../store'; +import { orderBy } from '../utils/sorting'; const noop = () => {}; const SHOWN_BLOCK_TYPES = 9; @@ -68,7 +64,7 @@ function createBlockCompleter() { collections, filterValue ) - : orderBy( items, [ 'frecency' ], [ 'desc' ] ); + : orderBy( items, 'frecency', 'desc' ); return initialFilteredItems .filter( ( item ) => item.name !== selectedBlockName ) diff --git a/packages/block-editor/src/autocompleters/link.js b/packages/block-editor/src/autocompleters/link.js index d439c5e6587aa2..ce9af28f19d000 100644 --- a/packages/block-editor/src/autocompleters/link.js +++ b/packages/block-editor/src/autocompleters/link.js @@ -1,6 +1,8 @@ /** * WordPress dependencies */ +// Disable Reason: Needs to be refactored. +// eslint-disable-next-line no-restricted-imports import apiFetch from '@wordpress/api-fetch'; import { addQueryArgs } from '@wordpress/url'; import { Icon, page, post } from '@wordpress/icons'; diff --git a/packages/block-editor/src/components/alignment-control/test/index.js b/packages/block-editor/src/components/alignment-control/test/index.js index 14f0d9a04e033c..c792b3e9b7f82b 100644 --- a/packages/block-editor/src/components/alignment-control/test/index.js +++ b/packages/block-editor/src/components/alignment-control/test/index.js @@ -52,7 +52,7 @@ describe( 'AlignmentUI', () => { advanceTimers: jest.advanceTimersByTime, } ); - render( + const { unmount } = render( { name: /^Align text \w+$/, } ) ).toHaveLength( 3 ); + + // Cancel running effects, like delayed dropdown menu popover positioning. + unmount(); } ); test( 'should call on change with undefined when a control is already active', async () => { diff --git a/packages/block-editor/src/components/alignment-control/ui.js b/packages/block-editor/src/components/alignment-control/ui.js index f50e69044f9e59..171a8d34c45f5c 100644 --- a/packages/block-editor/src/components/alignment-control/ui.js +++ b/packages/block-editor/src/components/alignment-control/ui.js @@ -1,8 +1,3 @@ -/** - * External dependencies - */ -import { find } from 'lodash'; - /** * WordPress dependencies */ @@ -46,8 +41,7 @@ function AlignmentUI( { return () => onChange( value === align ? undefined : align ); } - const activeAlignment = find( - alignmentControls, + const activeAlignment = alignmentControls.find( ( control ) => control.align === value ); diff --git a/packages/block-editor/src/components/block-alignment-control/test/index.js b/packages/block-editor/src/components/block-alignment-control/test/index.js index fd9f0f782e25d2..abf2a0a7c448ae 100644 --- a/packages/block-editor/src/components/block-alignment-control/test/index.js +++ b/packages/block-editor/src/components/block-alignment-control/test/index.js @@ -47,7 +47,7 @@ describe( 'BlockAlignmentUI', () => { advanceTimers: jest.advanceTimersByTime, } ); - render( + const { unmount } = render( { name: /^Align \w+$/, } ) ).toHaveLength( 3 ); + + // Cancel running effects, like delayed dropdown menu popover positioning. + unmount(); } ); test( 'should call onChange with undefined, when the control is already active', async () => { diff --git a/packages/block-editor/src/components/block-card/index.js b/packages/block-editor/src/components/block-card/index.js index 6362b1b4ca2c7c..dab832861ae661 100644 --- a/packages/block-editor/src/components/block-card/index.js +++ b/packages/block-editor/src/components/block-card/index.js @@ -1,3 +1,8 @@ +/** + * External dependencies + */ +import classnames from 'classnames'; + /** * WordPress dependencies */ @@ -5,19 +10,15 @@ import deprecated from '@wordpress/deprecated'; import { Button } from '@wordpress/components'; import { chevronLeft, chevronRight } from '@wordpress/icons'; import { __, isRTL } from '@wordpress/i18n'; +import { useSelect, useDispatch } from '@wordpress/data'; + /** * Internal dependencies */ import BlockIcon from '../block-icon'; +import { store as blockEditorStore } from '../../store'; -function BlockCard( { - title, - icon, - description, - blockType, - parentBlockClientId, - handleBackButton, -} ) { +function BlockCard( { title, icon, description, blockType, className } ) { if ( blockType ) { deprecated( '`blockType` property in `BlockCard component`', { since: '5.7', @@ -29,12 +30,29 @@ function BlockCard( { const isOffCanvasNavigationEditorEnabled = window?.__experimentalEnableOffCanvasNavigationEditor === true; + const { parentNavBlockClientId } = useSelect( ( select ) => { + const { getSelectedBlockClientId, getBlockParentsByBlockName } = + select( blockEditorStore ); + + const _selectedBlockClientId = getSelectedBlockClientId(); + + return { + parentNavBlockClientId: getBlockParentsByBlockName( + _selectedBlockClientId, + 'core/navigation', + true + )[ 0 ], + }; + }, [] ); + + const { selectBlock } = useDispatch( blockEditorStore ); + return ( -
- { isOffCanvasNavigationEditorEnabled && parentBlockClientId && ( +
+ { isOffCanvasNavigationEditorEnabled && parentNavBlockClientId && ( ); - const button = screen.getByRole( 'button' ); - fireEvent.mouseDown( button.parentElement ); + + fireEvent.mouseDown( screen.getByTestId( 'selection-clearer' ) ); expect( mockClearSelectedBlock ).toBeCalled(); } ); @@ -64,12 +64,12 @@ describe( 'BlockSelectionClearer component', () => { } ) ); render( - + ); - const button = screen.getByRole( 'button' ); - fireEvent.mouseDown( button.parentElement ); + + fireEvent.mouseDown( screen.getByTestId( 'selection-clearer' ) ); expect( mockClearSelectedBlock ).toBeCalled(); } ); @@ -82,12 +82,12 @@ describe( 'BlockSelectionClearer component', () => { } ) ); render( - + ); - const button = screen.getByRole( 'button' ); - fireEvent.mouseDown( button.parentElement ); + + fireEvent.mouseDown( screen.getByTestId( 'selection-clearer' ) ); expect( mockClearSelectedBlock ).not.toBeCalled(); } ); @@ -106,12 +106,12 @@ describe( 'BlockSelectionClearer component', () => { } ) ); render( - + ); - const button = screen.getByRole( 'button' ); - fireEvent.mouseDown( button.parentElement ); + + fireEvent.mouseDown( screen.getByTestId( 'selection-clearer' ) ); expect( mockClearSelectedBlock ).not.toBeCalled(); } ); diff --git a/packages/block-editor/src/components/block-styles/index.native.js b/packages/block-editor/src/components/block-styles/index.native.js index 142deafacfa7f1..7967e79c2c1cd8 100644 --- a/packages/block-editor/src/components/block-styles/index.native.js +++ b/packages/block-editor/src/components/block-styles/index.native.js @@ -2,7 +2,6 @@ * External dependencies */ import { ScrollView } from 'react-native'; -import { find } from 'lodash'; /** * WordPress dependencies @@ -35,7 +34,7 @@ function BlockStyles( { clientId, url } ) { const { updateBlockAttributes } = useDispatch( blockEditorStore ); - const renderedStyles = find( styles, 'isDefault' ) + const renderedStyles = styles?.find( ( style ) => style.isDefault ) ? styles : [ { diff --git a/packages/block-editor/src/components/block-styles/utils.js b/packages/block-editor/src/components/block-styles/utils.js index f60a23a4287b21..511e78da83da60 100644 --- a/packages/block-editor/src/components/block-styles/utils.js +++ b/packages/block-editor/src/components/block-styles/utils.js @@ -1,7 +1,3 @@ -/** - * External dependencies - */ -import { find } from 'lodash'; /** * WordPress dependencies */ @@ -23,13 +19,15 @@ export function getActiveStyle( styles, className ) { } const potentialStyleName = style.substring( 9 ); - const activeStyle = find( styles, { name: potentialStyleName } ); + const activeStyle = styles?.find( + ( { name } ) => name === potentialStyleName + ); if ( activeStyle ) { return activeStyle; } } - return find( styles, 'isDefault' ); + return getDefaultStyle( styles ); } /** @@ -88,5 +86,5 @@ export function getRenderedStyles( styles ) { * @return {Object?} The default style object, if found. */ export function getDefaultStyle( styles ) { - return find( styles, 'isDefault' ); + return styles?.find( ( style ) => style.isDefault ); } diff --git a/packages/block-editor/src/components/block-switcher/test/index.js b/packages/block-editor/src/components/block-switcher/test/index.js index 9d788f9d6eb14d..8c1983dd8569d9 100644 --- a/packages/block-editor/src/components/block-switcher/test/index.js +++ b/packages/block-editor/src/components/block-switcher/test/index.js @@ -15,6 +15,7 @@ import { copy } from '@wordpress/icons'; * Internal dependencies */ import { BlockSwitcher, BlockSwitcherDropdownMenu } from '../'; +import { act } from 'react-test-renderer'; jest.mock( '@wordpress/data/src/components/use-select', () => jest.fn() ); jest.mock( '../../block-title/use-block-display-title', () => @@ -210,6 +211,7 @@ describe( 'BlockSwitcherDropdownMenu', () => { } ), '[ArrowDown]' ); + await act( () => Promise.resolve() ); expect( screen.getByRole( 'button', { @@ -252,6 +254,7 @@ describe( 'BlockSwitcherDropdownMenu', () => { expanded: false, } ) ); + await act( () => Promise.resolve() ); expect( screen.getByRole( 'button', { @@ -282,6 +285,7 @@ describe( 'BlockSwitcherDropdownMenu', () => { expanded: false, } ) ); + await act( () => Promise.resolve() ); expect( within( diff --git a/packages/block-editor/src/components/block-toolbar/index.js b/packages/block-editor/src/components/block-toolbar/index.js index b2160b3db4b6b6..6727851e1647bf 100644 --- a/packages/block-editor/src/components/block-toolbar/index.js +++ b/packages/block-editor/src/components/block-toolbar/index.js @@ -9,7 +9,12 @@ import classnames from 'classnames'; import { useSelect, useDispatch } from '@wordpress/data'; import { useRef } from '@wordpress/element'; import { useViewportMatch } from '@wordpress/compose'; -import { getBlockType, hasBlockSupport } from '@wordpress/blocks'; +import { + getBlockType, + hasBlockSupport, + isReusableBlock, + isTemplatePart, +} from '@wordpress/blocks'; import { ToolbarGroup } from '@wordpress/components'; /** @@ -109,11 +114,13 @@ const BlockToolbar = ( { hideDragHandle } ) => { const shouldShowVisualToolbar = isValid && isVisual; const isMultiToolbar = blockClientIds.length > 1; + const isSynced = + isReusableBlock( blockType ) || isTemplatePart( blockType ); - const classes = classnames( - 'block-editor-block-toolbar', - shouldShowMovers && 'is-showing-movers' - ); + const classes = classnames( 'block-editor-block-toolbar', { + 'is-showing-movers': shouldShowMovers, + 'is-synced': isSynced, + } ); return (
diff --git a/packages/block-editor/src/components/block-toolbar/style.scss b/packages/block-editor/src/components/block-toolbar/style.scss index 00d8d0da47f34e..41faa1a46eaed2 100644 --- a/packages/block-editor/src/components/block-toolbar/style.scss +++ b/packages/block-editor/src/components/block-toolbar/style.scss @@ -39,6 +39,16 @@ border-right: $border-width solid $gray-300; } + &.is-synced .block-editor-block-switcher .components-button .block-editor-block-icon { + color: var(--wp-block-synced-color); + } + + &.is-synced .components-toolbar-button.block-editor-block-switcher__no-switcher-icon { + &:disabled .block-editor-block-icon.has-colors { + color: var(--wp-block-synced-color); + } + } + > :last-child, > :last-child .components-toolbar-group, > :last-child .components-toolbar { diff --git a/packages/block-editor/src/components/block-tools/insertion-point.js b/packages/block-editor/src/components/block-tools/insertion-point.js index b7cd30462e3594..2008cbc6b03ee6 100644 --- a/packages/block-editor/src/components/block-tools/insertion-point.js +++ b/packages/block-editor/src/components/block-tools/insertion-point.js @@ -79,7 +79,6 @@ function InbetweenInsertionPointPopover( { isInserterShown: insertionPoint?.__unstableWithInserter, }; }, [] ); - const isVertical = orientation === 'vertical'; const disableMotion = useReducedMotion(); @@ -105,65 +104,22 @@ function InbetweenInsertionPointPopover( { } } - // Define animation variants for the line element. - const horizontalLine = { - start: { - width: 0, - top: '50%', - bottom: '50%', - x: 0, - }, - rest: { - width: 4, - top: 0, - bottom: 0, - x: -2, - }, - hover: { - width: 4, - top: 0, - bottom: 0, - x: -2, - }, - }; - const verticalLine = { - start: { - height: 0, - left: '50%', - right: '50%', - y: 0, - }, - rest: { - height: 4, - left: 0, - right: 0, - y: -2, - }, - hover: { - height: 4, - left: 0, - right: 0, - y: -2, - }, - }; const lineVariants = { // Initial position starts from the center and invisible. start: { - ...( ! isVertical ? horizontalLine.start : verticalLine.start ), opacity: 0, + scale: 0, }, // The line expands to fill the container. If the inserter is visible it // is delayed so it appears orchestrated. rest: { - ...( ! isVertical ? horizontalLine.rest : verticalLine.rest ), opacity: 1, - borderRadius: '2px', + scale: 1, transition: { delay: isInserterShown ? 0.5 : 0, type: 'tween' }, }, hover: { - ...( ! isVertical ? horizontalLine.hover : verticalLine.hover ), opacity: 1, - borderRadius: '2px', + scale: 1, transition: { delay: 0.5, type: 'tween' }, }, }; diff --git a/packages/block-editor/src/components/block-tools/selected-block-popover.js b/packages/block-editor/src/components/block-tools/selected-block-popover.js index 343cd50058655a..294813b1f34a15 100644 --- a/packages/block-editor/src/components/block-tools/selected-block-popover.js +++ b/packages/block-editor/src/components/block-tools/selected-block-popover.js @@ -1,7 +1,6 @@ /** * External dependencies */ -import { find } from 'lodash'; import classnames from 'classnames'; /** @@ -240,8 +239,7 @@ function wrapperSelector( select ) { ); // Get the clientId of the topmost parent with the capture toolbars setting. - const capturingClientId = find( - blockParentsClientIds, + const capturingClientId = blockParentsClientIds.find( ( parentClientId ) => parentBlockListSettings[ parentClientId ] ?.__experimentalCaptureToolbars diff --git a/packages/block-editor/src/components/block-tools/style.scss b/packages/block-editor/src/components/block-tools/style.scss index e301859c165491..4bb2e07fcabb0f 100644 --- a/packages/block-editor/src/components/block-tools/style.scss +++ b/packages/block-editor/src/components/block-tools/style.scss @@ -13,17 +13,22 @@ .block-editor-block-list__insertion-point-indicator { position: absolute; background: var(--wp-admin-theme-color); + border-radius: 2px; + transform-origin: center; + opacity: 0; + will-change: transform, opacity; .block-editor-block-list__insertion-point.is-vertical > & { - top: 50%; - height: $border-width; + top: calc(50% - 2px); + height: 4px; + width: 100%; } .block-editor-block-list__insertion-point.is-horizontal > & { top: 0; - right: 0; - left: 50%; - width: $border-width; + bottom: 0; + left: calc(50% - 2px); + width: 4px; } } @@ -32,6 +37,8 @@ // Don't show on mobile. display: none; position: absolute; + will-change: transform; + @include break-mobile() { display: flex; } diff --git a/packages/block-editor/src/components/block-variation-picker/index.js b/packages/block-editor/src/components/block-variation-picker/index.js index 24bdfa272c34b2..27e214ec39bb07 100644 --- a/packages/block-editor/src/components/block-variation-picker/index.js +++ b/packages/block-editor/src/components/block-variation-picker/index.js @@ -49,10 +49,7 @@ function BlockVariationPicker( { className="block-editor-block-variation-picker__variation" label={ variation.description || variation.title } /> - + { variation.title } diff --git a/packages/block-editor/src/components/block-vertical-alignment-control/test/index.js b/packages/block-editor/src/components/block-vertical-alignment-control/test/index.js index 2e54ee90268549..f2645357a197c3 100644 --- a/packages/block-editor/src/components/block-vertical-alignment-control/test/index.js +++ b/packages/block-editor/src/components/block-vertical-alignment-control/test/index.js @@ -45,7 +45,7 @@ describe( 'BlockVerticalAlignmentUI', () => { advanceTimers: jest.advanceTimersByTime, } ); - render( + const { unmount } = render( { name: /^Align \w+$/, } ) ).toHaveLength( 3 ); + + // Cancel running effects, like delayed dropdown menu popover positioning. + unmount(); } ); it( 'should call onChange with undefined, when the control is already active', async () => { diff --git a/packages/block-editor/src/components/colors/utils.js b/packages/block-editor/src/components/colors/utils.js index e22b5ed8661d36..e48b599faa6b96 100644 --- a/packages/block-editor/src/components/colors/utils.js +++ b/packages/block-editor/src/components/colors/utils.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { find, kebabCase } from 'lodash'; +import { kebabCase } from 'lodash'; import { colord, extend } from 'colord'; import namesPlugin from 'colord/plugins/names'; import a11yPlugin from 'colord/plugins/a11y'; @@ -26,7 +26,9 @@ export const getColorObjectByAttributeValues = ( customColor ) => { if ( definedColor ) { - const colorObj = find( colors, { slug: definedColor } ); + const colorObj = colors?.find( + ( color ) => color.slug === definedColor + ); if ( colorObj ) { return colorObj; @@ -47,7 +49,7 @@ export const getColorObjectByAttributeValues = ( * Returns undefined if no color object matches this requirement. */ export const getColorObjectByColorValue = ( colors, colorValue ) => { - return find( colors, { color: colorValue } ); + return colors?.find( ( color ) => color.color === colorValue ); }; /** diff --git a/packages/block-editor/src/components/copy-handler/index.js b/packages/block-editor/src/components/copy-handler/index.js index 3e5a50a8fe49ea..3881ff06f2bf30 100644 --- a/packages/block-editor/src/components/copy-handler/index.js +++ b/packages/block-editor/src/components/copy-handler/index.js @@ -7,6 +7,8 @@ import { pasteHandler, store as blocksStore, createBlock, + findTransform, + getBlockTransforms, } from '@wordpress/blocks'; import { documentHasSelection, @@ -84,6 +86,7 @@ export function useClipboardHandler() { __unstableIsSelectionCollapsed, __unstableIsSelectionMergeable, __unstableGetSelectedBlocksWithPartialSelection, + canInsertBlockType, } = useSelect( blockEditorStore ); const { flashBlock, @@ -91,6 +94,7 @@ export function useClipboardHandler() { replaceBlocks, __unstableDeleteSelection, __unstableExpandSelection, + insertBlocks, } = useDispatch( blockEditorStore ); const notifyCopy = useNotifyCopy(); @@ -201,13 +205,55 @@ export function useClipboardHandler() { __experimentalCanUserUseUnfilteredHTML: canUserUseUnfilteredHTML, } = getSettings(); - const { plainText, html } = getPasteEventData( event ); - const blocks = pasteHandler( { - HTML: html, - plainText, - mode: 'BLOCKS', - canUserUseUnfilteredHTML, - } ); + const { plainText, html, files } = getPasteEventData( event ); + let blocks = []; + + if ( files.length ) { + const fromTransforms = getBlockTransforms( 'from' ); + blocks = files + .reduce( ( accumulator, file ) => { + const transformation = findTransform( + fromTransforms, + ( transform ) => + transform.type === 'files' && + transform.isMatch( [ file ] ) + ); + if ( transformation ) { + accumulator.push( + transformation.transform( [ file ] ) + ); + } + return accumulator; + }, [] ) + .flat(); + } else { + blocks = pasteHandler( { + HTML: html, + plainText, + mode: 'BLOCKS', + canUserUseUnfilteredHTML, + } ); + } + + if ( selectedBlockClientIds.length === 1 ) { + const [ selectedBlockClientId ] = selectedBlockClientIds; + + if ( + blocks.every( ( block ) => + canInsertBlockType( + block.name, + selectedBlockClientId + ) + ) + ) { + insertBlocks( + blocks, + undefined, + selectedBlockClientId + ); + return; + } + } replaceBlocks( selectedBlockClientIds, diff --git a/packages/block-editor/src/components/default-block-appender/style.scss b/packages/block-editor/src/components/default-block-appender/content.scss similarity index 100% rename from packages/block-editor/src/components/default-block-appender/style.scss rename to packages/block-editor/src/components/default-block-appender/content.scss diff --git a/packages/block-editor/src/components/font-sizes/fluid-utils.js b/packages/block-editor/src/components/font-sizes/fluid-utils.js index de8a27e3014e88..f5816e9823d7a3 100644 --- a/packages/block-editor/src/components/font-sizes/fluid-utils.js +++ b/packages/block-editor/src/components/font-sizes/fluid-utils.js @@ -40,6 +40,7 @@ const DEFAULT_MINIMUM_FONT_SIZE_LIMIT = '14px'; * @param {?string} args.minimumFontSize Minimum font size for any clamp() calculation. Optional. * @param {?number} args.scaleFactor A scale factor to determine how fast a font scales within boundaries. Optional. * @param {?number} args.minimumFontSizeFactor How much to scale defaultFontSize by to derive minimumFontSize. Optional. + * @param {?string} args.minimumFontSizeLimit The smallest a calculated font size may be. Optional. * * @return {string|null} A font-size value using clamp(). */ @@ -51,8 +52,13 @@ export function getComputedFluidTypographyValue( { maximumViewPortWidth = DEFAULT_MAXIMUM_VIEWPORT_WIDTH, scaleFactor = DEFAULT_SCALE_FACTOR, minimumFontSizeFactor = DEFAULT_MINIMUM_FONT_SIZE_FACTOR, - minimumFontSizeLimit = DEFAULT_MINIMUM_FONT_SIZE_LIMIT, + minimumFontSizeLimit, } ) { + // Validate incoming settings and set defaults. + minimumFontSizeLimit = !! getTypographyValueAndUnit( minimumFontSizeLimit ) + ? minimumFontSizeLimit + : DEFAULT_MINIMUM_FONT_SIZE_LIMIT; + /* * Calculates missing minimumFontSize and maximumFontSize from * defaultFontSize if provided. diff --git a/packages/block-editor/src/components/font-sizes/utils.js b/packages/block-editor/src/components/font-sizes/utils.js index 1237a13efd7db6..0948ab918f156c 100644 --- a/packages/block-editor/src/components/font-sizes/utils.js +++ b/packages/block-editor/src/components/font-sizes/utils.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { find, kebabCase } from 'lodash'; +import { kebabCase } from 'lodash'; /** * Returns the font size object based on an array of named font sizes and the namedFontSize and customFontSize values. @@ -20,7 +20,9 @@ export const getFontSize = ( customFontSizeAttribute ) => { if ( fontSizeAttribute ) { - const fontSizeObject = find( fontSizes, { slug: fontSizeAttribute } ); + const fontSizeObject = fontSizes?.find( + ( { slug } ) => slug === fontSizeAttribute + ); if ( fontSizeObject ) { return fontSizeObject; } @@ -39,7 +41,7 @@ export const getFontSize = ( * @return {Object} Font size object. */ export function getFontSizeObjectByValue( fontSizes, value ) { - const fontSizeObject = find( fontSizes, { size: value } ); + const fontSizeObject = fontSizes?.find( ( { size } ) => size === value ); if ( fontSizeObject ) { return fontSizeObject; } diff --git a/packages/block-editor/src/components/font-sizes/with-font-sizes.js b/packages/block-editor/src/components/font-sizes/with-font-sizes.js index e328a0b2740385..380ea5a3dc0c5c 100644 --- a/packages/block-editor/src/components/font-sizes/with-font-sizes.js +++ b/packages/block-editor/src/components/font-sizes/with-font-sizes.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { find, pickBy } from 'lodash'; +import { pickBy } from 'lodash'; /** * WordPress dependencies @@ -107,9 +107,9 @@ export default ( ...fontSizeNames ) => { customFontSizeAttributeName ) { return ( fontSizeValue ) => { - const fontSizeObject = find( this.props.fontSizes, { - size: Number( fontSizeValue ), - } ); + const fontSizeObject = this.props.fontSizes?.find( + ( { size } ) => size === Number( fontSizeValue ) + ); this.props.setAttributes( { [ fontSizeAttributeName ]: fontSizeObject && fontSizeObject.slug diff --git a/packages/block-editor/src/components/gradients/use-gradient.js b/packages/block-editor/src/components/gradients/use-gradient.js index 4acf09fc03b522..e899f9c9197708 100644 --- a/packages/block-editor/src/components/gradients/use-gradient.js +++ b/packages/block-editor/src/components/gradients/use-gradient.js @@ -1,8 +1,3 @@ -/** - * External dependencies - */ -import { find } from 'lodash'; - /** * WordPress dependencies */ @@ -32,7 +27,7 @@ export function __experimentalGetGradientClass( gradientSlug ) { * @return {string} Gradient value. */ export function getGradientValueBySlug( gradients, slug ) { - const gradient = find( gradients, [ 'slug', slug ] ); + const gradient = gradients?.find( ( g ) => g.slug === slug ); return gradient && gradient.gradient; } @@ -40,7 +35,7 @@ export function __experimentalGetGradientObjectByGradientValue( gradients, value ) { - const gradient = find( gradients, [ 'gradient', value ] ); + const gradient = gradients?.find( ( g ) => g.gradient === value ); return gradient; } diff --git a/packages/block-editor/src/components/height-control/index.js b/packages/block-editor/src/components/height-control/index.js new file mode 100644 index 00000000000000..cc1eea0b4bad86 --- /dev/null +++ b/packages/block-editor/src/components/height-control/index.js @@ -0,0 +1,123 @@ +/** + * WordPress dependencies + */ +import { useMemo } from '@wordpress/element'; +import { + BaseControl, + RangeControl, + Flex, + FlexItem, + __experimentalSpacer as Spacer, + __experimentalUseCustomUnits as useCustomUnits, + __experimentalUnitControl as UnitControl, + __experimentalParseQuantityAndUnitFromRawValue as parseQuantityAndUnitFromRawValue, +} from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import useSetting from '../use-setting'; + +const RANGE_CONTROL_CUSTOM_SETTINGS = { + px: { max: 1000, step: 1 }, + '%': { max: 100, step: 1 }, + vw: { max: 100, step: 1 }, + vh: { max: 100, step: 1 }, + em: { max: 50, step: 0.1 }, + rem: { max: 50, step: 0.1 }, +}; + +export default function HeightControl( { + onChange, + label = __( 'Height' ), + value, +} ) { + const customRangeValue = parseFloat( value ); + + const units = useCustomUnits( { + availableUnits: useSetting( 'spacing.units' ) || [ + '%', + 'px', + 'em', + 'rem', + 'vh', + 'vw', + ], + } ); + + const selectedUnit = + useMemo( + () => parseQuantityAndUnitFromRawValue( value ), + [ value ] + )[ 1 ] || + units[ 0 ]?.value || + 'px'; + + const handleSliderChange = ( next ) => { + onChange( [ next, selectedUnit ].join( '' ) ); + }; + + const handleUnitChange = ( newUnit ) => { + // Attempt to smooth over differences between currentUnit and newUnit. + // This should slightly improve the experience of switching between unit types. + const [ currentValue, currentUnit ] = + parseQuantityAndUnitFromRawValue( value ); + + if ( [ 'em', 'rem' ].includes( newUnit ) && currentUnit === 'px' ) { + // Convert pixel value to an approximate of the new unit, assuming a root size of 16px. + onChange( ( currentValue / 16 ).toFixed( 2 ) + newUnit ); + } else if ( + [ 'em', 'rem' ].includes( currentUnit ) && + newUnit === 'px' + ) { + // Convert to pixel value assuming a root size of 16px. + onChange( Math.round( currentValue * 16 ) + newUnit ); + } else if ( + [ 'vh', 'vw', '%' ].includes( newUnit ) && + currentValue > 100 + ) { + // When converting to `vh`, `vw`, or `%` units, cap the new value at 100. + onChange( 100 + newUnit ); + } + }; + + return ( +
+ + { label } + + + + + + + + + + + +
+ ); +} diff --git a/packages/block-editor/src/components/height-control/stories/index.js b/packages/block-editor/src/components/height-control/stories/index.js new file mode 100644 index 00000000000000..f4b586a96b0e33 --- /dev/null +++ b/packages/block-editor/src/components/height-control/stories/index.js @@ -0,0 +1,21 @@ +/** + * WordPress dependencies + */ +import { useState } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import HeightControl from '../'; + +export default { + component: HeightControl, + title: 'BlockEditor/HeightControl', +}; + +const Template = ( props ) => { + const [ value, setValue ] = useState(); + return ; +}; + +export const Default = Template.bind( {} ); diff --git a/packages/block-editor/src/components/height-control/style.scss b/packages/block-editor/src/components/height-control/style.scss new file mode 100644 index 00000000000000..add0866835f767 --- /dev/null +++ b/packages/block-editor/src/components/height-control/style.scss @@ -0,0 +1,5 @@ +.block-editor-height-control { + border: 0; + margin: 0; + padding: 0; +} diff --git a/packages/block-editor/src/components/iframe/index.js b/packages/block-editor/src/components/iframe/index.js index 25e36037ebac5b..b7413b54a2e445 100644 --- a/packages/block-editor/src/components/iframe/index.js +++ b/packages/block-editor/src/components/iframe/index.js @@ -188,7 +188,17 @@ async function loadScript( head, { id, src } ) { } function Iframe( - { contentRef, children, head, tabIndex = 0, assets, isZoomedOut, ...props }, + { + contentRef, + children, + head, + tabIndex = 0, + assets, + scale = 1, + frameSize = 0, + readonly, + ...props + }, ref ) { const [ , forceRender ] = useReducer( () => ( {} ) ); @@ -322,7 +332,7 @@ function Iframe( { head } , element ) } + + + ); + } +); + addFilter( 'blocks.registerBlockType', 'core/layout/addAttribute', @@ -426,6 +481,11 @@ addFilter( 'core/editor/layout/with-layout-styles', withLayoutStyles ); +addFilter( + 'editor.BlockListBlock', + 'core/editor/layout/with-child-layout-styles', + withChildLayoutStyles +); addFilter( 'editor.BlockEdit', 'core/editor/layout/with-inspector-controls', diff --git a/packages/block-editor/src/hooks/min-height.js b/packages/block-editor/src/hooks/min-height.js index 3167edba8a8293..e123f0cee98b22 100644 --- a/packages/block-editor/src/hooks/min-height.js +++ b/packages/block-editor/src/hooks/min-height.js @@ -2,16 +2,13 @@ * WordPress dependencies */ import { getBlockSupport } from '@wordpress/blocks'; -import { - __experimentalUseCustomUnits as useCustomUnits, - __experimentalUnitControl as UnitControl, -} from '@wordpress/components'; import { __ } from '@wordpress/i18n'; /** * Internal dependencies */ import useSetting from '../components/use-setting'; +import HeightControl from '../components/height-control'; import { DIMENSIONS_SUPPORT_KEY } from './dimensions'; import { cleanEmptyObject } from './utils'; @@ -81,17 +78,6 @@ export function MinHeightEdit( props ) { setAttributes, } = props; - const units = useCustomUnits( { - availableUnits: useSetting( 'dimensions.units' ) || [ - '%', - 'px', - 'em', - 'rem', - 'vh', - 'vw', - ], - } ); - if ( useIsMinHeightDisabled( props ) ) { return null; } @@ -109,13 +95,10 @@ export function MinHeightEdit( props ) { }; return ( - ); } diff --git a/packages/block-editor/src/hooks/test/use-typography-props.js b/packages/block-editor/src/hooks/test/use-typography-props.js index 00557881467ca8..12336eb2c44afd 100644 --- a/packages/block-editor/src/hooks/test/use-typography-props.js +++ b/packages/block-editor/src/hooks/test/use-typography-props.js @@ -47,4 +47,30 @@ describe( 'getTypographyClassesAndStyles', () => { }, } ); } ); + + it( 'should return configured fluid font size styles', () => { + const attributes = { + fontFamily: 'tofu', + style: { + typography: { + textDecoration: 'underline', + fontSize: '2rem', + textTransform: 'uppercase', + }, + }, + }; + expect( + getTypographyClassesAndStyles( attributes, { + minFontSize: '1rem', + } ) + ).toEqual( { + className: 'has-tofu-font-family', + style: { + textDecoration: 'underline', + fontSize: + 'clamp(1.5rem, 1.5rem + ((1vw - 0.48rem) * 0.962), 2rem)', + textTransform: 'uppercase', + }, + } ); + } ); } ); diff --git a/packages/block-editor/src/hooks/use-typography-props.js b/packages/block-editor/src/hooks/use-typography-props.js index d70ae08aafc593..da5869ad9aec07 100644 --- a/packages/block-editor/src/hooks/use-typography-props.js +++ b/packages/block-editor/src/hooks/use-typography-props.js @@ -19,23 +19,31 @@ import { getComputedFluidTypographyValue } from '../components/font-sizes/fluid- * Provides the CSS class names and inline styles for a block's typography support * attributes. * - * @param {Object} attributes Block attributes. - * @param {boolean} isFluidFontSizeActive Whether the function should try to convert font sizes to fluid values. + * @param {Object} attributes Block attributes. + * @param {Object|boolean} fluidTypographySettings If boolean, whether the function should try to convert font sizes to fluid values, + * otherwise an object containing theme fluid typography settings. * * @return {Object} Typography block support derived CSS classes & styles. */ export function getTypographyClassesAndStyles( attributes, - isFluidFontSizeActive + fluidTypographySettings ) { let typographyStyles = attributes?.style?.typography || {}; - if ( isFluidFontSizeActive ) { + if ( + !! fluidTypographySettings && + ( true === fluidTypographySettings || + Object.keys( fluidTypographySettings ).length !== 0 ) + ) { + const newFontSize = + getComputedFluidTypographyValue( { + fontSize: attributes?.style?.typography?.fontSize, + minimumFontSizeLimit: fluidTypographySettings?.minFontSize, + } ) || attributes?.style?.typography?.fontSize; typographyStyles = { ...typographyStyles, - fontSize: getComputedFluidTypographyValue( { - fontSize: attributes?.style?.typography?.fontSize, - } ), + fontSize: newFontSize, }; } diff --git a/packages/block-editor/src/store/reducer.js b/packages/block-editor/src/store/reducer.js index 924b563f5710e2..9c99126f54ba14 100644 --- a/packages/block-editor/src/store/reducer.js +++ b/packages/block-editor/src/store/reducer.js @@ -1,7 +1,8 @@ /** * External dependencies */ -import { omit, mapValues, isEqual, isEmpty } from 'lodash'; +import fastDeepEqual from 'fast-deep-equal/es6'; +import { omit, isEmpty } from 'lodash'; /** * WordPress dependencies @@ -28,16 +29,18 @@ const identity = ( x ) => x; * @return {Object} Block order map object. */ function mapBlockOrder( blocks, rootClientId = '' ) { - const result = { [ rootClientId ]: [] }; - + const result = new Map(); + const current = []; + result.set( rootClientId, current ); blocks.forEach( ( block ) => { const { clientId, innerBlocks } = block; - - result[ rootClientId ].push( clientId ); - - Object.assign( result, mapBlockOrder( innerBlocks, clientId ) ); + current.push( clientId ); + mapBlockOrder( innerBlocks, clientId ).forEach( + ( order, subClientId ) => { + result.set( subClientId, order ); + } + ); } ); - return result; } @@ -51,15 +54,18 @@ function mapBlockOrder( blocks, rootClientId = '' ) { * @return {Object} Block order map object. */ function mapBlockParents( blocks, rootClientId = '' ) { - return blocks.reduce( - ( result, block ) => - Object.assign( - result, - { [ block.clientId ]: rootClientId }, - mapBlockParents( block.innerBlocks, block.clientId ) - ), - {} - ); + const result = []; + const stack = [ [ rootClientId, blocks ] ]; + while ( stack.length ) { + const [ parent, currentBlocks ] = stack.shift(); + currentBlocks.forEach( ( { innerBlocks, ...block } ) => { + result.push( [ block.clientId, parent ] ); + if ( innerBlocks?.length ) { + stack.push( [ block.clientId, innerBlocks ] ); + } + } ); + } + return result; } /** @@ -70,16 +76,28 @@ function mapBlockParents( blocks, rootClientId = '' ) { * @param {Array} blocks Blocks to flatten. * @param {Function} transform Transforming function to be applied to each block. * - * @return {Object} Flattened object. + * @return {Array} Flattened object. */ function flattenBlocks( blocks, transform = identity ) { - const result = {}; + const result = []; const stack = [ ...blocks ]; while ( stack.length ) { const { innerBlocks, ...block } = stack.shift(); stack.push( ...innerBlocks ); - result[ block.clientId ] = transform( block ); + result.push( [ block.clientId, transform( block ) ] ); + } + + return result; +} + +function getFlattenedClientIds( blocks ) { + const result = {}; + const stack = [ ...blocks ]; + while ( stack.length ) { + const { innerBlocks, ...block } = stack.shift(); + stack.push( ...innerBlocks ); + result[ block.clientId ] = true; } return result; @@ -92,7 +110,7 @@ function flattenBlocks( blocks, transform = identity ) { * * @param {Array} blocks Blocks to flatten. * - * @return {Object} Flattened block attributes object. + * @return {Array} Flattened block attributes object. */ function getFlattenedBlocksWithoutAttributes( blocks ) { return flattenBlocks( blocks, ( block ) => omit( block, 'attributes' ) ); @@ -105,29 +123,12 @@ function getFlattenedBlocksWithoutAttributes( blocks ) { * * @param {Array} blocks Blocks to flatten. * - * @return {Object} Flattened block attributes object. + * @return {Array} Flattened block attributes object. */ function getFlattenedBlockAttributes( blocks ) { return flattenBlocks( blocks, ( block ) => block.attributes ); } -/** - * Returns an object against which it is safe to perform mutating operations, - * given the original object and its current working copy. - * - * @param {Object} original Original object. - * @param {Object} working Working object. - * - * @return {Object} Mutation-safe object. - */ -function getMutateSafeObject( original, working ) { - if ( original === working ) { - return { ...original }; - } - - return working; -} - /** * Returns true if the two object arguments have the same keys, or false * otherwise. @@ -138,7 +139,7 @@ function getMutateSafeObject( original, working ) { * @return {boolean} Whether the two objects have the same keys. */ export function hasSameKeys( a, b ) { - return isEqual( Object.keys( a ), Object.keys( b ) ); + return fastDeepEqual( Object.keys( a ), Object.keys( b ) ); } /** @@ -156,13 +157,13 @@ export function isUpdatingSameBlockAttribute( action, lastAction ) { action.type === 'UPDATE_BLOCK_ATTRIBUTES' && lastAction !== undefined && lastAction.type === 'UPDATE_BLOCK_ATTRIBUTES' && - isEqual( action.clientIds, lastAction.clientIds ) && + fastDeepEqual( action.clientIds, lastAction.clientIds ) && hasSameKeys( action.attributes, lastAction.attributes ) ); } -function buildBlockTree( state, blocks ) { - const result = {}; +function updateBlockTreeForBlocks( state, blocks ) { + const treeToUpdate = state.tree; const stack = [ ...blocks ]; const flattenedBlocks = [ ...blocks ]; while ( stack.length ) { @@ -172,33 +173,34 @@ function buildBlockTree( state, blocks ) { } // Create objects before mutating them, that way it's always defined. for ( const block of flattenedBlocks ) { - result[ block.clientId ] = {}; + treeToUpdate.set( block.clientId, {} ); } for ( const block of flattenedBlocks ) { - result[ block.clientId ] = Object.assign( result[ block.clientId ], { - ...state.byClientId[ block.clientId ], - attributes: state.attributes[ block.clientId ], - innerBlocks: block.innerBlocks.map( - ( subBlock ) => result[ subBlock.clientId ] - ), - } ); + treeToUpdate.set( + block.clientId, + Object.assign( treeToUpdate.get( block.clientId ), { + ...state.byClientId.get( block.clientId ), + attributes: state.attributes.get( block.clientId ), + innerBlocks: block.innerBlocks.map( ( subBlock ) => + treeToUpdate.get( subBlock.clientId ) + ), + } ) + ); } - - return result; } function updateParentInnerBlocksInTree( state, - tree, updatedClientIds, updateChildrenOfUpdatedClientIds = false ) { + const treeToUpdate = state.tree; const uncontrolledParents = new Set( [] ); const controlledParents = new Set(); for ( const clientId of updatedClientIds ) { let current = updateChildrenOfUpdatedClientIds ? clientId - : state.parents[ clientId ]; + : state.parents.get( clientId ); do { if ( state.controlledInnerBlocks[ current ] ) { // Should stop on controlled blocks. @@ -208,7 +210,7 @@ function updateParentInnerBlocksInTree( } else { // Else continue traversing up through parents. uncontrolledParents.add( current ); - current = state.parents[ current ]; + current = state.parents.get( current ); } } while ( current !== undefined ); } @@ -216,27 +218,23 @@ function updateParentInnerBlocksInTree( // To make sure the order of assignments doesn't matter, // we first create empty objects and mutates the inner blocks later. for ( const clientId of uncontrolledParents ) { - tree[ clientId ] = { - ...tree[ clientId ], - }; + treeToUpdate.set( clientId, { ...treeToUpdate.get( clientId ) } ); } for ( const clientId of uncontrolledParents ) { - tree[ clientId ].innerBlocks = ( state.order[ clientId ] || [] ).map( - ( subClientId ) => tree[ subClientId ] - ); + treeToUpdate.get( clientId ).innerBlocks = ( + state.order.get( clientId ) || [] + ).map( ( subClientId ) => treeToUpdate.get( subClientId ) ); } // Controlled parent blocks, need a dedicated key for their inner blocks // to be used when doing getBlocks( controlledBlockClientId ). for ( const clientId of controlledParents ) { - tree[ 'controlled||' + clientId ] = { - innerBlocks: ( state.order[ clientId ] || [] ).map( - ( subClientId ) => tree[ subClientId ] + treeToUpdate.set( 'controlled||' + clientId, { + innerBlocks: ( state.order.get( clientId ) || [] ).map( + ( subClientId ) => treeToUpdate.get( subClientId ) ), - }; + } ); } - - return tree; } /** @@ -257,82 +255,70 @@ const withBlockTree = return state; } - newState.tree = state.tree ? state.tree : {}; + newState.tree = state.tree ? state.tree : new Map(); switch ( action.type ) { case 'RECEIVE_BLOCKS': case 'INSERT_BLOCKS': { - const subTree = buildBlockTree( newState, action.blocks ); - newState.tree = updateParentInnerBlocksInTree( + newState.tree = new Map( newState.tree ); + updateBlockTreeForBlocks( newState, action.blocks ); + updateParentInnerBlocksInTree( newState, - { - ...newState.tree, - ...subTree, - }, action.rootClientId ? [ action.rootClientId ] : [ '' ], true ); break; } case 'UPDATE_BLOCK': - newState.tree = updateParentInnerBlocksInTree( + newState.tree = new Map( newState.tree ); + newState.tree.set( action.clientId, { + ...newState.tree.get( action.clientId ), + ...newState.byClientId.get( action.clientId ), + attributes: newState.attributes.get( action.clientId ), + } ); + updateParentInnerBlocksInTree( newState, - { - ...newState.tree, - [ action.clientId ]: { - ...newState.tree[ action.clientId ], - ...newState.byClientId[ action.clientId ], - attributes: newState.attributes[ action.clientId ], - }, - }, [ action.clientId ], false ); break; case 'UPDATE_BLOCK_ATTRIBUTES': { - const newSubTree = action.clientIds.reduce( - ( result, clientId ) => { - result[ clientId ] = { - ...newState.tree[ clientId ], - attributes: newState.attributes[ clientId ], - }; - return result; - }, - {} - ); - newState.tree = updateParentInnerBlocksInTree( + newState.tree = new Map( newState.tree ); + action.clientIds.forEach( ( clientId ) => { + newState.tree.set( clientId, { + ...newState.tree.get( clientId ), + attributes: newState.attributes.get( clientId ), + } ); + } ); + updateParentInnerBlocksInTree( newState, - { - ...newState.tree, - ...newSubTree, - }, action.clientIds, false ); break; } case 'REPLACE_BLOCKS_AUGMENTED_WITH_CHILDREN': { - const subTree = buildBlockTree( newState, action.blocks ); - newState.tree = updateParentInnerBlocksInTree( - newState, - { - ...omit( - newState.tree, - action.replacedClientIds.concat( - // Controlled inner blocks are only removed - // if the block doesn't move to another position - // otherwise their content will be lost. - action.replacedClientIds - .filter( - ( clientId ) => ! subTree[ clientId ] - ) - .map( - ( clientId ) => - 'controlled||' + clientId - ) + const inserterClientIds = getFlattenedClientIds( + action.blocks + ); + newState.tree = new Map( newState.tree ); + action.replacedClientIds + .concat( + // Controlled inner blocks are only removed + // if the block doesn't move to another position + // otherwise their content will be lost. + action.replacedClientIds + .filter( + ( clientId ) => ! inserterClientIds[ clientId ] ) - ), - ...subTree, - }, + .map( ( clientId ) => 'controlled||' + clientId ) + ) + .forEach( ( key ) => { + newState.tree.delete( key ); + } ); + + updateBlockTreeForBlocks( newState, action.blocks ); + updateParentInnerBlocksInTree( + newState, action.blocks.map( ( b ) => b.clientId ), false ); @@ -341,18 +327,19 @@ const withBlockTree = const parentsOfRemovedBlocks = []; for ( const clientId of action.clientIds ) { if ( - state.parents[ clientId ] !== undefined && - ( state.parents[ clientId ] === '' || - newState.byClientId[ state.parents[ clientId ] ] ) + state.parents.get( clientId ) !== undefined && + ( state.parents.get( clientId ) === '' || + newState.byClientId.get( + state.parents.get( clientId ) + ) ) ) { parentsOfRemovedBlocks.push( - state.parents[ clientId ] + state.parents.get( clientId ) ); } } - newState.tree = updateParentInnerBlocksInTree( + updateParentInnerBlocksInTree( newState, - newState.tree, parentsOfRemovedBlocks, true ); @@ -362,25 +349,29 @@ const withBlockTree = const parentsOfRemovedBlocks = []; for ( const clientId of action.clientIds ) { if ( - state.parents[ clientId ] !== undefined && - ( state.parents[ clientId ] === '' || - newState.byClientId[ state.parents[ clientId ] ] ) + state.parents.get( clientId ) !== undefined && + ( state.parents.get( clientId ) === '' || + newState.byClientId.get( + state.parents.get( clientId ) + ) ) ) { parentsOfRemovedBlocks.push( - state.parents[ clientId ] + state.parents.get( clientId ) ); } } - newState.tree = updateParentInnerBlocksInTree( - newState, - omit( - newState.tree, - action.removedClientIds.concat( - action.removedClientIds.map( - ( clientId ) => 'controlled||' + clientId - ) + newState.tree = new Map( newState.tree ); + action.removedClientIds + .concat( + action.removedClientIds.map( + ( clientId ) => 'controlled||' + clientId ) - ), + ) + .forEach( ( key ) => { + newState.tree.delete( key ); + } ); + updateParentInnerBlocksInTree( + newState, parentsOfRemovedBlocks, true ); @@ -395,9 +386,9 @@ const withBlockTree = if ( action.toRootClientId ) { updatedBlockUids.push( action.toRootClientId ); } - newState.tree = updateParentInnerBlocksInTree( + newState.tree = new Map( newState.tree ); + updateParentInnerBlocksInTree( newState, - newState.tree, updatedBlockUids, true ); @@ -408,39 +399,35 @@ const withBlockTree = const updatedBlockUids = [ action.rootClientId ? action.rootClientId : '', ]; - newState.tree = updateParentInnerBlocksInTree( + newState.tree = new Map( newState.tree ); + updateParentInnerBlocksInTree( newState, - newState.tree, updatedBlockUids, true ); break; } case 'SAVE_REUSABLE_BLOCK_SUCCESS': { - const updatedBlockUids = Object.entries( newState.attributes ) - .filter( ( [ clientId, attributes ] ) => { - return ( - newState.byClientId[ clientId ].name === - 'core/block' && - attributes.ref === action.updatedId - ); - } ) - .map( ( [ clientId ] ) => clientId ); - - newState.tree = updateParentInnerBlocksInTree( + const updatedBlockUids = []; + newState.attributes.forEach( ( attributes, clientId ) => { + if ( + newState.byClientId.get( clientId ).name === + 'core/block' && + attributes.ref === action.updatedId + ) { + updatedBlockUids.push( clientId ); + } + } ); + newState.tree = new Map( newState.tree ); + updatedBlockUids.forEach( ( clientId ) => { + newState.tree.set( clientId, { + ...newState.byClientId.get( clientId ), + attributes: newState.attributes.get( clientId ), + innerBlocks: newState.tree.get( clientId ).innerBlocks, + } ); + } ); + updateParentInnerBlocksInTree( newState, - { - ...newState.tree, - ...updatedBlockUids.reduce( ( result, clientId ) => { - result[ clientId ] = { - ...newState.byClientId[ clientId ], - attributes: newState.attributes[ clientId ], - innerBlocks: - newState.tree[ clientId ].innerBlocks, - }; - return result; - }, {} ), - }, updatedBlockUids, false ); @@ -549,7 +536,7 @@ const withInnerBlocksRemoveCascade = ( reducer ) => ( state, action ) => { let result = clientIds; for ( let i = 0; i < result.length; i++ ) { if ( - ! state.order[ result[ i ] ] || + ! state.order.get( result[ i ] ) || ( action.keepControlledInnerBlocks && action.keepControlledInnerBlocks[ result[ i ] ] ) ) { @@ -560,7 +547,7 @@ const withInnerBlocksRemoveCascade = ( reducer ) => ( state, action ) => { result = [ ...result ]; } - result.push( ...state.order[ result[ i ] ] ); + result.push( ...state.order.get( result[ i ] ) ); } return result; }; @@ -601,23 +588,22 @@ const withBlockReset = ( reducer ) => ( state, action ) => { if ( action.type === 'RESET_BLOCKS' ) { const newState = { ...state, - byClientId: getFlattenedBlocksWithoutAttributes( action.blocks ), - attributes: getFlattenedBlockAttributes( action.blocks ), + byClientId: new Map( + getFlattenedBlocksWithoutAttributes( action.blocks ) + ), + attributes: new Map( getFlattenedBlockAttributes( action.blocks ) ), order: mapBlockOrder( action.blocks ), - parents: mapBlockParents( action.blocks ), + parents: new Map( mapBlockParents( action.blocks ) ), controlledInnerBlocks: {}, }; - const subTree = buildBlockTree( newState, action.blocks ); - newState.tree = { - ...subTree, - // Root. - '': { - innerBlocks: action.blocks.map( - ( subBlock ) => subTree[ subBlock.clientId ] - ), - }, - }; + newState.tree = new Map( state?.tree ); + updateBlockTreeForBlocks( newState, action.blocks ); + newState.tree.set( '', { + innerBlocks: action.blocks.map( ( subBlock ) => + newState.tree.get( subBlock.clientId ) + ), + } ); return newState; } @@ -663,11 +649,11 @@ const withReplaceInnerBlocks = ( reducer ) => ( state, action ) => { // marked block in the block state so that they can be reattached to the // marked block when we re-insert everything a few lines below. let stateAfterBlocksRemoval = state; - if ( state.order[ action.rootClientId ] ) { + if ( state.order.get( action.rootClientId ) ) { stateAfterBlocksRemoval = reducer( stateAfterBlocksRemoval, { type: 'REMOVE_BLOCKS', keepControlledInnerBlocks: nestedControllers, - clientIds: state.order[ action.rootClientId ], + clientIds: state.order.get( action.rootClientId ), } ); } let stateAfterInsert = stateAfterBlocksRemoval; @@ -681,25 +667,20 @@ const withReplaceInnerBlocks = ( reducer ) => ( state, action ) => { // We need to re-attach the controlled inner blocks to the blocks tree and // preserve their block order. Otherwise, an inner block controller's blocks // will be deleted entirely from its entity. - stateAfterInsert.order = { - ...stateAfterInsert.order, - ...Object.keys( nestedControllers ).reduce( ( result, key ) => { - if ( state.order[ key ] ) { - result[ key ] = state.order[ key ]; - } - return result; - }, {} ), - }; - stateAfterInsert.tree = { - ...stateAfterInsert.tree, - ...Object.keys( nestedControllers ).reduce( ( result, _key ) => { - const key = `controlled||${ _key }`; - if ( state.tree[ key ] ) { - result[ key ] = state.tree[ key ]; - } - return result; - }, {} ), - }; + const stateAfterInsertOrder = new Map( stateAfterInsert.order ); + Object.keys( nestedControllers ).forEach( ( key ) => { + if ( state.order.get( key ) ) { + stateAfterInsertOrder.set( key, state.order.get( key ) ); + } + } ); + stateAfterInsert.order = stateAfterInsertOrder; + stateAfterInsert.tree = new Map( stateAfterInsert.tree ); + Object.keys( nestedControllers ).forEach( ( _key ) => { + const key = `controlled||${ _key }`; + if ( state.tree.has( key ) ) { + stateAfterInsert.tree.set( key, state.tree.get( key ) ); + } + } ); } return stateAfterInsert; }; @@ -724,21 +705,16 @@ const withSaveReusableBlock = ( reducer ) => ( state, action ) => { } state = { ...state }; - - state.attributes = mapValues( - state.attributes, - ( attributes, clientId ) => { - const { name } = state.byClientId[ clientId ]; - if ( name === 'core/block' && attributes.ref === id ) { - return { - ...attributes, - ref: updatedId, - }; - } - - return attributes; + state.attributes = new Map( state.attributes ); + state.attributes.forEach( ( attributes, clientId ) => { + const { name } = state.byClientId.get( clientId ); + if ( name === 'core/block' && attributes.ref === id ) { + state.attributes.set( clientId, { + ...attributes, + ref: updatedId, + } ); } - ); + } ); } return reducer( state, action ); @@ -784,18 +760,24 @@ export const blocks = pipe( withIgnoredBlockChange, withResetControlledBlocks )( { - byClientId( state = {}, action ) { + // The state is using a Map instead of a plain object for performance reasons. + // You can run the "./test/performance.js" unit test to check the impact + // code changes can have on this reducer. + byClientId( state = new Map(), action ) { switch ( action.type ) { case 'RECEIVE_BLOCKS': - case 'INSERT_BLOCKS': - return { - ...state, - ...getFlattenedBlocksWithoutAttributes( action.blocks ), - }; - - case 'UPDATE_BLOCK': + case 'INSERT_BLOCKS': { + const newState = new Map( state ); + getFlattenedBlocksWithoutAttributes( action.blocks ).forEach( + ( [ key, value ] ) => { + newState.set( key, value ); + } + ); + return newState; + } + case 'UPDATE_BLOCK': { // Ignore updates if block isn't known. - if ( ! state[ action.clientId ] ) { + if ( ! state.has( action.clientId ) ) { return state; } @@ -805,142 +787,184 @@ export const blocks = pipe( return state; } - return { - ...state, - [ action.clientId ]: { - ...state[ action.clientId ], - ...changes, - }, - }; + const newState = new Map( state ); + newState.set( action.clientId, { + ...state.get( action.clientId ), + ...changes, + } ); + return newState; + } - case 'REPLACE_BLOCKS_AUGMENTED_WITH_CHILDREN': + case 'REPLACE_BLOCKS_AUGMENTED_WITH_CHILDREN': { if ( ! action.blocks ) { return state; } - return { - ...omit( state, action.replacedClientIds ), - ...getFlattenedBlocksWithoutAttributes( action.blocks ), - }; + const newState = new Map( state ); + action.replacedClientIds.forEach( ( clientId ) => { + newState.delete( clientId ); + } ); - case 'REMOVE_BLOCKS_AUGMENTED_WITH_CHILDREN': - return omit( state, action.removedClientIds ); + getFlattenedBlocksWithoutAttributes( action.blocks ).forEach( + ( [ key, value ] ) => { + newState.set( key, value ); + } + ); + return newState; + } + + case 'REMOVE_BLOCKS_AUGMENTED_WITH_CHILDREN': { + const newState = new Map( state ); + action.removedClientIds.forEach( ( clientId ) => { + newState.delete( clientId ); + } ); + return newState; + } } return state; }, - attributes( state = {}, action ) { + // The state is using a Map instead of a plain object for performance reasons. + // You can run the "./test/performance.js" unit test to check the impact + // code changes can have on this reducer. + attributes( state = new Map(), action ) { switch ( action.type ) { case 'RECEIVE_BLOCKS': - case 'INSERT_BLOCKS': - return { - ...state, - ...getFlattenedBlockAttributes( action.blocks ), - }; + case 'INSERT_BLOCKS': { + const newState = new Map( state ); + getFlattenedBlockAttributes( action.blocks ).forEach( + ( [ key, value ] ) => { + newState.set( key, value ); + } + ); + return newState; + } - case 'UPDATE_BLOCK': + case 'UPDATE_BLOCK': { // Ignore updates if block isn't known or there are no attribute changes. if ( - ! state[ action.clientId ] || + ! state.get( action.clientId ) || ! action.updates.attributes ) { return state; } - return { - ...state, - [ action.clientId ]: { - ...state[ action.clientId ], - ...action.updates.attributes, - }, - }; + const newState = new Map( state ); + newState.set( action.clientId, { + ...state.get( action.clientId ), + ...action.updates.attributes, + } ); + return newState; + } case 'UPDATE_BLOCK_ATTRIBUTES': { // Avoid a state change if none of the block IDs are known. - if ( action.clientIds.every( ( id ) => ! state[ id ] ) ) { + if ( action.clientIds.every( ( id ) => ! state.get( id ) ) ) { return state; } - const next = action.clientIds.reduce( - ( accumulator, id ) => ( { - ...accumulator, - [ id ]: Object.entries( - action.uniqueByBlock - ? action.attributes[ id ] - : action.attributes ?? {} - ).reduce( ( result, [ key, value ] ) => { - // Consider as updates only changed values. - if ( value !== result[ key ] ) { - result = getMutateSafeObject( - state[ id ], - result - ); - result[ key ] = value; - } - - return result; - }, state[ id ] ), - } ), - {} - ); - - if ( - action.clientIds.every( - ( id ) => next[ id ] === state[ id ] - ) - ) { - return state; + let hasChange = false; + const newState = new Map( state ); + for ( const clientId of action.clientIds ) { + const updatedAttributeEntries = Object.entries( + action.uniqueByBlock + ? action.attributes[ clientId ] + : action.attributes ?? {} + ); + if ( updatedAttributeEntries.length === 0 ) { + continue; + } + let hasUpdatedAttributes = false; + const existingAttributes = state.get( clientId ); + const newAttributes = {}; + updatedAttributeEntries.forEach( ( [ key, value ] ) => { + if ( existingAttributes[ key ] !== value ) { + hasUpdatedAttributes = true; + newAttributes[ key ] = value; + } + } ); + hasChange = hasChange || hasUpdatedAttributes; + if ( hasUpdatedAttributes ) { + newState.set( clientId, { + ...existingAttributes, + ...newAttributes, + } ); + } } - return { ...state, ...next }; + return hasChange ? newState : state; } - case 'REPLACE_BLOCKS_AUGMENTED_WITH_CHILDREN': + case 'REPLACE_BLOCKS_AUGMENTED_WITH_CHILDREN': { if ( ! action.blocks ) { return state; } - return { - ...omit( state, action.replacedClientIds ), - ...getFlattenedBlockAttributes( action.blocks ), - }; + const newState = new Map( state ); + action.replacedClientIds.forEach( ( clientId ) => { + newState.delete( clientId ); + } ); + getFlattenedBlockAttributes( action.blocks ).forEach( + ( [ key, value ] ) => { + newState.set( key, value ); + } + ); + return newState; + } - case 'REMOVE_BLOCKS_AUGMENTED_WITH_CHILDREN': - return omit( state, action.removedClientIds ); + case 'REMOVE_BLOCKS_AUGMENTED_WITH_CHILDREN': { + const newState = new Map( state ); + action.removedClientIds.forEach( ( clientId ) => { + newState.delete( clientId ); + } ); + return newState; + } } return state; }, - order( state = {}, action ) { + // The state is using a Map instead of a plain object for performance reasons. + // You can run the "./test/performance.js" unit test to check the impact + // code changes can have on this reducer. + order( state = new Map(), action ) { switch ( action.type ) { case 'RECEIVE_BLOCKS': { const blockOrder = mapBlockOrder( action.blocks ); - return { - ...state, - ...omit( blockOrder, '' ), - '': ( state?.[ '' ] || [] ).concat( blockOrder[ '' ] ), - }; + const newState = new Map( state ); + blockOrder.forEach( ( order, clientId ) => { + if ( clientId !== '' ) { + newState.set( clientId, order ); + } + } ); + newState.set( + '', + ( state.get( '' ) ?? [] ).concat( blockOrder[ '' ] ) + ); + return newState; } case 'INSERT_BLOCKS': { const { rootClientId = '' } = action; - const subState = state[ rootClientId ] || []; + const subState = state.get( rootClientId ) || []; const mappedBlocks = mapBlockOrder( action.blocks, rootClientId ); const { index = subState.length } = action; - - return { - ...state, - ...mappedBlocks, - [ rootClientId ]: insertAt( + const newState = new Map( state ); + mappedBlocks.forEach( ( order, clientId ) => { + newState.set( clientId, order ); + } ); + newState.set( + rootClientId, + insertAt( subState, - mappedBlocks[ rootClientId ], + mappedBlocks.get( rootClientId ), index - ), - }; + ) + ); + return newState; } case 'MOVE_BLOCKS_TO_POSITION': { @@ -949,65 +973,68 @@ export const blocks = pipe( toRootClientId = '', clientIds, } = action; - const { index = state[ toRootClientId ].length } = action; + const { index = state.get( toRootClientId ).length } = action; // Moving inside the same parent block. if ( fromRootClientId === toRootClientId ) { - const subState = state[ toRootClientId ]; + const subState = state.get( toRootClientId ); const fromIndex = subState.indexOf( clientIds[ 0 ] ); - return { - ...state, - [ toRootClientId ]: moveTo( - state[ toRootClientId ], + const newState = new Map( state ); + newState.set( + toRootClientId, + moveTo( + state.get( toRootClientId ), fromIndex, index, clientIds.length - ), - }; + ) + ); + return newState; } // Moving from a parent block to another. - return { - ...state, - [ fromRootClientId ]: - state[ fromRootClientId ]?.filter( - ( id ) => ! clientIds.includes( id ) - ) ?? [], - [ toRootClientId ]: insertAt( - state[ toRootClientId ], - clientIds, - index - ), - }; + const newState = new Map( state ); + newState.set( + fromRootClientId, + state + .get( fromRootClientId ) + ?.filter( ( id ) => ! clientIds.includes( id ) ) ?? [] + ); + newState.set( + toRootClientId, + insertAt( state.get( toRootClientId ), clientIds, index ) + ); + return newState; } case 'MOVE_BLOCKS_UP': { const { clientIds, rootClientId = '' } = action; const firstClientId = clientIds[ 0 ]; - const subState = state[ rootClientId ]; + const subState = state.get( rootClientId ); if ( ! subState.length || firstClientId === subState[ 0 ] ) { return state; } const firstIndex = subState.indexOf( firstClientId ); - - return { - ...state, - [ rootClientId ]: moveTo( + const newState = new Map( state ); + newState.set( + rootClientId, + moveTo( subState, firstIndex, firstIndex - 1, clientIds.length - ), - }; + ) + ); + return newState; } case 'MOVE_BLOCKS_DOWN': { const { clientIds, rootClientId = '' } = action; const firstClientId = clientIds[ 0 ]; const lastClientId = clientIds[ clientIds.length - 1 ]; - const subState = state[ rootClientId ]; + const subState = state.get( rootClientId ); if ( ! subState.length || @@ -1017,16 +1044,17 @@ export const blocks = pipe( } const firstIndex = subState.indexOf( firstClientId ); - - return { - ...state, - [ rootClientId ]: moveTo( + const newState = new Map( state ); + newState.set( + rootClientId, + moveTo( subState, firstIndex, firstIndex + 1, clientIds.length - ), - }; + ) + ); + return newState; } case 'REPLACE_BLOCKS_AUGMENTED_WITH_CHILDREN': { @@ -1036,55 +1064,52 @@ export const blocks = pipe( } const mappedBlocks = mapBlockOrder( action.blocks ); + const newState = new Map( state ); + action.replacedClientIds.forEach( ( clientId ) => { + newState.delete( clientId ); + } ); + mappedBlocks.forEach( ( order, clientId ) => { + if ( clientId !== '' ) { + newState.set( clientId, order ); + } + } ); + newState.forEach( ( order, clientId ) => { + const newSubOrder = Object.values( order ).reduce( + ( result, subClientId ) => { + if ( subClientId === clientIds[ 0 ] ) { + return [ ...result, ...mappedBlocks.get( '' ) ]; + } - return pipe( [ - ( nextState ) => - omit( nextState, action.replacedClientIds ), - ( nextState ) => ( { - ...nextState, - ...omit( mappedBlocks, '' ), - } ), - ( nextState ) => - mapValues( nextState, ( subState ) => - Object.values( subState ).reduce( - ( result, clientId ) => { - if ( clientId === clientIds[ 0 ] ) { - return [ - ...result, - ...mappedBlocks[ '' ], - ]; - } - - if ( - clientIds.indexOf( clientId ) === -1 - ) { - result.push( clientId ); - } - - return result; - }, - [] - ) - ), - ] )( state ); + if ( clientIds.indexOf( subClientId ) === -1 ) { + result.push( subClientId ); + } + + return result; + }, + [] + ); + newState.set( clientId, newSubOrder ); + } ); + return newState; } - case 'REMOVE_BLOCKS_AUGMENTED_WITH_CHILDREN': - return pipe( [ - // Remove inner block ordering for removed blocks. - ( nextState ) => omit( nextState, action.removedClientIds ), - - // Remove deleted blocks from other blocks' orderings. - ( nextState ) => - mapValues( - nextState, - ( subState ) => - subState?.filter( - ( id ) => - ! action.removedClientIds.includes( id ) - ) ?? [] - ), - ] )( state ); + case 'REMOVE_BLOCKS_AUGMENTED_WITH_CHILDREN': { + const newState = new Map( state ); + // Remove inner block ordering for removed blocks. + action.removedClientIds.forEach( ( clientId ) => { + newState.delete( clientId ); + } ); + newState.forEach( ( order, clientId ) => { + const newSubOrder = + order?.filter( + ( id ) => ! action.removedClientIds.includes( id ) + ) ?? []; + if ( newSubOrder.length !== order.length ) { + newState.set( clientId, newSubOrder ); + } + } ); + return newState; + } } return state; @@ -1092,44 +1117,55 @@ export const blocks = pipe( // While technically redundant data as the inverse of `order`, it serves as // an optimization for the selectors which derive the ancestry of a block. - parents( state = {}, action ) { + parents( state = new Map(), action ) { switch ( action.type ) { - case 'RECEIVE_BLOCKS': - return { - ...state, - ...mapBlockParents( action.blocks ), - }; - - case 'INSERT_BLOCKS': - return { - ...state, - ...mapBlockParents( - action.blocks, - action.rootClientId || '' - ), - }; - + case 'RECEIVE_BLOCKS': { + const newState = new Map( state ); + mapBlockParents( action.blocks ).forEach( + ( [ key, value ] ) => { + newState.set( key, value ); + } + ); + return newState; + } + case 'INSERT_BLOCKS': { + const newState = new Map( state ); + mapBlockParents( + action.blocks, + action.rootClientId || '' + ).forEach( ( [ key, value ] ) => { + newState.set( key, value ); + } ); + return newState; + } case 'MOVE_BLOCKS_TO_POSITION': { - return { - ...state, - ...action.clientIds.reduce( ( accumulator, id ) => { - accumulator[ id ] = action.toRootClientId || ''; - return accumulator; - }, {} ), - }; + const newState = new Map( state ); + action.clientIds.forEach( ( id ) => { + newState.set( id, action.toRootClientId || '' ); + } ); + return newState; } - case 'REPLACE_BLOCKS_AUGMENTED_WITH_CHILDREN': - return { - ...omit( state, action.replacedClientIds ), - ...mapBlockParents( - action.blocks, - state[ action.clientIds[ 0 ] ] - ), - }; - - case 'REMOVE_BLOCKS_AUGMENTED_WITH_CHILDREN': - return omit( state, action.removedClientIds ); + case 'REPLACE_BLOCKS_AUGMENTED_WITH_CHILDREN': { + const newState = new Map( state ); + action.replacedClientIds.forEach( ( clientId ) => { + newState.delete( clientId ); + } ); + mapBlockParents( + action.blocks, + state.get( action.clientIds[ 0 ] ) + ).forEach( ( [ key, value ] ) => { + newState.set( key, value ); + } ); + return newState; + } + case 'REMOVE_BLOCKS_AUGMENTED_WITH_CHILDREN': { + const newState = new Map( state ); + action.removedClientIds.forEach( ( clientId ) => { + newState.delete( clientId ); + } ); + return newState; + } } return state; @@ -1492,7 +1528,7 @@ export function insertionPoint( state = null, action ) { }; // Bail out updates if the states are the same. - return isEqual( state, nextState ) ? state : nextState; + return fastDeepEqual( state, nextState ) ? state : nextState; } case 'HIDE_INSERTION_POINT': @@ -1617,7 +1653,7 @@ export const blockListSettings = ( state = {}, action ) => { return state; } - if ( isEqual( state[ clientId ], action.settings ) ) { + if ( fastDeepEqual( state[ clientId ], action.settings ) ) { return state; } diff --git a/packages/block-editor/src/store/selectors.js b/packages/block-editor/src/store/selectors.js index 0345b29ff93323..f096767bf6178c 100644 --- a/packages/block-editor/src/store/selectors.js +++ b/packages/block-editor/src/store/selectors.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { map, find, filter, orderBy } from 'lodash'; +import { map } from 'lodash'; import createSelector from 'rememo'; /** @@ -27,6 +27,7 @@ import deprecated from '@wordpress/deprecated'; * Internal dependencies */ import { mapRichTextSettings } from './utils'; +import { orderBy } from '../utils/sorting'; /** * A block selection object. @@ -65,12 +66,12 @@ const EMPTY_ARRAY = []; * @return {string} Block name. */ export function getBlockName( state, clientId ) { - const block = state.blocks.byClientId[ clientId ]; + const block = state.blocks.byClientId.get( clientId ); const socialLinkName = 'core/social-link'; if ( Platform.OS !== 'web' && block?.name === socialLinkName ) { - const attributes = state.blocks.attributes[ clientId ]; - const { service } = attributes; + const attributes = state.blocks.attributes.get( clientId ); + const { service } = attributes ?? {}; return service ? `${ socialLinkName }-${ service }` : socialLinkName; } @@ -86,7 +87,7 @@ export function getBlockName( state, clientId ) { * @return {boolean} Is Valid. */ export function isBlockValid( state, clientId ) { - const block = state.blocks.byClientId[ clientId ]; + const block = state.blocks.byClientId.get( clientId ); return !! block && block.isValid; } @@ -100,12 +101,12 @@ export function isBlockValid( state, clientId ) { * @return {Object?} Block attributes. */ export function getBlockAttributes( state, clientId ) { - const block = state.blocks.byClientId[ clientId ]; + const block = state.blocks.byClientId.get( clientId ); if ( ! block ) { return null; } - return state.blocks.attributes[ clientId ]; + return state.blocks.attributes.get( clientId ); } /** @@ -130,29 +131,27 @@ export function getBlockAttributes( state, clientId ) { * @return {Object} Parsed block object. */ export function getBlock( state, clientId ) { - const block = state.blocks.byClientId[ clientId ]; - if ( ! block ) { + if ( ! state.blocks.byClientId.has( clientId ) ) { return null; } - return state.blocks.tree[ clientId ]; + return state.blocks.tree.get( clientId ); } export const __unstableGetBlockWithoutInnerBlocks = createSelector( ( state, clientId ) => { - const block = state.blocks.byClientId[ clientId ]; - if ( ! block ) { + if ( ! state.blocks.byClientId.has( clientId ) ) { return null; } return { - ...block, + ...state.blocks.byClientId.get( clientId ), attributes: getBlockAttributes( state, clientId ), }; }, ( state, clientId ) => [ - state.blocks.byClientId[ clientId ], - state.blocks.attributes[ clientId ], + state.blocks.byClientId.get( clientId ), + state.blocks.attributes.get( clientId ), ] ); @@ -171,7 +170,7 @@ export function getBlocks( state, rootClientId ) { ! rootClientId || ! areInnerBlocksControlled( state, rootClientId ) ? rootClientId || '' : 'controlled||' + rootClientId; - return state.blocks.tree[ treeKey ]?.innerBlocks || EMPTY_ARRAY; + return state.blocks.tree.get( treeKey )?.innerBlocks || EMPTY_ARRAY; } /** @@ -274,7 +273,7 @@ export const getGlobalBlockCount = createSelector( return clientIds.length; } return clientIds.reduce( ( accumulator, clientId ) => { - const block = state.blocks.byClientId[ clientId ]; + const block = state.blocks.byClientId.get( clientId ); return block.name === blockName ? accumulator + 1 : accumulator; }, 0 ); }, @@ -296,7 +295,7 @@ export const __experimentalGetGlobalBlocksByName = createSelector( } const clientIds = getClientIdsWithDescendants( state ); const foundBlocks = clientIds.filter( ( clientId ) => { - const block = state.blocks.byClientId[ clientId ]; + const block = state.blocks.byClientId.get( clientId ); return block.name === blockName; } ); return foundBlocks.length > 0 ? foundBlocks : EMPTY_ARRAY; @@ -322,7 +321,7 @@ export const getBlocksByClientId = createSelector( ( state, clientIds ) => map( Array.isArray( clientIds ) ? clientIds : [ clientIds ], - ( clientId ) => state.blocks.tree[ clientId ] + ( clientId ) => state.blocks.tree.get( clientId ) ) ); @@ -463,8 +462,8 @@ export function getSelectedBlock( state ) { * @return {?string} Root client ID, if exists */ export function getBlockRootClientId( state, clientId ) { - return state.blocks.parents[ clientId ] !== undefined - ? state.blocks.parents[ clientId ] + return state.blocks.parents.has( clientId ) + ? state.blocks.parents.get( clientId ) : null; } @@ -481,8 +480,8 @@ export const getBlockParents = createSelector( ( state, clientId, ascending = false ) => { const parents = []; let current = clientId; - while ( !! state.blocks.parents[ current ] ) { - current = state.blocks.parents[ current ]; + while ( !! state.blocks.parents.get( current ) ) { + current = state.blocks.parents.get( current ); parents.push( current ); } @@ -509,24 +508,20 @@ export const getBlockParentsByBlockName = createSelector( ( state, clientId, blockName, ascending = false ) => { const parents = getBlockParents( state, clientId, ascending ); return map( - filter( - map( parents, ( id ) => ( { - id, - name: getBlockName( state, id ), - } ) ), - ( { name } ) => { - if ( Array.isArray( blockName ) ) { - return blockName.includes( name ); - } - return name === blockName; + map( parents, ( id ) => ( { + id, + name: getBlockName( state, id ), + } ) ).filter( ( { name } ) => { + if ( Array.isArray( blockName ) ) { + return blockName.includes( name ); } - ), + return name === blockName; + } ), ( { id } ) => id ); }, ( state ) => [ state.blocks.parents ] ); - /** * Given a block client ID, returns the root of the hierarchy from which the block is nested, return the block itself for root level blocks. * @@ -540,7 +535,7 @@ export function getBlockHierarchyRootClientId( state, clientId ) { let parent; do { parent = current; - current = state.blocks.parents[ current ]; + current = state.blocks.parents.get( current ); } while ( current ); return parent; } @@ -617,7 +612,7 @@ export function getAdjacentBlockClientId( state, startClientId, modifier = 1 ) { } const { order } = state.blocks; - const orderSet = order[ rootClientId ]; + const orderSet = order.get( rootClientId ); const index = orderSet.indexOf( startClientId ); const nextIndex = index + 1 * modifier; @@ -1132,7 +1127,7 @@ export const __unstableGetSelectedBlocksWithPartialSelection = ( state ) => { * @return {Array} Ordered client IDs of editor blocks. */ export function getBlockOrder( state, rootClientId ) { - return state.blocks.order[ rootClientId || '' ] || EMPTY_ARRAY; + return state.blocks.order.get( rootClientId || '' ) || EMPTY_ARRAY; } /** @@ -1431,19 +1426,14 @@ export function getTemplate( state ) { * @param {Object} state Editor state. * @param {?string} rootClientId Optional block root client ID. * - * @return {?string} Block Template Lock + * @return {string|false} Block Template Lock */ export function getTemplateLock( state, rootClientId ) { if ( ! rootClientId ) { - return state.settings.templateLock; - } - - const blockListSettings = getBlockListSettings( state, rootClientId ); - if ( ! blockListSettings ) { - return undefined; + return state.settings.templateLock ?? false; } - return blockListSettings.templateLock; + return getBlockListSettings( state, rootClientId )?.templateLock ?? false; } const checkAllowList = ( list, item, defaultResult = null ) => { @@ -1598,7 +1588,7 @@ export const canInsertBlockType = createSelector( canInsertBlockTypeUnmemoized, ( state, blockName, rootClientId ) => [ state.blockListSettings[ rootClientId ], - state.blocks.byClientId[ rootClientId ], + state.blocks.byClientId.get( rootClientId ), state.settings.allowedBlockTypes, state.settings.templateLock, ] @@ -2111,7 +2101,7 @@ export const getBlockTransformItems = createSelector( 'desc' ); }, - ( state, rootClientId ) => [ + ( state, blocks, rootClientId ) => [ state.blockListSettings[ rootClientId ], state.blocks.byClientId, state.preferences.insertUsage, @@ -2167,7 +2157,7 @@ export const __experimentalGetAllowedBlocks = createSelector( return; } - return filter( getBlockTypes(), ( blockType ) => + return getBlockTypes().filter( ( blockType ) => canIncludeBlockTypeInInserter( state, blockType, rootClientId ) ); }, @@ -2214,7 +2204,7 @@ export const __experimentalGetDirectInsertBlock = createSelector( }, ( state, rootClientId ) => [ state.blockListSettings[ rootClientId ], - state.blocks.tree[ rootClientId ], + state.blocks.tree.get( rootClientId ), ] ); @@ -2292,8 +2282,7 @@ const getAllAllowedPatterns = createSelector( export const __experimentalGetAllowedPatterns = createSelector( ( state, rootClientId = null ) => { const availableParsedPatterns = getAllAllowedPatterns( state ); - const patternsAllowed = filter( - availableParsedPatterns, + const patternsAllowed = availableParsedPatterns.filter( ( { blocks } ) => blocks.every( ( { name } ) => canInsertBlockType( state, name, rootClientId ) @@ -2307,7 +2296,7 @@ export const __experimentalGetAllowedPatterns = createSelector( state.settings.allowedBlockTypes, state.settings.templateLock, state.blockListSettings[ rootClientId ], - state.blocks.byClientId[ rootClientId ], + state.blocks.byClientId.get( rootClientId ), ] ); @@ -2340,7 +2329,7 @@ export const __experimentalGetPatternsByBlockTypes = createSelector( ) ); }, - ( state, rootClientId ) => [ + ( state, blockNames, rootClientId ) => [ ...__experimentalGetAllowedPatterns.getDependants( state, rootClientId @@ -2401,7 +2390,7 @@ export const __experimentalGetPatternTransformItems = createSelector( rootClientId ); }, - ( state, rootClientId ) => [ + ( state, blocks, rootClientId ) => [ ...__experimentalGetPatternsByBlockTypes.getDependants( state, rootClientId @@ -2480,8 +2469,7 @@ export const __experimentalGetBlockListSettingsForBlocks = createSelector( */ export const __experimentalGetReusableBlockTitle = createSelector( ( state, ref ) => { - const reusableBlock = find( - getReusableBlocks( state ), + const reusableBlock = getReusableBlocks( state ).find( ( block ) => block.id === ref ); if ( ! reusableBlock ) { @@ -2664,6 +2652,16 @@ export function wasBlockJustInserted( state, clientId, source ) { ); } +/** + * Gets the client id of the last inserted block. + * + * @param {Object} state Global application state. + * @return {string|undefined} Client Id of the last inserted block. + */ +export function getLastInsertedBlockClientId( state ) { + return state?.lastBlockInserted?.clientId; +} + /** * Tells if the block is visible on the canvas or not. * @@ -2701,8 +2699,8 @@ export const __unstableGetContentLockingParent = createSelector( ( state, clientId ) => { let current = clientId; let result; - while ( !! state.blocks.parents[ current ] ) { - current = state.blocks.parents[ current ]; + while ( state.blocks.parents.has( current ) ) { + current = state.blocks.parents.get( current ); if ( getTemplateLock( state, current ) === 'contentOnly' ) { result = current; } diff --git a/packages/block-editor/src/store/test/performance.js b/packages/block-editor/src/store/test/performance.js new file mode 100644 index 00000000000000..2da3c1a41db2a3 --- /dev/null +++ b/packages/block-editor/src/store/test/performance.js @@ -0,0 +1,71 @@ +/** + * Internal dependencies + */ +import reducer from '../reducer'; + +describe( 'performance', () => { + const state = reducer( undefined, { type: '@@init' } ); + const blocks = []; + for ( let i = 0; i < 100000; i++ ) { + blocks.push( { + clientId: `block-${ i }`, + attributes: { content: `paragraph ${ i }` }, + innerBlocks: [], + } ); + } + + let preparedState; + + it( 'should reset blocks', () => { + preparedState = reducer( state, { + type: 'RESET_BLOCKS', + blocks, + } ); + expect( preparedState ).toBeDefined(); + } ); + + it( 'should update blocks', () => { + const updatedState = reducer( preparedState, { + type: 'UPDATE_BLOCK_ATTRIBUTES', + clientIds: [ 'block-10' ], + attributes: { + content: 'updated paragraph 10', + }, + } ); + + expect( updatedState ).toBeDefined(); + } ); + + it( 'should replace blocks (Enter in paragraphs)', () => { + const updatedState = reducer( preparedState, { + type: 'REPLACE_BLOCKS', + clientIds: [ 'block-10' ], + blocks: [ + { + clientId: `block-10`, + attributes: { content: `paragraph 10` }, + innerBlocks: [], + }, + { + clientId: `block-10-2`, + attributes: { content: '' }, + innerBlocks: [], + }, + ], + indexToSelect: 10, + initialPosition: 0, + } ); + + expect( updatedState ).toBeDefined(); + } ); + + it( 'should move blocks', () => { + const updatedState = reducer( preparedState, { + type: 'MOVE_BLOCKS_DOWN', + clientIds: [ 'block-10' ], + rootClientId: '', + } ); + + expect( updatedState ).toBeDefined(); + } ); +} ); diff --git a/packages/block-editor/src/store/test/reducer.js b/packages/block-editor/src/store/test/reducer.js index 9f2e623c70111a..61a51a5f28cd0b 100644 --- a/packages/block-editor/src/store/test/reducer.js +++ b/packages/block-editor/src/store/test/reducer.js @@ -210,38 +210,48 @@ describe( 'state', () => { } ); it( 'can replace a child block', () => { const existingState = deepFreeze( { - byClientId: { - chicken: { - clientId: 'chicken', - name: 'core/test-parent-block', - isValid: true, - }, - 'chicken-child': { - clientId: 'chicken-child', - name: 'core/test-child-block', - isValid: true, - }, - }, - attributes: { - chicken: {}, - 'chicken-child': { - attr: true, - }, - }, - order: { - '': [ 'chicken' ], - chicken: [ 'chicken-child' ], - 'chicken-child': [], - }, - parents: { - chicken: '', - 'chicken-child': 'chicken', - }, - tree: { - '': {}, - chicken: {}, - 'chicken-child': {}, - }, + byClientId: new Map( + Object.entries( { + chicken: { + clientId: 'chicken', + name: 'core/test-parent-block', + isValid: true, + }, + 'chicken-child': { + clientId: 'chicken-child', + name: 'core/test-child-block', + isValid: true, + }, + } ) + ), + attributes: new Map( + Object.entries( { + chicken: {}, + 'chicken-child': { + attr: true, + }, + } ) + ), + order: new Map( + Object.entries( { + '': [ 'chicken' ], + chicken: [ 'chicken-child' ], + 'chicken-child': [], + } ) + ), + parents: new Map( + Object.entries( { + chicken: '', + 'chicken-child': 'chicken', + } ) + ), + tree: new Map( + Object.entries( { + '': {}, + chicken: {}, + 'chicken-child': {}, + } ) + ), controlledInnerBlocks: {}, } ); @@ -264,66 +274,84 @@ describe( 'state', () => { expect( restState ).toEqual( { isPersistentChange: true, isIgnoredChange: false, - byClientId: { - chicken: { - clientId: 'chicken', - name: 'core/test-parent-block', - isValid: true, - }, - [ newChildBlockId ]: { - clientId: newChildBlockId, - name: 'core/test-child-block', - isValid: true, - }, - }, - attributes: { - chicken: {}, - [ newChildBlockId ]: { - attr: false, - attr2: 'perfect', - }, - }, - order: { - '': [ 'chicken' ], - chicken: [ newChildBlockId ], - [ newChildBlockId ]: [], - }, - parents: { - [ newChildBlockId ]: 'chicken', - chicken: '', - }, + byClientId: new Map( + Object.entries( { + chicken: { + clientId: 'chicken', + name: 'core/test-parent-block', + isValid: true, + }, + [ newChildBlockId ]: { + clientId: newChildBlockId, + name: 'core/test-child-block', + isValid: true, + }, + } ) + ), + attributes: new Map( + Object.entries( { + chicken: {}, + [ newChildBlockId ]: { + attr: false, + attr2: 'perfect', + }, + } ) + ), + order: new Map( + Object.entries( { + '': [ 'chicken' ], + chicken: [ newChildBlockId ], + [ newChildBlockId ]: [], + } ) + ), + parents: new Map( + Object.entries( { + [ newChildBlockId ]: 'chicken', + chicken: '', + } ) + ), controlledInnerBlocks: {}, } ); - expect( state.tree.chicken ).not.toBe( - existingState.tree.chicken + expect( state.tree.get( 'chicken' ) ).not.toBe( + existingState.tree.get( 'chicken' ) ); } ); it( 'can insert a child block', () => { const existingState = deepFreeze( { - byClientId: { - chicken: { - clientId: 'chicken', - name: 'core/test-parent-block', - isValid: true, - }, - }, - attributes: { - chicken: {}, - }, - order: { - '': [ 'chicken' ], - chicken: [], - }, - parents: { - chicken: '', - }, - tree: { - '': { - innerBlocks: [], - }, - chicken: {}, - }, + byClientId: new Map( + Object.entries( { + chicken: { + clientId: 'chicken', + name: 'core/test-parent-block', + isValid: true, + }, + } ) + ), + attributes: new Map( + Object.entries( { + chicken: {}, + } ) + ), + order: new Map( + Object.entries( { + '': [ 'chicken' ], + chicken: [], + } ) + ), + parents: new Map( + Object.entries( { + chicken: '', + } ) + ), + tree: new Map( + Object.entries( { + '': { + innerBlocks: [], + }, + chicken: {}, + } ) + ), controlledInnerBlocks: {}, } ); @@ -346,46 +374,54 @@ describe( 'state', () => { expect( restState ).toEqual( { isPersistentChange: true, isIgnoredChange: false, - byClientId: { - chicken: { - clientId: 'chicken', - name: 'core/test-parent-block', - isValid: true, - }, - [ newChildBlockId ]: { - clientId: newChildBlockId, - name: 'core/test-child-block', - isValid: true, - }, - }, - attributes: { - chicken: {}, - [ newChildBlockId ]: { - attr: false, - attr2: 'perfect', - }, - }, - order: { - '': [ 'chicken' ], - chicken: [ newChildBlockId ], - [ newChildBlockId ]: [], - }, - parents: { - [ newChildBlockId ]: 'chicken', - chicken: '', - }, + byClientId: new Map( + Object.entries( { + chicken: { + clientId: 'chicken', + name: 'core/test-parent-block', + isValid: true, + }, + [ newChildBlockId ]: { + clientId: newChildBlockId, + name: 'core/test-child-block', + isValid: true, + }, + } ) + ), + attributes: new Map( + Object.entries( { + chicken: {}, + [ newChildBlockId ]: { + attr: false, + attr2: 'perfect', + }, + } ) + ), + order: new Map( + Object.entries( { + '': [ 'chicken' ], + chicken: [ newChildBlockId ], + [ newChildBlockId ]: [], + } ) + ), + parents: new Map( + Object.entries( { + [ newChildBlockId ]: 'chicken', + chicken: '', + } ) + ), controlledInnerBlocks: {}, } ); - expect( state.tree.chicken ).not.toBe( - existingState.tree.chicken + expect( state.tree.get( 'chicken' ) ).not.toBe( + existingState.tree.get( 'chicken' ) ); - expect( state.tree[ '' ].innerBlocks[ 0 ] ).toBe( - state.tree.chicken + expect( state.tree.get( '' ).innerBlocks[ 0 ] ).toBe( + state.tree.get( 'chicken' ) ); - expect( state.tree.chicken.innerBlocks[ 0 ] ).toBe( - state.tree[ newChildBlockId ] + expect( state.tree.get( 'chicken' ).innerBlocks[ 0 ] ).toBe( + state.tree.get( newChildBlockId ) ); - expect( state.tree[ newChildBlockId ] ).toEqual( { + expect( state.tree.get( newChildBlockId ) ).toEqual( { clientId: newChildBlockId, innerBlocks: [], isValid: true, @@ -399,44 +435,52 @@ describe( 'state', () => { it( 'can replace multiple child blocks', () => { const existingState = deepFreeze( { - byClientId: { - chicken: { - clientId: 'chicken', - name: 'core/test-parent-block', - isValid: true, - }, - 'chicken-child': { - clientId: 'chicken-child', - name: 'core/test-child-block', - isValid: true, - }, - 'chicken-child-2': { - clientId: 'chicken-child', - name: 'core/test-child-block', - isValid: true, - }, - }, - attributes: { - chicken: {}, - 'chicken-child': { - attr: true, - }, - 'chicken-child-2': { - attr2: 'ok', - }, - }, - order: { - '': [ 'chicken' ], - chicken: [ 'chicken-child', 'chicken-child-2' ], - 'chicken-child': [], - 'chicken-child-2': [], - }, - parents: { - chicken: '', - 'chicken-child': 'chicken', - 'chicken-child-2': 'chicken', - }, - tree: {}, + byClientId: new Map( + Object.entries( { + chicken: { + clientId: 'chicken', + name: 'core/test-parent-block', + isValid: true, + }, + 'chicken-child': { + clientId: 'chicken-child', + name: 'core/test-child-block', + isValid: true, + }, + 'chicken-child-2': { + clientId: 'chicken-child', + name: 'core/test-child-block', + isValid: true, + }, + } ) + ), + attributes: new Map( + Object.entries( { + chicken: {}, + 'chicken-child': { + attr: true, + }, + 'chicken-child-2': { + attr2: 'ok', + }, + } ) + ), + order: new Map( + Object.entries( { + '': [ 'chicken' ], + chicken: [ 'chicken-child', 'chicken-child-2' ], + 'chicken-child': [], + 'chicken-child-2': [], + } ) + ), + parents: new Map( + Object.entries( { + chicken: '', + 'chicken-child': 'chicken', + 'chicken-child-2': 'chicken', + } ) + ), + tree: new Map(), controlledInnerBlocks: {}, } ); @@ -470,75 +514,83 @@ describe( 'state', () => { expect( restState ).toEqual( { isPersistentChange: true, isIgnoredChange: false, - byClientId: { - chicken: { - clientId: 'chicken', - name: 'core/test-parent-block', - isValid: true, - }, - [ newChildBlockId1 ]: { - clientId: newChildBlockId1, - name: 'core/test-child-block', - isValid: true, - }, - [ newChildBlockId2 ]: { - clientId: newChildBlockId2, - name: 'core/test-child-block', - isValid: true, - }, - [ newChildBlockId3 ]: { - clientId: newChildBlockId3, - name: 'core/test-child-block', - isValid: true, - }, - }, - attributes: { - chicken: {}, - [ newChildBlockId1 ]: { - attr: false, - attr2: 'perfect', - }, - [ newChildBlockId2 ]: { - attr: true, - attr2: 'not-perfect', - }, - [ newChildBlockId3 ]: { - attr2: 'hello', - }, - }, - order: { - '': [ 'chicken' ], - chicken: [ - newChildBlockId1, - newChildBlockId2, - newChildBlockId3, - ], - [ newChildBlockId1 ]: [], - [ newChildBlockId2 ]: [], - [ newChildBlockId3 ]: [], - }, - parents: { - chicken: '', - [ newChildBlockId1 ]: 'chicken', - [ newChildBlockId2 ]: 'chicken', - [ newChildBlockId3 ]: 'chicken', - }, + byClientId: new Map( + Object.entries( { + chicken: { + clientId: 'chicken', + name: 'core/test-parent-block', + isValid: true, + }, + [ newChildBlockId1 ]: { + clientId: newChildBlockId1, + name: 'core/test-child-block', + isValid: true, + }, + [ newChildBlockId2 ]: { + clientId: newChildBlockId2, + name: 'core/test-child-block', + isValid: true, + }, + [ newChildBlockId3 ]: { + clientId: newChildBlockId3, + name: 'core/test-child-block', + isValid: true, + }, + } ) + ), + attributes: new Map( + Object.entries( { + chicken: {}, + [ newChildBlockId1 ]: { + attr: false, + attr2: 'perfect', + }, + [ newChildBlockId2 ]: { + attr: true, + attr2: 'not-perfect', + }, + [ newChildBlockId3 ]: { + attr2: 'hello', + }, + } ) + ), + order: new Map( + Object.entries( { + '': [ 'chicken' ], + chicken: [ + newChildBlockId1, + newChildBlockId2, + newChildBlockId3, + ], + [ newChildBlockId1 ]: [], + [ newChildBlockId2 ]: [], + [ newChildBlockId3 ]: [], + } ) + ), + parents: new Map( + Object.entries( { + chicken: '', + [ newChildBlockId1 ]: 'chicken', + [ newChildBlockId2 ]: 'chicken', + [ newChildBlockId3 ]: 'chicken', + } ) + ), controlledInnerBlocks: {}, } ); - expect( state.tree[ '' ].innerBlocks[ 0 ] ).toBe( - state.tree.chicken + expect( state.tree.get( '' ).innerBlocks[ 0 ] ).toBe( + state.tree.get( 'chicken' ) ); - expect( state.tree.chicken.innerBlocks[ 0 ] ).toBe( - state.tree[ newChildBlockId1 ] + expect( state.tree.get( 'chicken' ).innerBlocks[ 0 ] ).toBe( + state.tree.get( newChildBlockId1 ) ); - expect( state.tree.chicken.innerBlocks[ 1 ] ).toBe( - state.tree[ newChildBlockId2 ] + expect( state.tree.get( 'chicken' ).innerBlocks[ 1 ] ).toBe( + state.tree.get( newChildBlockId2 ) ); - expect( state.tree.chicken.innerBlocks[ 2 ] ).toBe( - state.tree[ newChildBlockId3 ] + expect( state.tree.get( 'chicken' ).innerBlocks[ 2 ] ).toBe( + state.tree.get( newChildBlockId3 ) ); - expect( state.tree[ newChildBlockId1 ] ).toEqual( { + expect( state.tree.get( newChildBlockId1 ) ).toEqual( { innerBlocks: [], clientId: newChildBlockId1, name: 'core/test-child-block', @@ -552,42 +604,52 @@ describe( 'state', () => { it( 'can replace a child block that has other children', () => { const existingState = deepFreeze( { - byClientId: { - chicken: { - clientId: 'chicken', - name: 'core/test-parent-block', - isValid: true, - }, - 'chicken-child': { - clientId: 'chicken-child', - name: 'core/test-child-block', - isValid: true, - }, - 'chicken-grand-child': { - clientId: 'chicken-child', - name: 'core/test-block', - isValid: true, - }, - }, - attributes: { - chicken: {}, - 'chicken-child': {}, - 'chicken-grand-child': {}, - }, - order: { - '': [ 'chicken' ], - chicken: [ 'chicken-child' ], - 'chicken-child': [ 'chicken-grand-child' ], - 'chicken-grand-child': [], - }, - parents: { - chicken: '', - 'chicken-child': 'chicken', - 'chicken-grand-child': 'chicken-child', - }, - tree: { - chicken: {}, - }, + byClientId: new Map( + Object.entries( { + chicken: { + clientId: 'chicken', + name: 'core/test-parent-block', + isValid: true, + }, + 'chicken-child': { + clientId: 'chicken-child', + name: 'core/test-child-block', + isValid: true, + }, + 'chicken-grand-child': { + clientId: 'chicken-child', + name: 'core/test-block', + isValid: true, + }, + } ) + ), + attributes: new Map( + Object.entries( { + chicken: {}, + 'chicken-child': {}, + 'chicken-grand-child': {}, + } ) + ), + order: new Map( + Object.entries( { + '': [ 'chicken' ], + chicken: [ 'chicken-child' ], + 'chicken-child': [ 'chicken-grand-child' ], + 'chicken-grand-child': [], + } ) + ), + parents: new Map( + Object.entries( { + chicken: '', + 'chicken-child': 'chicken', + 'chicken-grand-child': 'chicken-child', + } ) + ), + tree: new Map( + Object.entries( { + chicken: {}, + } ) + ), controlledInnerBlocks: {}, } ); @@ -607,37 +669,45 @@ describe( 'state', () => { expect( restState ).toEqual( { isPersistentChange: true, isIgnoredChange: false, - byClientId: { - chicken: { - clientId: 'chicken', - name: 'core/test-parent-block', - isValid: true, - }, - [ newChildBlockId ]: { - clientId: newChildBlockId, - name: 'core/test-block', - isValid: true, - }, - }, - attributes: { - chicken: {}, - [ newChildBlockId ]: {}, - }, - order: { - '': [ 'chicken' ], - chicken: [ newChildBlockId ], - [ newChildBlockId ]: [], - }, - parents: { - chicken: '', - [ newChildBlockId ]: 'chicken', - }, + byClientId: new Map( + Object.entries( { + chicken: { + clientId: 'chicken', + name: 'core/test-parent-block', + isValid: true, + }, + [ newChildBlockId ]: { + clientId: newChildBlockId, + name: 'core/test-block', + isValid: true, + }, + } ) + ), + attributes: new Map( + Object.entries( { + chicken: {}, + [ newChildBlockId ]: {}, + } ) + ), + order: new Map( + Object.entries( { + '': [ 'chicken' ], + chicken: [ newChildBlockId ], + [ newChildBlockId ]: [], + } ) + ), + parents: new Map( + Object.entries( { + chicken: '', + [ newChildBlockId ]: 'chicken', + } ) + ), controlledInnerBlocks: {}, } ); // The block object of the parent should be updated. - expect( state.tree.chicken ).not.toBe( - existingState.tree.chicken + expect( state.tree.get( 'chicken' ) ).not.toBe( + existingState.tree.get( 'chicken' ) ); } ); } ); @@ -646,13 +716,13 @@ describe( 'state', () => { const state = blocks( undefined, {} ); expect( state ).toEqual( { - byClientId: {}, - attributes: {}, - order: {}, - parents: {}, + byClientId: new Map(), + attributes: new Map(), + order: new Map(), + parents: new Map(), isPersistentChange: true, isIgnoredChange: false, - tree: {}, + tree: new Map(), controlledInnerBlocks: {}, } ); } ); @@ -664,20 +734,20 @@ describe( 'state', () => { blocks: [ { clientId: 'bananas', innerBlocks: [] } ], } ); - expect( Object.keys( state.byClientId ) ).toHaveLength( 1 ); - expect( Object.values( state.byClientId )[ 0 ].clientId ).toBe( + expect( state.byClientId.size ).toBe( 1 ); + expect( state.byClientId.get( 'bananas' ).clientId ).toBe( 'bananas' ); - expect( state.order ).toEqual( { + expect( Object.fromEntries( state.order ) ).toEqual( { '': [ 'bananas' ], bananas: [], } ); - expect( state.tree.bananas ).toEqual( { + expect( state.tree.get( 'bananas' ) ).toEqual( { clientId: 'bananas', innerBlocks: [], } ); - expect( state.tree[ '' ].innerBlocks[ 0 ] ).toBe( - state.tree.bananas + expect( state.tree.get( '' ).innerBlocks[ 0 ] ).toBe( + state.tree.get( 'bananas' ) ); } ); } ); @@ -696,8 +766,8 @@ describe( 'state', () => { ], } ); - expect( Object.keys( state.byClientId ) ).toHaveLength( 2 ); - expect( state.order ).toEqual( { + expect( state.byClientId.size ).toBe( 2 ); + expect( Object.fromEntries( state.order ) ).toEqual( { '': [ 'bananas' ], apples: [], bananas: [ 'apples' ], @@ -727,21 +797,21 @@ describe( 'state', () => { ], } ); - expect( Object.keys( state.byClientId ) ).toHaveLength( 2 ); - expect( Object.values( state.byClientId )[ 1 ].clientId ).toBe( - 'ribs' - ); - expect( state.order ).toEqual( { + expect( state.byClientId.size ).toBe( 2 ); + expect( state.byClientId.get( 'ribs' ).clientId ).toBe( 'ribs' ); + expect( Object.fromEntries( state.order ) ).toEqual( { '': [ 'chicken', 'ribs' ], chicken: [], ribs: [], } ); - expect( state.tree[ '' ].innerBlocks[ 0 ] ).toBe( - state.tree.chicken + expect( state.tree.get( '' ).innerBlocks[ 0 ] ).toBe( + state.tree.get( 'chicken' ) ); - expect( state.tree[ '' ].innerBlocks[ 1 ] ).toBe( state.tree.ribs ); - expect( state.tree.chicken ).toEqual( { + expect( state.tree.get( '' ).innerBlocks[ 1 ] ).toBe( + state.tree.get( 'ribs' ) + ); + expect( state.tree.get( 'chicken' ) ).toEqual( { clientId: 'chicken', name: 'core/test-block', attributes: {}, @@ -773,24 +843,22 @@ describe( 'state', () => { ], } ); - expect( Object.keys( state.byClientId ) ).toHaveLength( 1 ); - expect( Object.values( state.byClientId )[ 0 ].name ).toBe( + expect( state.byClientId.size ).toBe( 1 ); + expect( state.byClientId.get( 'wings' ).name ).toBe( 'core/freeform' ); - expect( Object.values( state.byClientId )[ 0 ].clientId ).toBe( - 'wings' - ); - expect( state.order ).toEqual( { + expect( state.byClientId.get( 'wings' ).clientId ).toBe( 'wings' ); + expect( Object.fromEntries( state.order ) ).toEqual( { '': [ 'wings' ], wings: [], } ); - expect( state.parents ).toEqual( { + expect( Object.fromEntries( state.parents ) ).toEqual( { wings: '', } ); - expect( state.tree[ '' ].innerBlocks[ 0 ] ).toBe( - state.tree.wings + expect( state.tree.get( '' ).innerBlocks[ 0 ] ).toBe( + state.tree.get( 'wings' ) ); - expect( state.tree.wings ).toEqual( { + expect( state.tree.get( 'wings' ) ).toEqual( { clientId: 'wings', name: 'core/freeform', innerBlocks: [], @@ -815,8 +883,8 @@ describe( 'state', () => { blocks: [], } ); - expect( Object.keys( state.byClientId ) ).toHaveLength( 0 ); - expect( state.tree[ '' ].innerBlocks ).toHaveLength( 0 ); + expect( state.byClientId.size ).toBe( 0 ); + expect( state.tree.get( '' ).innerBlocks ).toHaveLength( 0 ); } ); it( 'should replace the block and remove references to its inner blocks', () => { @@ -850,18 +918,18 @@ describe( 'state', () => { ], } ); - expect( Object.keys( state.byClientId ) ).toHaveLength( 1 ); - expect( state.order ).toEqual( { + expect( state.byClientId.size ).toBe( 1 ); + expect( Object.fromEntries( state.order ) ).toEqual( { '': [ 'wings' ], wings: [], } ); - expect( state.parents ).toEqual( { + expect( Object.fromEntries( state.parents ) ).toEqual( { wings: '', } ); - expect( state.tree[ '' ].innerBlocks[ 0 ] ).toBe( - state.tree.wings + expect( state.tree.get( '' ).innerBlocks[ 0 ] ).toBe( + state.tree.get( 'wings' ) ); - expect( state.tree.wings ).toEqual( { + expect( state.tree.get( 'wings' ) ).toEqual( { clientId: 'wings', name: 'core/freeform', innerBlocks: [], @@ -885,21 +953,21 @@ describe( 'state', () => { blocks: [ replacementBlock ], } ); - expect( state.order ).toEqual( { + expect( Object.fromEntries( state.order ) ).toEqual( { '': [ wrapperBlock.clientId ], [ wrapperBlock.clientId ]: [ replacementBlock.clientId ], [ replacementBlock.clientId ]: [], } ); - expect( state.parents ).toEqual( { + expect( Object.fromEntries( state.parents ) ).toEqual( { [ wrapperBlock.clientId ]: '', [ replacementBlock.clientId ]: wrapperBlock.clientId, } ); - expect( state.tree[ wrapperBlock.clientId ].innerBlocks[ 0 ] ).toBe( - state.tree[ replacementBlock.clientId ] - ); - expect( state.tree[ replacementBlock.clientId ] ).toEqual( { + expect( + state.tree.get( wrapperBlock.clientId ).innerBlocks[ 0 ] + ).toBe( state.tree.get( replacementBlock.clientId ) ); + expect( state.tree.get( replacementBlock.clientId ) ).toEqual( { clientId: replacementBlock.clientId, name: 'core/test-block', innerBlocks: [], @@ -932,22 +1000,22 @@ describe( 'state', () => { ], } ); - expect( Object.keys( replacedState.byClientId ) ).toHaveLength( 1 ); - expect( Object.values( originalState.byClientId )[ 0 ].name ).toBe( + expect( replacedState.byClientId.size ).toBe( 1 ); + expect( originalState.byClientId.get( 'chicken' ).name ).toBe( 'core/test-block' ); - expect( Object.values( replacedState.byClientId )[ 0 ].name ).toBe( + expect( replacedState.byClientId.get( 'chicken' ).name ).toBe( 'core/freeform' ); - expect( - Object.values( replacedState.byClientId )[ 0 ].clientId - ).toBe( 'chicken' ); - expect( replacedState.order ).toEqual( { + expect( replacedState.byClientId.get( 'chicken' ).clientId ).toBe( + 'chicken' + ); + expect( Object.fromEntries( replacedState.order ) ).toEqual( { '': [ 'chicken' ], chicken: [], } ); - expect( originalState.tree.chicken ).not.toBe( - replacedState.tree.chicken + expect( originalState.tree.get( 'chicken' ) ).not.toBe( + replacedState.tree.get( 'chicken' ) ); const nestedBlock = { @@ -977,16 +1045,16 @@ describe( 'state', () => { blocks: [ replacementNestedBlock ], } ); - expect( replacedNestedState.order ).toEqual( { + expect( Object.fromEntries( replacedNestedState.order ) ).toEqual( { '': [ wrapperBlock.clientId ], [ wrapperBlock.clientId ]: [ replacementNestedBlock.clientId ], [ replacementNestedBlock.clientId ]: [], } ); - expect( originalNestedState.byClientId.chicken.name ).toBe( + expect( originalNestedState.byClientId.get( 'chicken' ).name ).toBe( 'core/test-block' ); - expect( replacedNestedState.byClientId.chicken.name ).toBe( + expect( replacedNestedState.byClientId.get( 'chicken' ).name ).toBe( 'core/freeform' ); } ); @@ -1013,19 +1081,19 @@ describe( 'state', () => { }, } ); - expect( state.byClientId.chicken ).toEqual( { + expect( state.byClientId.get( 'chicken' ) ).toEqual( { clientId: 'chicken', name: 'core/test-block', isValid: true, } ); - expect( state.attributes.chicken ).toEqual( { + expect( state.attributes.get( 'chicken' ) ).toEqual( { content: 'ribs', } ); - expect( state.tree[ '' ].innerBlocks[ 0 ] ).toBe( - state.tree.chicken + expect( state.tree.get( '' ).innerBlocks[ 0 ] ).toBe( + state.tree.get( 'chicken' ) ); - expect( state.tree.chicken ).toEqual( { + expect( state.tree.get( 'chicken' ) ).toEqual( { clientId: 'chicken', name: 'core/test-block', innerBlocks: [], @@ -1058,20 +1126,20 @@ describe( 'state', () => { updatedId: 3, } ); - expect( state.byClientId.chicken ).toEqual( { + expect( state.byClientId.get( 'chicken' ) ).toEqual( { clientId: 'chicken', name: 'core/block', isValid: false, } ); - expect( state.attributes.chicken ).toEqual( { + expect( state.attributes.get( 'chicken' ) ).toEqual( { ref: 3, } ); - expect( state.tree[ '' ].innerBlocks[ 0 ] ).toBe( - state.tree.chicken + expect( state.tree.get( '' ).innerBlocks[ 0 ] ).toBe( + state.tree.get( 'chicken' ) ); - expect( state.tree.chicken ).toEqual( { + expect( state.tree.get( 'chicken' ) ).toEqual( { clientId: 'chicken', name: 'core/block', isValid: false, @@ -1105,12 +1173,16 @@ describe( 'state', () => { clientIds: [ 'ribs' ], } ); - expect( state.order[ '' ] ).toEqual( [ 'ribs', 'chicken' ] ); - expect( state.tree[ '' ].innerBlocks[ 0 ] ).toBe( state.tree.ribs ); - expect( state.tree[ '' ].innerBlocks[ 1 ] ).toBe( - state.tree.chicken + expect( state.order.get( '' ) ).toEqual( [ 'ribs', 'chicken' ] ); + expect( state.tree.get( '' ).innerBlocks[ 0 ] ).toBe( + state.tree.get( 'ribs' ) + ); + expect( state.tree.get( '' ).innerBlocks[ 1 ] ).toBe( + state.tree.get( 'chicken' ) + ); + expect( state.tree.get( 'chicken' ) ).toBe( + original.tree.get( 'chicken' ) ); - expect( state.tree.chicken ).toBe( original.tree.chicken ); } ); it( 'should move the nested block up', () => { @@ -1130,7 +1202,7 @@ describe( 'state', () => { rootClientId: wrapperBlock.clientId, } ); - expect( state.order ).toEqual( { + expect( Object.fromEntries( state.order ) ).toEqual( { '': [ wrapperBlock.clientId ], [ wrapperBlock.clientId ]: [ movedBlock.clientId, @@ -1140,14 +1212,14 @@ describe( 'state', () => { [ siblingBlock.clientId ]: [], } ); - expect( state.tree[ wrapperBlock.clientId ].innerBlocks[ 0 ] ).toBe( - state.tree[ movedBlock.clientId ] - ); - expect( state.tree[ wrapperBlock.clientId ].innerBlocks[ 1 ] ).toBe( - state.tree[ siblingBlock.clientId ] - ); - expect( state.tree[ movedBlock.clientId ] ).toBe( - original.tree[ movedBlock.clientId ] + expect( + state.tree.get( wrapperBlock.clientId ).innerBlocks[ 0 ] + ).toBe( state.tree.get( movedBlock.clientId ) ); + expect( + state.tree.get( wrapperBlock.clientId ).innerBlocks[ 1 ] + ).toBe( state.tree.get( siblingBlock.clientId ) ); + expect( state.tree.get( movedBlock.clientId ) ).toBe( + original.tree.get( movedBlock.clientId ) ); } ); @@ -1180,7 +1252,7 @@ describe( 'state', () => { clientIds: [ 'ribs', 'veggies' ], } ); - expect( state.order[ '' ] ).toEqual( [ + expect( state.order.get( '' ) ).toEqual( [ 'ribs', 'veggies', 'chicken', @@ -1206,7 +1278,7 @@ describe( 'state', () => { rootClientId: wrapperBlock.clientId, } ); - expect( state.order ).toEqual( { + expect( Object.fromEntries( state.order ) ).toEqual( { '': [ wrapperBlock.clientId ], [ wrapperBlock.clientId ]: [ movedBlockA.clientId, @@ -1268,7 +1340,7 @@ describe( 'state', () => { clientIds: [ 'chicken' ], } ); - expect( state.order[ '' ] ).toEqual( [ 'ribs', 'chicken' ] ); + expect( state.order.get( '' ) ).toEqual( [ 'ribs', 'chicken' ] ); } ); it( 'should move the nested block down', () => { @@ -1288,7 +1360,7 @@ describe( 'state', () => { rootClientId: wrapperBlock.clientId, } ); - expect( state.order ).toEqual( { + expect( Object.fromEntries( state.order ) ).toEqual( { '': [ wrapperBlock.clientId ], [ wrapperBlock.clientId ]: [ siblingBlock.clientId, @@ -1328,7 +1400,7 @@ describe( 'state', () => { clientIds: [ 'chicken', 'ribs' ], } ); - expect( state.order[ '' ] ).toEqual( [ + expect( state.order.get( '' ) ).toEqual( [ 'veggies', 'chicken', 'ribs', @@ -1354,7 +1426,7 @@ describe( 'state', () => { rootClientId: wrapperBlock.clientId, } ); - expect( state.order ).toEqual( { + expect( Object.fromEntries( state.order ) ).toEqual( { '': [ wrapperBlock.clientId ], [ wrapperBlock.clientId ]: [ siblingBlock.clientId, @@ -1416,21 +1488,23 @@ describe( 'state', () => { clientIds: [ 'chicken' ], } ); - expect( state.order[ '' ] ).toEqual( [ 'ribs' ] ); - expect( state.order ).not.toHaveProperty( 'chicken' ); - expect( state.parents ).toEqual( { + expect( state.order.get( '' ) ).toEqual( [ 'ribs' ] ); + expect( Object.fromEntries( state.order ) ).not.toHaveProperty( + 'chicken' + ); + expect( Object.fromEntries( state.parents ) ).toEqual( { ribs: '', } ); - expect( state.byClientId ).toEqual( { + expect( Object.fromEntries( state.byClientId ) ).toEqual( { ribs: { clientId: 'ribs', name: 'core/test-block', }, } ); - expect( state.attributes ).toEqual( { + expect( Object.fromEntries( state.attributes ) ).toEqual( { ribs: {}, } ); - expect( state.tree[ '' ].innerBlocks ).toHaveLength( 1 ); + expect( state.tree.get( '' ).innerBlocks ).toHaveLength( 1 ); } ); it( 'should remove multiple blocks', () => { @@ -1462,19 +1536,23 @@ describe( 'state', () => { clientIds: [ 'chicken', 'veggies' ], } ); - expect( state.order[ '' ] ).toEqual( [ 'ribs' ] ); - expect( state.order ).not.toHaveProperty( 'chicken' ); - expect( state.order ).not.toHaveProperty( 'veggies' ); - expect( state.parents ).toEqual( { + expect( state.order.get( '' ) ).toEqual( [ 'ribs' ] ); + expect( Object.fromEntries( state.order ) ).not.toHaveProperty( + 'chicken' + ); + expect( Object.fromEntries( state.order ) ).not.toHaveProperty( + 'veggies' + ); + expect( Object.fromEntries( state.parents ) ).toEqual( { ribs: '', } ); - expect( state.byClientId ).toEqual( { + expect( Object.fromEntries( state.byClientId ) ).toEqual( { ribs: { clientId: 'ribs', name: 'core/test-block', }, } ); - expect( state.attributes ).toEqual( { + expect( Object.fromEntries( state.attributes ) ).toEqual( { ribs: {}, } ); } ); @@ -1496,11 +1574,11 @@ describe( 'state', () => { clientIds: [ block.clientId ], } ); - expect( state.byClientId ).toEqual( {} ); - expect( state.order ).toEqual( { + expect( state.byClientId ).toEqual( new Map() ); + expect( Object.fromEntries( state.order ) ).toEqual( { '': [], } ); - expect( state.parents ).toEqual( {} ); + expect( Object.fromEntries( state.parents ) ).toEqual( {} ); } ); it( 'should insert at the specified index', () => { @@ -1534,8 +1612,8 @@ describe( 'state', () => { ], } ); - expect( Object.keys( state.byClientId ) ).toHaveLength( 3 ); - expect( state.order[ '' ] ).toEqual( [ + expect( state.byClientId.size ).toBe( 3 ); + expect( state.order.get( '' ) ).toEqual( [ 'kumquat', 'persimmon', 'loquat', @@ -1572,7 +1650,7 @@ describe( 'state', () => { index: 0, } ); - expect( state.order[ '' ] ).toEqual( [ + expect( state.order.get( '' ) ).toEqual( [ 'ribs', 'chicken', 'veggies', @@ -1609,7 +1687,7 @@ describe( 'state', () => { index: 2, } ); - expect( state.order[ '' ] ).toEqual( [ + expect( state.order.get( '' ) ).toEqual( [ 'chicken', 'veggies', 'ribs', @@ -1646,7 +1724,7 @@ describe( 'state', () => { index: 1, } ); - expect( state.order[ '' ] ).toEqual( [ + expect( state.order.get( '' ) ).toEqual( [ 'chicken', 'ribs', 'veggies', @@ -1683,7 +1761,7 @@ describe( 'state', () => { index: 0, } ); - expect( state.order[ '' ] ).toEqual( [ + expect( state.order.get( '' ) ).toEqual( [ 'ribs', 'veggies', 'chicken', @@ -1722,8 +1800,11 @@ describe( 'state', () => { index: 0, } ); - expect( state.order[ '' ] ).toEqual( [ 'chicken' ] ); - expect( state.order.chicken ).toEqual( [ 'ribs', 'veggies' ] ); + expect( state.order.get( '' ) ).toEqual( [ 'chicken' ] ); + expect( state.order.get( 'chicken' ) ).toEqual( [ + 'ribs', + 'veggies', + ] ); } ); describe( 'blocks', () => { @@ -1795,7 +1876,42 @@ describe( 'state', () => { }, } ); - expect( state.attributes.kumquat.updated ).toBe( true ); + expect( state.attributes.get( 'kumquat' ).updated ).toBe( + true + ); + } ); + + it( 'should not updated equal attributes', () => { + const original = deepFreeze( + blocks( undefined, { + type: 'RESET_BLOCKS', + blocks: [ + { + clientId: 'kumquat', + attributes: {}, + innerBlocks: [], + }, + ], + } ) + ); + const state = blocks( original, { + type: 'UPDATE_BLOCK_ATTRIBUTES', + clientIds: [ 'kumquat' ], + attributes: { + updated: true, + }, + } ); + const updatedState = blocks( state, { + type: 'UPDATE_BLOCK_ATTRIBUTES', + clientIds: [ 'kumquat' ], + attributes: { + updated: true, + }, + } ); + + expect( state.attributes.get( 'kumquat' ) ).toBe( + updatedState.attributes.get( 'kumquat' ) + ); } ); it( 'should return with attribute block updates when attributes are unique by block', () => { @@ -1820,7 +1936,9 @@ describe( 'state', () => { uniqueByBlock: true, } ); - expect( state.attributes.kumquat.updated ).toBe( true ); + expect( state.attributes.get( 'kumquat' ).updated ).toBe( + true + ); } ); it( 'should accumulate attribute block updates', () => { @@ -1846,7 +1964,7 @@ describe( 'state', () => { }, } ); - expect( state.attributes.kumquat ).toEqual( { + expect( state.attributes.get( 'kumquat' ) ).toEqual( { updated: true, moreUpdated: true, } ); @@ -1914,7 +2032,7 @@ describe( 'state', () => { clientIds: [ 'kumquat' ], } ); - expect( state.attributes.kumquat ).toEqual( {} ); + expect( state.attributes.get( 'kumquat' ) ).toEqual( {} ); } ); } ); @@ -2111,105 +2229,115 @@ describe( 'state', () => { expect( state.controlledInnerBlocks.chicken ).toBe( true ); // The previous content of the block should be removed expect( state.byClientId.child ).toBeUndefined(); - expect( state.tree.child ).toBeUndefined(); - expect( state.tree.chicken.innerBlocks ).toEqual( [] ); + expect( state.tree.get( 'child' ) ).toBeUndefined(); + expect( state.tree.get( 'chicken' ).innerBlocks ).toEqual( + [] + ); } ); it( 'should preserve the controlled blocks in state and re-attach them in other pieces of state(order, tree, etc..), when we replace inner blocks', () => { const initialState = { - byClientId: { - 'group-id': { - clientId: 'group-id', - name: 'core/group', - isValid: true, - }, - 'reusable-id': { - clientId: 'reusable-id', - name: 'core/block', - isValid: true, - }, - 'paragraph-id': { - clientId: 'paragraph-id', - name: 'core/paragraph', - isValid: true, - }, - }, - order: { - '': [ 'group-id' ], - 'group-id': [ 'reusable-id' ], - 'reusable-id': [ 'paragraph-id' ], - 'paragraph-id': [], - }, + byClientId: new Map( + Object.entries( { + 'group-id': { + clientId: 'group-id', + name: 'core/group', + isValid: true, + }, + 'reusable-id': { + clientId: 'reusable-id', + name: 'core/block', + isValid: true, + }, + 'paragraph-id': { + clientId: 'paragraph-id', + name: 'core/paragraph', + isValid: true, + }, + } ) + ), + order: new Map( + Object.entries( { + '': [ 'group-id' ], + 'group-id': [ 'reusable-id' ], + 'reusable-id': [ 'paragraph-id' ], + 'paragraph-id': [], + } ) + ), controlledInnerBlocks: { 'reusable-id': true, }, - parents: { - 'group-id': '', - 'reusable-id': 'group-id', - 'paragraph-id': 'reusable-id', - }, - tree: { - 'group-id': { - clientId: 'group-id', - name: 'core/group', - isValid: true, - innerBlocks: [ - { - clientId: 'reusable-id', - name: 'core/block', - isValid: true, - attributes: { - ref: 687, + parents: new Map( + Object.entries( { + 'group-id': '', + 'reusable-id': 'group-id', + 'paragraph-id': 'reusable-id', + } ) + ), + tree: new Map( + Object.entries( { + 'group-id': { + clientId: 'group-id', + name: 'core/group', + isValid: true, + innerBlocks: [ + { + clientId: 'reusable-id', + name: 'core/block', + isValid: true, + attributes: { + ref: 687, + }, + innerBlocks: [], }, - innerBlocks: [], + ], + }, + 'reusable-id': { + clientId: 'reusable-id', + name: 'core/block', + isValid: true, + attributes: { + ref: 687, }, - ], - }, - 'reusable-id': { - clientId: 'reusable-id', - name: 'core/block', - isValid: true, - attributes: { - ref: 687, + innerBlocks: [], }, - innerBlocks: [], - }, - '': { - innerBlocks: [ - { - clientId: 'group-id', - name: 'core/group', - isValid: true, - innerBlocks: [ - { - clientId: 'reusable-id', - name: 'core/block', - isValid: true, - attributes: { - ref: 687, + '': { + innerBlocks: [ + { + clientId: 'group-id', + name: 'core/group', + isValid: true, + innerBlocks: [ + { + clientId: 'reusable-id', + name: 'core/block', + isValid: true, + attributes: { + ref: 687, + }, + innerBlocks: [], }, - innerBlocks: [], - }, - ], - }, - ], - }, - 'paragraph-id': { - clientId: 'paragraph-id', - name: 'core/paragraph', - isValid: true, - innerBlocks: [], - }, - 'controlled||reusable-id': { - innerBlocks: [ - { - clientId: 'paragraph-id', - name: 'core/paragraph', - isValid: true, - innerBlocks: [], - }, - ], - }, - }, + ], + }, + ], + }, + 'paragraph-id': { + clientId: 'paragraph-id', + name: 'core/paragraph', + isValid: true, + innerBlocks: [], + }, + 'controlled||reusable-id': { + innerBlocks: [ + { + clientId: 'paragraph-id', + name: 'core/paragraph', + isValid: true, + innerBlocks: [], + }, + ], + }, + } ) + ), }; // We will dispatch an action that replaces the inner // blocks with the same inner blocks, which contain @@ -2231,11 +2359,15 @@ describe( 'state', () => { updateSelection: false, }; const state = blocks( initialState, action ); - expect( state.order ).toEqual( - expect.objectContaining( initialState.order ) + expect( Object.fromEntries( state.order ) ).toEqual( + expect.objectContaining( + Object.fromEntries( initialState.order ) + ) ); - expect( state.tree ).toEqual( - expect.objectContaining( initialState.tree ) + expect( Object.fromEntries( state.tree ) ).toEqual( + expect.objectContaining( + Object.fromEntries( initialState.tree ) + ) ); } ); } ); diff --git a/packages/block-editor/src/store/test/selectors.js b/packages/block-editor/src/store/test/selectors.js index 95038fe2f58b8e..ee4e9ee4c167ab 100644 --- a/packages/block-editor/src/store/test/selectors.js +++ b/packages/block-editor/src/store/test/selectors.js @@ -73,6 +73,7 @@ const { __experimentalGetPatternTransformItems, wasBlockJustInserted, __experimentalGetGlobalBlocksByName, + getLastInsertedBlockClientId, } = selectors; describe( 'selectors', () => { @@ -202,10 +203,10 @@ describe( 'selectors', () => { it( 'returns null if no block by clientId', () => { const state = { blocks: { - byClientId: {}, + byClientId: new Map(), attributes: {}, - order: {}, - parents: {}, + order: new Map(), + parents: new Map(), }, }; @@ -220,22 +221,31 @@ describe( 'selectors', () => { it( 'returns block name', () => { const state = { blocks: { - byClientId: { - 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1': { - clientId: 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1', - name: 'core/paragraph', - }, - }, - attributes: { - 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1': {}, - }, - order: { - '': [ 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1' ], - 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1': [], - }, - parents: { - 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1': '', - }, + byClientId: new Map( + Object.entries( { + 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1': { + clientId: + 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1', + name: 'core/paragraph', + }, + } ) + ), + attributes: new Map( + Object.entries( { + 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1': {}, + } ) + ), + order: new Map( + Object.entries( { + '': [ 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1' ], + 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1': [], + } ) + ), + parents: new Map( + Object.entries( { + 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1': '', + } ) + ), }, }; @@ -252,54 +262,68 @@ describe( 'selectors', () => { it( 'should return the block', () => { const state = { blocks: { - byClientId: { - 123: { clientId: 123, name: 'core/paragraph' }, - }, - attributes: { - 123: {}, - }, - order: { - '': [ 123 ], - 123: [], - }, - parents: { - 123: '', - }, - tree: { - 123: { - clientId: 123, - name: 'core/paragraph', - attributes: {}, - innerBlocks: [], - }, - }, + byClientId: new Map( + Object.entries( { + 123: { clientId: '123', name: 'core/paragraph' }, + } ) + ), + attributes: new Map( + Object.entries( { + 123: {}, + } ) + ), + order: new Map( + Object.entries( { + '': [ '123' ], + 123: [], + } ) + ), + parents: new Map( + Object.entries( { + 123: '', + } ) + ), + tree: new Map( + Object.entries( { + 123: { + clientId: '123', + name: 'core/paragraph', + attributes: {}, + innerBlocks: [], + }, + } ) + ), controlledInnerBlocks: {}, }, }; - expect( getBlock( state, 123 ) ).toBe( state.blocks.tree[ 123 ] ); + expect( getBlock( state, '123' ) ).toBe( + state.blocks.tree.get( '123' ) + ); } ); it( 'should return null if the block is not present in state', () => { const state = { blocks: { - byClientId: {}, - attributes: {}, - order: {}, - parents: {}, - tree: { - 123: { - clientId: 123, - name: 'core/paragraph', - attributes: {}, - innerBlocks: [], - }, - }, + byClientId: new Map(), + attributes: new Map(), + order: new Map(), + parents: new Map(), + tree: new Map( + Object.entries( { + 123: { + clientId: '123', + name: 'core/paragraph', + attributes: {}, + innerBlocks: [], + }, + } ) + ), controlledInnerBlocks: {}, }, }; - expect( getBlock( state, 123 ) ).toBe( null ); + expect( getBlock( state, '123' ) ).toBe( null ); } ); } ); @@ -307,47 +331,57 @@ describe( 'selectors', () => { it( 'should return the ordered blocks', () => { const state = { blocks: { - byClientId: { - 23: { clientId: 23, name: 'core/heading' }, - 123: { clientId: 123, name: 'core/paragraph' }, - }, - attributes: { - 23: {}, - 123: {}, - }, - order: { - '': [ 123, 23 ], - }, - parents: { - 123: '', - 23: '', - }, - tree: { - '': { - innerBlocks: [ - { - clientId: 123, - name: 'core/paragraph', - attributes: {}, - innerBlocks: [], - }, - { - clientId: 23, - name: 'core/heading', - attributes: {}, - innerBlocks: [], - }, - ], - }, - 123: {}, - 23: {}, - }, + byClientId: new Map( + Object.entries( { + 23: { clientId: '23', name: 'core/heading' }, + 123: { clientId: '123', name: 'core/paragraph' }, + } ) + ), + attributes: new Map( + Object.entries( { + 23: {}, + 123: {}, + } ) + ), + order: new Map( + Object.entries( { + '': [ '123', '23' ], + } ) + ), + parents: new Map( + Object.entries( { + 123: '', + 23: '', + } ) + ), + tree: new Map( + Object.entries( { + '': { + innerBlocks: [ + { + clientId: '123', + name: 'core/paragraph', + attributes: {}, + innerBlocks: [], + }, + { + clientId: '23', + name: 'core/heading', + attributes: {}, + innerBlocks: [], + }, + ], + }, + 123: {}, + 23: {}, + } ) + ), controlledInnerBlocks: {}, }, }; expect( getBlocks( state ) ).toBe( - state.blocks.tree[ '' ].innerBlocks + state.blocks.tree.get( '' ).innerBlocks ); } ); } ); @@ -356,30 +390,34 @@ describe( 'selectors', () => { it( 'should return parent blocks', () => { const state = { blocks: { - parents: { - 'client-id-01': '', - 'client-id-02': 'client-id-01', - 'client-id-03': 'client-id-02', - 'client-id-04': 'client-id-03', - }, - byClientId: { - 'client-id-01': { - clientId: 'client-id-01', - name: 'core/columns', - }, - 'client-id-02': { - clientId: 'client-id-02', - name: 'core/navigation', - }, - 'client-id-03': { - clientId: 'client-id-03', - name: 'core/navigation-link', - }, - 'client-id-04': { - clientId: 'client-id-04', - name: 'core/paragraph', - }, - }, + parents: new Map( + Object.entries( { + 'client-id-01': '', + 'client-id-02': 'client-id-01', + 'client-id-03': 'client-id-02', + 'client-id-04': 'client-id-03', + } ) + ), + byClientId: new Map( + Object.entries( { + 'client-id-01': { + clientId: 'client-id-01', + name: 'core/columns', + }, + 'client-id-02': { + clientId: 'client-id-02', + name: 'core/navigation', + }, + 'client-id-03': { + clientId: 'client-id-03', + name: 'core/navigation-link', + }, + 'client-id-04': { + clientId: 'client-id-04', + name: 'core/paragraph', + }, + } ) + ), cache: { 'client-id-01': {}, 'client-id-02': {}, @@ -403,35 +441,39 @@ describe( 'selectors', () => { describe( 'getBlockParentsByBlockName', () => { const state = { blocks: { - parents: { - 'client-id-01': '', - 'client-id-02': 'client-id-01', - 'client-id-03': 'client-id-02', - 'client-id-04': 'client-id-03', - 'client-id-05': 'client-id-04', - }, - byClientId: { - 'client-id-01': { - clientId: 'client-id-01', - name: 'core/navigation', - }, - 'client-id-02': { - clientId: 'client-id-02', - name: 'core/columns', - }, - 'client-id-03': { - clientId: 'client-id-03', - name: 'core/navigation', - }, - 'client-id-04': { - clientId: 'client-id-04', - name: 'core/navigation-link', - }, - 'client-id-05': { - clientId: 'client-id-05', - name: 'core/navigation-link', - }, - }, + parents: new Map( + Object.entries( { + 'client-id-01': '', + 'client-id-02': 'client-id-01', + 'client-id-03': 'client-id-02', + 'client-id-04': 'client-id-03', + 'client-id-05': 'client-id-04', + } ) + ), + byClientId: new Map( + Object.entries( { + 'client-id-01': { + clientId: 'client-id-01', + name: 'core/navigation', + }, + 'client-id-02': { + clientId: 'client-id-02', + name: 'core/columns', + }, + 'client-id-03': { + clientId: 'client-id-03', + name: 'core/navigation', + }, + 'client-id-04': { + clientId: 'client-id-04', + name: 'core/navigation-link', + }, + 'client-id-05': { + clientId: 'client-id-05', + name: 'core/navigation-link', + }, + } ) + ), cache: { 'client-id-01': {}, 'client-id-02': {}, @@ -487,89 +529,124 @@ describe( 'selectors', () => { it( 'should return the ids of any descendants in sequential order, given an array of clientIds', () => { const state = { blocks: { - byClientId: { - 'uuid-2': { clientId: 'uuid-2', name: 'core/image' }, - 'uuid-4': { - clientId: 'uuid-4', - name: 'core/paragraph', - }, - 'uuid-6': { - clientId: 'uuid-6', - name: 'core/paragraph', - }, - 'uuid-8': { clientId: 'uuid-8', name: 'core/block' }, - 'uuid-10': { - clientId: 'uuid-10', - name: 'core/columns', - }, - 'uuid-12': { clientId: 'uuid-12', name: 'core/column' }, - 'uuid-14': { clientId: 'uuid-14', name: 'core/column' }, - 'uuid-16': { clientId: 'uuid-16', name: 'core/quote' }, - 'uuid-18': { clientId: 'uuid-18', name: 'core/block' }, - 'uuid-20': { - clientId: 'uuid-20', - name: 'core/gallery', - }, - 'uuid-22': { clientId: 'uuid-22', name: 'core/block' }, - 'uuid-24': { - clientId: 'uuid-24', - name: 'core/columns', - }, - 'uuid-26': { clientId: 'uuid-26', name: 'core/column' }, - 'uuid-28': { clientId: 'uuid-28', name: 'core/column' }, - 'uuid-30': { - clientId: 'uuid-30', - name: 'core/paragraph', - }, - }, - attributes: { - 'uuid-2': {}, - 'uuid-4': {}, - 'uuid-6': {}, - 'uuid-8': {}, - 'uuid-10': {}, - 'uuid-12': {}, - 'uuid-14': {}, - 'uuid-16': {}, - 'uuid-18': {}, - 'uuid-20': {}, - 'uuid-22': {}, - 'uuid-24': {}, - 'uuid-26': {}, - 'uuid-28': {}, - 'uuid-30': {}, - }, - order: { - '': [ 'uuid-6', 'uuid-8', 'uuid-10', 'uuid-22' ], - 'uuid-2': [], - 'uuid-4': [], - 'uuid-6': [], - 'uuid-8': [], - 'uuid-10': [ 'uuid-12', 'uuid-14' ], - 'uuid-12': [ 'uuid-16' ], - 'uuid-14': [ 'uuid-18' ], - 'uuid-16': [], - 'uuid-18': [ 'uuid-24' ], - 'uuid-20': [], - 'uuid-22': [], - 'uuid-24': [ 'uuid-26', 'uuid-28' ], - 'uuid-26': [], - 'uuid-28': [ 'uuid-30' ], - }, - parents: { - 'uuid-6': '', - 'uuid-8': '', - 'uuid-10': '', - 'uuid-22': '', - 'uuid-12': 'uuid-10', - 'uuid-14': 'uuid-10', - 'uuid-16': 'uuid-12', - 'uuid-18': 'uuid-14', - 'uuid-24': 'uuid-18', - 'uuid-26': 'uuid-24', - 'uuid-28': 'uuid-24', - 'uuid-30': 'uuid-28', - }, + byClientId: new Map( + Object.entries( { + 'uuid-2': { + clientId: 'uuid-2', + name: 'core/image', + }, + 'uuid-4': { + clientId: 'uuid-4', + name: 'core/paragraph', + }, + 'uuid-6': { + clientId: 'uuid-6', + name: 'core/paragraph', + }, + 'uuid-8': { + clientId: 'uuid-8', + name: 'core/block', + }, + 'uuid-10': { + clientId: 'uuid-10', + name: 'core/columns', + }, + 'uuid-12': { + clientId: 'uuid-12', + name: 'core/column', + }, + 'uuid-14': { + clientId: 'uuid-14', + name: 'core/column', + }, + 'uuid-16': { + clientId: 'uuid-16', + name: 'core/quote', + }, + 'uuid-18': { + clientId: 'uuid-18', + name: 'core/block', + }, + 'uuid-20': { + clientId: 'uuid-20', + name: 'core/gallery', + }, + 'uuid-22': { + clientId: 'uuid-22', + name: 'core/block', + }, + 'uuid-24': { + clientId: 'uuid-24', + name: 'core/columns', + }, + 'uuid-26': { + clientId: 'uuid-26', + name: 'core/column', + }, + 'uuid-28': { + clientId: 'uuid-28', + name: 'core/column', + }, + 'uuid-30': { + clientId: 'uuid-30', + name: 'core/paragraph', + }, + } ) + ), + attributes: new Map( + Object.entries( { + 'uuid-2': {}, + 'uuid-4': {}, + 'uuid-6': {}, + 'uuid-8': {}, + 'uuid-10': {}, + 'uuid-12': {}, + 'uuid-14': {}, + 'uuid-16': {}, + 'uuid-18': {}, + 'uuid-20': {}, + 'uuid-22': {}, + 'uuid-24': {}, + 'uuid-26': {}, + 'uuid-28': {}, + 'uuid-30': {}, + } ) + ), + order: new Map( + Object.entries( { + '': [ 'uuid-6', 'uuid-8', 'uuid-10', 'uuid-22' ], + 'uuid-2': [], + 'uuid-4': [], + 'uuid-6': [], + 'uuid-8': [], + 'uuid-10': [ 'uuid-12', 'uuid-14' ], + 'uuid-12': [ 'uuid-16' ], + 'uuid-14': [ 'uuid-18' ], + 'uuid-16': [], + 'uuid-18': [ 'uuid-24' ], + 'uuid-20': [], + 'uuid-22': [], + 'uuid-24': [ 'uuid-26', 'uuid-28' ], + 'uuid-26': [], + 'uuid-28': [ 'uuid-30' ], + } ) + ), + parents: new Map( + Object.entries( { + 'uuid-6': '', + 'uuid-8': '', + 'uuid-10': '', + 'uuid-22': '', + 'uuid-12': 'uuid-10', + 'uuid-14': 'uuid-10', + 'uuid-16': 'uuid-12', + 'uuid-18': 'uuid-14', + 'uuid-24': 'uuid-18', + 'uuid-26': 'uuid-24', + 'uuid-28': 'uuid-24', + 'uuid-30': 'uuid-28', + } ) + ), controlledInnerBlocks: {}, }, }; @@ -592,89 +669,124 @@ describe( 'selectors', () => { it( 'should return the ids for top-level blocks and their descendants of any depth (for nested blocks) in sequential order.', () => { const state = { blocks: { - byClientId: { - 'uuid-2': { clientId: 'uuid-2', name: 'core/image' }, - 'uuid-4': { - clientId: 'uuid-4', - name: 'core/paragraph', - }, - 'uuid-6': { - clientId: 'uuid-6', - name: 'core/paragraph', - }, - 'uuid-8': { clientId: 'uuid-8', name: 'core/block' }, - 'uuid-10': { - clientId: 'uuid-10', - name: 'core/columns', - }, - 'uuid-12': { clientId: 'uuid-12', name: 'core/column' }, - 'uuid-14': { clientId: 'uuid-14', name: 'core/column' }, - 'uuid-16': { clientId: 'uuid-16', name: 'core/quote' }, - 'uuid-18': { clientId: 'uuid-18', name: 'core/block' }, - 'uuid-20': { - clientId: 'uuid-20', - name: 'core/gallery', - }, - 'uuid-22': { clientId: 'uuid-22', name: 'core/block' }, - 'uuid-24': { - clientId: 'uuid-24', - name: 'core/columns', - }, - 'uuid-26': { clientId: 'uuid-26', name: 'core/column' }, - 'uuid-28': { clientId: 'uuid-28', name: 'core/column' }, - 'uuid-30': { - clientId: 'uuid-30', - name: 'core/paragraph', - }, - }, - attributes: { - 'uuid-2': {}, - 'uuid-4': {}, - 'uuid-6': {}, - 'uuid-8': {}, - 'uuid-10': {}, - 'uuid-12': {}, - 'uuid-14': {}, - 'uuid-16': {}, - 'uuid-18': {}, - 'uuid-20': {}, - 'uuid-22': {}, - 'uuid-24': {}, - 'uuid-26': {}, - 'uuid-28': {}, - 'uuid-30': {}, - }, - order: { - '': [ 'uuid-6', 'uuid-8', 'uuid-10', 'uuid-22' ], - 'uuid-2': [], - 'uuid-4': [], - 'uuid-6': [], - 'uuid-8': [], - 'uuid-10': [ 'uuid-12', 'uuid-14' ], - 'uuid-12': [ 'uuid-16' ], - 'uuid-14': [ 'uuid-18' ], - 'uuid-16': [], - 'uuid-18': [ 'uuid-24' ], - 'uuid-20': [], - 'uuid-22': [], - 'uuid-24': [ 'uuid-26', 'uuid-28' ], - 'uuid-26': [], - 'uuid-28': [ 'uuid-30' ], - }, - parents: { - 'uuid-6': '', - 'uuid-8': '', - 'uuid-10': '', - 'uuid-22': '', - 'uuid-12': 'uuid-10', - 'uuid-14': 'uuid-10', - 'uuid-16': 'uuid-12', - 'uuid-18': 'uuid-14', - 'uuid-24': 'uuid-18', - 'uuid-26': 'uuid-24', - 'uuid-28': 'uuid-24', - 'uuid-30': 'uuid-28', - }, + byClientId: new Map( + Object.entries( { + 'uuid-2': { + clientId: 'uuid-2', + name: 'core/image', + }, + 'uuid-4': { + clientId: 'uuid-4', + name: 'core/paragraph', + }, + 'uuid-6': { + clientId: 'uuid-6', + name: 'core/paragraph', + }, + 'uuid-8': { + clientId: 'uuid-8', + name: 'core/block', + }, + 'uuid-10': { + clientId: 'uuid-10', + name: 'core/columns', + }, + 'uuid-12': { + clientId: 'uuid-12', + name: 'core/column', + }, + 'uuid-14': { + clientId: 'uuid-14', + name: 'core/column', + }, + 'uuid-16': { + clientId: 'uuid-16', + name: 'core/quote', + }, + 'uuid-18': { + clientId: 'uuid-18', + name: 'core/block', + }, + 'uuid-20': { + clientId: 'uuid-20', + name: 'core/gallery', + }, + 'uuid-22': { + clientId: 'uuid-22', + name: 'core/block', + }, + 'uuid-24': { + clientId: 'uuid-24', + name: 'core/columns', + }, + 'uuid-26': { + clientId: 'uuid-26', + name: 'core/column', + }, + 'uuid-28': { + clientId: 'uuid-28', + name: 'core/column', + }, + 'uuid-30': { + clientId: 'uuid-30', + name: 'core/paragraph', + }, + } ) + ), + attributes: new Map( + Object.entries( { + 'uuid-2': {}, + 'uuid-4': {}, + 'uuid-6': {}, + 'uuid-8': {}, + 'uuid-10': {}, + 'uuid-12': {}, + 'uuid-14': {}, + 'uuid-16': {}, + 'uuid-18': {}, + 'uuid-20': {}, + 'uuid-22': {}, + 'uuid-24': {}, + 'uuid-26': {}, + 'uuid-28': {}, + 'uuid-30': {}, + } ) + ), + order: new Map( + Object.entries( { + '': [ 'uuid-6', 'uuid-8', 'uuid-10', 'uuid-22' ], + 'uuid-2': [], + 'uuid-4': [], + 'uuid-6': [], + 'uuid-8': [], + 'uuid-10': [ 'uuid-12', 'uuid-14' ], + 'uuid-12': [ 'uuid-16' ], + 'uuid-14': [ 'uuid-18' ], + 'uuid-16': [], + 'uuid-18': [ 'uuid-24' ], + 'uuid-20': [], + 'uuid-22': [], + 'uuid-24': [ 'uuid-26', 'uuid-28' ], + 'uuid-26': [], + 'uuid-28': [ 'uuid-30' ], + } ) + ), + parents: new Map( + Object.entries( { + 'uuid-6': '', + 'uuid-8': '', + 'uuid-10': '', + 'uuid-22': '', + 'uuid-12': 'uuid-10', + 'uuid-14': 'uuid-10', + 'uuid-16': 'uuid-12', + 'uuid-18': 'uuid-14', + 'uuid-24': 'uuid-18', + 'uuid-26': 'uuid-24', + 'uuid-28': 'uuid-24', + 'uuid-30': 'uuid-28', + } ) + ), }, }; expect( getClientIdsWithDescendants( state ) ).toEqual( [ @@ -698,17 +810,23 @@ describe( 'selectors', () => { it( 'should return the number of top-level blocks in the post', () => { const state = { blocks: { - byClientId: { - 23: { clientId: 23, name: 'core/heading' }, - 123: { clientId: 123, name: 'core/paragraph' }, - }, - attributes: { - 23: {}, - 123: {}, - }, - order: { - '': [ 123, 23 ], - }, + byClientId: new Map( + Object.entries( { + 23: { clientId: '23', name: 'core/heading' }, + 123: { clientId: '123', name: 'core/paragraph' }, + } ) + ), + attributes: new Map( + Object.entries( { + 23: {}, + 123: {}, + } ) + ), + order: new Map( + Object.entries( { + '': [ '123', '23' ], + } ) + ), }, }; @@ -718,25 +836,33 @@ describe( 'selectors', () => { it( 'should return the number of blocks in a nested context', () => { const state = { blocks: { - byClientId: { - 123: { clientId: 123, name: 'core/columns' }, - 456: { clientId: 456, name: 'core/paragraph' }, - 789: { clientId: 789, name: 'core/paragraph' }, - }, - attributes: { - 123: {}, - 456: {}, - 789: {}, - }, - order: { - '': [ 123 ], - 123: [ 456, 789 ], - }, - parents: { - 123: '', - 456: 123, - 789: 123, - }, + byClientId: new Map( + Object.entries( { + 123: { clientId: '123', name: 'core/columns' }, + 456: { clientId: '456', name: 'core/paragraph' }, + 789: { clientId: '789', name: 'core/paragraph' }, + } ) + ), + attributes: new Map( + Object.entries( { + 123: {}, + 456: {}, + 789: {}, + } ) + ), + order: new Map( + Object.entries( { + '': [ '123' ], + 123: [ '456', '789' ], + } ) + ), + parents: new Map( + Object.entries( { + 123: '', + 456: '123', + 789: '123', + } ) + ), }, }; @@ -790,23 +916,31 @@ describe( 'selectors', () => { describe( 'getGlobalBlockCount', () => { const state = { blocks: { - byClientId: { - 123: { clientId: 123, name: 'core/heading' }, - 456: { clientId: 456, name: 'core/paragraph' }, - 789: { clientId: 789, name: 'core/paragraph' }, - }, - attributes: { - 123: {}, - 456: {}, - 789: {}, - }, - order: { - '': [ 123, 456 ], - }, - parents: { - 123: '', - 456: '', - }, + byClientId: new Map( + Object.entries( { + 123: { clientId: '123', name: 'core/heading' }, + 456: { clientId: '456', name: 'core/paragraph' }, + 789: { clientId: '789', name: 'core/paragraph' }, + } ) + ), + attributes: new Map( + Object.entries( { + 123: {}, + 456: {}, + 789: {}, + } ) + ), + order: new Map( + Object.entries( { + '': [ '123', '456' ], + } ) + ), + parents: new Map( + Object.entries( { + 123: '', + 456: '', + } ) + ), }, }; @@ -821,10 +955,10 @@ describe( 'selectors', () => { it( 'should return 0 if no blocks exist', () => { const emptyState = { blocks: { - byClientId: {}, - attributes: {}, - order: {}, - parents: {}, + byClientId: new Map(), + attributes: new Map(), + order: new Map(), + parents: new Map(), }, }; expect( getGlobalBlockCount( emptyState ) ).toBe( 0 ); @@ -837,46 +971,54 @@ describe( 'selectors', () => { describe( '__experimentalGetGlobalBlocksByName', () => { const state = { blocks: { - byClientId: { - 123: { clientId: 123, name: 'core/heading' }, - 456: { clientId: 456, name: 'core/paragraph' }, - 789: { clientId: 789, name: 'core/paragraph' }, - 1011: { clientId: 1011, name: 'core/group' }, - 1213: { clientId: 1213, name: 'core/paragraph' }, - 1415: { clientId: 1213, name: 'core/paragraph' }, - }, - attributes: { - 123: {}, - 456: {}, - 789: {}, - 1011: {}, - 1213: {}, - 1415: {}, - }, - order: { - '': [ 123, 456, 1011 ], - 1011: [ 1415, 1213 ], - }, - parents: { - 123: '', - 456: '', - 1011: '', - 1213: 1011, - 1415: 1011, - }, + byClientId: new Map( + Object.entries( { + 123: { clientId: '123', name: 'core/heading' }, + 456: { clientId: '456', name: 'core/paragraph' }, + 789: { clientId: '789', name: 'core/paragraph' }, + 1011: { clientId: '1011', name: 'core/group' }, + 1213: { clientId: '1213', name: 'core/paragraph' }, + 1415: { clientId: '1213', name: 'core/paragraph' }, + } ) + ), + attributes: new Map( + Object.entries( { + 123: {}, + 456: {}, + 789: {}, + 1011: {}, + 1213: {}, + 1415: {}, + } ) + ), + order: new Map( + Object.entries( { + '': [ '123', '456', '1011' ], + 1011: [ '1415', '1213' ], + } ) + ), + parents: new Map( + Object.entries( { + 123: '', + 456: '', + 1011: '', + 1213: '1011', + 1415: '1011', + } ) + ), }, }; it( 'should return the clientIds of blocks of a given type', () => { expect( __experimentalGetGlobalBlocksByName( state, 'core/heading' ) - ).toStrictEqual( [ 123 ] ); + ).toStrictEqual( [ '123' ] ); } ); it( 'should return the clientIds of blocks of a given type even if blocks are nested', () => { expect( __experimentalGetGlobalBlocksByName( state, 'core/paragraph' ) - ).toStrictEqual( [ 456, 1415, 1213 ] ); + ).toStrictEqual( [ '456', '1415', '1213' ] ); } ); it( 'Should return empty array if no blocks match. The empty array should be the same reference', () => { @@ -911,8 +1053,8 @@ describe( 'selectors', () => { it( 'should return null if there is multi selection', () => { const state = { selection: { - selectionStart: { clientId: 23 }, - selectionEnd: { clientId: 123 }, + selectionStart: { clientId: '23' }, + selectionEnd: { clientId: '123' }, }, }; @@ -922,19 +1064,21 @@ describe( 'selectors', () => { it( 'should return the selected block ClientId', () => { const state = { blocks: { - byClientId: { - 23: { - name: 'fake block', - }, - }, + byClientId: new Map( + Object.entries( { + 23: { + name: 'fake block', + }, + } ) + ), }, selection: { - selectionStart: { clientId: 23 }, - selectionEnd: { clientId: 23 }, + selectionStart: { clientId: '23' }, + selectionEnd: { clientId: '23' }, }, }; - expect( getSelectedBlockClientId( state ) ).toEqual( 23 ); + expect( getSelectedBlockClientId( state ) ).toEqual( '23' ); } ); } ); @@ -942,31 +1086,41 @@ describe( 'selectors', () => { it( 'should return null if no block is selected', () => { const state = { blocks: { - byClientId: { - 23: { clientId: 23, name: 'core/heading' }, - 123: { clientId: 123, name: 'core/paragraph' }, - }, - attributes: { - 23: {}, - 123: {}, - }, - order: { - '': [ 23, 123 ], - 23: [], - 123: [], - }, - parents: { - 23: '', - 123: '', - }, - tree: { - 23: { - clientId: 23, - name: 'core/heading', - attributes: {}, - innerBlocks: [], - }, - }, + byClientId: new Map( + Object.entries( { + 23: { clientId: '23', name: 'core/heading' }, + 123: { clientId: '123', name: 'core/paragraph' }, + } ) + ), + attributes: new Map( + Object.entries( { + 23: {}, + 123: {}, + } ) + ), + order: new Map( + Object.entries( { + '': [ '23', '123' ], + 23: [], + 123: [], + } ) + ), + parents: new Map( + Object.entries( { + 23: '', + 123: '', + } ) + ), + tree: new Map( + Object.entries( { + 23: { + clientId: '23', + name: 'core/heading', + attributes: {}, + innerBlocks: [], + }, + } ) + ), }, selection: { selectionStart: {}, @@ -980,35 +1134,45 @@ describe( 'selectors', () => { it( 'should return null if there is multi selection', () => { const state = { blocks: { - byClientId: { - 23: { clientId: 23, name: 'core/heading' }, - 123: { clientId: 123, name: 'core/paragraph' }, - }, - attributes: { - 23: {}, - 123: {}, - }, - order: { - '': [ 23, 123 ], - 23: [], - 123: [], - }, - parents: { - 123: '', - 23: '', - }, - tree: { - 23: { - clientId: 23, - name: 'core/heading', - attributes: {}, - innerBlocks: [], - }, - }, + byClientId: new Map( + Object.entries( { + 23: { clientId: '23', name: 'core/heading' }, + 123: { clientId: '123', name: 'core/paragraph' }, + } ) + ), + attributes: new Map( + Object.entries( { + 23: {}, + 123: {}, + } ) + ), + order: new Map( + Object.entries( { + '': [ '23', '123' ], + 23: [], + 123: [], + } ) + ), + parents: new Map( + Object.entries( { + 123: '', + 23: '', + } ) + ), + tree: new Map( + Object.entries( { + 23: { + clientId: '23', + name: 'core/heading', + attributes: {}, + innerBlocks: [], + }, + } ) + ), }, selection: { - selectionStart: { clientId: 23 }, - selectionEnd: { clientId: 123 }, + selectionStart: { clientId: '23' }, + selectionEnd: { clientId: '123' }, }, }; @@ -1018,41 +1182,51 @@ describe( 'selectors', () => { it( 'should return the selected block', () => { const state = { blocks: { - byClientId: { - 23: { clientId: 23, name: 'core/heading' }, - 123: { clientId: 123, name: 'core/paragraph' }, - }, - attributes: { - 23: {}, - 123: {}, - }, - order: { - '': [ 23, 123 ], - 23: [], - 123: [], - }, - parents: { - 123: '', - 23: '', - }, - tree: { - 23: { - clientId: 23, - name: 'core/heading', - attributes: {}, - innerBlocks: [], - }, - }, + byClientId: new Map( + Object.entries( { + 23: { clientId: '23', name: 'core/heading' }, + 123: { clientId: '123', name: 'core/paragraph' }, + } ) + ), + attributes: new Map( + Object.entries( { + 23: {}, + 123: {}, + } ) + ), + order: new Map( + Object.entries( { + '': [ '23', '123' ], + 23: [], + 123: [], + } ) + ), + parents: new Map( + Object.entries( { + 123: '', + 23: '', + } ) + ), + tree: new Map( + Object.entries( { + 23: { + clientId: '23', + name: 'core/heading', + attributes: {}, + innerBlocks: [], + }, + } ) + ), controlledInnerBlocks: {}, }, selection: { - selectionStart: { clientId: 23 }, - selectionEnd: { clientId: 23 }, + selectionStart: { clientId: '23' }, + selectionEnd: { clientId: '23' }, }, }; expect( getSelectedBlock( state ) ).toEqual( - getBlock( state, 23 ) + getBlock( state, '23' ) ); } ); } ); @@ -1061,8 +1235,8 @@ describe( 'selectors', () => { it( 'should return null if the block does not exist', () => { const state = { blocks: { - order: {}, - parents: {}, + order: new Map(), + parents: new Map(), }, }; @@ -1072,20 +1246,24 @@ describe( 'selectors', () => { it( 'should return root ClientId relative the block ClientId', () => { const state = { blocks: { - order: { - '': [ 123, 23 ], - 123: [ 456, 56 ], - }, - parents: { - 123: '', - 23: '', - 456: 123, - 56: 123, - }, + order: new Map( + Object.entries( { + '': [ '123', '23' ], + 123: [ '456', '56' ], + } ) + ), + parents: new Map( + Object.entries( { + 123: '', + 23: '', + 456: '123', + 56: '123', + } ) + ), }, }; - expect( getBlockRootClientId( state, 56 ) ).toBe( 123 ); + expect( getBlockRootClientId( state, '56' ) ).toBe( '123' ); } ); } ); @@ -1093,8 +1271,8 @@ describe( 'selectors', () => { it( 'should return the given block if the block has no parents', () => { const state = { blocks: { - order: {}, - parents: {}, + order: new Map(), + parents: new Map(), }, }; @@ -1104,16 +1282,20 @@ describe( 'selectors', () => { it( 'should return root ClientId relative the block ClientId', () => { const state = { blocks: { - order: { - '': [ 'a', 'b' ], - a: [ 'c', 'd' ], - }, - parents: { - a: '', - b: '', - c: 'a', - d: 'a', - }, + order: new Map( + Object.entries( { + '': [ 'a', 'b' ], + a: [ 'c', 'd' ], + } ) + ), + parents: new Map( + Object.entries( { + a: '', + b: '', + c: 'a', + d: 'a', + } ) + ), }, }; @@ -1123,18 +1305,22 @@ describe( 'selectors', () => { it( 'should return the top level root ClientId relative the block ClientId', () => { const state = { blocks: { - order: { - '': [ 'a', 'b' ], - a: [ 'c', 'd' ], - d: [ 'e' ], - }, - parents: { - a: '', - b: '', - c: 'a', - d: 'a', - e: 'd', - }, + order: new Map( + Object.entries( { + '': [ 'a', 'b' ], + a: [ 'c', 'd' ], + d: [ 'e' ], + } ) + ), + parents: new Map( + Object.entries( { + a: '', + b: '', + c: 'a', + d: 'a', + e: 'd', + } ) + ), }, }; @@ -1146,13 +1332,17 @@ describe( 'selectors', () => { it( 'should return empty if there is no selection', () => { const state = { blocks: { - order: { - '': [ 123, 23 ], - }, - parents: { - 123: '', - 23: '', - }, + order: new Map( + Object.entries( { + '': [ '123', '23' ], + } ) + ), + parents: new Map( + Object.entries( { + 123: '', + 23: '', + } ) + ), }, selection: { selectionStart: {}, @@ -1166,75 +1356,95 @@ describe( 'selectors', () => { it( 'should return the selected block clientId if there is a selection', () => { const state = { blocks: { - order: { - '': [ 5, 4, 3, 2, 1 ], - }, - parents: { - 1: '', - 2: '', - 3: '', - 4: '', - 5: '', - }, + order: new Map( + Object.entries( { + '': [ '5', '4', '3', '2', '1' ], + } ) + ), + parents: new Map( + Object.entries( { + 1: '', + 2: '', + 3: '', + 4: '', + 5: '', + } ) + ), }, selection: { - selectionStart: { clientId: 2 }, - selectionEnd: { clientId: 2 }, + selectionStart: { clientId: '2' }, + selectionEnd: { clientId: '2' }, }, }; - expect( getSelectedBlockClientIds( state ) ).toEqual( [ 2 ] ); + expect( getSelectedBlockClientIds( state ) ).toEqual( [ '2' ] ); } ); it( 'should return selected block clientIds if there is multi selection', () => { const state = { blocks: { - order: { - '': [ 5, 4, 3, 2, 1 ], - }, - parents: { - 1: '', - 2: '', - 3: '', - 4: '', - 5: '', - }, + order: new Map( + Object.entries( { + '': [ '5', '4', '3', '2', '1' ], + } ) + ), + parents: new Map( + Object.entries( { + 1: '', + 2: '', + 3: '', + 4: '', + 5: '', + } ) + ), }, selection: { - selectionStart: { clientId: 2 }, - selectionEnd: { clientId: 4 }, + selectionStart: { clientId: '2' }, + selectionEnd: { clientId: '4' }, }, }; - expect( getSelectedBlockClientIds( state ) ).toEqual( [ 4, 3, 2 ] ); + expect( getSelectedBlockClientIds( state ) ).toEqual( [ + '4', + '3', + '2', + ] ); } ); it( 'should return selected block clientIds if there is multi selection (nested context)', () => { const state = { blocks: { - order: { - '': [ 5, 4, 3, 2, 1 ], - 4: [ 9, 8, 7, 6 ], - }, - parents: { - 1: '', - 2: '', - 3: '', - 4: '', - 5: '', - 6: 4, - 7: 4, - 8: 4, - 9: 4, - }, + order: new Map( + Object.entries( { + '': [ '5', '4', '3', '2', '1' ], + 4: [ '9', '8', '7', '6' ], + } ) + ), + parents: new Map( + Object.entries( { + 1: '', + 2: '', + 3: '', + 4: '', + 5: '', + 6: '4', + 7: '4', + 8: '4', + 9: '4', + } ) + ), }, selection: { - selectionStart: { clientId: 7 }, - selectionEnd: { clientId: 9 }, + selectionStart: { clientId: '7' }, + selectionEnd: { clientId: '9' }, }, }; - expect( getSelectedBlockClientIds( state ) ).toEqual( [ 9, 8, 7 ] ); + expect( getSelectedBlockClientIds( state ) ).toEqual( [ + '9', + '8', + '7', + ] ); } ); } ); @@ -1242,13 +1452,17 @@ describe( 'selectors', () => { it( 'should return empty if there is no multi selection', () => { const state = { blocks: { - order: { - '': [ 123, 23 ], - }, - parents: { - 23: '', - 123: '', - }, + order: new Map( + Object.entries( { + '': [ '123', '23' ], + } ) + ), + parents: new Map( + Object.entries( { + 23: '', + 123: '', + } ) + ), }, selection: { selectionStart: {}, @@ -1262,55 +1476,67 @@ describe( 'selectors', () => { it( 'should return selected block clientIds if there is multi selection', () => { const state = { blocks: { - order: { - '': [ 5, 4, 3, 2, 1 ], - }, - parents: { - 1: '', - 2: '', - 3: '', - 4: '', - 5: '', - }, + order: new Map( + Object.entries( { + '': [ '5', '4', '3', '2', '1' ], + } ) + ), + parents: new Map( + Object.entries( { + 1: '', + 2: '', + 3: '', + 4: '', + 5: '', + } ) + ), }, selection: { - selectionStart: { clientId: 2 }, - selectionEnd: { clientId: 4 }, + selectionStart: { clientId: '2' }, + selectionEnd: { clientId: '4' }, }, }; expect( getMultiSelectedBlockClientIds( state ) ).toEqual( [ - 4, 3, 2, + '4', + '3', + '2', ] ); } ); it( 'should return selected block clientIds if there is multi selection (nested context)', () => { const state = { blocks: { - order: { - '': [ 5, 4, 3, 2, 1 ], - 4: [ 9, 8, 7, 6 ], - }, - parents: { - 1: '', - 2: '', - 3: '', - 4: '', - 5: '', - 6: 4, - 7: 4, - 8: 4, - 9: 4, - }, + order: new Map( + Object.entries( { + '': [ '5', '4', '3', '2', '1' ], + 4: [ '9', '8', '7', '6' ], + } ) + ), + parents: new Map( + Object.entries( { + 1: '', + 2: '', + 3: '', + 4: '', + 5: '', + 6: '4', + 7: '4', + 8: '4', + 9: '4', + } ) + ), }, selection: { - selectionStart: { clientId: 7 }, - selectionEnd: { clientId: 9 }, + selectionStart: { clientId: '7' }, + selectionEnd: { clientId: '9' }, }, }; expect( getMultiSelectedBlockClientIds( state ) ).toEqual( [ - 9, 8, 7, + '9', + '8', + '7', ] ); } ); } ); @@ -1319,10 +1545,10 @@ describe( 'selectors', () => { it( 'should return the same reference on subsequent invocations of empty selection', () => { const state = { blocks: { - byClientId: {}, - attributes: {}, - order: {}, - parents: {}, + byClientId: new Map(), + attributes: new Map(), + order: new Map(), + parents: new Map(), }, selection: { selectionStart: {}, @@ -1351,12 +1577,12 @@ describe( 'selectors', () => { it( 'returns multi selection start', () => { const state = { selection: { - selectionStart: { clientId: 2 }, - selectionEnd: { clientId: 4 }, + selectionStart: { clientId: '2' }, + selectionEnd: { clientId: '4' }, }, }; - expect( getMultiSelectedBlocksStartClientId( state ) ).toBe( 2 ); + expect( getMultiSelectedBlocksStartClientId( state ) ).toBe( '2' ); } ); } ); @@ -1375,12 +1601,12 @@ describe( 'selectors', () => { it( 'returns multi selection end', () => { const state = { selection: { - selectionStart: { clientId: 2 }, - selectionEnd: { clientId: 4 }, + selectionStart: { clientId: '2' }, + selectionEnd: { clientId: '4' }, }, }; - expect( getMultiSelectedBlocksEndClientId( state ) ).toBe( 4 ); + expect( getMultiSelectedBlocksEndClientId( state ) ).toBe( '4' ); } ); } ); @@ -1388,35 +1614,43 @@ describe( 'selectors', () => { it( 'should return the ordered block ClientIds of top-level blocks by default', () => { const state = { blocks: { - order: { - '': [ 123, 23 ], - }, - parents: { - 23: '', - 123: '', - }, + order: new Map( + Object.entries( { + '': [ '123', '23' ], + } ) + ), + parents: new Map( + Object.entries( { + 23: '', + 123: '', + } ) + ), }, }; - expect( getBlockOrder( state ) ).toEqual( [ 123, 23 ] ); + expect( getBlockOrder( state ) ).toEqual( [ '123', '23' ] ); } ); it( 'should return the ordered block ClientIds at a specified rootClientId', () => { const state = { blocks: { - order: { - '': [ 123, 23 ], - 123: [ 456 ], - }, - parents: { - 23: '', - 123: '', - 456: 123, - }, + order: new Map( + Object.entries( { + '': [ '123', '23' ], + 123: [ '456' ], + } ) + ), + parents: new Map( + Object.entries( { + 23: '', + 123: '', + 456: '123', + } ) + ), }, }; - expect( getBlockOrder( state, '123' ) ).toEqual( [ 456 ] ); + expect( getBlockOrder( state, '123' ) ).toEqual( [ '456' ] ); } ); } ); @@ -1424,36 +1658,44 @@ describe( 'selectors', () => { it( 'should return the block order', () => { const state = { blocks: { - order: { - '': [ 123, 23 ], - }, - parents: { - 23: '', - 123: '', - }, + order: new Map( + Object.entries( { + '': [ '123', '23' ], + } ) + ), + parents: new Map( + Object.entries( { + 23: '', + 123: '', + } ) + ), }, }; - expect( getBlockIndex( state, 23 ) ).toBe( 1 ); + expect( getBlockIndex( state, '23' ) ).toBe( 1 ); } ); it( 'should return the block order (nested context)', () => { const state = { blocks: { - order: { - '': [ 123, 23 ], - 123: [ 456, 56 ], - }, - parents: { - 23: '', - 123: '', - 56: 123, - 456: 123, - }, + order: new Map( + Object.entries( { + '': [ '123', '23' ], + 123: [ '456', '56' ], + } ) + ), + parents: new Map( + Object.entries( { + 23: '', + 123: '', + 56: '123', + 456: '123', + } ) + ), }, }; - expect( getBlockIndex( state, 56 ) ).toBe( 1 ); + expect( getBlockIndex( state, '56' ) ).toBe( 1 ); } ); } ); @@ -1461,73 +1703,91 @@ describe( 'selectors', () => { it( 'should return the previous block', () => { const state = { blocks: { - order: { - '': [ 123, 23 ], - }, - parents: { - 23: '', - 123: '', - }, + order: new Map( + Object.entries( { + '': [ '123', '23' ], + } ) + ), + parents: new Map( + Object.entries( { + 23: '', + 123: '', + } ) + ), }, }; - expect( getPreviousBlockClientId( state, 23 ) ).toEqual( 123 ); + expect( getPreviousBlockClientId( state, '23' ) ).toEqual( '123' ); } ); it( 'should return the previous block (nested context)', () => { const state = { blocks: { - order: { - '': [ 123, 23 ], - 123: [ 456, 56 ], - }, - parents: { - 23: '', - 123: '', - 456: 123, - 56: 123, - }, - }, - }; - - expect( getPreviousBlockClientId( state, 56, '123' ) ).toEqual( - 456 + order: new Map( + Object.entries( { + '': [ '123', '23' ], + 123: [ '456', '56' ], + } ) + ), + parents: new Map( + Object.entries( { + 23: '', + 123: '', + 456: '123', + 56: '123', + } ) + ), + }, + }; + + expect( getPreviousBlockClientId( state, '56', '123' ) ).toEqual( + '456' ); } ); it( 'should return null for the first block', () => { const state = { blocks: { - order: { - '': [ 123, 23 ], - }, - parents: { - 23: '', - 123: '', - }, + order: new Map( + Object.entries( { + '': [ '123', '23' ], + } ) + ), + parents: new Map( + Object.entries( { + 23: '', + 123: '', + } ) + ), }, }; - expect( getPreviousBlockClientId( state, 123 ) ).toBeNull(); + expect( getPreviousBlockClientId( state, '123' ) ).toBeNull(); } ); it( 'should return null for the first block (nested context)', () => { const state = { blocks: { - order: { - '': [ 123, 23 ], - 123: [ 456, 56 ], - }, - parents: { - 23: '', - 123: '', - 456: 123, - 56: 123, - }, + order: new Map( + Object.entries( { + '': [ '123', '23' ], + 123: [ '456', '56' ], + } ) + ), + parents: new Map( + Object.entries( { + 23: '', + 123: '', + 456: '123', + 56: '123', + } ) + ), }, }; - expect( getPreviousBlockClientId( state, 456, '123' ) ).toBeNull(); + expect( + getPreviousBlockClientId( state, '456', '123' ) + ).toBeNull(); } ); } ); @@ -1535,71 +1795,89 @@ describe( 'selectors', () => { it( 'should return the following block', () => { const state = { blocks: { - order: { - '': [ 123, 23 ], - }, - parents: { - 23: '', - 123: '', - }, + order: new Map( + Object.entries( { + '': [ '123', '23' ], + } ) + ), + parents: new Map( + Object.entries( { + 23: '', + 123: '', + } ) + ), }, }; - expect( getNextBlockClientId( state, 123 ) ).toEqual( 23 ); + expect( getNextBlockClientId( state, '123' ) ).toEqual( '23' ); } ); it( 'should return the following block (nested context)', () => { const state = { blocks: { - order: { - '': [ 123, 23 ], - 123: [ 456, 56 ], - }, - parents: { - 23: '', - 123: '', - 456: 123, - 56: 123, - }, - }, - }; - - expect( getNextBlockClientId( state, 456, '123' ) ).toEqual( 56 ); + order: new Map( + Object.entries( { + '': [ '123', '23' ], + 123: [ '456', '56' ], + } ) + ), + parents: new Map( + Object.entries( { + 23: '', + 123: '', + 456: '123', + 56: '123', + } ) + ), + }, + }; + + expect( getNextBlockClientId( state, '456', '123' ) ).toEqual( + '56' + ); } ); it( 'should return null for the last block', () => { const state = { - blocks: { - order: { - '': [ 123, 23 ], - }, - parents: { - 23: '', - 123: '', - }, + blocks: { + order: new Map( + Object.entries( { + '': [ '123', '23' ], + } ) + ), + parents: new Map( + Object.entries( { + 23: '', + 123: '', + } ) + ), }, }; - expect( getNextBlockClientId( state, 23 ) ).toBeNull(); + expect( getNextBlockClientId( state, '23' ) ).toBeNull(); } ); it( 'should return null for the last block (nested context)', () => { const state = { blocks: { - order: { - '': [ 123, 23 ], - 123: [ 456, 56 ], - }, - parents: { - 23: '', - 123: '', - 456: 123, - 56: 123, - }, + order: new Map( + Object.entries( { + '': [ '123', '23' ], + 123: [ '456', '56' ], + } ) + ), + parents: new Map( + Object.entries( { + 23: '', + 123: '', + 456: '123', + 56: '123', + } ) + ), }, }; - expect( getNextBlockClientId( state, 56, '123' ) ).toBeNull(); + expect( getNextBlockClientId( state, '56', '123' ) ).toBeNull(); } ); } ); @@ -1607,23 +1885,23 @@ describe( 'selectors', () => { it( 'should return true if the block is selected', () => { const state = { selection: { - selectionStart: { clientId: 123 }, - selectionEnd: { clientId: 123 }, + selectionStart: { clientId: '123' }, + selectionEnd: { clientId: '123' }, }, }; - expect( isBlockSelected( state, 123 ) ).toBe( true ); + expect( isBlockSelected( state, '123' ) ).toBe( true ); } ); it( 'should return false if a multi-selection range exists', () => { const state = { selection: { - selectionStart: { clientId: 123 }, - selectionEnd: { clientId: 124 }, + selectionStart: { clientId: '123' }, + selectionEnd: { clientId: '124' }, }, }; - expect( isBlockSelected( state, 123 ) ).toBe( false ); + expect( isBlockSelected( state, '123' ) ).toBe( false ); } ); it( 'should return false if the block is not selected', () => { @@ -1634,7 +1912,7 @@ describe( 'selectors', () => { }, }; - expect( isBlockSelected( state, 23 ) ).toBe( false ); + expect( isBlockSelected( state, '23' ) ).toBe( false ); } ); } ); @@ -1642,87 +1920,103 @@ describe( 'selectors', () => { it( 'should return false if the selected block is a child of the given ClientId', () => { const state = { selection: { - selectionStart: { clientId: 5 }, - selectionEnd: { clientId: 5 }, + selectionStart: { clientId: '5' }, + selectionEnd: { clientId: '5' }, }, blocks: { - order: { - 4: [ 3, 2, 1 ], - }, - parents: { - 1: 4, - 2: 4, - 3: 4, - }, + order: new Map( + Object.entries( { + 4: [ '3', '2', '1' ], + } ) + ), + parents: new Map( + Object.entries( { + 1: '4', + 2: '4', + 3: '4', + } ) + ), }, }; - expect( hasSelectedInnerBlock( state, 4 ) ).toBe( false ); + expect( hasSelectedInnerBlock( state, '4' ) ).toBe( false ); } ); it( 'should return true if the selected block is a child of the given ClientId', () => { const state = { selection: { - selectionStart: { clientId: 3 }, - selectionEnd: { clientId: 3 }, + selectionStart: { clientId: '3' }, + selectionEnd: { clientId: '3' }, }, blocks: { - order: { - 4: [ 3, 2, 1 ], - }, - parents: { - 1: 4, - 2: 4, - 3: 4, - }, + order: new Map( + Object.entries( { + 4: [ '3', '2', '1' ], + } ) + ), + parents: new Map( + Object.entries( { + 1: '4', + 2: '4', + 3: '4', + } ) + ), }, }; - expect( hasSelectedInnerBlock( state, 4 ) ).toBe( true ); + expect( hasSelectedInnerBlock( state, '4' ) ).toBe( true ); } ); it( 'should return true if a multi selection exists that contains children of the block with the given ClientId', () => { const state = { blocks: { - order: { - 6: [ 5, 4, 3, 2, 1 ], - }, - parents: { - 1: 6, - 2: 6, - 3: 6, - 4: 6, - 5: 6, - }, + order: new Map( + Object.entries( { + 6: [ '5', '4', '3', '2', '1' ], + } ) + ), + parents: new Map( + Object.entries( { + 1: '6', + 2: '6', + 3: '6', + 4: '6', + 5: '6', + } ) + ), }, selection: { - selectionStart: { clientId: 2 }, - selectionEnd: { clientId: 4 }, + selectionStart: { clientId: '2' }, + selectionEnd: { clientId: '4' }, }, }; - expect( hasSelectedInnerBlock( state, 6 ) ).toBe( true ); + expect( hasSelectedInnerBlock( state, '6' ) ).toBe( true ); } ); it( 'should return false if a multi selection exists bot does not contains children of the block with the given ClientId', () => { const state = { blocks: { - order: { - 3: [ 2, 1 ], - 6: [ 5, 4 ], - }, - parents: { - 1: 3, - 2: 3, - 4: 6, - 5: 6, - }, + order: new Map( + Object.entries( { + 3: [ '2', '1' ], + 6: [ '5', '4' ], + } ) + ), + parents: new Map( + Object.entries( { + 1: '3', + 2: '3', + 4: '6', + 5: '6', + } ) + ), }, selection: { - selectionStart: { clientId: 5 }, - selectionEnd: { clientId: 4 }, + selectionStart: { clientId: '5' }, + selectionEnd: { clientId: '4' }, }, }; - expect( hasSelectedInnerBlock( state, 3 ) ).toBe( false ); + expect( hasSelectedInnerBlock( state, '3' ) ).toBe( false ); } ); } ); @@ -1730,70 +2024,82 @@ describe( 'selectors', () => { it( 'should return true if the block is selected but not the last', () => { const state = { selection: { - selectionStart: { clientId: 5 }, - selectionEnd: { clientId: 3 }, + selectionStart: { clientId: '5' }, + selectionEnd: { clientId: '3' }, }, blocks: { - order: { - '': [ 5, 4, 3, 2, 1 ], - }, - parents: { - 1: '', - 2: '', - 3: '', - 4: '', - 5: '', - }, + order: new Map( + Object.entries( { + '': [ '5', '4', '3', '2', '1' ], + } ) + ), + parents: new Map( + Object.entries( { + 1: '', + 2: '', + 3: '', + 4: '', + 5: '', + } ) + ), }, }; - expect( isBlockWithinSelection( state, 4 ) ).toBe( true ); + expect( isBlockWithinSelection( state, '4' ) ).toBe( true ); } ); it( 'should return false if the block is the last selected', () => { const state = { selection: { - selectionStart: { clientId: 5 }, - selectionEnd: { clientId: 3 }, + selectionStart: { clientId: '5' }, + selectionEnd: { clientId: '3' }, }, blocks: { - order: { - '': [ 5, 4, 3, 2, 1 ], - }, - parents: { - 1: '', - 2: '', - 3: '', - 4: '', - 5: '', - }, + order: new Map( + Object.entries( { + '': [ '5', '4', '3', '2', '1' ], + } ) + ), + parents: new Map( + Object.entries( { + 1: '', + 2: '', + 3: '', + 4: '', + 5: '', + } ) + ), }, }; - expect( isBlockWithinSelection( state, 3 ) ).toBe( false ); + expect( isBlockWithinSelection( state, '3' ) ).toBe( false ); } ); it( 'should return false if the block is not selected', () => { const state = { selection: { - selectionStart: { clientId: 5 }, - selectionEnd: { clientId: 3 }, + selectionStart: { clientId: '5' }, + selectionEnd: { clientId: '3' }, }, blocks: { - order: { - '': [ 5, 4, 3, 2, 1 ], - }, - parents: { - 1: '', - 2: '', - 3: '', - 4: '', - 5: '', - }, + order: new Map( + Object.entries( { + '': [ '5', '4', '3', '2', '1' ], + } ) + ), + parents: new Map( + Object.entries( { + 1: '', + 2: '', + 3: '', + 4: '', + 5: '', + } ) + ), }, }; - expect( isBlockWithinSelection( state, 2 ) ).toBe( false ); + expect( isBlockWithinSelection( state, '2' ) ).toBe( false ); } ); it( 'should return false if there is no selection', () => { @@ -1803,20 +2109,24 @@ describe( 'selectors', () => { selectionEnd: {}, }, blocks: { - order: { - '': [ 5, 4, 3, 2, 1 ], - }, - parents: { - 1: '', - 2: '', - 3: '', - 4: '', - 5: '', - }, + order: new Map( + Object.entries( { + '': [ '5', '4', '3', '2', '1' ], + } ) + ), + parents: new Map( + Object.entries( { + 1: '', + 2: '', + 3: '', + 4: '', + 5: '', + } ) + ), }, }; - expect( isBlockWithinSelection( state, 4 ) ).toBe( false ); + expect( isBlockWithinSelection( state, '4' ) ).toBe( false ); } ); } ); @@ -1866,58 +2176,66 @@ describe( 'selectors', () => { describe( 'isBlockMultiSelected', () => { const state = { blocks: { - order: { - '': [ 5, 4, 3, 2, 1 ], - }, - parents: { - 1: '', - 2: '', - 3: '', - 4: '', - 5: '', - }, + order: new Map( + Object.entries( { + '': [ '5', '4', '3', '2', '1' ], + } ) + ), + parents: new Map( + Object.entries( { + 1: '', + 2: '', + 3: '', + 4: '', + 5: '', + } ) + ), }, selection: { - selectionStart: { clientId: 2 }, - selectionEnd: { clientId: 4 }, + selectionStart: { clientId: '2' }, + selectionEnd: { clientId: '4' }, }, }; it( 'should return true if the block is multi selected', () => { - expect( isBlockMultiSelected( state, 3 ) ).toBe( true ); + expect( isBlockMultiSelected( state, '3' ) ).toBe( true ); } ); it( 'should return false if the block is not multi selected', () => { - expect( isBlockMultiSelected( state, 5 ) ).toBe( false ); + expect( isBlockMultiSelected( state, '5' ) ).toBe( false ); } ); } ); describe( 'isFirstMultiSelectedBlock', () => { const state = { blocks: { - order: { - '': [ 5, 4, 3, 2, 1 ], - }, - parents: { - 1: '', - 2: '', - 3: '', - 4: '', - 5: '', - }, + order: new Map( + Object.entries( { + '': [ '5', '4', '3', '2', '1' ], + } ) + ), + parents: new Map( + Object.entries( { + 1: '', + 2: '', + 3: '', + 4: '', + 5: '', + } ) + ), }, selection: { - selectionStart: { clientId: 2 }, - selectionEnd: { clientId: 4 }, + selectionStart: { clientId: '2' }, + selectionEnd: { clientId: '4' }, }, }; it( 'should return true if the block is first in multi selection', () => { - expect( isFirstMultiSelectedBlock( state, 4 ) ).toBe( true ); + expect( isFirstMultiSelectedBlock( state, '4' ) ).toBe( true ); } ); it( 'should return false if the block is not first in multi selection', () => { - expect( isFirstMultiSelectedBlock( state, 3 ) ).toBe( false ); + expect( isFirstMultiSelectedBlock( state, '3' ) ).toBe( false ); } ); } ); @@ -1927,7 +2245,7 @@ describe( 'selectors', () => { blocksMode: {}, }; - expect( getBlockMode( state, 123 ) ).toEqual( 'visual' ); + expect( getBlockMode( state, '123' ) ).toEqual( 'visual' ); } ); it( 'should return the block mode', () => { @@ -1937,7 +2255,7 @@ describe( 'selectors', () => { }, }; - expect( getBlockMode( state, 123 ) ).toEqual( 'html' ); + expect( getBlockMode( state, '123' ) ).toEqual( 'html' ); } ); } ); @@ -2031,10 +2349,12 @@ describe( 'selectors', () => { const state = { draggedBlocks: [ 'block-1_grandparent' ], blocks: { - parents: { - 'block-1': 'block-1_parent', - 'block-1_parent': 'block-1_grandparent', - }, + parents: new Map( + Object.entries( { + 'block-1': 'block-1_parent', + 'block-1_parent': 'block-1_grandparent', + } ) + ), }, }; expect( isAncestorBeingDragged( state, 'block-1' ) ).toBe( true ); @@ -2044,10 +2364,12 @@ describe( 'selectors', () => { const state = { draggedBlocks: [ 'block-2_grandparent' ], blocks: { - parents: { - 'block-1': 'block-1_parent', - 'block-1_parent': 'block-1_grandparent', - }, + parents: new Map( + Object.entries( { + 'block-1': 'block-1_parent', + 'block-1_parent': 'block-1_grandparent', + } ) + ), }, }; expect( isAncestorBeingDragged( state, 'block-1' ) ).toBe( false ); @@ -2057,10 +2379,12 @@ describe( 'selectors', () => { const state = { draggedBlocks: [], blocks: { - parents: { - 'block-1': 'block-1_parent', - 'block-1_parent': 'block-1_grandparent', - }, + parents: new Map( + Object.entries( { + 'block-1': 'block-1_parent', + 'block-1_parent': 'block-1_grandparent', + } ) + ), }, }; expect( isAncestorBeingDragged( state, 'block-1' ) ).toBe( false ); @@ -2093,23 +2417,31 @@ describe( 'selectors', () => { selectionEnd: { clientId: 'clientId2' }, }, blocks: { - byClientId: { - clientId1: { clientId: 'clientId1' }, - clientId2: { clientId: 'clientId2' }, - }, - attributes: { - clientId1: {}, - clientId2: {}, - }, - order: { - '': [ 'clientId1' ], - clientId1: [ 'clientId2' ], - clientId2: [], - }, - parents: { - clientId1: '', - clientId2: 'clientId1', - }, + byClientId: new Map( + Object.entries( { + clientId1: { clientId: 'clientId1' }, + clientId2: { clientId: 'clientId2' }, + } ) + ), + attributes: new Map( + Object.entries( { + clientId1: {}, + clientId2: {}, + } ) + ), + order: new Map( + Object.entries( { + '': [ 'clientId1' ], + clientId1: [ 'clientId2' ], + clientId2: [], + } ) + ), + parents: new Map( + Object.entries( { + clientId1: '', + clientId2: 'clientId1', + } ) + ), }, insertionPoint: { rootClientId: undefined, @@ -2130,19 +2462,27 @@ describe( 'selectors', () => { selectionEnd: { clientId: 'clientId1' }, }, blocks: { - byClientId: { - clientId1: { clientId: 'clientId1' }, - }, - attributes: { - clientId1: {}, - }, - order: { - '': [ 'clientId1' ], - clientId1: [], - }, - parents: { - clientId1: '', - }, + byClientId: new Map( + Object.entries( { + clientId1: { clientId: 'clientId1' }, + } ) + ), + attributes: new Map( + Object.entries( { + clientId1: {}, + } ) + ), + order: new Map( + Object.entries( { + '': [ 'clientId1' ], + clientId1: [], + } ) + ), + parents: new Map( + Object.entries( { + clientId1: '', + } ) + ), }, insertionPoint: null, }; @@ -2160,23 +2500,31 @@ describe( 'selectors', () => { selectionEnd: { clientId: 'clientId2' }, }, blocks: { - byClientId: { - clientId1: { clientId: 'clientId1' }, - clientId2: { clientId: 'clientId2' }, - }, - attributes: { - clientId1: {}, - clientId2: {}, - }, - order: { - '': [ 'clientId1' ], - clientId1: [ 'clientId2' ], - clientId2: [], - }, - parents: { - clientId1: '', - clientId2: 'clientId1', - }, + byClientId: new Map( + Object.entries( { + clientId1: { clientId: 'clientId1' }, + clientId2: { clientId: 'clientId2' }, + } ) + ), + attributes: new Map( + Object.entries( { + clientId1: {}, + clientId2: {}, + } ) + ), + order: new Map( + Object.entries( { + '': [ 'clientId1' ], + clientId1: [ 'clientId2' ], + clientId2: [], + } ) + ), + parents: new Map( + Object.entries( { + clientId1: '', + clientId2: 'clientId1', + } ) + ), }, insertionPoint: null, }; @@ -2194,23 +2542,31 @@ describe( 'selectors', () => { selectionEnd: { clientId: 'clientId2' }, }, blocks: { - byClientId: { - clientId1: { clientId: 'clientId1' }, - clientId2: { clientId: 'clientId2' }, - }, - attributes: { - clientId1: {}, - clientId2: {}, - }, - order: { - '': [ 'clientId1', 'clientId2' ], - clientId1: [], - clientId2: [], - }, - parents: { - clientId1: '', - clientId2: '', - }, + byClientId: new Map( + Object.entries( { + clientId1: { clientId: 'clientId1' }, + clientId2: { clientId: 'clientId2' }, + } ) + ), + attributes: new Map( + Object.entries( { + clientId1: {}, + clientId2: {}, + } ) + ), + order: new Map( + Object.entries( { + '': [ 'clientId1', 'clientId2' ], + clientId1: [], + clientId2: [], + } ) + ), + parents: new Map( + Object.entries( { + clientId1: '', + clientId2: '', + } ) + ), }, insertionPoint: null, }; @@ -2228,23 +2584,31 @@ describe( 'selectors', () => { selectionEnd: {}, }, blocks: { - byClientId: { - clientId1: { clientId: 'clientId1' }, - clientId2: { clientId: 'clientId2' }, - }, - attributes: { - clientId1: {}, - clientId2: {}, - }, - order: { - '': [ 'clientId1', 'clientId2' ], - clientId1: [], - clientId2: [], - }, - parents: { - clientId1: '', - clientId2: '', - }, + byClientId: new Map( + Object.entries( { + clientId1: { clientId: 'clientId1' }, + clientId2: { clientId: 'clientId2' }, + } ) + ), + attributes: new Map( + Object.entries( { + clientId1: {}, + clientId2: {}, + } ) + ), + order: new Map( + Object.entries( { + '': [ 'clientId1', 'clientId2' ], + clientId1: [], + clientId2: [], + } ) + ), + parents: new Map( + Object.entries( { + clientId1: '', + clientId2: '', + } ) + ), }, insertionPoint: null, }; @@ -2281,8 +2645,8 @@ describe( 'selectors', () => { it( 'should deny blocks that are not registered', () => { const state = { blocks: { - byClientId: {}, - attributes: {}, + byClientId: new Map(), + attributes: new Map(), }, blockListSettings: {}, settings: {}, @@ -2293,8 +2657,8 @@ describe( 'selectors', () => { it( 'should deny blocks that are not allowed by the editor', () => { const state = { blocks: { - byClientId: {}, - attributes: {}, + byClientId: new Map(), + attributes: new Map(), }, blockListSettings: {}, settings: { @@ -2309,8 +2673,8 @@ describe( 'selectors', () => { it( 'should allow blocks that are allowed by the editor', () => { const state = { blocks: { - byClientId: {}, - attributes: {}, + byClientId: new Map(), + attributes: new Map(), }, blockListSettings: {}, settings: { @@ -2325,8 +2689,8 @@ describe( 'selectors', () => { it( 'should deny blocks when the editor has a template lock', () => { const state = { blocks: { - byClientId: {}, - attributes: {}, + byClientId: new Map(), + attributes: new Map(), }, blockListSettings: {}, settings: { @@ -2341,8 +2705,8 @@ describe( 'selectors', () => { it( 'should deny blocks that restrict parent from being inserted into the root', () => { const state = { blocks: { - byClientId: {}, - attributes: {}, + byClientId: new Map(), + attributes: new Map(), }, blockListSettings: {}, settings: {}, @@ -2355,12 +2719,16 @@ describe( 'selectors', () => { it( 'should deny blocks that restrict parent from being inserted into a restricted parent', () => { const state = { blocks: { - byClientId: { - block1: { name: 'core/test-block-a' }, - }, - attributes: { - block1: {}, - }, + byClientId: new Map( + Object.entries( { + block1: { name: 'core/test-block-a' }, + } ) + ), + attributes: new Map( + Object.entries( { + block1: {}, + } ) + ), }, blockListSettings: {}, settings: {}, @@ -2373,12 +2741,16 @@ describe( 'selectors', () => { it( 'should allow blocks to be inserted into an allowed parent', () => { const state = { blocks: { - byClientId: { - block1: { name: 'core/test-block-b' }, - }, - attributes: { - block1: {}, - }, + byClientId: new Map( + Object.entries( { + block1: { name: 'core/test-block-b' }, + } ) + ), + attributes: new Map( + Object.entries( { + block1: {}, + } ) + ), }, blockListSettings: { block1: {}, @@ -2393,12 +2765,16 @@ describe( 'selectors', () => { it( 'should deny blocks from being inserted into a block that does not allow inner blocks', () => { const state = { blocks: { - byClientId: { - block1: { name: 'core/test-block-b' }, - }, - attributes: { - block1: {}, - }, + byClientId: new Map( + Object.entries( { + block1: { name: 'core/test-block-b' }, + } ) + ), + attributes: new Map( + Object.entries( { + block1: {}, + } ) + ), }, blockListSettings: { block1: {}, @@ -2413,12 +2789,16 @@ describe( 'selectors', () => { it( 'should deny restricted blocks from being inserted into a block that restricts allowedBlocks', () => { const state = { blocks: { - byClientId: { - block1: { name: 'core/test-block-a' }, - }, - attributes: { - block1: {}, - }, + byClientId: new Map( + Object.entries( { + block1: { name: 'core/test-block-a' }, + } ) + ), + attributes: new Map( + Object.entries( { + block1: {}, + } ) + ), }, blockListSettings: { block1: { @@ -2435,12 +2815,16 @@ describe( 'selectors', () => { it( 'should allow allowed blocks to be inserted into a block that restricts allowedBlocks', () => { const state = { blocks: { - byClientId: { - block1: { name: 'core/test-block-a' }, - }, - attributes: { - block1: {}, - }, + byClientId: new Map( + Object.entries( { + block1: { name: 'core/test-block-a' }, + } ) + ), + attributes: new Map( + Object.entries( { + block1: {}, + } ) + ), }, blockListSettings: { block1: { @@ -2457,12 +2841,16 @@ describe( 'selectors', () => { it( 'should prioritise parent over allowedBlocks', () => { const state = { blocks: { - byClientId: { - block1: { name: 'core/test-block-b' }, - }, - attributes: { - block1: {}, - }, + byClientId: new Map( + Object.entries( { + block1: { name: 'core/test-block-b' }, + } ) + ), + attributes: new Map( + Object.entries( { + block1: {}, + } ) + ), }, blockListSettings: { block1: { @@ -2479,12 +2867,16 @@ describe( 'selectors', () => { it( 'should deny blocks that restrict parent to core/post-content when not in editor root', () => { const state = { blocks: { - byClientId: { - block1: { name: 'core/test-block-c' }, - }, - attributes: { - block1: {}, - }, + byClientId: new Map( + Object.entries( { + block1: { name: 'core/test-block-c' }, + } ) + ), + attributes: new Map( + Object.entries( { + block1: {}, + } ) + ), }, blockListSettings: {}, settings: {}, @@ -2497,8 +2889,8 @@ describe( 'selectors', () => { it( 'should allow blocks that restrict parent to core/post-content when in editor root', () => { const state = { blocks: { - byClientId: {}, - attributes: {}, + byClientId: new Map(), + attributes: new Map(), }, blockListSettings: {}, settings: {}, @@ -2511,17 +2903,23 @@ describe( 'selectors', () => { it( 'should allow blocks to be inserted in a descendant of a required ancestor', () => { const state = { blocks: { - byClientId: { - block1: { name: 'core/test-block-ancestor' }, - block2: { name: 'core/block' }, - }, - attributes: { - block1: {}, - block2: {}, - }, - parents: { - block2: 'block1', - }, + byClientId: new Map( + Object.entries( { + block1: { name: 'core/test-block-ancestor' }, + block2: { name: 'core/block' }, + } ) + ), + attributes: new Map( + Object.entries( { + block1: {}, + block2: {}, + } ) + ), + parents: new Map( + Object.entries( { + block2: 'block1', + } ) + ), }, blockListSettings: { block1: {}, @@ -2541,20 +2939,26 @@ describe( 'selectors', () => { it( 'should allow blocks to be inserted if both parent and ancestor restrictions are met', () => { const state = { blocks: { - byClientId: { - block1: { name: 'core/test-block-ancestor' }, - block2: { name: 'core/block' }, - block3: { name: 'core/test-block-parent' }, - }, - attributes: { - block1: {}, - block2: {}, - block3: {}, - }, - parents: { - block2: 'block1', - block3: 'block2', - }, + byClientId: new Map( + Object.entries( { + block1: { name: 'core/test-block-ancestor' }, + block2: { name: 'core/block' }, + block3: { name: 'core/test-block-parent' }, + } ) + ), + attributes: new Map( + Object.entries( { + block1: {}, + block2: {}, + block3: {}, + } ) + ), + parents: new Map( + Object.entries( { + block2: 'block1', + block3: 'block2', + } ) + ), }, blockListSettings: { block1: {}, @@ -2575,19 +2979,25 @@ describe( 'selectors', () => { it( 'should deny blocks from being inserted outside a required ancestor', () => { const state = { blocks: { - byClientId: { - block1: { name: 'core/test-block-ancestor' }, - block2: { name: 'core/block' }, - block3: { name: 'core/block' }, - }, - attributes: { - block1: {}, - block2: {}, - block3: {}, - }, - parents: { - block3: 'block2', - }, + byClientId: new Map( + Object.entries( { + block1: { name: 'core/test-block-ancestor' }, + block2: { name: 'core/block' }, + block3: { name: 'core/block' }, + } ) + ), + attributes: new Map( + Object.entries( { + block1: {}, + block2: {}, + block3: {}, + } ) + ), + parents: new Map( + Object.entries( { + block3: 'block2', + } ) + ), }, blockListSettings: { block1: {}, @@ -2608,19 +3018,25 @@ describe( 'selectors', () => { it( 'should deny blocks from being inserted outside of a required ancestor, even if parent matches', () => { const state = { blocks: { - byClientId: { - block1: { name: 'core/test-block-ancestor' }, - block2: { name: 'core/block' }, - block3: { name: 'core/test-block-parent' }, - }, - attributes: { - block1: {}, - block2: {}, - block3: {}, - }, - parents: { - block3: 'block2', - }, + byClientId: new Map( + Object.entries( { + block1: { name: 'core/test-block-ancestor' }, + block2: { name: 'core/block' }, + block3: { name: 'core/test-block-parent' }, + } ) + ), + attributes: new Map( + Object.entries( { + block1: {}, + block2: {}, + block3: {}, + } ) + ), + parents: new Map( + Object.entries( { + block3: 'block2', + } ) + ), }, blockListSettings: { block1: {}, @@ -2641,17 +3057,23 @@ describe( 'selectors', () => { it( 'should deny blocks from being inserted inside ancestor if parent restricts allowedBlocks', () => { const state = { blocks: { - byClientId: { - block1: { name: 'core/test-block-ancestor' }, - block2: { name: 'core/block' }, - }, - attributes: { - block1: {}, - block2: {}, - }, - parents: { - block2: 'block1', - }, + byClientId: new Map( + Object.entries( { + block1: { name: 'core/test-block-ancestor' }, + block2: { name: 'core/block' }, + } ) + ), + attributes: new Map( + Object.entries( { + block1: {}, + block2: {}, + } ) + ), + parents: new Map( + Object.entries( { + block2: 'block1', + } ) + ), }, blockListSettings: { block1: {}, @@ -2673,17 +3095,23 @@ describe( 'selectors', () => { it( 'should deny blocks from being inserted inside ancestor if parent restriction is not met', () => { const state = { blocks: { - byClientId: { - block1: { name: 'core/test-block-ancestor' }, - block2: { name: 'core/block' }, - }, - attributes: { - block1: {}, - block2: {}, - }, - parents: { - block2: 'block1', - }, + byClientId: new Map( + Object.entries( { + block1: { name: 'core/test-block-ancestor' }, + block2: { name: 'core/block' }, + } ) + ), + attributes: new Map( + Object.entries( { + block1: {}, + block2: {}, + } ) + ), + parents: new Map( + Object.entries( { + block2: 'block1', + } ) + ), }, blockListSettings: { block1: {}, @@ -2705,16 +3133,20 @@ describe( 'selectors', () => { it( 'should allow blocks', () => { const state = { blocks: { - byClientId: { - 1: { name: 'core/test-block-a' }, - 2: { name: 'core/test-block-b' }, - 3: { name: 'core/test-block-c' }, - }, - attributes: { - 1: {}, - 2: {}, - 3: {}, - }, + byClientId: new Map( + Object.entries( { + 1: { name: 'core/test-block-a' }, + 2: { name: 'core/test-block-b' }, + 3: { name: 'core/test-block-c' }, + } ) + ), + attributes: new Map( + Object.entries( { + 1: {}, + 2: {}, + 3: {}, + } ) + ), }, blockListSettings: { 1: { @@ -2732,16 +3164,20 @@ describe( 'selectors', () => { it( 'should deny blocks', () => { const state = { blocks: { - byClientId: { - 1: { name: 'core/test-block-a' }, - 2: { name: 'core/test-block-b' }, - 3: { name: 'core/test-block-c' }, - }, - attributes: { - 1: {}, - 2: {}, - 3: {}, - }, + byClientId: new Map( + Object.entries( { + 1: { name: 'core/test-block-a' }, + 2: { name: 'core/test-block-b' }, + 3: { name: 'core/test-block-c' }, + } ) + ), + attributes: new Map( + Object.entries( { + 1: {}, + 2: {}, + 3: {}, + } ) + ), }, blockListSettings: { 1: { @@ -2758,15 +3194,17 @@ describe( 'selectors', () => { it( 'should properly list block type and reusable block items', () => { const state = { blocks: { - byClientId: {}, - attributes: {}, - order: {}, - parents: {}, - tree: { - '': { - innerBlocks: [], - }, - }, + byClientId: new Map(), + attributes: new Map(), + order: new Map(), + parents: new Map(), + tree: new Map( + Object.entries( { + '': { + innerBlocks: [], + }, + } ) + ), }, settings: { __experimentalReusableBlocks: [ @@ -2827,35 +3265,45 @@ describe( 'selectors', () => { it( 'should correctly cache the return values', () => { const state = { blocks: { - byClientId: { - block3: { name: 'core/test-block-a' }, - block4: { name: 'core/test-block-a' }, - }, - attributes: { - block3: {}, - block4: {}, - }, - order: { - '': [ 'block3', 'block4' ], - }, - parents: { - block3: '', - block4: '', - }, - tree: { - block3: { - clientId: 'block3', - name: 'core/test-block-a', - attributes: {}, - innerBlocks: [], - }, - block4: { - clientId: 'block4', - name: 'core/test-block-a', - attributes: {}, - innerBlocks: [], - }, - }, + byClientId: new Map( + Object.entries( { + block3: { name: 'core/test-block-a' }, + block4: { name: 'core/test-block-a' }, + } ) + ), + attributes: new Map( + Object.entries( { + block3: {}, + block4: {}, + } ) + ), + order: new Map( + Object.entries( { + '': [ 'block3', 'block4' ], + } ) + ), + parents: new Map( + Object.entries( { + block3: '', + block4: '', + } ) + ), + tree: new Map( + Object.entries( { + block3: { + clientId: 'block3', + name: 'core/test-block-a', + attributes: {}, + innerBlocks: [], + }, + block4: { + clientId: 'block4', + name: 'core/test-block-a', + attributes: {}, + innerBlocks: [], + }, + } ) + ), controlledInnerBlocks: {}, }, settings: { @@ -2934,28 +3382,36 @@ describe( 'selectors', () => { it( 'should set isDisabled when a block with `multiple: false` has been used', () => { const state = { blocks: { - byClientId: { - block1: { - clientId: 'block1', - name: 'core/test-block-b', - }, - }, - attributes: { - block1: { attribute: {} }, - }, - order: { - '': [ 'block1' ], - }, - tree: { - block1: { - clientId: 'block1', - name: 'core/test-block-b', - attributes: {}, - innerBlocks: [], - }, - }, + byClientId: new Map( + Object.entries( { + block1: { + clientId: 'block1', + name: 'core/test-block-b', + }, + } ) + ), + attributes: new Map( + Object.entries( { + block1: { attribute: {} }, + } ) + ), + order: new Map( + Object.entries( { + '': [ 'block1' ], + } ) + ), + tree: new Map( + Object.entries( { + block1: { + clientId: 'block1', + name: 'core/test-block-b', + attributes: {}, + innerBlocks: [], + }, + } ) + ), controlledInnerBlocks: {}, - parents: {}, + parents: new Map(), }, preferences: { insertUsage: {}, @@ -2973,10 +3429,10 @@ describe( 'selectors', () => { it( 'should set a frecency', () => { const state = { blocks: { - byClientId: {}, - attributes: {}, - order: {}, - parents: {}, + byClientId: new Map(), + attributes: new Map(), + order: new Map(), + parents: new Map(), cache: {}, }, preferences: { @@ -3068,10 +3524,10 @@ describe( 'selectors', () => { it( 'should properly return block type items', () => { const state = { blocks: { - byClientId: {}, - attributes: {}, - order: {}, - parents: {}, + byClientId: new Map(), + attributes: new Map(), + order: new Map(), + parents: new Map(), cache: {}, }, settings: {}, @@ -3108,10 +3564,10 @@ describe( 'selectors', () => { it( 'should support single block object', () => { const state = { blocks: { - byClientId: {}, - attributes: {}, - order: {}, - parents: {}, + byClientId: new Map(), + attributes: new Map(), + order: new Map(), + parents: new Map(), cache: {}, }, settings: {}, @@ -3125,19 +3581,25 @@ describe( 'selectors', () => { it( 'should return only eligible blocks for transformation - `allowedBlocks`', () => { const state = { blocks: { - byClientId: { - block1: { name: 'core/with-tranforms-b' }, - block2: { name: 'core/with-tranforms-a' }, - }, - attributes: { - block1: {}, - block2: {}, - }, - order: {}, - parents: { - block1: '', - block2: 'block1', - }, + byClientId: new Map( + Object.entries( { + block1: { name: 'core/with-tranforms-b' }, + block2: { name: 'core/with-tranforms-a' }, + } ) + ), + attributes: new Map( + Object.entries( { + block1: {}, + block2: {}, + } ) + ), + order: new Map(), + parents: new Map( + Object.entries( { + block1: '', + block2: 'block1', + } ) + ), cache: {}, controlledInnerBlocks: {}, }, @@ -3160,28 +3622,36 @@ describe( 'selectors', () => { it( 'should take into account the usage of blocks settings `multiple` - if multiple blocks of the same type are allowed', () => { const state = { blocks: { - byClientId: { - block1: { - clientId: 'block1', - name: 'core/with-tranforms-c', - }, - }, - attributes: { - block1: { attribute: {} }, - }, - order: { - '': [ 'block1' ], - }, - tree: { - block1: { - clientId: 'block1', - name: 'core/with-tranforms-c', - attributes: {}, - innerBlocks: [], - }, - }, + byClientId: new Map( + Object.entries( { + block1: { + clientId: 'block1', + name: 'core/with-tranforms-c', + }, + } ) + ), + attributes: new Map( + Object.entries( { + block1: { attribute: {} }, + } ) + ), + order: new Map( + Object.entries( { + '': [ 'block1' ], + } ) + ), + tree: new Map( + Object.entries( { + block1: { + clientId: 'block1', + name: 'core/with-tranforms-c', + attributes: {}, + innerBlocks: [], + }, + } ) + ), controlledInnerBlocks: {}, - parents: {}, + parents: new Map(), }, preferences: { insertUsage: {}, @@ -3208,10 +3678,10 @@ describe( 'selectors', () => { it( 'should set frecency', () => { const state = { blocks: { - byClientId: {}, - attributes: {}, - order: {}, - parents: {}, + byClientId: new Map(), + attributes: new Map(), + order: new Map(), + parents: new Map(), cache: {}, }, preferences: { @@ -3264,7 +3734,7 @@ describe( 'selectors', () => { } ); describe( 'getTemplateLock', () => { - it( 'should return the general template lock if no clientId was set', () => { + it( 'should return the general template lock if no clientId was specified', () => { const state = { settings: { templateLock: 'all' }, }; @@ -3272,7 +3742,7 @@ describe( 'selectors', () => { expect( getTemplateLock( state ) ).toBe( 'all' ); } ); - it( 'should return undefined if the specified clientId was not found', () => { + it( 'should return false if the specified clientId was not found', () => { const state = { settings: { templateLock: 'all' }, blockListSettings: { @@ -3282,10 +3752,10 @@ describe( 'selectors', () => { }, }; - expect( getTemplateLock( state, 'ribs' ) ).toBe( undefined ); + expect( getTemplateLock( state, 'ribs' ) ).toBe( false ); } ); - it( 'should return undefined if template lock was not set on the specified block', () => { + it( 'should return false if template lock was not set on the specified block', () => { const state = { settings: { templateLock: 'all' }, blockListSettings: { @@ -3295,7 +3765,7 @@ describe( 'selectors', () => { }, }; - expect( getTemplateLock( state, 'ribs' ) ).toBe( undefined ); + expect( getTemplateLock( state, 'chicken' ) ).toBe( false ); } ); it( 'should return the template lock for the specified clientId', () => { @@ -3429,20 +3899,24 @@ describe( 'selectors', () => { describe( 'getLowestCommonAncestorWithSelectedBlock', () => { const blocks = { - order: { - '': [ 'a', 'b' ], - a: [ 'c', 'd' ], - d: [ 'e' ], - b: [ 'f' ], - }, - parents: { - a: '', - b: '', - c: 'a', - d: 'a', - e: 'd', - f: 'b', - }, + order: new Map( + Object.entries( { + '': [ 'a', 'b' ], + a: [ 'c', 'd' ], + d: [ 'e' ], + b: [ 'f' ], + } ) + ), + parents: new Map( + Object.entries( { + a: '', + b: '', + c: 'a', + d: 'a', + e: 'd', + f: 'b', + } ) + ), }; it( 'should not be defined if there is no block selected', () => { @@ -3541,35 +4015,39 @@ describe( 'selectors', () => { }, }, blocks: { - parents: { - 'client-id-01': '', - 'client-id-02': 'client-id-01', - 'client-id-03': 'client-id-02', - 'client-id-04': 'client-id-03', - 'client-id-05': 'client-id-03', - }, - byClientId: { - 'client-id-01': { - clientId: 'client-id-01', - name: 'core/columns', - }, - 'client-id-02': { - clientId: 'client-id-02', - name: 'core/navigation', - }, - 'client-id-03': { - clientId: 'client-id-03', - name: 'core/navigation-link', - }, - 'client-id-04': { - clientId: 'client-id-04', - name: 'core/navigation-link', - }, - 'client-id-05': { - clientId: 'client-id-05', - name: 'core/navigation-link', - }, - }, + parents: new Map( + Object.entries( { + 'client-id-01': '', + 'client-id-02': 'client-id-01', + 'client-id-03': 'client-id-02', + 'client-id-04': 'client-id-03', + 'client-id-05': 'client-id-03', + } ) + ), + byClientId: new Map( + Object.entries( { + 'client-id-01': { + clientId: 'client-id-01', + name: 'core/columns', + }, + 'client-id-02': { + clientId: 'client-id-02', + name: 'core/navigation', + }, + 'client-id-03': { + clientId: 'client-id-03', + name: 'core/navigation-link', + }, + 'client-id-04': { + clientId: 'client-id-04', + name: 'core/navigation-link', + }, + 'client-id-05': { + clientId: 'client-id-05', + name: 'core/navigation-link', + }, + } ) + ), cache: { 'client-id-01': {}, 'client-id-02': {}, @@ -3577,9 +4055,11 @@ describe( 'selectors', () => { 'client-id-04': {}, 'client-id-05': {}, }, - order: { - 'client-id-03': [ 'client-id-04', 'client-id-05' ], - }, + order: new Map( + Object.entries( { + 'client-id-03': [ 'client-id-04', 'client-id-05' ], + } ) + ), controlledInnerBlocks: {}, }, }; @@ -3611,14 +4091,18 @@ describe( 'selectors', () => { describe( '__experimentalGetAllowedPatterns', () => { const state = { blocks: { - byClientId: { - block1: { name: 'core/test-block-a' }, - block2: { name: 'core/test-block-b' }, - }, - attributes: { - block1: {}, - block2: {}, - }, + byClientId: new Map( + Object.entries( { + block1: { name: 'core/test-block-a' }, + block2: { name: 'core/test-block-b' }, + } ) + ), + attributes: new Map( + Object.entries( { + block1: {}, + block2: {}, + } ) + ), }, blockListSettings: { block1: { @@ -3670,7 +4154,7 @@ describe( 'selectors', () => { it( 'should return empty array if only patterns hidden from UI exist', () => { expect( __experimentalGetAllowedPatterns( { - blocks: { byClientId: {} }, + blocks: { byClientId: new Map() }, blockListSettings: {}, settings: { __experimentalBlockPatterns: [ @@ -3742,9 +4226,11 @@ describe( 'selectors', () => { describe( '__experimentalGetPatternsByBlockTypes', () => { const state = { blocks: { - byClientId: { - block1: { name: 'core/test-block-a' }, - }, + byClientId: new Map( + Object.entries( { + block1: { name: 'core/test-block-a' }, + } ) + ), }, blockListSettings: { block1: { @@ -3816,10 +4302,12 @@ describe( 'selectors', () => { describe( '__experimentalGetPatternTransformItems', () => { const state = { blocks: { - byClientId: { - block1: { name: 'core/test-block-a' }, - block2: { name: 'core/test-block-b' }, - }, + byClientId: new Map( + Object.entries( { + block1: { name: 'core/test-block-a' }, + block2: { name: 'core/test-block-b' }, + } ) + ), controlledInnerBlocks: { 'block2-clientId': true }, }, blockListSettings: { @@ -4073,10 +4561,10 @@ describe( 'getInserterItems with core blocks prioritization', () => { it( 'should prioritize core blocks by sorting them at the top of the returned list', () => { const state = { blocks: { - byClientId: {}, - attributes: {}, - order: {}, - parents: {}, + byClientId: new Map(), + attributes: new Map(), + order: new Map(), + parents: new Map(), cache: {}, }, settings: {}, @@ -4104,11 +4592,13 @@ describe( '__unstableGetClientIdWithClientIdsTree', () => { it( "should return a stripped down block object containing only its client ID and its inner blocks' client IDs", () => { const state = { blocks: { - order: { - '': [ 'foo' ], - foo: [ 'bar', 'baz' ], - bar: [ 'qux' ], - }, + order: new Map( + Object.entries( { + '': [ 'foo' ], + foo: [ 'bar', 'baz' ], + bar: [ 'qux' ], + } ) + ), }, }; @@ -4130,11 +4620,13 @@ describe( '__unstableGetClientIdsTree', () => { it( "should return the full content tree starting from the given root, consisting of stripped down block object containing only its client ID and its inner blocks' client IDs", () => { const state = { blocks: { - order: { - '': [ 'foo' ], - foo: [ 'bar', 'baz' ], - bar: [ 'qux' ], - }, + order: new Map( + Object.entries( { + '': [ 'foo' ], + foo: [ 'bar', 'baz' ], + bar: [ 'qux' ], + } ) + ), }, }; @@ -4150,11 +4642,13 @@ describe( '__unstableGetClientIdsTree', () => { it( "should return the full content tree starting from the root, consisting of stripped down block object containing only its client ID and its inner blocks' client IDs", () => { const state = { blocks: { - order: { - '': [ 'foo' ], - foo: [ 'bar', 'baz' ], - bar: [ 'qux' ], - }, + order: new Map( + Object.entries( { + '': [ 'foo' ], + foo: [ 'bar', 'baz' ], + bar: [ 'qux' ], + } ) + ), }, }; @@ -4172,3 +4666,23 @@ describe( '__unstableGetClientIdsTree', () => { ] ); } ); } ); + +describe( 'getLastInsertedBlockClientId', () => { + it( 'should return undefined if no blocks have been inserted', () => { + const state = { + lastBlockInserted: {}, + }; + + expect( getLastInsertedBlockClientId( state ) ).toEqual( undefined ); + } ); + + it( 'should return clientId if blocks have been inserted', () => { + const state = { + lastBlockInserted: { + clientId: '123456', + }, + }; + + expect( getLastInsertedBlockClientId( state ) ).toEqual( '123456' ); + } ); +} ); diff --git a/packages/block-editor/src/style.scss b/packages/block-editor/src/style.scss index 853a030f045945..76da3c16325db9 100644 --- a/packages/block-editor/src/style.scss +++ b/packages/block-editor/src/style.scss @@ -2,14 +2,11 @@ @import "./components/block-alignment-control/style.scss"; @import "./components/block-icon/style.scss"; @import "./components/block-inspector/style.scss"; -@import "./components/block-list/style.scss"; @import "./components/block-tools/style.scss"; -@import "./components/block-list-appender/style.scss"; @import "./components/block-lock/style.scss"; @import "./components/block-breadcrumb/style.scss"; @import "./components/block-card/style.scss"; @import "./components/block-compare/style.scss"; -@import "./components/block-content-overlay/style.scss"; @import "./components/block-draggable/style.scss"; @import "./components/block-mover/style.scss"; @import "./components/block-navigation/style.scss"; @@ -28,21 +25,18 @@ @import "./components/button-block-appender/style.scss"; @import "./components/colors-gradients/style.scss"; @import "./components/contrast-checker/style.scss"; -@import "./components/default-block-appender/style.scss"; @import "./components/date-format-picker/style.scss"; @import "./components/duotone-control/style.scss"; @import "./components/font-appearance-control/style.scss"; +@import "./components/height-control/style.scss"; @import "./components/image-size-control/style.scss"; -@import "./components/inner-blocks/style.scss"; @import "./components/inserter-list-item/style.scss"; @import "./components/inspector-popover-header/style.scss"; @import "./components/justify-content-control/style.scss"; @import "./components/link-control/style.scss"; @import "./components/list-view/style.scss"; @import "./components/media-replace-flow/style.scss"; -@import "./components/media-placeholder/style.scss"; @import "./components/multi-selection-inspector/style.scss"; -@import "./components/plain-text/style.scss"; @import "./components/responsive-block-control/style.scss"; @import "./components/rich-text/style.scss"; @import "./components/skip-to-selected-block/style.scss"; @@ -65,4 +59,7 @@ @import "./components/preview-options/style.scss"; @import "./components/spacing-sizes-control/style.scss"; +// Experiments. +@import "./components/off-canvas-editor/style.scss"; + @include wordpress-admin-schemes(); diff --git a/packages/block-editor/src/utils/pasting.js b/packages/block-editor/src/utils/pasting.js index 366b79a3294229..e962e11050a1d9 100644 --- a/packages/block-editor/src/utils/pasting.js +++ b/packages/block-editor/src/utils/pasting.js @@ -1,7 +1,6 @@ /** * WordPress dependencies */ -import { createBlobURL } from '@wordpress/blob'; import { getFilesFromDataTransfer } from '@wordpress/dom'; export function getPasteEventData( { clipboardData } ) { @@ -25,21 +24,16 @@ export function getPasteEventData( { clipboardData } ) { } } - const files = getFilesFromDataTransfer( clipboardData ).filter( - ( { type } ) => /^image\/(?:jpe?g|png|gif|webp)$/.test( type ) - ); + const files = getFilesFromDataTransfer( clipboardData ); if ( files.length && ! shouldDismissPastedFiles( files, html, plainText ) ) { - html = files - .map( ( file ) => `` ) - .join( '' ); - plainText = ''; + return { files }; } - return { html, plainText }; + return { html, plainText, files: [] }; } /** diff --git a/packages/block-editor/src/utils/sorting.js b/packages/block-editor/src/utils/sorting.js new file mode 100644 index 00000000000000..84cf65a08e8274 --- /dev/null +++ b/packages/block-editor/src/utils/sorting.js @@ -0,0 +1,54 @@ +/** + * Recursive stable sorting comparator function. + * + * @param {string|Function} field Field to sort by. + * @param {Array} items Items to sort. + * @param {string} order Order, 'asc' or 'desc'. + * @return {Function} Comparison function to be used in a `.sort()`. + */ +const comparator = ( field, items, order ) => { + return ( a, b ) => { + let cmpA, cmpB; + + if ( typeof field === 'function' ) { + cmpA = field( a ); + cmpB = field( b ); + } else { + cmpA = a[ field ]; + cmpB = b[ field ]; + } + + if ( cmpA > cmpB ) { + return order === 'asc' ? 1 : -1; + } else if ( cmpB > cmpA ) { + return order === 'asc' ? -1 : 1; + } + + const orderA = items.findIndex( ( item ) => item === a ); + const orderB = items.findIndex( ( item ) => item === b ); + + // Stable sort: maintaining original array order + if ( orderA > orderB ) { + return 1; + } else if ( orderB > orderA ) { + return -1; + } + + return 0; + }; +}; + +/** + * Order items by a certain key. + * Supports decorator functions that allow complex picking of a comparison field. + * Sorts in ascending order by default, but supports descending as well. + * Stable sort - maintains original order of equal items. + * + * @param {Array} items Items to order. + * @param {string|Function} field Field to order by. + * @param {string} order Sorting order, `asc` or `desc`. + * @return {Array} Sorted items. + */ +export function orderBy( items, field, order = 'asc' ) { + return items.concat().sort( comparator( field, items, order ) ); +} diff --git a/packages/block-editor/src/utils/test/sorting.js b/packages/block-editor/src/utils/test/sorting.js new file mode 100644 index 00000000000000..f1038cda5809cc --- /dev/null +++ b/packages/block-editor/src/utils/test/sorting.js @@ -0,0 +1,49 @@ +/** + * Internal dependencies + */ +import { orderBy } from '../sorting'; + +describe( 'orderBy', () => { + it( 'should not mutate original input', () => { + const input = []; + expect( orderBy( input, 'x' ) ).not.toBe( input ); + } ); + + it( 'should sort items by a field when it is specified as a string', () => { + const input = [ { x: 2 }, { x: 1 }, { x: 3 } ]; + const expected = [ { x: 1 }, { x: 2 }, { x: 3 } ]; + expect( orderBy( input, 'x' ) ).toEqual( expected ); + } ); + + it( 'should support functions for picking the field', () => { + const input = [ { x: 2 }, { x: 1 }, { x: 3 } ]; + const expected = [ { x: 1 }, { x: 2 }, { x: 3 } ]; + expect( orderBy( input, ( item ) => item.x ) ).toEqual( expected ); + } ); + + it( 'should support sorting in a descending order', () => { + const input = [ { x: 2 }, { x: 1 }, { x: 3 } ]; + const expected = [ { x: 3 }, { x: 2 }, { x: 1 } ]; + expect( orderBy( input, 'x', 'desc' ) ).toEqual( expected ); + } ); + + it( 'should maintain original order of equal items', () => { + const a = { x: 1, a: 1 }; + const b = { x: 1, b: 2 }; + const c = { x: 0 }; + const d = { x: 1, b: 4 }; + const input = [ a, b, c, d ]; + const expected = [ c, a, b, d ]; + expect( orderBy( input, 'x' ) ).toEqual( expected ); + } ); + + it( 'should maintain original order of equal items in descencing order', () => { + const a = { x: 1, a: 1 }; + const b = { x: 1, b: 2 }; + const c = { x: 0 }; + const d = { x: 1, b: 4 }; + const input = [ a, b, c, d ]; + const expected = [ a, b, d, c ]; + expect( orderBy( input, 'x', 'desc' ) ).toEqual( expected ); + } ); +} ); diff --git a/packages/block-library/CHANGELOG.md b/packages/block-library/CHANGELOG.md index 1dce136b8f867d..c11fbb8ab8d9b1 100644 --- a/packages/block-library/CHANGELOG.md +++ b/packages/block-library/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### Breaking Changes + +- Updated dependencies to require React 18 ([45235](https://github.com/WordPress/gutenberg/pull/45235)) + ## 7.19.0 (2022-11-16) ## 7.18.0 (2022-11-02) diff --git a/packages/block-library/package.json b/packages/block-library/package.json index d5671a8d30064c..7510c3b22a7c20 100644 --- a/packages/block-library/package.json +++ b/packages/block-library/package.json @@ -45,6 +45,7 @@ "@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", @@ -62,14 +63,15 @@ "colord": "^2.7.0", "escape-html": "^1.0.3", "fast-average-color": "^9.1.1", + "fast-deep-equal": "^3.1.3", "lodash": "^4.17.21", "memize": "^1.1.0", "micromodal": "^0.4.10", "remove-accents": "^0.4.2" }, "peerDependencies": { - "react": "^17.0.0", - "react-dom": "^17.0.0" + "react": "^18.0.0", + "react-dom": "^18.0.0" }, "publishConfig": { "access": "public" diff --git a/packages/block-library/src/audio/edit.native.js b/packages/block-library/src/audio/edit.native.js index 30ee44ff3f9487..fbc39ba190d3e2 100644 --- a/packages/block-library/src/audio/edit.native.js +++ b/packages/block-library/src/audio/edit.native.js @@ -2,7 +2,6 @@ * External dependencies */ import { TouchableWithoutFeedback } from 'react-native'; -import { isEmpty } from 'lodash'; /** * WordPress dependencies @@ -228,7 +227,7 @@ function AudioEdit( { - isEmpty( caption ) + ! caption ? /* translators: accessibility text. Empty Audio caption. */ __( 'Audio caption. Empty' ) : sprintf( diff --git a/packages/block-library/src/audio/index.js b/packages/block-library/src/audio/index.js index 0d3d05936ef90f..aa1bba1c341d2f 100644 --- a/packages/block-library/src/audio/index.js +++ b/packages/block-library/src/audio/index.js @@ -23,6 +23,7 @@ export const settings = { attributes: { src: 'https://upload.wikimedia.org/wikipedia/commons/d/dd/Armstrong_Small_Step.ogg', }, + viewportWidth: 350, }, transforms, deprecated, diff --git a/packages/block-library/src/avatar/edit.js b/packages/block-library/src/avatar/edit.js index 7df772677ebb7d..2741b7adcd3dae 100644 --- a/packages/block-library/src/avatar/edit.js +++ b/packages/block-library/src/avatar/edit.js @@ -35,6 +35,7 @@ const AvatarInspectorControls = ( { setAttributes( { diff --git a/packages/block-library/src/avatar/user-control.js b/packages/block-library/src/avatar/user-control.js index 49370b39716eff..598c05011eaed7 100644 --- a/packages/block-library/src/avatar/user-control.js +++ b/packages/block-library/src/avatar/user-control.js @@ -33,6 +33,7 @@ function UserControl( { value, onChange } ) { return ( select( blockEditorStore ).canRemoveBlock( clientId ), + const { canRemove, innerBlockCount } = useSelect( + ( select ) => { + const { canRemoveBlock, getBlockCount } = + select( blockEditorStore ); + return { + canRemove: canRemoveBlock( clientId ), + innerBlockCount: getBlockCount( clientId ), + }; + }, [ clientId ] ); @@ -109,7 +116,11 @@ export default function ReusableBlockEdit( { attributes: { ref }, clientId } ) { convertBlockToStatic( clientId ) } - label={ __( 'Convert to regular blocks' ) } + label={ + innerBlockCount > 1 + ? __( 'Convert to regular blocks' ) + : __( 'Convert to regular block' ) + } icon={ ungroup } showTooltip /> diff --git a/packages/block-library/src/block/edit.native.js b/packages/block-library/src/block/edit.native.js index ce2a3999e2b553..96472470efebdd 100644 --- a/packages/block-library/src/block/edit.native.js +++ b/packages/block-library/src/block/edit.native.js @@ -77,7 +77,7 @@ export default function ReusableBlockEdit( { styles.spinnerDark ); - const { hasResolved, isEditing, isMissing } = useSelect( + const { hasResolved, isEditing, isMissing, innerBlockCount } = useSelect( ( select ) => { const persistedBlock = select( coreStore ).getEntityRecord( 'postType', @@ -88,6 +88,9 @@ export default function ReusableBlockEdit( { 'getEntityRecord', [ 'postType', 'wp_block', ref ] ); + + const { getBlockCount } = select( blockEditorStore ); + return { hasResolved: hasResolvedBlock, isEditing: @@ -95,6 +98,7 @@ export default function ReusableBlockEdit( { reusableBlocksStore ).__experimentalIsEditingReusableBlock( clientId ), isMissing: hasResolvedBlock && ! persistedBlock, + innerBlockCount: getBlockCount( clientId ), }; }, [ ref, clientId ] @@ -122,13 +126,13 @@ export default function ReusableBlockEdit( { } const onConvertToRegularBlocks = useCallback( () => { - createSuccessNotice( - sprintf( - /* translators: %s: name of the reusable block */ - __( '%s converted to regular blocks' ), - title - ) - ); + const successNotice = + innerBlockCount > 1 + ? /* translators: %s: name of the reusable block */ + __( '%s converted to regular blocks' ) + : /* translators: %s: name of the reusable block */ + __( '%s converted to regular block' ); + createSuccessNotice( sprintf( successNotice, title ) ); clearSelectedBlock(); // Convert action is executed at the end of the current JavaScript execution block @@ -162,12 +166,20 @@ export default function ReusableBlockEdit( { { infoTitle } - { __( - 'Alternatively, you can detach and edit these blocks separately by tapping “Convert to regular blocks”.' - ) } + { innerBlockCount > 1 + ? __( + 'Alternatively, you can detach and edit these blocks separately by tapping “Convert to regular blocks”.' + ) + : __( + 'Alternatively, you can detach and edit this block separately by tapping “Convert to regular block”.' + ) } 1 + ? __( 'Convert to regular blocks' ) + : __( 'Convert to regular block' ) + } separatorType="topFullWidth" onPress={ onConvertToRegularBlocks } labelStyle={ actionButtonStyle } diff --git a/packages/block-library/src/block/editor.scss b/packages/block-library/src/block/editor.scss index b3eb1a67e99459..c49675392a525e 100644 --- a/packages/block-library/src/block/editor.scss +++ b/packages/block-library/src/block/editor.scss @@ -14,3 +14,20 @@ display: none; } } + +.edit-post-visual-editor .block-editor-block-list__block:not(.remove-outline).is-reusable { + &.is-highlighted, + &.is-selected { + box-shadow: inset 0 0 0 var(--wp-admin-border-width-focus) var(--wp-block-synced-color); + } + + &.block-editor-block-list__block:not([contenteditable]):focus { + &::after { + box-shadow: 0 0 0 var(--wp-admin-border-width-focus) var(--wp-block-synced-color); + // Show a light color for dark themes. + .is-dark-theme & { + box-shadow: 0 0 0 var(--wp-admin-border-width-focus) $dark-theme-focus; + } + } + } +} diff --git a/packages/block-library/src/block/index.php b/packages/block-library/src/block/index.php index 2cfd672aa3ada1..d51b35d68b23d9 100644 --- a/packages/block-library/src/block/index.php +++ b/packages/block-library/src/block/index.php @@ -27,8 +27,7 @@ function render_block_core_block( $attributes ) { if ( isset( $seen_refs[ $attributes['ref'] ] ) ) { // WP_DEBUG_DISPLAY must only be honored when WP_DEBUG. This precedent // is set in `wp_debug_mode()`. - $is_debug = defined( 'WP_DEBUG' ) && WP_DEBUG && - defined( 'WP_DEBUG_DISPLAY' ) && WP_DEBUG_DISPLAY; + $is_debug = WP_DEBUG && WP_DEBUG_DISPLAY; return $is_debug ? // translators: Visible only in the front end, this warning takes the place of a faulty block. diff --git a/packages/block-library/src/block/test/edit.native.js b/packages/block-library/src/block/test/edit.native.js index a683426a751e20..4652f8ba20f385 100644 --- a/packages/block-library/src/block/test/edit.native.js +++ b/packages/block-library/src/block/test/edit.native.js @@ -5,7 +5,6 @@ import { getEditorHtml, initializeEditor, fireEvent, - waitFor, within, } from 'test/helpers'; @@ -24,7 +23,7 @@ const getMockedReusableBlock = ( id ) => ( { content: { raw: ` -

First Reusable block

+

First Reusable block

@@ -78,17 +77,16 @@ describe( 'Reusable block', () => { return Promise.resolve( response ); } ); - const { getByLabelText, getByTestId, getByText } = - await initializeEditor( { - initialHtml: '', - capabilities: { reusableBlock: true }, - } ); + const screen = await initializeEditor( { + initialHtml: '', + capabilities: { reusableBlock: true }, + } ); // Open the inserter menu. - fireEvent.press( await waitFor( () => getByLabelText( 'Add block' ) ) ); + fireEvent.press( await screen.findByLabelText( 'Add block' ) ); // Navigate to reusable tab. - const reusableSegment = await waitFor( () => getByText( 'Reusable' ) ); + const reusableSegment = await screen.findByText( 'Reusable' ); // onLayout event is required by Segment component. fireEvent( reusableSegment, 'layout', { nativeEvent: { @@ -99,7 +97,9 @@ describe( 'Reusable block', () => { } ); fireEvent.press( reusableSegment ); - const reusableBlockList = getByTestId( 'InserterUI-ReusableBlocks' ); + const reusableBlockList = screen.getByTestId( + 'InserterUI-ReusableBlocks' + ); // onScroll event used to force the FlatList to render all items. fireEvent.scroll( reusableBlockList, { nativeEvent: { @@ -110,13 +110,11 @@ describe( 'Reusable block', () => { } ); // Insert a reusable block. - fireEvent.press( - await waitFor( () => getByText( `Reusable block - 1` ) ) - ); + fireEvent.press( await screen.findByText( `Reusable block - 1` ) ); // Get the reusable block. - const reusableBlock = await waitFor( () => - getByLabelText( /Reusable block Block\. Row 1/ ) + const [ reusableBlock ] = await screen.findAllByLabelText( + /Reusable block Block\. Row 1/ ); expect( reusableBlock ).toBeDefined(); @@ -128,18 +126,16 @@ describe( 'Reusable block', () => { const id = 3; const initialHtml = ``; - const { getByLabelText } = await initializeEditor( { + const screen = await initializeEditor( { initialHtml, } ); - const reusableBlock = await waitFor( () => - getByLabelText( /Reusable block Block\. Row 1/ ) + const [ reusableBlock ] = await screen.findAllByLabelText( + /Reusable block Block\. Row 1/ ); - const blockDeleted = await waitFor( () => - within( reusableBlock ).getByText( - 'Block has been deleted or is unavailable.' - ) + const blockDeleted = within( reusableBlock ).getByText( + 'Block has been deleted or is unavailable.' ); expect( reusableBlock ).toBeDefined(); @@ -163,17 +159,17 @@ describe( 'Reusable block', () => { return Promise.resolve( response ); } ); - const { getByLabelText } = await initializeEditor( { + const screen = await initializeEditor( { initialHtml, } ); - const reusableBlock = await waitFor( () => - getByLabelText( /Reusable block Block\. Row 1/ ) + const [ reusableBlock ] = await screen.findByLabelText( + /Reusable block Block\. Row 1/ ); - const innerBlockListWrapper = await waitFor( () => - within( reusableBlock ).getByTestId( 'block-list-wrapper' ) - ); + const innerBlockListWrapper = await within( + reusableBlock + ).findByTestId( 'block-list-wrapper' ); // onLayout event has to be explicitly dispatched in BlockList component, // otherwise the inner blocks are not rendered. @@ -185,10 +181,10 @@ describe( 'Reusable block', () => { }, } ); - const headingInnerBlock = await waitFor( () => - within( reusableBlock ).getByLabelText( - 'Heading Block. Row 1. Level 2. First Reusable block' - ) + const [ headingInnerBlock ] = await within( + reusableBlock + ).findAllByLabelText( + 'Heading Block. Row 1. Level 2. First Reusable block' ); expect( reusableBlock ).toBeDefined(); diff --git a/packages/block-library/src/button/edit.native.js b/packages/block-library/src/button/edit.native.js index 39082488f1f654..214be33c7df2ed 100644 --- a/packages/block-library/src/button/edit.native.js +++ b/packages/block-library/src/button/edit.native.js @@ -31,6 +31,7 @@ import { useMobileGlobalStylesColors, } from '@wordpress/components'; import { link } from '@wordpress/icons'; +// eslint-disable-next-line no-restricted-imports import { store as editPostStore } from '@wordpress/edit-post'; /** diff --git a/packages/block-library/src/buttons/test/edit.native.js b/packages/block-library/src/buttons/test/edit.native.js index a6a25ee0590a90..9d23413fd79be5 100644 --- a/packages/block-library/src/buttons/test/edit.native.js +++ b/packages/block-library/src/buttons/test/edit.native.js @@ -3,7 +3,6 @@ */ import { fireEvent, - waitFor, getEditorHtml, within, getBlock, @@ -50,20 +49,20 @@ describe( 'Buttons block', () => {
`; - const { getByLabelText } = await initializeEditor( { + const editor = await initializeEditor( { initialHtml, } ); - const buttonsBlock = await waitFor( () => - getByLabelText( /Buttons Block\. Row 1/ ) + const [ buttonsBlock ] = await editor.findAllByLabelText( + /Buttons Block\. Row 1/ ); fireEvent.press( buttonsBlock ); // onLayout event has to be explicitly dispatched in BlockList component, // otherwise the inner blocks are not rendered. - const innerBlockListWrapper = await waitFor( () => - within( buttonsBlock ).getByTestId( 'block-list-wrapper' ) - ); + const innerBlockListWrapper = await within( + buttonsBlock + ).findByTestId( 'block-list-wrapper' ); fireEvent( innerBlockListWrapper, 'layout', { nativeEvent: { layout: { @@ -72,22 +71,22 @@ describe( 'Buttons block', () => { }, } ); - const buttonInnerBlock = await waitFor( () => - within( buttonsBlock ).getByLabelText( /Button Block\. Row 1/ ) - ); + const [ buttonInnerBlock ] = await within( + buttonsBlock + ).findAllByLabelText( /Button Block\. Row 1/ ); fireEvent.press( buttonInnerBlock ); - const settingsButton = await waitFor( () => - getByLabelText( 'Open Settings' ) + const settingsButton = await editor.findByLabelText( + 'Open Settings' ); fireEvent.press( settingsButton ); - const radiusStepper = await waitFor( () => - getByLabelText( /Border Radius/ ) + const radiusStepper = await editor.findByLabelText( + /Border Radius/ ); - const incrementButton = await waitFor( () => - within( radiusStepper ).getByTestId( 'Increment' ) + const incrementButton = await within( radiusStepper ).findByTestId( + 'Increment' ); fireEvent( incrementButton, 'onPressIn' ); @@ -98,15 +97,14 @@ describe( 'Buttons block', () => { const screen = await initializeEditor( { initialHtml: BUTTONS_HTML, } ); - const { getByLabelText } = screen; // Get block const buttonsBlock = await getBlock( screen, 'Buttons' ); // Trigger inner blocks layout - const innerBlockListWrapper = await waitFor( () => - within( buttonsBlock ).getByTestId( 'block-list-wrapper' ) - ); + const innerBlockListWrapper = await within( + buttonsBlock + ).findByTestId( 'block-list-wrapper' ); fireEvent( innerBlockListWrapper, 'layout', { nativeEvent: { layout: { @@ -125,14 +123,14 @@ describe( 'Buttons block', () => { fireEvent.press( appenderButton ); // Check for new button - const secondButtonBlock = await waitFor( () => - within( buttonsBlock ).getByLabelText( /Button Block\. Row 2/ ) - ); + const [ secondButtonBlock ] = await within( + buttonsBlock + ).findAllByLabelText( /Button Block\. Row 2/ ); expect( secondButtonBlock ).toBeVisible(); // Add a Paragraph block using the empty placeholder at the bottom - const paragraphPlaceholder = await waitFor( () => - getByLabelText( 'Add paragraph block' ) + const paragraphPlaceholder = await screen.findByLabelText( + 'Add paragraph block' ); fireEvent.press( paragraphPlaceholder ); @@ -148,21 +146,15 @@ describe( 'Buttons block', () => { const screen = await initializeEditor( { initialHtml: BUTTONS_HTML, } ); - const { - getByLabelText, - getByTestId, - queryAllByLabelText, - getByText, - } = screen; // Get block const buttonsBlock = await getBlock( screen, 'Buttons' ); fireEvent.press( buttonsBlock ); // Trigger inner blocks layout - const innerBlockListWrapper = await waitFor( () => - within( buttonsBlock ).getByTestId( 'block-list-wrapper' ) - ); + const innerBlockListWrapper = await within( + buttonsBlock + ).findByTestId( 'block-list-wrapper' ); fireEvent( innerBlockListWrapper, 'layout', { nativeEvent: { layout: { @@ -176,9 +168,9 @@ describe( 'Buttons block', () => { fireEvent.press( buttonBlock ); // Open the block inserter - fireEvent.press( getByLabelText( 'Add block' ) ); + fireEvent.press( screen.getByLabelText( 'Add block' ) ); - const blockList = getByTestId( 'InserterUI-Blocks' ); + const blockList = screen.getByTestId( 'InserterUI-Blocks' ); // onScroll event used to force the FlatList to render all items fireEvent.scroll( blockList, { nativeEvent: { @@ -190,11 +182,11 @@ describe( 'Buttons block', () => { // Check the Add block here placeholder is not visible const addBlockHerePlaceholders = - queryAllByLabelText( 'ADD BLOCK HERE' ); + screen.queryAllByLabelText( 'ADD BLOCK HERE' ); expect( addBlockHerePlaceholders.length ).toBe( 0 ); // Add a new Button block - fireEvent.press( await waitFor( () => getByText( 'Button' ) ) ); + fireEvent.press( await screen.findByText( 'Button' ) ); // Get new button const secondButtonBlock = await getBlock( screen, 'Button', { @@ -214,15 +206,14 @@ describe( 'Buttons block', () => { const screen = await initializeEditor( { initialHtml: BUTTONS_HTML, } ); - const { getByLabelText } = screen; // Get block const buttonsBlock = await getBlock( screen, 'Buttons' ); // Trigger inner blocks layout - const innerBlockListWrapper = await waitFor( () => - within( buttonsBlock ).getByTestId( 'block-list-wrapper' ) - ); + const innerBlockListWrapper = await within( + buttonsBlock + ).findByTestId( 'block-list-wrapper' ); fireEvent( innerBlockListWrapper, 'layout', { nativeEvent: { layout: { @@ -236,13 +227,13 @@ describe( 'Buttons block', () => { fireEvent.press( buttonBlock ); // Open block actions menu - const blockActionsButton = getByLabelText( + const blockActionsButton = screen.getByLabelText( /Open Block Actions Menu/ ); fireEvent.press( blockActionsButton ); // Delete block - const deleteButton = getByLabelText( /Remove block/ ); + const deleteButton = screen.getByLabelText( /Remove block/ ); fireEvent.press( deleteButton ); expect( getEditorHtml() ).toMatchSnapshot(); @@ -260,22 +251,20 @@ describe( 'Buttons block', () => { const initialHtml = `
`; - const { getByLabelText, getByText } = await initializeEditor( { - initialHtml, - } ); + const screen = await initializeEditor( { initialHtml } ); - const block = await waitFor( () => - getByLabelText( /Buttons Block\. Row 1/ ) + const [ block ] = await screen.findAllByLabelText( + /Buttons Block\. Row 1/ ); fireEvent.press( block ); fireEvent.press( - getByLabelText( 'Change items justification' ) + screen.getByLabelText( 'Change items justification' ) ); // Select alignment option. fireEvent.press( - await waitFor( () => getByText( justificationOption ) ) + await screen.findByText( justificationOption ) ); expect( getEditorHtml() ).toMatchSnapshot(); diff --git a/packages/block-library/src/code/transforms.js b/packages/block-library/src/code/transforms.js index b19d584fb7b056..fcfb41fb0262c6 100644 --- a/packages/block-library/src/code/transforms.js +++ b/packages/block-library/src/code/transforms.js @@ -43,7 +43,9 @@ const transforms = { type: 'block', blocks: [ 'core/paragraph' ], transform: ( { content } ) => { - return createBlock( 'core/paragraph', { content } ); + return createBlock( 'core/paragraph', { + content: content.replace( /\n/g, '
' ), + } ); }, }, ], diff --git a/packages/block-library/src/columns/edit.js b/packages/block-library/src/columns/edit.js index 8879f0e52f2e7a..b978c411107d5e 100644 --- a/packages/block-library/src/columns/edit.js +++ b/packages/block-library/src/columns/edit.js @@ -95,6 +95,7 @@ function ColumnsEditContainer( { updateColumns( count, value ) } diff --git a/packages/block-library/src/columns/edit.native.js b/packages/block-library/src/columns/edit.native.js index 96855e3da17e46..6331097f96b991 100644 --- a/packages/block-library/src/columns/edit.native.js +++ b/packages/block-library/src/columns/edit.native.js @@ -2,7 +2,7 @@ * External dependencies */ import { View, Dimensions } from 'react-native'; -import { map } from 'lodash'; + /** * WordPress dependencies */ @@ -332,11 +332,6 @@ const ColumnsEditContainerWrapper = withDispatch( width: value, } ); }, - updateBlockSettings( settings ) { - const { clientId } = ownProps; - const { updateBlockListSettings } = dispatch( blockEditorStore ); - updateBlockListSettings( clientId, settings ); - }, /** * Updates the column columnCount, including necessary revisions to child Column * blocks to grant required or redistribute available space. @@ -450,7 +445,7 @@ const ColumnsEdit = ( props ) => { const { columnCount, isDefaultColumns, - innerWidths = [], + innerBlocks, hasParents, parentBlockAlignment, editorSidebarOpened, @@ -463,24 +458,20 @@ const ColumnsEdit = ( props ) => { getBlockAttributes, } = select( blockEditorStore ); const { isEditorSidebarOpened } = select( 'core/edit-post' ); - const innerBlocks = getBlocks( clientId ); - const isContentEmpty = map( - innerBlocks, - ( innerBlock ) => innerBlock.innerBlocks.length + const innerBlocksList = getBlocks( clientId ); + + const isContentEmpty = innerBlocksList.every( + ( innerBlock ) => innerBlock.innerBlocks.length === 0 ); - const innerColumnsWidths = innerBlocks.map( ( inn ) => ( { - clientId: inn.clientId, - attributes: { width: inn.attributes.width }, - } ) ); const parents = getBlockParents( clientId, true ); return { columnCount: getBlockCount( clientId ), - isDefaultColumns: ! isContentEmpty.filter( Boolean ).length, - innerWidths: innerColumnsWidths, - hasParents: !! parents.length, + isDefaultColumns: isContentEmpty, + innerBlocks: innerBlocksList, + hasParents: parents.length > 0, parentBlockAlignment: getBlockAttributes( parents[ 0 ] )?.align, editorSidebarOpened: isSelected && isEditorSidebarOpened(), }; @@ -488,13 +479,14 @@ const ColumnsEdit = ( props ) => { [ clientId, isSelected ] ); - const memoizedInnerWidths = useMemo( () => { - return innerWidths; - }, [ - // The JSON.stringify is used because innerWidth is always a new reference. - // The innerBlocks is a new reference after each attribute change of any nested block. - JSON.stringify( innerWidths ), - ] ); + const innerWidths = useMemo( + () => + innerBlocks.map( ( inn ) => ( { + clientId: inn.clientId, + attributes: { width: inn.attributes.width }, + } ) ), + [ innerBlocks ] + ); const [ isVisible, setIsVisible ] = useState( false ); @@ -514,7 +506,7 @@ const ColumnsEdit = ( props ) => { setAttributes( { diff --git a/packages/block-library/src/comments/index.php b/packages/block-library/src/comments/index.php index 69044c081c74e0..a5e51cfbf4e793 100644 --- a/packages/block-library/src/comments/index.php +++ b/packages/block-library/src/comments/index.php @@ -202,7 +202,6 @@ function register_legacy_post_comments_block() { 'wp-block-buttons', 'wp-block-button', ), - 'editorStyle' => 'wp-block-post-comments-editor', 'render_callback' => 'render_block_core_comments', 'skip_inner_blocks' => true, ); diff --git a/packages/block-library/src/cover/edit.native.js b/packages/block-library/src/cover/edit.native.js index 900298354ded24..67bf968c3a8f41 100644 --- a/packages/block-library/src/cover/edit.native.js +++ b/packages/block-library/src/cover/edit.native.js @@ -58,6 +58,7 @@ import { } from '@wordpress/element'; import { cover as icon, replace, image, warning } from '@wordpress/icons'; import { getProtocol } from '@wordpress/url'; +// eslint-disable-next-line no-restricted-imports import { store as editPostStore } from '@wordpress/edit-post'; /** @@ -87,6 +88,36 @@ const INNER_BLOCKS_TEMPLATE = [ ], ]; +function useIsScreenReaderEnabled() { + const [ isScreenReaderEnabled, setIsScreenReaderEnabled ] = + useState( false ); + + useEffect( () => { + let mounted = true; + + const changeListener = AccessibilityInfo.addEventListener( + 'screenReaderChanged', + ( enabled ) => setIsScreenReaderEnabled( enabled ) + ); + + AccessibilityInfo.isScreenReaderEnabled().then( + ( screenReaderEnabled ) => { + if ( mounted && screenReaderEnabled ) { + setIsScreenReaderEnabled( screenReaderEnabled ); + } + } + ); + + return () => { + mounted = false; + + changeListener.remove(); + }; + }, [] ); + + return isScreenReaderEnabled; +} + const Cover = ( { attributes, getStylesFromColorScheme, @@ -117,29 +148,11 @@ const Cover = ( { overlayColor, isDark, } = attributes; - const [ isScreenReaderEnabled, setIsScreenReaderEnabled ] = - useState( false ); + const isScreenReaderEnabled = useIsScreenReaderEnabled(); useEffect( () => { - let isCurrent = true; - // Sync with local media store. mediaUploadSync(); - const a11yInfoChangeSubscription = AccessibilityInfo.addEventListener( - 'screenReaderChanged', - setIsScreenReaderEnabled - ); - - AccessibilityInfo.isScreenReaderEnabled().then( () => { - if ( isCurrent ) { - setIsScreenReaderEnabled(); - } - } ); - - return () => { - isCurrent = false; - a11yInfoChangeSubscription.remove(); - }; }, [] ); const convertedMinHeight = useConvertUnitToMobile( diff --git a/packages/block-library/src/cover/edit/inspector-controls.js b/packages/block-library/src/cover/edit/inspector-controls.js index cfcf012badd62c..d3f65672e09328 100644 --- a/packages/block-library/src/cover/edit/inspector-controls.js +++ b/packages/block-library/src/cover/edit/inspector-controls.js @@ -162,6 +162,7 @@ export default function CoverInspectorControls( { ) } { showFocalPointPicker && ( diff --git a/packages/block-library/src/cover/test/edit.native.js b/packages/block-library/src/cover/test/edit.native.js index 10dbff032160a9..6a5bef376c18f8 100644 --- a/packages/block-library/src/cover/test/edit.native.js +++ b/packages/block-library/src/cover/test/edit.native.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { AccessibilityInfo, Image } from 'react-native'; +import { Image } from 'react-native'; import { getEditorHtml, initializeEditor, @@ -90,10 +90,6 @@ beforeAll( () => { const getSizeSpy = jest.spyOn( Image, 'getSize' ); getSizeSpy.mockImplementation( ( _url, callback ) => callback( 300, 200 ) ); - AccessibilityInfo.isScreenReaderEnabled.mockResolvedValue( - Promise.resolve( true ) - ); - // Register required blocks. paragraph.init(); cover.init(); @@ -103,7 +99,6 @@ beforeAll( () => { afterAll( () => { // Restore mocks. Image.getSize.mockRestore(); - AccessibilityInfo.isScreenReaderEnabled.mockReset(); // Clean up registered blocks. unregisterBlockType( paragraph.name ); @@ -134,32 +129,32 @@ describe( 'when no media is attached', () => { describe( 'when an image is attached', () => { it( 'edits the image', async () => { - const { getByLabelText, getByText } = render( + const screen = render( ); - fireEvent.press( getByLabelText( 'Edit image' ) ); - const editButton = await waitFor( () => getByText( 'Edit' ) ); + fireEvent.press( screen.getByLabelText( 'Edit image' ) ); + const editButton = await screen.findByText( 'Edit' ); fireEvent.press( editButton ); expect( requestMediaEditor ).toHaveBeenCalled(); } ); it( 'replaces the image', async () => { - const { getByLabelText, getByText } = render( + const screen = render( ); - fireEvent.press( getByLabelText( 'Edit image' ) ); - const replaceButton = await waitFor( () => getByText( 'Replace' ) ); + fireEvent.press( screen.getByLabelText( 'Edit image' ) ); + const replaceButton = await screen.findByText( 'Replace' ); fireEvent.press( replaceButton ); - const mediaLibraryButton = await waitFor( () => - getByText( 'WordPress Media Library' ) + const mediaLibraryButton = await screen.findByText( + 'WordPress Media Library' ); fireEvent.press( mediaLibraryButton ); @@ -167,17 +162,17 @@ describe( 'when an image is attached', () => { } ); it( 'clears the image within image edit button', async () => { - const { getByLabelText, getAllByText } = render( + const screen = render( ); - fireEvent.press( getByLabelText( 'Edit image' ) ); - const clearMediaButton = await waitFor( () => - getAllByText( 'Clear Media' ) + fireEvent.press( screen.getByLabelText( 'Edit image' ) ); + const [ clearMediaButton ] = await screen.findAllByText( + 'Clear Media' ); - fireEvent.press( clearMediaButton[ 0 ] ); + fireEvent.press( clearMediaButton ); expect( setAttributes ).toHaveBeenCalledWith( expect.objectContaining( { @@ -209,22 +204,22 @@ describe( 'when an image is attached', () => { } ); it( 'edits the focal point with a slider', async () => { - const { getByText, getByLabelText, getByTestId } = render( + const screen = render( ); - const editFocalPointButton = await waitFor( () => - getByText( 'Edit focal point' ) + const editFocalPointButton = await screen.findByText( + 'Edit focal point' ); fireEvent.press( editFocalPointButton ); fireEvent( - getByTestId( 'Slider Y-Axis Position' ), + screen.getByTestId( 'Slider Y-Axis Position' ), 'valueChange', '52' ); - fireEvent.press( getByLabelText( 'Apply' ) ); + fireEvent.press( screen.getByLabelText( 'Apply' ) ); expect( setAttributes ).toHaveBeenCalledWith( expect.objectContaining( { @@ -234,21 +229,24 @@ describe( 'when an image is attached', () => { } ); it( 'edits the focal point with a text input', async () => { - const { getByText, getByLabelText } = render( + const screen = render( ); - const editFocalPointButton = await waitFor( () => - getByText( 'Edit focal point' ) + const editFocalPointButton = await screen.findByText( + 'Edit focal point' ); fireEvent.press( editFocalPointButton ); fireEvent.press( - getByText( ( attributes.focalPoint.x * 100 ).toString() ) + screen.getByText( ( attributes.focalPoint.x * 100 ).toString() ) ); - fireEvent.changeText( getByLabelText( 'X-Axis Position' ), '99' ); - fireEvent.press( getByLabelText( 'Apply' ) ); + fireEvent.changeText( + screen.getByLabelText( 'X-Axis Position' ), + '99' + ); + fireEvent.press( screen.getByLabelText( 'Apply' ) ); expect( setAttributes ).toHaveBeenCalledWith( expect.objectContaining( { @@ -282,15 +280,13 @@ describe( 'when an image is attached', () => { } ); it( 'clears the media within cell button', async () => { - const { getByText } = render( + const screen = render( ); - const clearMediaButton = await waitFor( () => - getByText( 'Clear Media' ) - ); + const clearMediaButton = await screen.findByText( 'Clear Media' ); fireEvent.press( clearMediaButton ); expect( setAttributes ).toHaveBeenCalledWith( @@ -334,97 +330,79 @@ describe( 'when an image is attached', () => { describe( 'color settings', () => { it( 'sets a color for the overlay background when the placeholder is visible', async () => { - const { getByTestId, getByLabelText } = await initializeEditor( { + const screen = await initializeEditor( { initialHtml: COVER_BLOCK_PLACEHOLDER_HTML, } ); - const block = await waitFor( () => - getByLabelText( 'Cover block. Empty' ) - ); + const block = await screen.findByLabelText( 'Cover block. Empty' ); expect( block ).toBeDefined(); // Select a color from the placeholder palette. - const colorPalette = await waitFor( () => - getByTestId( 'color-palette' ) - ); + const colorPalette = await screen.findByTestId( 'color-palette' ); const colorButton = within( colorPalette ).getByTestId( COLOR_PINK ); expect( colorButton ).toBeDefined(); fireEvent.press( colorButton ); // Wait for the block to be created. - const coverBlockWithOverlay = await waitFor( () => - getByLabelText( /Cover Block\. Row 1/ ) + const [ coverBlockWithOverlay ] = await screen.findAllByLabelText( + /Cover Block\. Row 1/ ); fireEvent.press( coverBlockWithOverlay ); // Open Block Settings. - const settingsButton = await waitFor( () => - getByLabelText( 'Open Settings' ) - ); + const settingsButton = await screen.findByLabelText( 'Open Settings' ); fireEvent.press( settingsButton ); // Wait for Block Settings to be visible. - const blockSettingsModal = getByTestId( 'block-settings-modal' ); + const blockSettingsModal = screen.getByTestId( 'block-settings-modal' ); await waitFor( () => blockSettingsModal.props.isVisible ); // Open the overlay color settings. - const colorOverlay = await waitFor( () => - getByLabelText( 'Color. Empty' ) - ); + const colorOverlay = await screen.findByLabelText( 'Color. Empty' ); expect( colorOverlay ).toBeDefined(); fireEvent.press( colorOverlay ); // Find the selected color. - const colorPaletteButton = await waitFor( () => - getByTestId( COLOR_PINK ) - ); + const colorPaletteButton = await screen.findByTestId( COLOR_PINK ); expect( colorPaletteButton ).toBeDefined(); // Select another color. - const newColorButton = await waitFor( () => getByTestId( COLOR_RED ) ); + const newColorButton = await screen.findByTestId( COLOR_RED ); fireEvent.press( newColorButton ); expect( getEditorHtml() ).toMatchSnapshot(); } ); it( 'sets a gradient overlay background when a solid background was already selected', async () => { - const { getByTestId, getByLabelText } = await initializeEditor( { + const screen = await initializeEditor( { initialHtml: COVER_BLOCK_SOLID_COLOR_HTML, } ); // Wait for the block to be created. - const coverBlock = await waitFor( () => - getByLabelText( /Cover Block\. Row 1/ ) + const [ coverBlock ] = await screen.findAllByLabelText( + /Cover Block\. Row 1/ ); - expect( coverBlock ).toBeDefined(); fireEvent.press( coverBlock ); // Open Block Settings. - const settingsButton = await waitFor( () => - getByLabelText( 'Open Settings' ) - ); + const settingsButton = await screen.findByLabelText( 'Open Settings' ); fireEvent.press( settingsButton ); // Wait for Block Settings to be visible. - const blockSettingsModal = getByTestId( 'block-settings-modal' ); + const blockSettingsModal = screen.getByTestId( 'block-settings-modal' ); await waitFor( () => blockSettingsModal.props.isVisible ); // Open the overlay color settings. - const colorOverlay = await waitFor( () => - getByLabelText( 'Color. Empty' ) - ); - expect( colorOverlay ).toBeDefined(); + const colorOverlay = await screen.findByLabelText( 'Color. Empty' ); fireEvent.press( colorOverlay ); // Find the selected color. - const colorButton = await waitFor( () => getByTestId( COLOR_GRAY ) ); + const colorButton = await screen.findByTestId( COLOR_GRAY ); expect( colorButton ).toBeDefined(); // Open the gradients. - const gradientsButton = await waitFor( () => - getByLabelText( 'Gradient' ) - ); + const gradientsButton = await screen.findByLabelText( 'Gradient' ); expect( gradientsButton ).toBeDefined(); fireEvent( gradientsButton, 'layout', { @@ -433,10 +411,7 @@ describe( 'color settings', () => { fireEvent.press( gradientsButton ); // Find the gradient color. - const newGradientButton = await waitFor( () => - getByTestId( GRADIENT_GREEN ) - ); - expect( newGradientButton ).toBeDefined(); + const newGradientButton = await screen.findByTestId( GRADIENT_GREEN ); fireEvent.press( newGradientButton ); // Dismiss the Block Settings modal. @@ -446,62 +421,48 @@ describe( 'color settings', () => { } ); it( 'toggles between solid colors and gradients', async () => { - const { getByTestId, getByLabelText } = await initializeEditor( { + const screen = await initializeEditor( { initialHtml: COVER_BLOCK_PLACEHOLDER_HTML, } ); - const block = await waitFor( () => - getByLabelText( 'Cover block. Empty' ) - ); + const block = await screen.findByLabelText( 'Cover block. Empty' ); expect( block ).toBeDefined(); // Select a color from the placeholder palette. - const colorPalette = await waitFor( () => - getByTestId( 'color-palette' ) - ); + const colorPalette = await screen.findByTestId( 'color-palette' ); const colorButton = within( colorPalette ).getByTestId( COLOR_PINK ); expect( colorButton ).toBeDefined(); fireEvent.press( colorButton ); // Wait for the block to be created. - const coverBlockWithOverlay = await waitFor( () => - getByLabelText( /Cover Block\. Row 1/ ) + const [ coverBlockWithOverlay ] = await screen.findAllByLabelText( + /Cover Block\. Row 1/ ); fireEvent.press( coverBlockWithOverlay ); // Open Block Settings. - const settingsButton = await waitFor( () => - getByLabelText( 'Open Settings' ) - ); + const settingsButton = await screen.findByLabelText( 'Open Settings' ); fireEvent.press( settingsButton ); // Wait for Block Settings to be visible. - const blockSettingsModal = getByTestId( 'block-settings-modal' ); + const blockSettingsModal = screen.getByTestId( 'block-settings-modal' ); await waitFor( () => blockSettingsModal.props.isVisible ); // Open the overlay color settings. - const colorOverlay = await waitFor( () => - getByLabelText( 'Color. Empty' ) - ); - expect( colorOverlay ).toBeDefined(); + const colorOverlay = await screen.findByLabelText( 'Color. Empty' ); fireEvent.press( colorOverlay ); // Find the selected color. - const colorPaletteButton = await waitFor( () => - getByTestId( COLOR_PINK ) - ); + const colorPaletteButton = await screen.findByTestId( COLOR_PINK ); expect( colorPaletteButton ).toBeDefined(); // Select another color. - const newColorButton = await waitFor( () => getByTestId( COLOR_RED ) ); + const newColorButton = await screen.findByTestId( COLOR_RED ); fireEvent.press( newColorButton ); // Open the gradients. - const gradientsButton = await waitFor( () => - getByLabelText( 'Gradient' ) - ); - expect( gradientsButton ).toBeDefined(); + const gradientsButton = await screen.findByLabelText( 'Gradient' ); fireEvent( gradientsButton, 'layout', { nativeEvent: { layout: { width: 80, height: 26 } }, @@ -509,20 +470,14 @@ describe( 'color settings', () => { fireEvent.press( gradientsButton ); // Find the gradient color. - const newGradientButton = await waitFor( () => - getByTestId( GRADIENT_GREEN ) - ); - expect( newGradientButton ).toBeDefined(); + const newGradientButton = await screen.findByTestId( GRADIENT_GREEN ); fireEvent.press( newGradientButton ); // Go back to the settings list. - fireEvent.press( await waitFor( () => getByLabelText( 'Go back' ) ) ); + fireEvent.press( await screen.findByLabelText( 'Go back' ) ); // Find the color setting. - const colorSetting = await waitFor( () => - getByLabelText( 'Color. Empty' ) - ); - expect( colorSetting ).toBeDefined(); + const colorSetting = await screen.findByLabelText( 'Color. Empty' ); fireEvent.press( colorSetting ); // Dismiss the Block Settings modal. @@ -532,42 +487,34 @@ describe( 'color settings', () => { } ); it( 'clears the selected overlay color and mantains the inner blocks', async () => { - const { getByTestId, getByLabelText, getByText } = - await initializeEditor( { - initialHtml: COVER_BLOCK_SOLID_COLOR_HTML, - } ); + const screen = await initializeEditor( { + initialHtml: COVER_BLOCK_SOLID_COLOR_HTML, + } ); // Wait for the block to be created. - const coverBlock = await waitFor( () => - getByLabelText( /Cover Block\. Row 1/ ) + const [ coverBlock ] = await screen.findAllByLabelText( + /Cover Block\. Row 1/ ); - expect( coverBlock ).toBeDefined(); fireEvent.press( coverBlock ); // Open Block Settings. - const settingsButton = await waitFor( () => - getByLabelText( 'Open Settings' ) - ); + const settingsButton = await screen.findByLabelText( 'Open Settings' ); fireEvent.press( settingsButton ); // Wait for Block Settings to be visible. - const blockSettingsModal = getByTestId( 'block-settings-modal' ); + const blockSettingsModal = screen.getByTestId( 'block-settings-modal' ); await waitFor( () => blockSettingsModal.props.isVisible ); // Open the overlay color settings. - const colorOverlay = await waitFor( () => - getByLabelText( 'Color. Empty' ) - ); - expect( colorOverlay ).toBeDefined(); + const colorOverlay = await screen.findByLabelText( 'Color. Empty' ); fireEvent.press( colorOverlay ); // Find the selected color. - const colorButton = await waitFor( () => getByTestId( COLOR_GRAY ) ); + const colorButton = await screen.findByTestId( COLOR_GRAY ); expect( colorButton ).toBeDefined(); // Reset the selected color. - const resetButton = await waitFor( () => getByText( 'Reset' ) ); - expect( resetButton ).toBeDefined(); + const resetButton = await screen.findByText( 'Reset' ); fireEvent.press( resetButton ); expect( getEditorHtml() ).toMatchSnapshot(); diff --git a/packages/block-library/src/embed/embed-controls.native.js b/packages/block-library/src/embed/embed-controls.native.js index b1249308ebdf54..524f2da619bcb6 100644 --- a/packages/block-library/src/embed/embed-controls.native.js +++ b/packages/block-library/src/embed/embed-controls.native.js @@ -9,6 +9,7 @@ import { __ } from '@wordpress/i18n'; import { PanelBody, ToggleControl } from '@wordpress/components'; import { InspectorControls } from '@wordpress/block-editor'; import { useDispatch } from '@wordpress/data'; +// eslint-disable-next-line no-restricted-imports import { store as editPostStore } from '@wordpress/edit-post'; function getResponsiveHelp( checked ) { diff --git a/packages/block-library/src/embed/embed-preview.native.js b/packages/block-library/src/embed/embed-preview.native.js index bb8df663baf281..87abc38348d56a 100644 --- a/packages/block-library/src/embed/embed-preview.native.js +++ b/packages/block-library/src/embed/embed-preview.native.js @@ -2,7 +2,6 @@ * External dependencies */ import { TouchableWithoutFeedback } from 'react-native'; -import { isEmpty } from 'lodash'; import classnames from 'classnames/dedupe'; /** @@ -52,7 +51,7 @@ const EmbedPreview = ( { styles[ `embed-preview__sandbox--align-${ align }` ]; function accessibilityLabelCreator( caption ) { - return isEmpty( caption ) + return ! caption ? /* translators: accessibility text. Empty Embed caption. */ __( 'Embed caption. Empty' ) : sprintf( diff --git a/packages/block-library/src/embed/test/index.native.js b/packages/block-library/src/embed/test/index.native.js index 60bc6604b8cffb..d4b7fa99ded83e 100644 --- a/packages/block-library/src/embed/test/index.native.js +++ b/packages/block-library/src/embed/test/index.native.js @@ -29,6 +29,7 @@ import { requestPreview } from '@wordpress/react-native-bridge'; */ import * as paragraph from '../../paragraph'; import * as embed from '..'; +import { WebView } from 'react-native-webview'; // Override modal mock to prevent unmounting it when is not visible. // This is required to be able to trigger onClose and onDismiss events when @@ -121,55 +122,54 @@ const MOST_USED_PROVIDERS = embed.settings.variations.filter( ( { name } ) => // Return specified mocked responses for the oembed endpoint. const mockEmbedResponses = ( mockedResponses ) => { - fetchRequest.mockImplementation( ( { path } ) => { - if ( path.startsWith( '/wp/v2/themes' ) ) { - return Promise.resolve( [ - { theme_supports: { 'responsive-embeds': true } }, - ] ); - } - - if ( path.startsWith( '/wp/v2/block-patterns/categories' ) ) { - return Promise.resolve( [] ); - } - + fetchRequest.mockImplementation( async ( req ) => { const matchedEmbedResponse = mockedResponses.find( ( mockedResponse ) => - path === + req.path === `/oembed/1.0/proxy?url=${ encodeURIComponent( mockedResponse.url ) }` ); - return Promise.resolve( matchedEmbedResponse || {} ); + + return matchedEmbedResponse || mockOtherResponses( req ); } ); }; +async function mockOtherResponses( { path } ) { + if ( path.startsWith( '/wp/v2/themes' ) ) { + return [ { theme_supports: { 'responsive-embeds': true } } ]; + } + + if ( path.startsWith( '/wp/v2/block-patterns/patterns' ) ) { + return []; + } + + if ( path.startsWith( '/wp/v2/block-patterns/categories' ) ) { + return []; + } + + return {}; +} + const insertEmbedBlock = async ( blockTitle = 'Embed' ) => { - const editor = await initializeEditor( { - initialHtml: '', - } ); - const { getByLabelText, getByText } = editor; + const editor = await initializeEditor( { initialHtml: '' } ); // Open inserter menu. - fireEvent.press( await waitFor( () => getByLabelText( 'Add block' ) ) ); + fireEvent.press( await editor.findByLabelText( 'Add block' ) ); // Insert embed block. - fireEvent.press( await waitFor( () => getByText( blockTitle ) ) ); + fireEvent.press( await editor.findByText( blockTitle ) ); // Return the embed block. - const block = await waitFor( () => - getByLabelText( /Embed Block\. Row 1/ ) - ); + const [ block ] = await editor.findAllByLabelText( /Embed Block\. Row 1/ ); return { ...editor, block }; }; const initializeWithEmbedBlock = async ( initialHtml, selectBlock = true ) => { const editor = await initializeEditor( { initialHtml } ); - const { getByLabelText } = editor; - const block = await waitFor( () => - getByLabelText( /Embed Block\. Row 1/ ) - ); + const [ block ] = await editor.findAllByLabelText( /Embed Block\. Row 1/ ); if ( selectBlock ) { // Select block. @@ -232,10 +232,12 @@ describe( 'Embed block', () => { describe( 'set URL upon block insertion', () => { it( 'sets empty URL when dismissing edit URL modal', async () => { - const { getByTestId } = await insertEmbedBlock(); + const editor = await insertEmbedBlock(); // Wait for edit URL modal to be visible. - const embedEditURLModal = getByTestId( 'embed-edit-url-modal' ); + const embedEditURLModal = editor.getByTestId( + 'embed-edit-url-modal' + ); await waitFor( () => embedEditURLModal.props.isVisible ); // Dismiss the edit URL modal. @@ -248,15 +250,16 @@ describe( 'Embed block', () => { it( 'sets a valid URL when dismissing edit URL modal', async () => { const expectedURL = 'https://twitter.com/notnownikki'; - const { getByPlaceholderText, getByTestId } = - await insertEmbedBlock(); + const editor = await insertEmbedBlock(); // Wait for edit URL modal to be visible. - const embedEditURLModal = getByTestId( 'embed-edit-url-modal' ); + const embedEditURLModal = editor.getByTestId( + 'embed-edit-url-modal' + ); await waitFor( () => embedEditURLModal.props.isVisible ); // Set an URL. - const linkTextInput = getByPlaceholderText( 'Add link' ); + const linkTextInput = editor.getByPlaceholderText( 'Add link' ); fireEvent( linkTextInput, 'focus' ); fireEvent.changeText( linkTextInput, expectedURL ); @@ -264,8 +267,13 @@ describe( 'Embed block', () => { fireEvent( embedEditURLModal, 'backdropPress' ); fireEvent( embedEditURLModal, MODAL_DISMISS_EVENT ); - const blockSettingsModal = await waitFor( () => - getByTestId( 'block-settings-modal' ) + // Wait until the WebView with the rich preview appears + await waitFor( () => editor.UNSAFE_getByType( WebView ) ); + // Wait until responsiveness settings appear, driven by `theme_supports.responsive-embeds` + await editor.findByText( 'Media settings' ); + + const blockSettingsModal = await editor.findByTestId( + 'block-settings-modal' ); // Get Twitter link field. const twitterLinkField = within( @@ -282,23 +290,26 @@ describe( 'Embed block', () => { // Mock clipboard. Clipboard.getString.mockResolvedValue( clipboardURL ); - const { getByTestId, getByText } = await insertEmbedBlock(); + const editor = await insertEmbedBlock(); // Wait for edit URL modal to be visible. - const embedEditURLModal = getByTestId( 'embed-edit-url-modal' ); + const embedEditURLModal = editor.getByTestId( + 'embed-edit-url-modal' + ); await waitFor( () => embedEditURLModal.props.isVisible ); // Get embed link with auto-pasted URL. - const autopastedLinkField = await waitFor( () => - getByText( clipboardURL ) - ); + const autopastedLinkField = await editor.findByText( clipboardURL ); // Dismiss the edit URL modal. fireEvent( embedEditURLModal, 'backdropPress' ); fireEvent( embedEditURLModal, MODAL_DISMISS_EVENT ); - const blockSettingsModal = await waitFor( () => - getByTestId( 'block-settings-modal' ) + await waitFor( () => editor.UNSAFE_getByType( WebView ) ); + await editor.findByText( 'Media settings' ); + + const blockSettingsModal = await editor.findByTestId( + 'block-settings-modal' ); // Get Twitter link field. const twitterLinkField = within( @@ -315,15 +326,15 @@ describe( 'Embed block', () => { describe( 'set URL when empty block', () => { it( 'sets empty URL when dismissing edit URL modal', async () => { - const { getByTestId, getByText } = await initializeWithEmbedBlock( - EMPTY_EMBED_HTML - ); + const editor = await initializeWithEmbedBlock( EMPTY_EMBED_HTML ); // Edit URL. - fireEvent.press( await waitFor( () => getByText( 'ADD LINK' ) ) ); + fireEvent.press( await editor.findByText( 'ADD LINK' ) ); // Wait for edit URL modal to be visible. - const embedEditURLModal = getByTestId( 'embed-edit-url-modal' ); + const embedEditURLModal = editor.getByTestId( + 'embed-edit-url-modal' + ); await waitFor( () => embedEditURLModal.props.isVisible ); // Dismiss the edit URL modal. @@ -336,18 +347,19 @@ describe( 'Embed block', () => { it( 'sets a valid URL when dismissing edit URL modal', async () => { const expectedURL = 'https://twitter.com/notnownikki'; - const { getByPlaceholderText, getByTestId, getByText } = - await initializeWithEmbedBlock( EMPTY_EMBED_HTML ); + const editor = await initializeWithEmbedBlock( EMPTY_EMBED_HTML ); // Edit URL. - fireEvent.press( getByText( 'ADD LINK' ) ); + fireEvent.press( editor.getByText( 'ADD LINK' ) ); // Wait for edit URL modal to be visible. - const embedEditURLModal = getByTestId( 'embed-edit-url-modal' ); + const embedEditURLModal = editor.getByTestId( + 'embed-edit-url-modal' + ); await waitFor( () => embedEditURLModal.props.isVisible ); // Set an URL. - const linkTextInput = getByPlaceholderText( 'Add link' ); + const linkTextInput = editor.getByPlaceholderText( 'Add link' ); fireEvent( linkTextInput, 'focus' ); fireEvent.changeText( linkTextInput, expectedURL ); @@ -355,8 +367,11 @@ describe( 'Embed block', () => { fireEvent( embedEditURLModal, 'backdropPress' ); fireEvent( embedEditURLModal, MODAL_DISMISS_EVENT ); - const blockSettingsModal = await waitFor( () => - getByTestId( 'block-settings-modal' ) + await waitFor( () => editor.UNSAFE_getByType( WebView ) ); + await editor.findByText( 'Media settings' ); + + const blockSettingsModal = await editor.findByTestId( + 'block-settings-modal' ); // Get Twitter link field. const twitterLinkField = within( @@ -373,26 +388,29 @@ describe( 'Embed block', () => { // Mock clipboard. Clipboard.getString.mockResolvedValue( clipboardURL ); - const { getByTestId, getByText } = await initializeWithEmbedBlock( - EMPTY_EMBED_HTML - ); + const editor = await initializeWithEmbedBlock( EMPTY_EMBED_HTML ); // Edit URL. - fireEvent.press( getByText( 'ADD LINK' ) ); + fireEvent.press( editor.getByText( 'ADD LINK' ) ); // Wait for edit URL modal to be visible. - const embedEditURLModal = getByTestId( 'embed-edit-url-modal' ); + const embedEditURLModal = editor.getByTestId( + 'embed-edit-url-modal' + ); await waitFor( () => embedEditURLModal.props.isVisible ); // Get embed link. - const embedLink = await waitFor( () => getByText( clipboardURL ) ); + const embedLink = await editor.findByText( clipboardURL ); // Dismiss the edit URL modal. fireEvent( embedEditURLModal, 'backdropPress' ); fireEvent( embedEditURLModal, MODAL_DISMISS_EVENT ); - const blockSettingsModal = await waitFor( () => - getByTestId( 'block-settings-modal' ) + await waitFor( () => editor.UNSAFE_getByType( WebView ) ); + await editor.findByText( 'Media settings' ); + + const blockSettingsModal = await editor.findByTestId( + 'block-settings-modal' ); // Get Twitter link field. const twitterLinkField = within( @@ -409,16 +427,17 @@ describe( 'Embed block', () => { describe( 'edit URL', () => { it( 'keeps the previous URL if no URL is set', async () => { - const { getByLabelText, getByTestId } = - await initializeWithEmbedBlock( RICH_TEXT_EMBED_HTML ); + const editor = await initializeWithEmbedBlock( + RICH_TEXT_EMBED_HTML + ); // Open Block Settings. - fireEvent.press( - await waitFor( () => getByLabelText( 'Open Settings' ) ) - ); + fireEvent.press( await editor.findByLabelText( 'Open Settings' ) ); // Wait for Block Settings to be visible. - const blockSettingsModal = getByTestId( 'block-settings-modal' ); + const blockSettingsModal = editor.getByTestId( + 'block-settings-modal' + ); await waitFor( () => blockSettingsModal.props.isVisible ); // Dismiss the Block Settings modal. @@ -432,16 +451,17 @@ describe( 'Embed block', () => { const initialURL = 'https://twitter.com/notnownikki'; const expectedURL = 'https://www.youtube.com/watch?v=lXMskKTw3Bc'; - const { getByLabelText, getByDisplayValue, getByTestId } = - await initializeWithEmbedBlock( RICH_TEXT_EMBED_HTML ); + const editor = await initializeWithEmbedBlock( + RICH_TEXT_EMBED_HTML + ); // Open Block Settings. - fireEvent.press( - await waitFor( () => getByLabelText( 'Open Settings' ) ) - ); + fireEvent.press( await editor.findByLabelText( 'Open Settings' ) ); // Wait for Block Settings to be visible. - const blockSettingsModal = getByTestId( 'block-settings-modal' ); + const blockSettingsModal = editor.getByTestId( + 'block-settings-modal' + ); await waitFor( () => blockSettingsModal.props.isVisible ); // Start editing link. @@ -452,7 +472,7 @@ describe( 'Embed block', () => { ); // Replace URL. - const linkTextInput = getByDisplayValue( initialURL ); + const linkTextInput = editor.getByDisplayValue( initialURL ); fireEvent( linkTextInput, 'focus' ); fireEvent.changeText( linkTextInput, expectedURL ); @@ -460,12 +480,13 @@ describe( 'Embed block', () => { fireEvent( blockSettingsModal, 'backdropPress' ); fireEvent( blockSettingsModal, MODAL_DISMISS_EVENT ); + await waitFor( () => editor.UNSAFE_getByType( WebView ) ); + await editor.findByText( 'Media settings' ); + // Get YouTube link field. - const youtubeLinkField = await waitFor( () => - within( blockSettingsModal ).getByLabelText( - `YouTube link, ${ expectedURL }` - ) - ); + const youtubeLinkField = await within( + blockSettingsModal + ).findByLabelText( `YouTube link, ${ expectedURL }` ); expect( youtubeLinkField ).toBeDefined(); expect( getEditorHtml() ).toMatchSnapshot(); @@ -475,20 +496,17 @@ describe( 'Embed block', () => { const previousURL = 'https://twitter.com/notnownikki'; const invalidURL = 'http://'; - const { - getByLabelText, - getByDisplayValue, - getByTestId, - getByText, - } = await initializeWithEmbedBlock( RICH_TEXT_EMBED_HTML ); + const editor = await initializeWithEmbedBlock( + RICH_TEXT_EMBED_HTML + ); // Open Block Settings. - fireEvent.press( - await waitFor( () => getByLabelText( 'Open Settings' ) ) - ); + fireEvent.press( await editor.findByLabelText( 'Open Settings' ) ); // Wait for Block Settings to be visible. - const blockSettingsModal = getByTestId( 'block-settings-modal' ); + const blockSettingsModal = editor.getByTestId( + 'block-settings-modal' + ); await waitFor( () => blockSettingsModal.props.isVisible ); // Start editing link. @@ -499,7 +517,7 @@ describe( 'Embed block', () => { ); // Replace URL. - const linkTextInput = getByDisplayValue( previousURL ); + const linkTextInput = editor.getByDisplayValue( previousURL ); fireEvent( linkTextInput, 'focus' ); fireEvent.changeText( linkTextInput, invalidURL ); @@ -507,8 +525,8 @@ describe( 'Embed block', () => { fireEvent( blockSettingsModal, 'backdropPress' ); fireEvent( blockSettingsModal, MODAL_DISMISS_EVENT ); - const errorNotice = await waitFor( () => - getByText( 'Invalid URL. Please enter a valid URL.' ) + const errorNotice = await editor.findByText( + 'Invalid URL. Please enter a valid URL.' ); expect( errorNotice ).toBeDefined(); @@ -518,20 +536,17 @@ describe( 'Embed block', () => { it( 'sets empty state when setting an empty URL', async () => { const previousURL = 'https://twitter.com/notnownikki'; - const { - getByLabelText, - getByDisplayValue, - getByTestId, - getByPlaceholderText, - } = await initializeWithEmbedBlock( RICH_TEXT_EMBED_HTML ); + const editor = await initializeWithEmbedBlock( + RICH_TEXT_EMBED_HTML + ); // Open Block Settings. - fireEvent.press( - await waitFor( () => getByLabelText( 'Open Settings' ) ) - ); + fireEvent.press( await editor.findByLabelText( 'Open Settings' ) ); // Get Block Settings modal. - const blockSettingsModal = getByTestId( 'block-settings-modal' ); + const blockSettingsModal = editor.getByTestId( + 'block-settings-modal' + ); // Start editing link. fireEvent.press( @@ -541,7 +556,7 @@ describe( 'Embed block', () => { ); // Replace URL with empty value. - const linkTextInput = getByDisplayValue( previousURL ); + const linkTextInput = editor.getByDisplayValue( previousURL ); fireEvent( linkTextInput, 'focus' ); fireEvent.changeText( linkTextInput, '' ); @@ -550,8 +565,8 @@ describe( 'Embed block', () => { fireEvent( blockSettingsModal, MODAL_DISMISS_EVENT ); // Get empty embed link. - const emptyLinkTextInput = await waitFor( () => - getByPlaceholderText( 'Add link' ) + const emptyLinkTextInput = await editor.findByPlaceholderText( + 'Add link' ); expect( emptyLinkTextInput ).toBeDefined(); @@ -560,10 +575,12 @@ describe( 'Embed block', () => { // This test case covers the bug fixed in PR #35460. it( 'edits URL after dismissing two times the edit URL bottom sheet with empty value', async () => { - const { block, getByTestId, getByText } = await insertEmbedBlock(); + const editor = await insertEmbedBlock(); // Wait for edit URL modal to be visible. - const embedEditURLModal = getByTestId( 'embed-edit-url-modal' ); + const embedEditURLModal = editor.getByTestId( + 'embed-edit-url-modal' + ); await waitFor( () => embedEditURLModal.props.isVisible ); // Dismiss the edit URL modal. @@ -571,10 +588,10 @@ describe( 'Embed block', () => { fireEvent( embedEditURLModal, MODAL_DISMISS_EVENT ); // Select block. - fireEvent.press( block ); + fireEvent.press( editor.block ); // Edit URL. - fireEvent.press( getByText( 'ADD LINK' ) ); + fireEvent.press( editor.getByText( 'ADD LINK' ) ); // Wait for edit URL modal to be visible. await waitFor( () => embedEditURLModal.props.isVisible ); @@ -584,7 +601,7 @@ describe( 'Embed block', () => { fireEvent( embedEditURLModal, MODAL_DISMISS_EVENT ); // Edit URL. - fireEvent.press( getByText( 'ADD LINK' ) ); + fireEvent.press( editor.getByText( 'ADD LINK' ) ); // Wait for edit URL modal to be visible. const isVisibleThirdTime = await waitFor( @@ -599,19 +616,16 @@ describe( 'Embed block', () => { const badURL = 'https://youtu.be/BAD_URL'; const expectedURL = 'https://twitter.com/notnownikki'; - const { - getByLabelText, - getByDisplayValue, - getByPlaceholderText, - getByTestId, - } = await insertEmbedBlock(); + const editor = await insertEmbedBlock(); // Wait for edit URL modal to be visible. - const embedEditURLModal = getByTestId( 'embed-edit-url-modal' ); + const embedEditURLModal = editor.getByTestId( + 'embed-edit-url-modal' + ); await waitFor( () => embedEditURLModal.props.isVisible ); // Set an bad URL. - let linkTextInput = getByPlaceholderText( 'Add link' ); + let linkTextInput = editor.getByPlaceholderText( 'Add link' ); fireEvent( linkTextInput, 'focus' ); fireEvent.changeText( linkTextInput, badURL ); @@ -620,12 +634,12 @@ describe( 'Embed block', () => { fireEvent( embedEditURLModal, MODAL_DISMISS_EVENT ); // Open Block Settings. - fireEvent.press( - await waitFor( () => getByLabelText( 'Open Settings' ) ) - ); + fireEvent.press( await editor.findByLabelText( 'Open Settings' ) ); // Wait for Block Settings to be visible. - const blockSettingsModal = getByTestId( 'block-settings-modal' ); + const blockSettingsModal = editor.getByTestId( + 'block-settings-modal' + ); await waitFor( () => blockSettingsModal.props.isVisible ); // Start editing link. @@ -636,7 +650,7 @@ describe( 'Embed block', () => { ); // Replace URL. - linkTextInput = getByDisplayValue( badURL ); + linkTextInput = editor.getByDisplayValue( badURL ); fireEvent( linkTextInput, 'focus' ); fireEvent.changeText( linkTextInput, expectedURL ); @@ -645,11 +659,9 @@ describe( 'Embed block', () => { fireEvent( blockSettingsModal, MODAL_DISMISS_EVENT ); // Get Twitter link field. - const twitterLinkField = await waitFor( () => - within( blockSettingsModal ).getByLabelText( - `Twitter link, ${ expectedURL }` - ) - ); + const twitterLinkField = await within( + blockSettingsModal + ).findByLabelText( `Twitter link, ${ expectedURL }` ); expect( twitterLinkField ).toBeDefined(); expect( getEditorHtml() ).toMatchSnapshot(); @@ -665,18 +677,15 @@ describe( 'Embed block', () => { 'Full width', ].forEach( ( alignmentOption ) => it( `sets ${ alignmentOption } option`, async () => { - const { getByLabelText, getByText } = - await initializeWithEmbedBlock( RICH_TEXT_EMBED_HTML ); + const editor = await initializeWithEmbedBlock( + RICH_TEXT_EMBED_HTML + ); // Open alignment options. - fireEvent.press( - await waitFor( () => getByLabelText( 'Align' ) ) - ); + fireEvent.press( await editor.findByLabelText( 'Align' ) ); // Select alignment option. - fireEvent.press( - await waitFor( () => getByText( alignmentOption ) ) - ); + fireEvent.press( await editor.findByText( alignmentOption ) ); expect( getEditorHtml() ).toMatchSnapshot(); } ) @@ -690,33 +699,33 @@ describe( 'Embed block', () => { // Return bad response for the first request to oembed endpoint // and success response for the rest of requests. let isFirstEmbedRequest = true; - fetchRequest.mockImplementation( ( { path } ) => { - let response = {}; - const isEmbedRequest = path.startsWith( '/oembed/1.0/proxy' ); - if ( isEmbedRequest ) { + fetchRequest.mockImplementation( async ( req ) => { + if ( req.path.startsWith( '/oembed/1.0/proxy' ) ) { if ( isFirstEmbedRequest ) { isFirstEmbedRequest = false; - response = MOCK_BAD_WORDPRESS_RESPONSE; - } else { - response = RICH_TEXT_EMBED_SUCCESS_RESPONSE; + return MOCK_BAD_WORDPRESS_RESPONSE; } + return RICH_TEXT_EMBED_SUCCESS_RESPONSE; } - if ( path.startsWith( '/wp/v2/block-patterns/categories' ) ) { - response = []; - } - return Promise.resolve( response ); + + return mockOtherResponses( req ); } ); - const { getByTestId, getByText } = await initializeWithEmbedBlock( + const editor = await initializeWithEmbedBlock( RICH_TEXT_EMBED_HTML ); + await editor.findByText( 'Unable to embed media' ); + // Retry request. - fireEvent.press( getByText( 'More options' ) ); - fireEvent.press( getByText( 'Retry' ) ); + fireEvent.press( editor.getByText( 'More options' ) ); + fireEvent.press( editor.getByText( 'Retry' ) ); + + await waitFor( () => editor.UNSAFE_getByType( WebView ) ); + await editor.findByText( 'Media settings' ); - const blockSettingsModal = await waitFor( () => - getByTestId( 'block-settings-modal' ) + const blockSettingsModal = await editor.findByTestId( + 'block-settings-modal' ); // Get Twitter link field. const twitterLinkField = within( @@ -729,26 +738,25 @@ describe( 'Embed block', () => { it( 'converts to link if preview request failed', async () => { // Return bad response for requests to oembed endpoint. - fetchRequest.mockImplementation( ( { path } ) => { - if ( path.startsWith( '/wp/v2/block-patterns/categories' ) ) { - return Promise.resolve( [] ); + fetchRequest.mockImplementation( async ( req ) => { + if ( req.path.startsWith( '/oembed/1.0/proxy' ) ) { + return MOCK_BAD_WORDPRESS_RESPONSE; } - const isEmbedRequest = path.startsWith( '/oembed/1.0/proxy' ); - return Promise.resolve( - isEmbedRequest ? MOCK_BAD_WORDPRESS_RESPONSE : {} - ); + + return mockOtherResponses( req ); } ); - const { getByLabelText, getByText } = - await initializeWithEmbedBlock( RICH_TEXT_EMBED_HTML ); + const editor = await initializeWithEmbedBlock( + RICH_TEXT_EMBED_HTML + ); // Convert embed to link. - fireEvent.press( getByText( 'More options' ) ); - fireEvent.press( getByText( 'Convert to link' ) ); + fireEvent.press( editor.getByText( 'More options' ) ); + fireEvent.press( editor.getByText( 'Convert to link' ) ); // Get paragraph block where the link is created. - const paragraphBlock = await waitFor( () => - getByLabelText( /Paragraph Block\. Row 1/ ) + const [ paragraphBlock ] = await editor.findAllByLabelText( + /Paragraph Block\. Row 1/ ); expect( paragraphBlock ).toBeDefined(); @@ -760,50 +768,48 @@ describe( 'Embed block', () => { const successURL = 'https://twitter.com/notnownikki'; // Return bad response for WordPress URL and success for Twitter URL. - fetchRequest.mockImplementation( ( { path } ) => { + fetchRequest.mockImplementation( async ( req ) => { const matchesPath = ( url ) => - path === + req.path === `/oembed/1.0/proxy?url=${ encodeURIComponent( url ) }`; - let response = {}; if ( matchesPath( failURL ) ) { - response = MOCK_BAD_WORDPRESS_RESPONSE; - } else if ( matchesPath( successURL ) ) { - response = RICH_TEXT_EMBED_SUCCESS_RESPONSE; - } else if ( - path.startsWith( '/wp/v2/block-patterns/categories' ) - ) { - response = []; + return MOCK_BAD_WORDPRESS_RESPONSE; } - return Promise.resolve( response ); + if ( matchesPath( successURL ) ) { + return RICH_TEXT_EMBED_SUCCESS_RESPONSE; + } + + return mockOtherResponses( req ); } ); - const { - getByLabelText, - getByText, - getByTestId, - getByDisplayValue, - } = await initializeWithEmbedBlock( WP_EMBED_HTML ); + const editor = await initializeWithEmbedBlock( WP_EMBED_HTML ); - fireEvent.press( getByText( 'More options' ) ); - fireEvent.press( getByText( 'Edit link' ) ); + fireEvent.press( editor.getByText( 'More options' ) ); + fireEvent.press( editor.getByText( 'Edit link' ) ); // Start editing link. - fireEvent.press( getByLabelText( `WordPress link, ${ failURL }` ) ); + fireEvent.press( + editor.getByLabelText( `WordPress link, ${ failURL }` ) + ); // Set an URL. - const linkTextInput = getByDisplayValue( failURL ); + const linkTextInput = editor.getByDisplayValue( failURL ); fireEvent( linkTextInput, 'focus' ); fireEvent.changeText( linkTextInput, successURL ); // Dismiss the edit URL modal. - const embedEditURLModal = getByTestId( 'embed-edit-url-modal' ); + const embedEditURLModal = editor.getByTestId( + 'embed-edit-url-modal' + ); fireEvent( embedEditURLModal, 'backdropPress' ); fireEvent( embedEditURLModal, MODAL_DISMISS_EVENT ); - const blockSettingsModal = await waitFor( () => - getByTestId( 'block-settings-modal' ) + await waitFor( () => editor.UNSAFE_getByType( WebView ) ); + + const blockSettingsModal = await editor.findByTestId( + 'block-settings-modal' ); // Get Twitter link field. const twitterLinkField = within( @@ -864,17 +870,13 @@ describe( 'Embed block', () => { it( 'creates embed block when pasting URL in paragraph block', async () => { const expectedURL = 'https://www.youtube.com/watch?v=lXMskKTw3Bc'; - const { - getByLabelText, - getByPlaceholderText, - getByTestId, - getByText, - } = await initializeEditor( { + const editor = await initializeEditor( { initialHtml: EMPTY_PARAGRAPH_HTML, } ); // Paste URL in paragraph block. - const paragraphText = getByPlaceholderText( 'Start writing…' ); + const paragraphText = + editor.getByPlaceholderText( 'Start writing…' ); fireEvent( paragraphText, 'focus' ); fireEvent( paragraphText, 'paste', { preventDefault: jest.fn(), @@ -888,36 +890,37 @@ describe( 'Embed block', () => { } ); // Wait for embed handler picker to be visible. - await waitFor( - () => getByTestId( 'embed-handler-picker' ).props.isVisible + const embedHandlerPicker = editor.getByTestId( + 'embed-handler-picker' ); + await waitFor( () => embedHandlerPicker.props.isVisible ); // Select create embed option. - fireEvent.press( getByText( 'Create embed' ) ); + fireEvent.press( editor.getByText( 'Create embed' ) ); // Get the created embed block. - const embedBlock = await waitFor( () => - getByLabelText( /Embed Block\. Row 1/ ) + const [ embedBlock ] = await editor.findAllByLabelText( + /Embed Block\. Row 1/ ); expect( embedBlock ).toBeDefined(); + + await waitFor( () => editor.UNSAFE_getByType( WebView ) ); + await editor.findByText( 'Media settings' ); + expect( getEditorHtml() ).toMatchSnapshot(); } ); it( 'creates link when pasting URL in paragraph block', async () => { const expectedURL = 'https://www.youtube.com/watch?v=lXMskKTw3Bc'; - const { - getByDisplayValue, - getByPlaceholderText, - getByTestId, - getByText, - } = await initializeEditor( { + const editor = await initializeEditor( { initialHtml: EMPTY_PARAGRAPH_HTML, } ); // Paste URL in paragraph block. - const paragraphText = getByPlaceholderText( 'Start writing…' ); + const paragraphText = + editor.getByPlaceholderText( 'Start writing…' ); fireEvent( paragraphText, 'focus' ); fireEvent( paragraphText, 'paste', { preventDefault: jest.fn(), @@ -931,16 +934,17 @@ describe( 'Embed block', () => { } ); // Wait for embed handler picker to be visible. - await waitFor( - () => getByTestId( 'embed-handler-picker' ).props.isVisible + const embedHandlerPicker = editor.getByTestId( + 'embed-handler-picker' ); + await waitFor( () => embedHandlerPicker.props.isVisible ); // Select create link option. - fireEvent.press( getByText( 'Create link' ) ); + fireEvent.press( editor.getByText( 'Create link' ) ); // Get the link text. const linkText = await waitFor( () => - getByDisplayValue( + editor.getByDisplayValue( `

${ expectedURL }

` ) ); @@ -953,10 +957,12 @@ describe( 'Embed block', () => { describe( 'insert via slash inserter', () => { it( 'insert generic embed block', async () => { const embedBlockSlashInserter = '/Embed'; - const { getByPlaceholderText, getByLabelText, getByText } = - await initializeEditor( { initialHtml: EMPTY_PARAGRAPH_HTML } ); + const editor = await initializeEditor( { + initialHtml: EMPTY_PARAGRAPH_HTML, + } ); - const paragraphText = getByPlaceholderText( 'Start writing…' ); + const paragraphText = + editor.getByPlaceholderText( 'Start writing…' ); fireEvent( paragraphText, 'focus' ); // Trigger onSelectionChange to update both the current text and text selection. // This event is required by the autocompleter, as it only displays the slash inserter @@ -977,10 +983,10 @@ describe( 'Embed block', () => { } ); - fireEvent.press( await waitFor( () => getByText( 'Embed' ) ) ); + fireEvent.press( await editor.findByText( 'Embed' ) ); - const block = await waitFor( () => - getByLabelText( /Embed Block\. Row 1/ ) + const [ block ] = await editor.findAllByLabelText( + /Embed Block\. Row 1/ ); const blockName = within( block ).getByText( 'Embed' ); @@ -992,12 +998,12 @@ describe( 'Embed block', () => { MOST_USED_PROVIDERS.forEach( ( { title } ) => it( `inserts ${ title } embed block`, async () => { const embedBlockSlashInserter = `/${ title }`; - const { getByPlaceholderText, getByLabelText, getByText } = - await initializeEditor( { - initialHtml: EMPTY_PARAGRAPH_HTML, - } ); + const editor = await initializeEditor( { + initialHtml: EMPTY_PARAGRAPH_HTML, + } ); - const paragraphText = getByPlaceholderText( 'Start writing…' ); + const paragraphText = + editor.getByPlaceholderText( 'Start writing…' ); fireEvent( paragraphText, 'focus' ); // Trigger onSelectionChange to update both the current text and text selection. // This event is required by the autocompleter, as it only displays the slash inserter @@ -1018,10 +1024,10 @@ describe( 'Embed block', () => { } ); - fireEvent.press( await waitFor( () => getByText( title ) ) ); + fireEvent.press( await editor.findByText( title ) ); - const block = await waitFor( () => - getByLabelText( /Embed Block\. Row 1/ ) + const [ block ] = await editor.findAllByLabelText( + /Embed Block\. Row 1/ ); const blockName = within( block ).getByText( title ); @@ -1060,12 +1066,12 @@ describe( 'Embed block', () => { it( 'displays cannot embed on the placeholder if preview data is null', async () => { // Return null response for requests to oembed endpoint. - fetchRequest.mockImplementation( ( { path } ) => { - if ( path.startsWith( '/wp/v2/block-patterns/categories' ) ) { - return Promise.resolve( [] ); + fetchRequest.mockImplementation( async ( req ) => { + if ( req.path.startsWith( '/oembed/1.0/proxy' ) ) { + return EMBED_NULL_RESPONSE; } - const isEmbedRequest = path.startsWith( '/oembed/1.0/proxy' ); - return Promise.resolve( isEmbedRequest ? EMBED_NULL_RESPONSE : {} ); + + return mockOtherResponses( req ); } ); const { getByText } = await initializeWithEmbedBlock( diff --git a/packages/block-library/src/file/inspector.js b/packages/block-library/src/file/inspector.js index 121f5e0473e767..71e0885eae1c6b 100644 --- a/packages/block-library/src/file/inspector.js +++ b/packages/block-library/src/file/inspector.js @@ -56,6 +56,7 @@ export default function FileBlockInspector( { /> { displayPreview && ( - - + /> - - + /> id === imageAttributes.id ) : null; let newClassName; @@ -217,9 +218,19 @@ function GalleryEdit( props ) { } function isValidFileType( file ) { + // It's necessary to retrieve the media type from the raw image data for already-uploaded images on native. + const nativeFileData = + Platform.isNative && file.id + ? imageData.find( ( { id } ) => id === file.id ) + : null; + + const mediaTypeSelector = nativeFileData + ? nativeFileData?.media_type + : file.type; + return ( ALLOWED_MEDIA_TYPES.some( - ( mediaType ) => file.type?.indexOf( mediaType ) === 0 + ( mediaType ) => mediaTypeSelector?.indexOf( mediaType ) === 0 ) || file.url?.indexOf( 'blob:' ) === 0 ); } @@ -323,7 +334,7 @@ function GalleryEdit( props ) { getBlock( clientId ).innerBlocks.forEach( ( block ) => { blocks.push( block.clientId ); const image = block.attributes.id - ? find( imageData, { id: block.attributes.id } ) + ? imageData.find( ( { id } ) => id === block.attributes.id ) : null; changedAttributes[ block.clientId ] = getHrefAndDestination( image, @@ -391,7 +402,7 @@ function GalleryEdit( props ) { getBlock( clientId ).innerBlocks.forEach( ( block ) => { blocks.push( block.clientId ); const image = block.attributes.id - ? find( imageData, { id: block.attributes.id } ) + ? imageData.find( ( { id } ) => id === block.attributes.id ) : null; changedAttributes[ block.clientId ] = getImageSizeAttributes( image, @@ -474,8 +485,20 @@ function GalleryEdit( props ) { className: classnames( className, 'has-nested-images' ), } ); + const innerBlocksProps = useInnerBlocksProps( blockProps, { + allowedBlocks, + orientation: 'horizontal', + renderAppender: false, + __experimentalLayout: { type: 'default', alignments: [] }, + } ); + if ( ! hasImages ) { - return { mediaPlaceholder }; + return ( + + { innerBlocksProps.children } + { mediaPlaceholder } + + ); } const hasLinkTo = linkTo && linkTo !== 'none'; @@ -486,6 +509,7 @@ function GalleryEdit( props ) { { images.length > 1 && ( diff --git a/packages/block-library/src/gallery/gallery.js b/packages/block-library/src/gallery/gallery.js index e6176cc8a7256c..957a141d51c6b7 100644 --- a/packages/block-library/src/gallery/gallery.js +++ b/packages/block-library/src/gallery/gallery.js @@ -8,7 +8,6 @@ import classnames from 'classnames'; */ import { RichText, - useInnerBlocksProps, __experimentalGetElementClassName, } from '@wordpress/block-editor'; import { VisuallyHidden } from '@wordpress/components'; @@ -16,8 +15,6 @@ import { __ } from '@wordpress/i18n'; import { createBlock, getDefaultBlockName } from '@wordpress/blocks'; import { View } from '@wordpress/primitives'; -const allowedBlocks = [ 'core/image' ]; - export const Gallery = ( props ) => { const { attributes, @@ -31,16 +28,9 @@ export const Gallery = ( props ) => { const { align, columns, caption, imageCrop } = attributes; - const { children, ...innerBlocksProps } = useInnerBlocksProps( blockProps, { - allowedBlocks, - orientation: 'horizontal', - renderAppender: false, - __experimentalLayout: { type: 'default', alignments: [] }, - } ); - return (
{ } ) } > - { children } - { isSelected && ! children && ( + { blockProps.children } + { isSelected && ! blockProps.children && ( { mediaPlaceholder } diff --git a/packages/block-library/src/gallery/gallery.native.js b/packages/block-library/src/gallery/gallery.native.js index 16c504f6ccc19a..a5174d0215c424 100644 --- a/packages/block-library/src/gallery/gallery.native.js +++ b/packages/block-library/src/gallery/gallery.native.js @@ -2,7 +2,6 @@ * External dependencies */ import { View } from 'react-native'; -import { isEmpty } from 'lodash'; /** * Internal dependencies @@ -100,7 +99,7 @@ export const Gallery = ( props ) => { isSelected={ isCaptionSelected } accessible={ true } accessibilityLabelCreator={ ( caption ) => - isEmpty( caption ) + ! caption ? /* translators: accessibility text. Empty gallery caption. */ 'Gallery caption. Empty' diff --git a/packages/block-library/src/gallery/shared.js b/packages/block-library/src/gallery/shared.js index 4765b4395994f7..f6a614f1e2466b 100644 --- a/packages/block-library/src/gallery/shared.js +++ b/packages/block-library/src/gallery/shared.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { get, pick } from 'lodash'; +import { get } from 'lodash'; /** * WordPress dependencies @@ -13,7 +13,12 @@ export function defaultColumnsNumber( imageCount ) { } export const pickRelevantMediaFiles = ( image, sizeSlug = 'large' ) => { - const imageProps = pick( image, [ 'alt', 'id', 'link' ] ); + const imageProps = Object.fromEntries( + Object.entries( image ?? {} ).filter( ( [ key ] ) => + [ 'alt', 'id', 'link' ].includes( key ) + ) + ); + imageProps.url = get( image, [ 'sizes', sizeSlug, 'url' ] ) || get( image, [ 'media_details', 'sizes', sizeSlug, 'source_url' ] ) || @@ -44,18 +49,9 @@ function getGalleryBlockV2Enabled() { * can be removed when minimum supported WP version >=5.9. */ export function isGalleryV2Enabled() { - // The logic for the native version is located in a different if statement - // due to a lint rule that prohibits a single conditional combining - // `process.env.IS_GUTENBERG_PLUGIN` with a native platform check. if ( Platform.isNative ) { return getGalleryBlockV2Enabled(); } - // Only run the Gallery version compat check if the plugin is running, otherwise - // assume we are in 5.9 core and enable by default. - if ( process.env.IS_GUTENBERG_PLUGIN ) { - return getGalleryBlockV2Enabled(); - } - return true; } diff --git a/packages/block-library/src/gallery/transforms.js b/packages/block-library/src/gallery/transforms.js index 3c49c32330144e..f499b689b2a0df 100644 --- a/packages/block-library/src/gallery/transforms.js +++ b/packages/block-library/src/gallery/transforms.js @@ -1,8 +1,3 @@ -/** - * External dependencies - */ -import { filter } from 'lodash'; - /** * WordPress dependencies */ @@ -144,7 +139,7 @@ const transforms = { ? sizeSlug : undefined; - const validImages = filter( attributes, ( { url } ) => url ); + const validImages = attributes.filter( ( { url } ) => url ); if ( isGalleryV2Enabled() ) { const innerBlocks = validImages.map( ( image ) => { diff --git a/packages/block-library/src/gallery/use-get-media.js b/packages/block-library/src/gallery/use-get-media.js index 36559447ae291f..a6dbe9faabf0b9 100644 --- a/packages/block-library/src/gallery/use-get-media.js +++ b/packages/block-library/src/gallery/use-get-media.js @@ -28,7 +28,7 @@ export default function useGetMedia( innerBlockImages ) { return ( select( coreStore ).getMediaItems( { include: imageIds.join( ',' ), - per_page: imageIds.length, + per_page: -1, orderby: 'include', } ) ?? EMPTY_IMAGE_MEDIA ); diff --git a/packages/block-library/src/gallery/use-get-media.native.js b/packages/block-library/src/gallery/use-get-media.native.js index 994a2152f756c2..b03defbdb49d53 100644 --- a/packages/block-library/src/gallery/use-get-media.native.js +++ b/packages/block-library/src/gallery/use-get-media.native.js @@ -34,7 +34,8 @@ export default function useGetMedia( innerBlockImages ) { return ( select( coreStore ).getMediaItems( { include: imageIds.join( ',' ), - per_page: imageIds.length, + per_page: + imageIds.length /* 'hard' limit necessary as unbounded queries aren't supported on native */, orderby: 'include', } ) ?? EMPTY_IMAGE_MEDIA ); diff --git a/packages/block-library/src/gallery/v1/edit.js b/packages/block-library/src/gallery/v1/edit.js index 2ac1df11c72797..9c36f02ec028ad 100644 --- a/packages/block-library/src/gallery/v1/edit.js +++ b/packages/block-library/src/gallery/v1/edit.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { filter, find, get, isEmpty, map } from 'lodash'; +import { get, isEmpty, map } from 'lodash'; /** * WordPress dependencies @@ -195,7 +195,7 @@ function GalleryEdit( props ) { function onRemoveImage( index ) { return () => { - const newImages = filter( images, ( img, i ) => index !== i ); + const newImages = images.filter( ( img, i ) => index !== i ); setSelectedImage(); setAttributes( { images: newImages, @@ -211,7 +211,7 @@ function GalleryEdit( props ) { // string, so ensure comparison works correctly by converting the // newImage.id to a string. const newImageId = newImage.id.toString(); - const currentImage = find( images, { id: newImageId } ); + const currentImage = images.find( ( { id } ) => id === newImageId ); const currentImageCaption = currentImage ? currentImage.caption : newImage.caption; @@ -220,9 +220,9 @@ function GalleryEdit( props ) { return currentImageCaption; } - const attachment = find( attachmentCaptions, { - id: newImageId, - } ); + const attachment = attachmentCaptions.find( + ( { id } ) => id === newImageId + ); // If the attachment caption is updated. if ( attachment && attachment.caption !== newImage.caption ) { @@ -299,7 +299,7 @@ function GalleryEdit( props ) { function getImagesSizeOptions() { const resizedImageSizes = Object.values( resizedImages ); return map( - filter( imageSizes, ( { slug } ) => + imageSizes.filter( ( { slug } ) => resizedImageSizes.some( ( sizes ) => sizes[ slug ] ) ), ( { name, slug } ) => ( { value: slug, label: name } ) @@ -403,6 +403,7 @@ function GalleryEdit( props ) { { images.length > 1 && ( { isSelected={ isCaptionSelected } accessible={ true } accessibilityLabelCreator={ ( caption ) => - isEmpty( caption ) + ! caption ? /* translators: accessibility text. Empty gallery caption. */ 'Gallery caption. Empty' : sprintf( diff --git a/packages/block-library/src/gallery/v1/shared.js b/packages/block-library/src/gallery/v1/shared.js index 484020cb9d58cf..9a0957cc7a120d 100644 --- a/packages/block-library/src/gallery/v1/shared.js +++ b/packages/block-library/src/gallery/v1/shared.js @@ -1,10 +1,15 @@ /** * External dependencies */ -import { get, pick } from 'lodash'; +import { get } from 'lodash'; export const pickRelevantMediaFiles = ( image, sizeSlug = 'large' ) => { - const imageProps = pick( image, [ 'alt', 'id', 'link', 'caption' ] ); + const imageProps = Object.fromEntries( + Object.entries( image ?? {} ).filter( ( [ key ] ) => + [ 'alt', 'id', 'link', 'caption' ].includes( key ) + ) + ); + imageProps.url = get( image, [ 'sizes', sizeSlug, 'url' ] ) || get( image, [ 'media_details', 'sizes', sizeSlug, 'source_url' ] ) || diff --git a/packages/block-library/src/group/block.json b/packages/block-library/src/group/block.json index 3f409ba052c95d..a3997db0621c54 100644 --- a/packages/block-library/src/group/block.json +++ b/packages/block-library/src/group/block.json @@ -69,7 +69,9 @@ "fontSize": true } }, - "__experimentalLayout": true + "__experimentalLayout": { + "allowSizingOnChildren": true + } }, "editorStyle": "wp-block-group-editor", "style": "wp-block-group" diff --git a/packages/block-library/src/group/edit.js b/packages/block-library/src/group/edit.js index 7c324d76c587d3..8962d8a8a3bf16 100644 --- a/packages/block-library/src/group/edit.js +++ b/packages/block-library/src/group/edit.js @@ -12,7 +12,6 @@ import { } from '@wordpress/block-editor'; import { SelectControl } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; -import { useCallback } from '@wordpress/element'; /** * Internal dependencies @@ -123,13 +122,10 @@ function GroupEdit( { ); const { selectBlock } = useDispatch( blockEditorStore ); - const updateSelection = useCallback( - ( newClientId ) => selectBlock( newClientId, -1 ), - [ selectBlock ] - ); + const selectVariation = ( nextVariation ) => { setAttributes( nextVariation.attributes ); - updateSelection( clientId ); + selectBlock( clientId, -1 ); setShowPlaceholder( false ); }; diff --git a/packages/block-library/src/group/placeholder.js b/packages/block-library/src/group/placeholder.js index 0f8fbc253c8986..daf535df8bf607 100644 --- a/packages/block-library/src/group/placeholder.js +++ b/packages/block-library/src/group/placeholder.js @@ -24,10 +24,7 @@ const getGroupPlaceholderIcons = ( name = 'group' ) => { height="32" viewBox="0 0 44 32" > - + ), 'group-row': ( @@ -37,10 +34,7 @@ const getGroupPlaceholderIcons = ( name = 'group' ) => { height="32" viewBox="0 0 44 32" > - + ), 'group-stack': ( @@ -50,10 +44,7 @@ const getGroupPlaceholderIcons = ( name = 'group' ) => { height="32" viewBox="0 0 44 32" > - + ), }; @@ -130,23 +121,13 @@ export function useShouldShowPlaceHolder( { * @return {JSX.Element} The placeholder. */ function GroupPlaceHolder( { name, onSelect } ) { - const { defaultVariation, variations } = useSelect( - ( select ) => { - const { getBlockVariations, getDefaultBlockVariation } = - select( blocksStore ); - return { - defaultVariation: getDefaultBlockVariation( name, 'block' ), - variations: getBlockVariations( name, 'block' ) || [], - }; - }, + const variations = useSelect( + ( select ) => select( blocksStore ).getBlockVariations( name, 'block' ), [ name ] ); const blockProps = useBlockProps( { className: 'wp-block-group__placeholder', } ); - const selectVariation = ( nextVariation = defaultVariation ) => - onSelect( nextVariation ); - return (
selectVariation( variation ) } + onClick={ () => onSelect( variation ) } className="wp-block-group-placeholder__variation-button" label={ `${ variation.title }: ${ variation.description }` } /> diff --git a/packages/block-library/src/group/test/__snapshots__/edit.native.js.snap b/packages/block-library/src/group/test/__snapshots__/edit.native.js.snap index ee8488edf88642..7234dc54ee4d59 100644 --- a/packages/block-library/src/group/test/__snapshots__/edit.native.js.snap +++ b/packages/block-library/src/group/test/__snapshots__/edit.native.js.snap @@ -3,7 +3,7 @@ exports[`Group block inserts block and adds a Heading block as an inner block 1`] = ` "
-

+

" `; diff --git a/packages/block-library/src/heading/block.json b/packages/block-library/src/heading/block.json index 89c1514808911e..f9d701eace964d 100644 --- a/packages/block-library/src/heading/block.json +++ b/packages/block-library/src/heading/block.json @@ -29,7 +29,7 @@ "supports": { "align": [ "wide", "full" ], "anchor": true, - "className": false, + "className": true, "color": { "gradients": true, "link": true, @@ -57,7 +57,6 @@ "textTransform": true } }, - "__experimentalSelector": "h1,h2,h3,h4,h5,h6", "__unstablePasteTextInline": true, "__experimentalSlashInserter": true }, diff --git a/packages/block-library/src/heading/deprecated.js b/packages/block-library/src/heading/deprecated.js index 9eaee486622777..6ea881acbc19f6 100644 --- a/packages/block-library/src/heading/deprecated.js +++ b/packages/block-library/src/heading/deprecated.js @@ -63,155 +63,228 @@ const migrateTextAlign = ( attributes ) => { : attributes; }; -const deprecated = [ - { - supports: { - align: [ 'wide', 'full' ], - anchor: true, - className: false, - color: { link: true }, - fontSize: true, - lineHeight: true, - __experimentalSelector: { - 'core/heading/h1': 'h1', - 'core/heading/h2': 'h2', - 'core/heading/h3': 'h3', - 'core/heading/h4': 'h4', - 'core/heading/h5': 'h5', - 'core/heading/h6': 'h6', - }, - __unstablePasteTextInline: true, +const v1 = { + supports: blockSupports, + attributes: { + ...blockAttributes, + customTextColor: { + type: 'string', }, - attributes: blockAttributes, - isEligible: ( { align } ) => TEXT_ALIGN_OPTIONS.includes( align ), - migrate: migrateTextAlign, - save( { attributes } ) { - const { align, content, level } = attributes; - const TagName = 'h' + level; - - const className = classnames( { - [ `has-text-align-${ align }` ]: align, - } ); - - return ( - - - - ); + textColor: { + type: 'string', }, }, - { - supports: blockSupports, - attributes: { - ...blockAttributes, - customTextColor: { - type: 'string', - }, - textColor: { - type: 'string', - }, + migrate: ( attributes ) => + migrateCustomColors( migrateTextAlign( attributes ) ), + save( { attributes } ) { + const { align, level, content, textColor, customTextColor } = + attributes; + const tagName = 'h' + level; + + const textClass = getColorClassName( 'color', textColor ); + + const className = classnames( { + [ textClass ]: textClass, + } ); + + return ( + + ); + }, +}; +const v2 = { + attributes: { + ...blockAttributes, + customTextColor: { + type: 'string', }, - migrate: ( attributes ) => - migrateCustomColors( migrateTextAlign( attributes ) ), - save( { attributes } ) { - const { align, content, customTextColor, level, textColor } = - attributes; - const tagName = 'h' + level; - - const textClass = getColorClassName( 'color', textColor ); - - const className = classnames( { - [ textClass ]: textClass, - 'has-text-color': textColor || customTextColor, - [ `has-text-align-${ align }` ]: align, - } ); - - return ( - - ); + textColor: { + type: 'string', }, }, - { - attributes: { - ...blockAttributes, - customTextColor: { - type: 'string', - }, - textColor: { - type: 'string', - }, + migrate: ( attributes ) => + migrateCustomColors( migrateTextAlign( attributes ) ), + save( { attributes } ) { + const { align, content, customTextColor, level, textColor } = + attributes; + const tagName = 'h' + level; + + const textClass = getColorClassName( 'color', textColor ); + + const className = classnames( { + [ textClass ]: textClass, + [ `has-text-align-${ align }` ]: align, + } ); + + return ( + + ); + }, + supports: blockSupports, +}; +const v3 = { + supports: blockSupports, + attributes: { + ...blockAttributes, + customTextColor: { + type: 'string', }, - migrate: ( attributes ) => - migrateCustomColors( migrateTextAlign( attributes ) ), - save( { attributes } ) { - const { align, content, customTextColor, level, textColor } = - attributes; - const tagName = 'h' + level; - - const textClass = getColorClassName( 'color', textColor ); - - const className = classnames( { - [ textClass ]: textClass, - [ `has-text-align-${ align }` ]: align, - } ); - - return ( - - ); + textColor: { + type: 'string', }, - supports: blockSupports, }, - { - supports: blockSupports, - attributes: { - ...blockAttributes, - customTextColor: { - type: 'string', + migrate: ( attributes ) => + migrateCustomColors( migrateTextAlign( attributes ) ), + save( { attributes } ) { + const { align, content, customTextColor, level, textColor } = + attributes; + const tagName = 'h' + level; + + const textClass = getColorClassName( 'color', textColor ); + + const className = classnames( { + [ textClass ]: textClass, + 'has-text-color': textColor || customTextColor, + [ `has-text-align-${ align }` ]: align, + } ); + + return ( + + ); + }, +}; +const v4 = { + supports: { + align: [ 'wide', 'full' ], + anchor: true, + className: false, + color: { link: true }, + fontSize: true, + lineHeight: true, + __experimentalSelector: { + 'core/heading/h1': 'h1', + 'core/heading/h2': 'h2', + 'core/heading/h3': 'h3', + 'core/heading/h4': 'h4', + 'core/heading/h5': 'h5', + 'core/heading/h6': 'h6', + }, + __unstablePasteTextInline: true, + }, + attributes: blockAttributes, + isEligible: ( { align } ) => TEXT_ALIGN_OPTIONS.includes( align ), + migrate: migrateTextAlign, + save( { attributes } ) { + const { align, content, level } = attributes; + const TagName = 'h' + level; + + const className = classnames( { + [ `has-text-align-${ align }` ]: align, + } ); + + return ( + + + + ); + }, +}; + +// This deprecation covers the serialization of the `wp-block-heading` class +// into the block's markup after className support was enabled. +const v5 = { + supports: { + align: [ 'wide', 'full' ], + anchor: true, + className: false, + color: { + gradients: true, + link: true, + __experimentalDefaultControls: { + background: true, + text: true, }, - textColor: { - type: 'string', + }, + spacing: { + margin: true, + padding: true, + }, + typography: { + fontSize: true, + lineHeight: true, + __experimentalFontFamily: true, + __experimentalFontStyle: true, + __experimentalFontWeight: true, + __experimentalLetterSpacing: true, + __experimentalTextTransform: true, + __experimentalTextDecoration: true, + __experimentalDefaultControls: { + fontSize: true, + fontAppearance: true, + textTransform: true, }, }, - migrate: ( attributes ) => - migrateCustomColors( migrateTextAlign( attributes ) ), - save( { attributes } ) { - const { align, level, content, textColor, customTextColor } = - attributes; - const tagName = 'h' + level; - - const textClass = getColorClassName( 'color', textColor ); - - const className = classnames( { - [ textClass ]: textClass, - } ); - - return ( - - ); + __experimentalSelector: 'h1,h2,h3,h4,h5,h6', + __unstablePasteTextInline: true, + __experimentalSlashInserter: true, + }, + attributes: { + textAlign: { + type: 'string', + }, + content: { + type: 'string', + source: 'html', + selector: 'h1,h2,h3,h4,h5,h6', + default: '', + __experimentalRole: 'content', + }, + level: { + type: 'number', + default: 2, + }, + placeholder: { + type: 'string', }, }, -]; + save( { attributes } ) { + const { textAlign, content, level } = attributes; + const TagName = 'h' + level; + + const className = classnames( { + [ `has-text-align-${ textAlign }` ]: textAlign, + } ); + + return ( + + + + ); + }, +}; + +const deprecated = [ v5, v4, v3, v2, v1 ]; export default deprecated; diff --git a/packages/block-library/src/heading/index.php b/packages/block-library/src/heading/index.php new file mode 100644 index 00000000000000..5a7e8dbaab43cf --- /dev/null +++ b/packages/block-library/src/heading/index.php @@ -0,0 +1,52 @@ +Hello World + * + * Would be transformed to: + *

Hello World

+ * + * @param array $attributes Attributes of the block being rendered. + * @param string $content Content of the block being rendered. + * + * @return string The content of the block being rendered. + */ +function block_core_heading_render( $attributes, $content ) { + if ( ! $content ) { + return $content; + } + + $p = new WP_HTML_Tag_Processor( $content ); + + $header_tags = array( 'H1', 'H2', 'H3', 'H4', 'H5', 'H6' ); + while ( $p->next_tag() ) { + if ( in_array( $p->get_tag(), $header_tags, true ) ) { + $p->add_class( 'wp-block-heading' ); + break; + } + } + + return $p->get_updated_html(); +} + +/** + * Registers the `core/heading` block on server. + */ +function register_block_core_heading() { + register_block_type_from_metadata( + __DIR__ . '/heading', + array( + 'render_callback' => 'block_core_heading_render', + ) + ); +} + +add_action( 'init', 'register_block_core_heading' ); diff --git a/packages/block-library/src/heading/test/__snapshots__/index.native.js.snap b/packages/block-library/src/heading/test/__snapshots__/index.native.js.snap new file mode 100644 index 00000000000000..8f3d3518636d3f --- /dev/null +++ b/packages/block-library/src/heading/test/__snapshots__/index.native.js.snap @@ -0,0 +1,7 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Heading block inserts block 1`] = ` +" +

+" +`; diff --git a/packages/block-library/src/heading/test/index.native.js b/packages/block-library/src/heading/test/index.native.js new file mode 100644 index 00000000000000..fce294cf9c9920 --- /dev/null +++ b/packages/block-library/src/heading/test/index.native.js @@ -0,0 +1,44 @@ +/** + * External dependencies + */ +import { + fireEvent, + getEditorHtml, + initializeEditor, + addBlock, + getBlock, +} from 'test/helpers'; + +/** + * WordPress dependencies + */ +import { getBlockTypes, unregisterBlockType } from '@wordpress/blocks'; +import { registerCoreBlocks } from '@wordpress/block-library'; + +beforeAll( () => { + // Register all core blocks + registerCoreBlocks(); +} ); + +afterAll( () => { + // Clean up registered blocks + getBlockTypes().forEach( ( block ) => { + unregisterBlockType( block.name ); + } ); +} ); + +describe( 'Heading block', () => { + it( 'inserts block', async () => { + const screen = await initializeEditor(); + + // Add block + await addBlock( screen, 'Heading' ); + + // Get block + const headingBlock = await getBlock( screen, 'Heading' ); + fireEvent.press( headingBlock ); + expect( headingBlock ).toBeVisible(); + + expect( getEditorHtml() ).toMatchSnapshot(); + } ); +} ); diff --git a/packages/block-library/src/home-link/index.php b/packages/block-library/src/home-link/index.php index 5bf7aeda5505df..33f41057c936bb 100644 --- a/packages/block-library/src/home-link/index.php +++ b/packages/block-library/src/home-link/index.php @@ -128,7 +128,7 @@ function render_block_core_home_link( $attributes, $content, $block ) { $aria_current = is_home() || ( is_front_page() && 'page' === get_option( 'show_on_front' ) ) ? ' aria-current="page"' : ''; return sprintf( - '
  • %4$s
  • ', + '
  • %4$s
  • ', block_core_home_link_build_li_wrapper_attributes( $block->context ), esc_url( home_url() ), $aria_current, diff --git a/packages/block-library/src/html/block.json b/packages/block-library/src/html/block.json index e72a674373e144..c1e1e94b87496b 100644 --- a/packages/block-library/src/html/block.json +++ b/packages/block-library/src/html/block.json @@ -10,7 +10,7 @@ "attributes": { "content": { "type": "string", - "source": "html" + "source": "raw" } }, "supports": { diff --git a/packages/block-library/src/image/edit.js b/packages/block-library/src/image/edit.js index baba2cee24ec33..4e504896b47094 100644 --- a/packages/block-library/src/image/edit.js +++ b/packages/block-library/src/image/edit.js @@ -2,7 +2,7 @@ * External dependencies */ import classnames from 'classnames'; -import { get, isEmpty, pick } from 'lodash'; +import { get, isEmpty } from 'lodash'; /** * WordPress dependencies @@ -58,7 +58,12 @@ import { } from './constants'; export const pickRelevantMediaFiles = ( image, size ) => { - const imageProps = pick( image, [ 'alt', 'id', 'link', 'caption' ] ); + const imageProps = Object.fromEntries( + Object.entries( image ?? {} ).filter( ( [ key ] ) => + [ 'alt', 'id', 'link', 'caption' ].includes( key ) + ) + ); + imageProps.url = get( image, [ 'sizes', size, 'url' ] ) || get( image, [ 'media_details', 'sizes', size, 'source_url' ] ) || diff --git a/packages/block-library/src/image/edit.native.js b/packages/block-library/src/image/edit.native.js index 8a9881fa95c719..7be5d157374f3d 100644 --- a/packages/block-library/src/image/edit.native.js +++ b/packages/block-library/src/image/edit.native.js @@ -61,8 +61,9 @@ import { textColor, } from '@wordpress/icons'; import { store as coreStore } from '@wordpress/core-data'; -import { store as editPostStore } from '@wordpress/edit-post'; import { store as noticesStore } from '@wordpress/notices'; +// eslint-disable-next-line no-restricted-imports +import { store as editPostStore } from '@wordpress/edit-post'; /** * Internal dependencies diff --git a/packages/block-library/src/image/editor.scss b/packages/block-library/src/image/editor.scss index 3e4bb7dd6392f0..fb32ab54298bc2 100644 --- a/packages/block-library/src/image/editor.scss +++ b/packages/block-library/src/image/editor.scss @@ -161,17 +161,6 @@ figure.wp-block-image:not(.wp-block) { min-width: 260px; overflow: visible !important; } - - .components-range-control { - flex: 1; - } - - .components-base-control__field { - display: flex; - margin-bottom: 0; - flex-direction: column; - align-items: flex-start; - } } .wp-block-image__aspect-ratio { diff --git a/packages/block-library/src/image/image.js b/packages/block-library/src/image/image.js index 7245ce232c64b7..5eb511b455864d 100644 --- a/packages/block-library/src/image/image.js +++ b/packages/block-library/src/image/image.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { get, filter, isEmpty, map, pick } from 'lodash'; +import { get, isEmpty, map } from 'lodash'; /** * WordPress dependencies @@ -135,12 +135,16 @@ export default function Image( { } = select( blockEditorStore ); const rootClientId = getBlockRootClientId( clientId ); - const settings = pick( getSettings(), [ - 'imageEditing', - 'imageSizes', - 'maxWidth', - 'mediaUpload', - ] ); + const settings = Object.fromEntries( + Object.entries( getSettings() ).filter( ( [ key ] ) => + [ + 'imageEditing', + 'imageSizes', + 'maxWidth', + 'mediaUpload', + ].includes( key ) + ) + ); return { ...settings, @@ -169,7 +173,7 @@ export default function Image( { ! isContentLocked && ! ( isWideAligned && isLargeViewport ); const imageSizeOptions = map( - filter( imageSizes, ( { slug } ) => + imageSizes.filter( ( { slug } ) => get( image, [ 'media_details', 'sizes', slug, 'source_url' ] ) ), ( { name, slug } ) => ( { value: slug, label: name } ) @@ -607,6 +611,7 @@ export default function Image( { height: parseInt( currentHeight + delta.height, 10 ), } ); } } + resizeRatio={ align === 'center' ? 2 : 1 } > { img } diff --git a/packages/block-library/src/image/test/edit.native.js b/packages/block-library/src/image/test/edit.native.js index 2f8aa703ee2b62..73f4ec22a554d2 100644 --- a/packages/block-library/src/image/test/edit.native.js +++ b/packages/block-library/src/image/test/edit.native.js @@ -81,7 +81,8 @@ describe( 'Image Block', () => { // We must await the image fetch via `getMedia` await act( () => apiFetchPromise ); - fireEvent.press( screen.getByLabelText( /Image Block/ ) ); + const [ imageBlock ] = screen.getAllByLabelText( /Image Block/ ); + fireEvent.press( imageBlock ); // Awaiting navigation event seemingly required due to React Navigation bug // https://github.com/react-navigation/react-navigation/issues/9701 await act( () => @@ -107,7 +108,8 @@ describe( 'Image Block', () => { // We must await the image fetch via `getMedia` await act( () => apiFetchPromise ); - fireEvent.press( screen.getByLabelText( /Image Block/ ) ); + const [ imageBlock ] = screen.getAllByLabelText( /Image Block/ ); + fireEvent.press( imageBlock ); // Awaiting navigation event seemingly required due to React Navigation bug // https://github.com/react-navigation/react-navigation/issues/9701 await act( () => @@ -133,7 +135,8 @@ describe( 'Image Block', () => { // We must await the image fetch via `getMedia` await act( () => apiFetchPromise ); - fireEvent.press( screen.getByLabelText( /Image Block/ ) ); + const [ imageBlock ] = screen.getAllByLabelText( /Image Block/ ); + fireEvent.press( imageBlock ); // Awaiting navigation event seemingly required due to React Navigation bug // https://github.com/react-navigation/react-navigation/issues/9701 await act( () => @@ -169,7 +172,8 @@ describe( 'Image Block', () => { // We must await the image fetch via `getMedia` await act( () => apiFetchPromise ); - fireEvent.press( screen.getByLabelText( /Image Block/ ) ); + const [ imageBlock ] = screen.getAllByLabelText( /Image Block/ ); + fireEvent.press( imageBlock ); // Awaiting navigation event seemingly required due to React Navigation bug // https://github.com/react-navigation/react-navigation/issues/9701 await act( () => @@ -177,7 +181,7 @@ describe( 'Image Block', () => { ); fireEvent.press( screen.getByText( 'None' ) ); fireEvent.press( screen.getByText( 'Media File' ) ); - await waitFor( () => screen.getByText( 'Custom URL' ) ); + await screen.findByText( 'Custom URL' ); fireEvent.press( screen.getByText( 'Custom URL' ) ); // Await asynchronous fetch of clipboard await act( () => clipboardPromise ); @@ -186,8 +190,7 @@ describe( 'Image Block', () => { 'wordpress.org' ); fireEvent.press( screen.getByLabelText( 'Apply' ) ); - await waitFor( () => screen.getByText( 'Custom URL' ) ); - fireEvent.press( screen.getByText( 'Custom URL' ) ); + fireEvent.press( await screen.findByText( 'Custom URL' ) ); // Await asynchronous fetch of clipboard await act( () => clipboardPromise ); fireEvent.press( screen.getByText( 'Media File' ) ); @@ -211,7 +214,8 @@ describe( 'Image Block', () => { // We must await the image fetch via `getMedia` await act( () => apiFetchPromise ); - fireEvent.press( screen.getByLabelText( /Image Block/ ) ); + const [ imageBlock ] = screen.getAllByLabelText( /Image Block/ ); + fireEvent.press( imageBlock ); // Awaiting navigation event seemingly required due to React Navigation bug // https://github.com/react-navigation/react-navigation/issues/9701 await act( () => @@ -235,7 +239,7 @@ describe( 'Image Block', () => { // We must await the image fetch via `getMedia` await act( () => apiFetchPromise ); - const imageBlock = screen.getByLabelText( /Image Block/ ); + const [ imageBlock ] = screen.getAllByLabelText( /Image Block/ ); fireEvent.press( imageBlock ); const settingsButton = screen.getByLabelText( 'Open Settings' ); @@ -266,7 +270,7 @@ describe( 'Image Block', () => { // We must await the image fetch via `getMedia` await act( () => apiFetchPromise ); - const imageBlock = screen.getByLabelText( /Image Block/ ); + const [ imageBlock ] = screen.getAllByLabelText( /Image Block/ ); fireEvent.press( imageBlock ); const settingsButton = screen.getByLabelText( 'Open Settings' ); diff --git a/packages/block-library/src/index.js b/packages/block-library/src/index.js index 88d2c26f02403a..34ba9fd5a2c8f1 100644 --- a/packages/block-library/src/index.js +++ b/packages/block-library/src/index.js @@ -67,6 +67,7 @@ import * as navigationSubmenu from './navigation-submenu'; import * as nextpage from './nextpage'; import * as pattern from './pattern'; import * as pageList from './page-list'; +import * as pageListItem from './page-list-item'; import * as paragraph from './paragraph'; import * as postAuthor from './post-author'; import * as postAuthorName from './post-author-name'; @@ -155,6 +156,7 @@ const getAllBlocks = () => more, nextpage, pageList, + pageListItem, pattern, preformatted, pullquote, diff --git a/packages/block-library/src/latest-comments/edit.js b/packages/block-library/src/latest-comments/edit.js index a376ebb94b6de2..01eb9e11dc3dec 100644 --- a/packages/block-library/src/latest-comments/edit.js +++ b/packages/block-library/src/latest-comments/edit.js @@ -56,6 +56,7 @@ export default function LatestComments( { attributes, setAttributes } ) { } /> diff --git a/packages/block-library/src/latest-posts/edit.js b/packages/block-library/src/latest-posts/edit.js index 3671ec555e600c..45ffe1338aaabf 100644 --- a/packages/block-library/src/latest-posts/edit.js +++ b/packages/block-library/src/latest-posts/edit.js @@ -234,6 +234,7 @@ export default function LatestPostsEdit( { attributes, setAttributes } ) { { displayPostContent && displayPostContentRadio === 'excerpt' && ( @@ -355,6 +356,7 @@ export default function LatestPostsEdit( { attributes, setAttributes } ) { { postLayout === 'grid' && ( diff --git a/packages/block-library/src/latest-posts/test/__snapshots__/edit.native.js.snap b/packages/block-library/src/latest-posts/test/__snapshots__/edit.native.js.snap new file mode 100644 index 00000000000000..dfdb88ce271d2c --- /dev/null +++ b/packages/block-library/src/latest-posts/test/__snapshots__/edit.native.js.snap @@ -0,0 +1,3 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Latest Posts block inserts block 1`] = `""`; diff --git a/packages/block-library/src/latest-posts/test/edit.native.js b/packages/block-library/src/latest-posts/test/edit.native.js new file mode 100644 index 00000000000000..aa7397cdcd404d --- /dev/null +++ b/packages/block-library/src/latest-posts/test/edit.native.js @@ -0,0 +1,49 @@ +/** + * External dependencies + */ +import { + addBlock, + getEditorHtml, + initializeEditor, + getBlock, +} from 'test/helpers'; + +/** + * WordPress dependencies + */ +import { getBlockTypes, unregisterBlockType } from '@wordpress/blocks'; +import { registerCoreBlocks } from '@wordpress/block-library'; +import apiFetch from '@wordpress/api-fetch'; + +beforeAll( () => { + // Register all core blocks + registerCoreBlocks(); +} ); + +afterAll( () => { + // Clean up registered blocks + getBlockTypes().forEach( ( block ) => { + unregisterBlockType( block.name ); + } ); +} ); + +describe( 'Latest Posts block', () => { + afterEach( () => { + apiFetch.mockReset(); + } ); + + it( 'inserts block', async () => { + const screen = await initializeEditor(); + + // Mock return value for categories + apiFetch.mockReturnValueOnce( Promise.resolve( [] ) ); + + // Add block + await addBlock( screen, 'Latest Posts' ); + + // Get block + const latestPostsBlock = await getBlock( screen, 'Latest Posts' ); + expect( latestPostsBlock ).toBeVisible(); + expect( getEditorHtml() ).toMatchSnapshot(); + } ); +} ); diff --git a/packages/block-library/src/list-item/index.js b/packages/block-library/src/list-item/index.js index 61756019baf920..00adc1c2c40266 100644 --- a/packages/block-library/src/list-item/index.js +++ b/packages/block-library/src/list-item/index.js @@ -10,6 +10,7 @@ import initBlock from '../utils/init-block'; import metadata from './block.json'; import edit from './edit'; import save from './save'; +import transforms from './transforms'; const { name } = metadata; @@ -25,6 +26,7 @@ export const settings = { content: attributes.content + attributesToMerge.content, }; }, + transforms, }; export const init = () => initBlock( { name, metadata, settings } ); diff --git a/packages/block-library/src/list-item/transforms.js b/packages/block-library/src/list-item/transforms.js new file mode 100644 index 00000000000000..6e05f8501b5a3b --- /dev/null +++ b/packages/block-library/src/list-item/transforms.js @@ -0,0 +1,17 @@ +/** + * WordPress dependencies + */ +import { createBlock } from '@wordpress/blocks'; + +const transforms = { + to: [ + { + type: 'block', + blocks: [ 'core/paragraph' ], + transform: ( attributes ) => + createBlock( 'core/paragraph', attributes ), + }, + ], +}; + +export default transforms; diff --git a/packages/block-library/src/list-item/utils.js b/packages/block-library/src/list-item/utils.js index 41f242c84e0555..ac7f99ac849e00 100644 --- a/packages/block-library/src/list-item/utils.js +++ b/packages/block-library/src/list-item/utils.js @@ -8,6 +8,7 @@ import { createBlock, switchToBlockType } from '@wordpress/blocks'; */ import { name as listItemName } from './block.json'; import { name as listName } from '../list/block.json'; +import { name as paragraphName } from '../paragraph/block.json'; export function createListItem( listItemAttributes, listAttributes, children ) { return createBlock( @@ -19,6 +20,14 @@ export function createListItem( listItemAttributes, listAttributes, children ) { ); } +function convertBlockToList( block ) { + const list = switchToBlockType( block, listName ); + if ( list ) return list; + const paragraph = switchToBlockType( block, paragraphName ); + if ( paragraph ) return switchToBlockType( paragraph, listName ); + return null; +} + export function convertToListItems( blocks ) { const listItems = []; @@ -27,7 +36,7 @@ export function convertToListItems( blocks ) { listItems.push( block ); } else if ( block.name === listName ) { listItems.push( ...block.innerBlocks ); - } else if ( ( block = switchToBlockType( block, listName ) ) ) { + } else if ( ( block = convertBlockToList( block ) ) ) { for ( const { innerBlocks } of block ) { listItems.push( ...innerBlocks ); } diff --git a/packages/block-library/src/list/test/edit.native.js b/packages/block-library/src/list/test/edit.native.js index 9defa338782dbe..5b70952925cd9a 100644 --- a/packages/block-library/src/list/test/edit.native.js +++ b/packages/block-library/src/list/test/edit.native.js @@ -63,16 +63,18 @@ describe( 'List block', () => {
  • `; - const { getByLabelText } = await initializeEditor( { + const screen = await initializeEditor( { initialHtml, } ); // Select List block - const listBlock = getByLabelText( /List Block\. Row 1/ ); + const [ listBlock ] = screen.getAllByLabelText( /List Block\. Row 1/ ); fireEvent.press( listBlock ); // Select List Item block - const listItemBlock = getByLabelText( /List item Block\. Row 1/ ); + const [ listItemBlock ] = screen.getAllByLabelText( + /List item Block\. Row 1/ + ); fireEvent.press( listItemBlock ); const listItemField = @@ -109,25 +111,25 @@ describe( 'List block', () => { `; - const { getByLabelText } = await initializeEditor( { + const screen = await initializeEditor( { initialHtml, } ); // Select List block - const listBlock = getByLabelText( /List Block\. Row 1/ ); + const [ listBlock ] = screen.getAllByLabelText( /List Block\. Row 1/ ); fireEvent.press( listBlock ); // Select List Item block - const firstNestedLevelBlock = within( listBlock ).getByLabelText( + const [ firstNestedLevelBlock ] = within( listBlock ).getAllByLabelText( /List item Block\. Row 2/ ); fireEvent.press( firstNestedLevelBlock ); // Select second level list - const secondNestedLevelBlock = within( + const [ secondNestedLevelBlock ] = within( firstNestedLevelBlock - ).getByLabelText( /List Block\. Row 1/ ); + ).getAllByLabelText( /List Block\. Row 1/ ); fireEvent.press( secondNestedLevelBlock ); expect( getEditorHtml() ).toMatchSnapshot(); @@ -143,20 +145,22 @@ describe( 'List block', () => { `; - const { getByLabelText } = await initializeEditor( { + const screen = await initializeEditor( { initialHtml, } ); // Select List block - const listBlock = getByLabelText( /List Block\. Row 1/ ); + const [ listBlock ] = screen.getAllByLabelText( /List Block\. Row 1/ ); fireEvent.press( listBlock ); // Select Secont List Item block - const listItemBlock = getByLabelText( /List item Block\. Row 2/ ); + const [ listItemBlock ] = screen.getAllByLabelText( + /List item Block\. Row 2/ + ); fireEvent.press( listItemBlock ); // Update indentation - const indentButton = getByLabelText( 'Indent' ); + const indentButton = screen.getByLabelText( 'Indent' ); fireEvent.press( indentButton ); expect( getEditorHtml() ).toMatchSnapshot(); @@ -173,33 +177,33 @@ describe( 'List block', () => { `; - const { getByLabelText } = await initializeEditor( { + const screen = await initializeEditor( { initialHtml, } ); // Select List block - const listBlock = getByLabelText( /List Block\. Row 1/ ); + const [ listBlock ] = screen.getAllByLabelText( /List Block\. Row 1/ ); fireEvent.press( listBlock ); // Select List Item block - const firstNestedLevelBlock = within( listBlock ).getByLabelText( + const [ firstNestedLevelBlock ] = within( listBlock ).getAllByLabelText( /List item Block\. Row 1/ ); fireEvent.press( firstNestedLevelBlock ); // Select Inner block List - const innerBlockList = within( firstNestedLevelBlock ).getByLabelText( - /List Block\. Row 1/ - ); + const [ innerBlockList ] = within( + firstNestedLevelBlock + ).getAllByLabelText( /List Block\. Row 1/ ); // Select nested List Item block - const listItemBlock = within( innerBlockList ).getByLabelText( + const [ listItemBlock ] = within( innerBlockList ).getAllByLabelText( /List item Block\. Row 1/ ); fireEvent.press( listItemBlock ); // Update indentation - const outdentButton = getByLabelText( 'Outdent' ); + const outdentButton = screen.getByLabelText( 'Outdent' ); fireEvent.press( outdentButton ); expect( getEditorHtml() ).toMatchSnapshot(); @@ -218,16 +222,16 @@ describe( 'List block', () => { `; - const { getByLabelText } = await initializeEditor( { + const screen = await initializeEditor( { initialHtml, } ); // Select List block - const listBlock = getByLabelText( /List Block\. Row 1/ ); + const [ listBlock ] = screen.getAllByLabelText( /List Block\. Row 1/ ); fireEvent.press( listBlock ); // Update to ordered list - const orderedButton = getByLabelText( 'Ordered' ); + const orderedButton = screen.getByLabelText( 'Ordered' ); fireEvent.press( orderedButton ); expect( getEditorHtml() ).toMatchSnapshot(); @@ -246,27 +250,29 @@ describe( 'List block', () => { `; - const { getByLabelText, getByTestId } = await initializeEditor( { + const screen = await initializeEditor( { initialHtml, } ); // Select List block - const listBlock = getByLabelText( /List Block\. Row 1/ ); + const [ listBlock ] = screen.getAllByLabelText( /List Block\. Row 1/ ); fireEvent.press( listBlock ); // Update to ordered list - const orderedButton = getByLabelText( 'Ordered' ); + const orderedButton = screen.getByLabelText( 'Ordered' ); fireEvent.press( orderedButton ); // Set order to reverse // Open block settings - fireEvent.press( getByLabelText( 'Open Settings' ) ); + fireEvent.press( screen.getByLabelText( 'Open Settings' ) ); await waitFor( - () => getByTestId( 'block-settings-modal' ).props.isVisible + () => screen.getByTestId( 'block-settings-modal' ).props.isVisible ); - const reverseButton = getByLabelText( /Reverse list numbering\. Off/ ); + const reverseButton = screen.getByLabelText( + /Reverse list numbering\. Off/ + ); fireEvent.press( reverseButton ); expect( getEditorHtml() ).toMatchSnapshot(); @@ -285,27 +291,27 @@ describe( 'List block', () => { `; - const { getByLabelText, getByTestId } = await initializeEditor( { + const screen = await initializeEditor( { initialHtml, } ); // Select List block - const listBlock = getByLabelText( /List Block\. Row 1/ ); + const [ listBlock ] = screen.getAllByLabelText( /List Block\. Row 1/ ); fireEvent.press( listBlock ); // Update to ordered list - const orderedButton = getByLabelText( 'Ordered' ); + const orderedButton = screen.getByLabelText( 'Ordered' ); fireEvent.press( orderedButton ); // Set order to reverse // Open block settings - fireEvent.press( getByLabelText( 'Open Settings' ) ); + fireEvent.press( screen.getByLabelText( 'Open Settings' ) ); await waitFor( - () => getByTestId( 'block-settings-modal' ).props.isVisible + () => screen.getByTestId( 'block-settings-modal' ).props.isVisible ); - const startValueButton = getByLabelText( /Start value\. Empty/ ); + const startValueButton = screen.getByLabelText( /Start value\. Empty/ ); fireEvent.press( startValueButton ); const startValueInput = within( startValueButton ).getByDisplayValue( '' ); @@ -328,11 +334,11 @@ describe( 'List block', () => { } ); // Select List block - const listBlock = screen.getByLabelText( /List Block\. Row 2/ ); + const [ listBlock ] = screen.getAllByLabelText( /List Block\. Row 2/ ); fireEvent.press( listBlock ); // Select List Item block - const listItemBlock = within( listBlock ).getByLabelText( + const [ listItemBlock ] = within( listBlock ).getAllByLabelText( /List item Block\. Row 1/ ); fireEvent.press( listItemBlock ); @@ -361,7 +367,7 @@ describe( 'List block', () => { ` ); } ); - it( 'unwraps list items when attempting to merge with non-list block', async () => { + it( 'unwraps first item when attempting to merge with non-list block', async () => { const initialHtml = `

    A quick brown fox.

    @@ -376,11 +382,11 @@ describe( 'List block', () => { } ); // Select List block - const listBlock = screen.getByLabelText( /List Block\. Row 2/ ); + const [ listBlock ] = screen.getAllByLabelText( /List Block\. Row 2/ ); fireEvent.press( listBlock ); // Select List Item block - const listItemBlock = within( listBlock ).getByLabelText( + const [ listItemBlock ] = within( listBlock ).getAllByLabelText( /List item Block\. Row 1/ ); fireEvent.press( listItemBlock ); @@ -400,14 +406,16 @@ describe( 'List block', () => { "

    A quick brown fox.

    - +

    One

    - - -

    Two

    - " + + +
      +
    • Two
    • +
    + " ` ); } ); } ); diff --git a/packages/block-library/src/list/transforms.js b/packages/block-library/src/list/transforms.js index 2f11119768ef89..a6263d7ad639c7 100644 --- a/packages/block-library/src/list/transforms.js +++ b/packages/block-library/src/list/transforms.js @@ -114,17 +114,6 @@ const transforms = { ); }, } ) ), - { - type: 'block', - blocks: [ '*' ], - transform: ( _attributes, childBlocks ) => { - return getListContentFlat( childBlocks ).map( ( content ) => - createBlock( 'core/paragraph', { - content, - } ) - ); - }, - }, ], }; diff --git a/packages/block-library/src/list/utils.js b/packages/block-library/src/list/utils.js index a80ce456e29ab0..4fa2a0e0aed949 100644 --- a/packages/block-library/src/list/utils.js +++ b/packages/block-library/src/list/utils.js @@ -56,7 +56,8 @@ export function createListBlockFromDOMElement( listElement ) { } export function migrateToListV2( attributes ) { - const { values, start, reversed, ordered, type } = attributes; + const { values, start, reversed, ordered, type, ...otherAttributes } = + attributes; const list = document.createElement( ordered ? 'ol' : 'ul' ); list.innerHTML = values; @@ -72,5 +73,8 @@ export function migrateToListV2( attributes ) { const [ listBlock ] = rawHandler( { HTML: list.outerHTML } ); - return [ listBlock.attributes, listBlock.innerBlocks ]; + return [ + { ...otherAttributes, ...listBlock.attributes }, + listBlock.innerBlocks, + ]; } diff --git a/packages/block-library/src/media-text/edit.js b/packages/block-library/src/media-text/edit.js index 5a88830d385d18..83b7074e3d1550 100644 --- a/packages/block-library/src/media-text/edit.js +++ b/packages/block-library/src/media-text/edit.js @@ -2,7 +2,7 @@ * External dependencies */ import classnames from 'classnames'; -import { map, filter } from 'lodash'; +import { map } from 'lodash'; /** * WordPress dependencies @@ -199,7 +199,7 @@ function MediaTextEdit( { attributes, isSelected, setAttributes, clientId } ) { setAttributes( { mediaWidth: applyWidthConstraints( width ), } ); - setTemporaryMediaWidth( applyWidthConstraints( width ) ); + setTemporaryMediaWidth( null ); }; const classNames = classnames( { @@ -226,7 +226,7 @@ function MediaTextEdit( { attributes, isSelected, setAttributes, clientId } ) { }; const imageSizeOptions = map( - filter( imageSizes, ( { slug } ) => + imageSizes.filter( ( { slug } ) => getImageSourceUrlBySizeSlug( image, slug ) ), ( { name, slug } ) => ( { value: slug, label: name } ) @@ -268,6 +268,7 @@ function MediaTextEdit( { attributes, isSelected, setAttributes, clientId } ) { ) } { imageFill && mediaUrl && mediaType === 'image' && ( { const initialHtml = `
    12
    34
    `; - const { getByLabelText } = await initializeEditor( { + const screen = await initializeEditor( { initialHtml, } ); - const missingBlock = await waitFor( () => - getByLabelText( /Unsupported Block\. Row 1/ ) + const [ missingBlock ] = await screen.findAllByLabelText( + /Unsupported Block\. Row 1/ ); const translatedTableTitle = @@ -58,24 +58,22 @@ describe( 'Unsupported block', () => { const initialHtml = `
    12
    34
    `; - const { getByLabelText, getByText } = await initializeEditor( { + const screen = await initializeEditor( { initialHtml, } ); - const missingBlock = await waitFor( () => - getByLabelText( /Unsupported Block\. Row 1/ ) + const [ missingBlock ] = await screen.findAllByLabelText( + /Unsupported Block\. Row 1/ ); fireEvent.press( missingBlock ); - const helpButton = await waitFor( () => - getByLabelText( 'Help button' ) - ); + const [ helpButton ] = await screen.findAllByLabelText( 'Help button' ); fireEvent.press( helpButton ); - const bottomSheetTitle = await waitFor( () => - getByText( '«Tabla» no es totalmente compatible' ) + const bottomSheetTitle = await screen.findByText( + '«Tabla» no es totalmente compatible' ); expect( bottomSheetTitle ).toBeDefined(); diff --git a/packages/block-library/src/more/test/__snapshots__/edit.native.js.snap b/packages/block-library/src/more/test/__snapshots__/edit.native.js.snap new file mode 100644 index 00000000000000..0bc3714894afb7 --- /dev/null +++ b/packages/block-library/src/more/test/__snapshots__/edit.native.js.snap @@ -0,0 +1,7 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`More block inserts block 1`] = ` +" + +" +`; diff --git a/packages/block-library/src/more/test/edit.native.js b/packages/block-library/src/more/test/edit.native.js new file mode 100644 index 00000000000000..cc84c7f3fc2b79 --- /dev/null +++ b/packages/block-library/src/more/test/edit.native.js @@ -0,0 +1,41 @@ +/** + * External dependencies + */ +import { + addBlock, + getEditorHtml, + initializeEditor, + getBlock, +} from 'test/helpers'; + +/** + * WordPress dependencies + */ +import { getBlockTypes, unregisterBlockType } from '@wordpress/blocks'; +import { registerCoreBlocks } from '@wordpress/block-library'; + +beforeAll( () => { + // Register all core blocks + registerCoreBlocks(); +} ); + +afterAll( () => { + // Clean up registered blocks + getBlockTypes().forEach( ( block ) => { + unregisterBlockType( block.name ); + } ); +} ); + +describe( 'More block', () => { + it( 'inserts block', async () => { + const screen = await initializeEditor(); + + // Add block + await addBlock( screen, 'More' ); + + // Get block + const moreBlock = await getBlock( screen, 'More' ); + expect( moreBlock ).toBeVisible(); + expect( getEditorHtml() ).toMatchSnapshot(); + } ); +} ); diff --git a/packages/block-library/src/navigation-link/edit.js b/packages/block-library/src/navigation-link/edit.js index ed0acced4efe88..7e58527d947f5c 100644 --- a/packages/block-library/src/navigation-link/edit.js +++ b/packages/block-library/src/navigation-link/edit.js @@ -2,18 +2,15 @@ * External dependencies */ import classnames from 'classnames'; -import escapeHtml from 'escape-html'; import { unescape } from 'lodash'; /** * WordPress dependencies */ -import { createBlock, switchToBlockType } from '@wordpress/blocks'; +import { createBlock } from '@wordpress/blocks'; import { useSelect, useDispatch } from '@wordpress/data'; import { - Button, PanelBody, - Popover, TextControl, TextareaControl, ToolbarButton, @@ -22,38 +19,35 @@ import { KeyboardShortcuts, } from '@wordpress/components'; import { displayShortcut, isKeyboardEvent, ENTER } from '@wordpress/keycodes'; -import { __, sprintf } from '@wordpress/i18n'; +import { __ } from '@wordpress/i18n'; import { BlockControls, - BlockIcon, InspectorControls, RichText, - __experimentalLinkControl as LinkControl, useBlockProps, store as blockEditorStore, getColorClassName, + useInnerBlocksProps, } from '@wordpress/block-editor'; -import { isURL, prependHTTP, safeDecodeURI } from '@wordpress/url'; +import { isURL, prependHTTP } from '@wordpress/url'; +import { Fragment, useState, useEffect, useRef } from '@wordpress/element'; import { - Fragment, - useState, - useEffect, - useRef, - createInterpolateElement, -} from '@wordpress/element'; -import { placeCaretAtHorizontalEdge } from '@wordpress/dom'; + placeCaretAtHorizontalEdge, + __unstableStripHTML as stripHTML, +} from '@wordpress/dom'; import { link as linkIcon, addSubmenu } from '@wordpress/icons'; import { store as coreStore, useResourcePermissions, } from '@wordpress/core-data'; -import { decodeEntities } from '@wordpress/html-entities'; import { useMergeRefs } from '@wordpress/compose'; /** * Internal dependencies */ import { name } from './block.json'; +import { LinkUI } from './link-ui'; +import { updateAttributes } from './update-attributes'; /** * A React hook to determine if it's dragging within the target element. @@ -106,36 +100,6 @@ const useIsDraggingWithin = ( elementRef ) => { return isDraggingWithin; }; -/** - * Given the Link block's type attribute, return the query params to give to - * /wp/v2/search. - * - * @param {string} type Link block's type attribute. - * @param {string} kind Link block's entity of kind (post-type|taxonomy) - * @return {{ type?: string, subtype?: string }} Search query params. - */ -function getSuggestionsQuery( type, kind ) { - switch ( type ) { - case 'post': - case 'page': - return { type: 'post', subtype: type }; - case 'category': - return { type: 'term', subtype: 'category' }; - case 'tag': - return { type: 'term', subtype: 'post_tag' }; - case 'post_format': - return { type: 'post-format' }; - default: - if ( kind === 'taxonomy' ) { - return { type: 'term', subtype: type }; - } - if ( kind === 'post-type' ) { - return { type: 'post', subtype: type }; - } - return {}; - } -} - /** * Determine the colors for a menu. * @@ -191,102 +155,6 @@ function getColors( context, isSubMenu ) { return colors; } -/** - * @typedef {'post-type'|'custom'|'taxonomy'|'post-type-archive'} WPNavigationLinkKind - */ - -/** - * Navigation Link Block Attributes - * - * @typedef {Object} WPNavigationLinkBlockAttributes - * - * @property {string} [label] Link text. - * @property {WPNavigationLinkKind} [kind] Kind is used to differentiate between term and post ids to check post draft status. - * @property {string} [type] The type such as post, page, tag, category and other custom types. - * @property {string} [rel] The relationship of the linked URL. - * @property {number} [id] A post or term id. - * @property {boolean} [opensInNewTab] Sets link target to _blank when true. - * @property {string} [url] Link href. - * @property {string} [title] Link title attribute. - */ - -/** - * Link Control onChange handler that updates block attributes when a setting is changed. - * - * @param {Object} updatedValue New block attributes to update. - * @param {Function} setAttributes Block attribute update function. - * @param {WPNavigationLinkBlockAttributes} blockAttributes Current block attributes. - * - */ -export const updateNavigationLinkBlockAttributes = ( - updatedValue = {}, - setAttributes, - blockAttributes = {} -) => { - const { - label: originalLabel = '', - kind: originalKind = '', - type: originalType = '', - } = blockAttributes; - - const { - title: newLabel = '', // the title of any provided Post. - url: newUrl = '', - - opensInNewTab, - id, - kind: newKind = originalKind, - type: newType = originalType, - } = updatedValue; - - const newLabelWithoutHttp = newLabel.replace( /http(s?):\/\//gi, '' ); - const newUrlWithoutHttp = newUrl.replace( /http(s?):\/\//gi, '' ); - - const useNewLabel = - newLabel && - newLabel !== originalLabel && - // LinkControl without the title field relies - // on the check below. Specifically, it assumes that - // the URL is the same as a title. - // This logic a) looks suspicious and b) should really - // live in the LinkControl and not here. It's a great - // candidate for future refactoring. - newLabelWithoutHttp !== newUrlWithoutHttp; - - // Unfortunately this causes the escaping model to be inverted. - // The escaped content is stored in the block attributes (and ultimately in the database), - // and then the raw data is "recovered" when outputting into the DOM. - // It would be preferable to store the **raw** data in the block attributes and escape it in JS. - // Why? Because there isn't one way to escape data. Depending on the context, you need to do - // different transforms. It doesn't make sense to me to choose one of them for the purposes of storage. - // See also: - // - https://github.com/WordPress/gutenberg/pull/41063 - // - https://github.com/WordPress/gutenberg/pull/18617. - const label = useNewLabel - ? escapeHtml( newLabel ) - : originalLabel || escapeHtml( newUrlWithoutHttp ); - - // In https://github.com/WordPress/gutenberg/pull/24670 we decided to use "tag" in favor of "post_tag" - const type = newType === 'post_tag' ? 'tag' : newType.replace( '-', '_' ); - - const isBuiltInType = - [ 'post', 'page', 'tag', 'category' ].indexOf( type ) > -1; - - const isCustomLink = - ( ! newKind && ! isBuiltInType ) || newKind === 'custom'; - const kind = isCustomLink ? 'custom' : newKind; - - setAttributes( { - // Passed `url` may already be encoded. To prevent double encoding, decodeURI is executed to revert to the original string. - ...( newUrl && { url: encodeURI( safeDecodeURI( newUrl ) ) } ), - ...( label && { label } ), - ...( undefined !== opensInNewTab && { opensInNewTab } ), - ...( id && Number.isInteger( id ) && { id } ), - ...( kind && { kind } ), - ...( type && type !== 'URL' && { type } ), - } ); -}; - const useIsInvalidLink = ( kind, type, id ) => { const isPostType = kind === 'post-type' || type === 'post' || type === 'page'; @@ -345,90 +213,6 @@ function getMissingText( type ) { return missingText; } -/** - * Removes HTML from a given string. - * Note the does not provide XSS protection or otherwise attempt - * to filter strings with malicious intent. - * - * See also: https://github.com/WordPress/gutenberg/pull/35539 - * - * @param {string} html the string from which HTML should be removed. - * @return {string} the "cleaned" string. - */ -function navStripHTML( html ) { - const doc = document.implementation.createHTMLDocument( '' ); - doc.body.innerHTML = html; - return doc.body.textContent || ''; -} - -/** - * Add transforms to Link Control - */ - -function LinkControlTransforms( { clientId, replace } ) { - const { getBlock, blockTransforms } = useSelect( - ( select ) => { - const { - getBlock: _getBlock, - getBlockRootClientId, - getBlockTransformItems, - } = select( blockEditorStore ); - - return { - getBlock: _getBlock, - blockTransforms: getBlockTransformItems( - _getBlock( clientId ), - getBlockRootClientId( clientId ) - ), - }; - }, - [ clientId ] - ); - - const featuredBlocks = [ - 'core/site-logo', - 'core/social-links', - 'core/search', - ]; - const transforms = blockTransforms.filter( ( item ) => { - return featuredBlocks.includes( item.name ); - } ); - - if ( ! transforms?.length ) { - return null; - } - - return ( -
    -

    - { __( 'Transform' ) } -

    -
    - { transforms.map( ( item, index ) => { - return ( - - ); - } ) } -
    -
    - ); -} - export default function NavigationLinkEdit( { attributes, isSelected, @@ -439,27 +223,11 @@ export default function NavigationLinkEdit( { context, clientId, } ) { - const { - id, - label, - type, - opensInNewTab, - url, - description, - rel, - title, - kind, - } = attributes; + const { id, label, type, url, description, rel, title, kind } = attributes; const [ isInvalid, isDraft ] = useIsInvalidLink( kind, type, id ); const { maxNestingLevel } = context; - const link = { - url, - opensInNewTab, - title: label && navStripHTML( label ), // don't allow HTML to display inside the - }; - const { saveEntityRecord } = useDispatch( coreStore ); const { replaceBlock, __unstableMarkNextChangeAsNotPersistent } = useDispatch( blockEditorStore ); const [ isLinkOpen, setIsLinkOpen ] = useState( false ); @@ -527,7 +295,9 @@ export default function NavigationLinkEdit( { const newSubmenu = createBlock( 'core/navigation-submenu', attributes, - innerBlocks + innerBlocks.length > 0 + ? innerBlocks + : [ createBlock( 'core/navigation-link' ) ] ); replaceBlock( clientId, newSubmenu ); } @@ -540,11 +310,14 @@ export default function NavigationLinkEdit( { if ( ! url ) { setIsLinkOpen( true ); } + }, [ url ] ); + + useEffect( () => { // If block has inner blocks, transform to Submenu. if ( hasChildren ) { transformToSubmenu(); } - }, [] ); + }, [ hasChildren ] ); /** * The hook shouldn't be necessary but due to a focus loss happening @@ -612,33 +385,6 @@ export default function NavigationLinkEdit( { userCanCreate = postsPermissions.canCreate; } - async function handleCreate( pageTitle ) { - const postType = type || 'page'; - - const page = await saveEntityRecord( 'postType', postType, { - title: pageTitle, - status: 'draft', - } ); - - return { - id: page.id, - type: postType, - // Make `title` property consistent with that in `fetchLinkSuggestions` where the `rendered` title (containing HTML entities) - // is also being decoded. By being consistent in both locations we avoid having to branch in the rendering output code. - // Ideally in the future we will update both APIs to utilise the "raw" form of the title which is better suited to edit contexts. - // e.g. - // - title.raw = "Yes & No" - // - title.rendered = "Yes & No" - // - decodeEntities( title.rendered ) = "Yes & No" - // See: - // - https://github.com/WordPress/gutenberg/pull/41063 - // - https://github.com/WordPress/gutenberg/blob/a1e1fdc0e6278457e9f4fc0b31ac6d2095f5450b/packages/core-data/src/fetch/__experimental-fetch-link-suggestions.js#L212-L218 - title: decodeEntities( page.title.rendered ), - url: page.link, - kind: 'post-type', - }; - } - const { textColor, customTextColor, @@ -675,6 +421,21 @@ export default function NavigationLinkEdit( { onKeyDown, } ); + const ALLOWED_BLOCKS = [ + 'core/navigation-link', + 'core/navigation-submenu', + 'core/page-list', + ]; + const DEFAULT_BLOCK = { + name: 'core/navigation-link', + }; + const innerBlocksProps = useInnerBlocksProps( blockProps, { + allowedBlocks: ALLOWED_BLOCKS, + __experimentalDefaultBlock: DEFAULT_BLOCK, + __experimentalDirectInsert: true, + renderAppender: false, + } ); + if ( ! url || isInvalid || isDraft ) { blockProps.onClick = () => setIsLinkOpen( true ); } @@ -717,6 +478,14 @@ export default function NavigationLinkEdit( { { /* Warning, this duplicated in packages/block-library/src/navigation-submenu/edit.js */ } + { + setAttributes( { label: labelValue } ); + } } + label={ __( 'Label' ) } + autoComplete="off" + /> { @@ -839,7 +608,7 @@ export default function NavigationLinkEdit( { // Ideally they would be stored in a raw, unescaped form. // Unescape is used here to "recover" the escaped characters // so they display without encoding. - // See `updateNavigationLinkBlockAttributes` for more details. + // See `updateAttributes` for more details. `${ unescape( label ) } ${ placeholderText }`.trim() @@ -855,66 +624,25 @@ export default function NavigationLinkEdit( { ) } { isLinkOpen && ( - setIsLinkOpen( false ) } anchor={ popoverAnchor } - shift - > - { - let format; - if ( type === 'post' ) { - /* translators: %s: search term. */ - format = __( - 'Create draft post: %s' - ); - } else { - /* translators: %s: search term. */ - format = __( - 'Create draft page: %s' - ); - } - return createInterpolateElement( - sprintf( format, searchTerm ), - { mark: } - ); - } } - noDirectEntry={ !! type } - noURLSuggestion={ !! type } - suggestionsQuery={ getSuggestionsQuery( - type, - kind - ) } - onChange={ ( updatedValue ) => - updateNavigationLinkBlockAttributes( - updatedValue, - setAttributes, - attributes - ) - } - onRemove={ removeLink } - renderControlBottom={ - ! url - ? () => ( - - ) - : null - } - /> - + hasCreateSuggestion={ userCanCreate } + onRemove={ removeLink } + onChange={ ( updatedValue ) => { + updateAttributes( + updatedValue, + setAttributes, + attributes + ); + } } + /> ) } +
    ); diff --git a/packages/block-library/src/navigation-link/index.php b/packages/block-library/src/navigation-link/index.php index f32437b41a3f84..36bfbf5d1fc0ae 100644 --- a/packages/block-library/src/navigation-link/index.php +++ b/packages/block-library/src/navigation-link/index.php @@ -344,3 +344,67 @@ function register_block_core_navigation_link() { ); } add_action( 'init', 'register_block_core_navigation_link' ); + +/** + * Disables the display of block inspector tabs for the Navigation Link block. + * + * This is only a temporary measure until we have a TabPanel and mechanism that + * will allow the Navigation Link to programmatically select a tab when edited + * via a specific context. + * + * See: + * - https://github.com/WordPress/gutenberg/issues/45951 + * - https://github.com/WordPress/gutenberg/pull/46321 + * - https://github.com/WordPress/gutenberg/pull/46271 + * + * @param array $settings Default editor settings. + * @return array Filtered editor settings. + */ +function gutenberg_disable_tabs_for_navigation_link_block( $settings ) { + $current_tab_settings = _wp_array_get( + $settings, + array( '__experimentalBlockInspectorTabs' ), + array() + ); + + $settings['__experimentalBlockInspectorTabs'] = array_merge( + $current_tab_settings, + array( 'core/navigation-link' => false ) + ); + + return $settings; +} + +add_filter( 'block_editor_settings_all', 'gutenberg_disable_tabs_for_navigation_link_block' ); + +/** + * Enables animation of the block inspector for the Navigation Link block. + * + * See: + * - https://github.com/WordPress/gutenberg/pull/46342 + * - https://github.com/WordPress/gutenberg/issues/45884 + * + * @param array $settings Default editor settings. + * @return array Filtered editor settings. + */ +function gutenberg_enable_animation_for_navigation_link_inspector( $settings ) { + $current_animation_settings = _wp_array_get( + $settings, + array( '__experimentalBlockInspectorAnimation' ), + array() + ); + + $settings['__experimentalBlockInspectorAnimation'] = array_merge( + $current_animation_settings, + array( + 'core/navigation-link' => + array( + 'enterDirection' => 'rightToLeft', + ), + ) + ); + + return $settings; +} + +add_filter( 'block_editor_settings_all', 'gutenberg_enable_animation_for_navigation_link_inspector' ); diff --git a/packages/block-library/src/navigation-link/link-ui.js b/packages/block-library/src/navigation-link/link-ui.js new file mode 100644 index 00000000000000..a2df4f2a294055 --- /dev/null +++ b/packages/block-library/src/navigation-link/link-ui.js @@ -0,0 +1,213 @@ +/** + * WordPress dependencies + */ +import { __unstableStripHTML as stripHTML } from '@wordpress/dom'; +import { Popover, Button } from '@wordpress/components'; +import { __, sprintf } from '@wordpress/i18n'; +import { + __experimentalLinkControl as LinkControl, + BlockIcon, + store as blockEditorStore, +} from '@wordpress/block-editor'; +import { createInterpolateElement } from '@wordpress/element'; +import { store as coreStore } from '@wordpress/core-data'; +import { decodeEntities } from '@wordpress/html-entities'; +import { switchToBlockType } from '@wordpress/blocks'; +import { useSelect, useDispatch } from '@wordpress/data'; + +/** + * Given the Link block's type attribute, return the query params to give to + * /wp/v2/search. + * + * @param {string} type Link block's type attribute. + * @param {string} kind Link block's entity of kind (post-type|taxonomy) + * @return {{ type?: string, subtype?: string }} Search query params. + */ +export function getSuggestionsQuery( type, kind ) { + switch ( type ) { + case 'post': + case 'page': + return { type: 'post', subtype: type }; + case 'category': + return { type: 'term', subtype: 'category' }; + case 'tag': + return { type: 'term', subtype: 'post_tag' }; + case 'post_format': + return { type: 'post-format' }; + default: + if ( kind === 'taxonomy' ) { + return { type: 'term', subtype: type }; + } + if ( kind === 'post-type' ) { + return { type: 'post', subtype: type }; + } + return {}; + } +} + +/** + * Add transforms to Link Control + * + * @param {Object} props Component props. + * @param {string} props.clientId Block client ID. + */ +function LinkControlTransforms( { clientId } ) { + const { getBlock, blockTransforms } = useSelect( + ( select ) => { + const { + getBlock: _getBlock, + getBlockRootClientId, + getBlockTransformItems, + } = select( blockEditorStore ); + + return { + getBlock: _getBlock, + blockTransforms: getBlockTransformItems( + _getBlock( clientId ), + getBlockRootClientId( clientId ) + ), + }; + }, + [ clientId ] + ); + + const { replaceBlock } = useDispatch( blockEditorStore ); + + const featuredBlocks = [ + 'core/page-list', + 'core/site-logo', + 'core/social-links', + 'core/search', + ]; + + const transforms = blockTransforms.filter( ( item ) => { + return featuredBlocks.includes( item.name ); + } ); + + if ( ! transforms?.length ) { + return null; + } + + if ( ! clientId ) { + return null; + } + + return ( +
    +

    + { __( 'Transform' ) } +

    +
    + { transforms.map( ( item, index ) => { + return ( + + ); + } ) } +
    +
    + ); +} + +export function LinkUI( props ) { + const { saveEntityRecord } = useDispatch( coreStore ); + + async function handleCreate( pageTitle ) { + const postType = props.link.type || 'page'; + + const page = await saveEntityRecord( 'postType', postType, { + title: pageTitle, + status: 'draft', + } ); + + return { + id: page.id, + type: postType, + // Make `title` property consistent with that in `fetchLinkSuggestions` where the `rendered` title (containing HTML entities) + // is also being decoded. By being consistent in both locations we avoid having to branch in the rendering output code. + // Ideally in the future we will update both APIs to utilise the "raw" form of the title which is better suited to edit contexts. + // e.g. + // - title.raw = "Yes & No" + // - title.rendered = "Yes & No" + // - decodeEntities( title.rendered ) = "Yes & No" + // See: + // - https://github.com/WordPress/gutenberg/pull/41063 + // - https://github.com/WordPress/gutenberg/blob/a1e1fdc0e6278457e9f4fc0b31ac6d2095f5450b/packages/core-data/src/fetch/__experimental-fetch-link-suggestions.js#L212-L218 + title: decodeEntities( page.title.rendered ), + url: page.link, + kind: 'post-type', + }; + } + + const { label, url, opensInNewTab, type, kind } = props.link; + const link = { + url, + opensInNewTab, + title: label && stripHTML( label ), + }; + + return ( + + { + let format; + + if ( type === 'post' ) { + /* translators: %s: search term. */ + format = __( 'Create draft post: %s' ); + } else { + /* translators: %s: search term. */ + format = __( 'Create draft page: %s' ); + } + + return createInterpolateElement( + sprintf( format, searchTerm ), + { + mark: , + } + ); + } } + noDirectEntry={ !! type } + noURLSuggestion={ !! type } + suggestionsQuery={ getSuggestionsQuery( type, kind ) } + onChange={ props.onChange } + onRemove={ props.onRemove } + renderControlBottom={ + ! url + ? () => ( + + ) + : null + } + /> + + ); +} diff --git a/packages/block-library/src/navigation-link/test/edit.js b/packages/block-library/src/navigation-link/test/edit.js index 8d052a5e3f133a..ec396a64b20586 100644 --- a/packages/block-library/src/navigation-link/test/edit.js +++ b/packages/block-library/src/navigation-link/test/edit.js @@ -1,10 +1,10 @@ /** * Internal dependencies */ -import { updateNavigationLinkBlockAttributes } from '../edit'; +import { updateAttributes } from '../update-attributes'; describe( 'edit', () => { - describe( 'updateNavigationLinkBlockAttributes', () => { + describe( 'updateAttributes', () => { // Data shapes are linked to fetchLinkSuggestions from // core-data/src/fetch/__experimental-fetch-link-suggestions.js. it( 'can update a post link', () => { @@ -18,10 +18,7 @@ describe( 'edit', () => { type: 'post', }; - updateNavigationLinkBlockAttributes( - linkSuggestion, - setAttributes - ); + updateAttributes( linkSuggestion, setAttributes ); expect( setAttributes ).toHaveBeenCalledWith( { id: 1337, label: 'Menu Test', @@ -42,10 +39,7 @@ describe( 'edit', () => { type: 'page', url: 'http://wordpress.local/sample-page/', }; - updateNavigationLinkBlockAttributes( - linkSuggestion, - setAttributes - ); + updateAttributes( linkSuggestion, setAttributes ); expect( setAttributes ).toHaveBeenCalledWith( { id: 2, kind: 'post-type', @@ -66,10 +60,7 @@ describe( 'edit', () => { type: 'post_tag', url: 'http://wordpress.local/tag/bar/', }; - updateNavigationLinkBlockAttributes( - linkSuggestion, - setAttributes - ); + updateAttributes( linkSuggestion, setAttributes ); expect( setAttributes ).toHaveBeenCalledWith( { id: 15, kind: 'taxonomy', @@ -90,10 +81,7 @@ describe( 'edit', () => { type: 'category', url: 'http://wordpress.local/category/cats/', }; - updateNavigationLinkBlockAttributes( - linkSuggestion, - setAttributes - ); + updateAttributes( linkSuggestion, setAttributes ); expect( setAttributes ).toHaveBeenCalledWith( { id: 9, kind: 'taxonomy', @@ -114,10 +102,7 @@ describe( 'edit', () => { type: 'portfolio', url: 'http://wordpress.local/portfolio/fall/', }; - updateNavigationLinkBlockAttributes( - linkSuggestion, - setAttributes - ); + updateAttributes( linkSuggestion, setAttributes ); expect( setAttributes ).toHaveBeenCalledWith( { id: 131, kind: 'post-type', @@ -138,10 +123,7 @@ describe( 'edit', () => { type: 'portfolio_tag', url: 'http://wordpress.local/portfolio_tag/PortfolioTag/', }; - updateNavigationLinkBlockAttributes( - linkSuggestion, - setAttributes - ); + updateAttributes( linkSuggestion, setAttributes ); expect( setAttributes ).toHaveBeenCalledWith( { id: 4, kind: 'taxonomy', @@ -162,10 +144,7 @@ describe( 'edit', () => { type: 'portfolio_category', url: 'http://wordpress.local/portfolio_category/Portfolio-category/', }; - updateNavigationLinkBlockAttributes( - linkSuggestion, - setAttributes - ); + updateAttributes( linkSuggestion, setAttributes ); expect( setAttributes ).toHaveBeenCalledWith( { id: 2, kind: 'taxonomy', @@ -186,10 +165,7 @@ describe( 'edit', () => { type: 'post-format', url: 'http://wordpress.local/type/video/', }; - updateNavigationLinkBlockAttributes( - linkSuggestion, - setAttributes - ); + updateAttributes( linkSuggestion, setAttributes ); // post_format returns a slug ID value from the Search API // we do not persist this ID since we expect this value to be a post or term ID. expect( setAttributes ).toHaveBeenCalledWith( { @@ -208,10 +184,7 @@ describe( 'edit', () => { opensInNewTab: false, url: 'www.wordpress.org', }; - updateNavigationLinkBlockAttributes( - linkSuggestion, - setAttributes - ); + updateAttributes( linkSuggestion, setAttributes ); expect( setAttributes ).toHaveBeenCalledWith( { opensInNewTab: false, url: 'www.wordpress.org', @@ -229,10 +202,7 @@ describe( 'edit', () => { type: 'URL', url: 'http://www.wordpress.org', }; - updateNavigationLinkBlockAttributes( - linkSuggestion, - setAttributes - ); + updateAttributes( linkSuggestion, setAttributes ); expect( setAttributes ).toHaveBeenCalledWith( { opensInNewTab: false, label: 'www.wordpress.org', @@ -250,10 +220,7 @@ describe( 'edit', () => { type: 'mailto', url: 'mailto:foo@example.com', }; - updateNavigationLinkBlockAttributes( - linkSuggestion, - setAttributes - ); + updateAttributes( linkSuggestion, setAttributes ); expect( setAttributes ).toHaveBeenCalledWith( { opensInNewTab: false, label: 'mailto:foo@example.com', @@ -272,10 +239,7 @@ describe( 'edit', () => { type: 'internal', url: '#foo', }; - updateNavigationLinkBlockAttributes( - linkSuggestion, - setAttributes - ); + updateAttributes( linkSuggestion, setAttributes ); expect( setAttributes ).toHaveBeenCalledWith( { opensInNewTab: false, label: '#foo', @@ -294,10 +258,7 @@ describe( 'edit', () => { type: 'tel', url: 'tel:5555555', }; - updateNavigationLinkBlockAttributes( - linkSuggestion, - setAttributes - ); + updateAttributes( linkSuggestion, setAttributes ); expect( setAttributes ).toHaveBeenCalledWith( { opensInNewTab: false, label: 'tel:5555555', @@ -319,10 +280,7 @@ describe( 'edit', () => { type: 'URL', url: 'https://www.wordpress.org', }; - updateNavigationLinkBlockAttributes( - linkSuggestion, - setAttributes - ); + updateAttributes( linkSuggestion, setAttributes ); expect( setAttributes ).toHaveBeenCalledWith( { opensInNewTab: false, label: 'www.wordpress.org', @@ -339,10 +297,7 @@ describe( 'edit', () => { type: 'URL', url: 'wordpress.org', }; - updateNavigationLinkBlockAttributes( - linkSuggestion, - setAttributes - ); + updateAttributes( linkSuggestion, setAttributes ); expect( setAttributes ).toHaveBeenCalledWith( { opensInNewTab: false, label: 'Custom Title', @@ -359,10 +314,7 @@ describe( 'edit', () => { type: 'URL', url: 'https://wordpress.org', }; - updateNavigationLinkBlockAttributes( - linkSuggestion, - setAttributes - ); + updateAttributes( linkSuggestion, setAttributes ); expect( setAttributes ).toHaveBeenCalledWith( { opensInNewTab: false, label: 'Custom Title', @@ -380,10 +332,7 @@ describe( 'edit', () => { type: 'URL', url: 'http://wordpress.org/?s=<>', }; - updateNavigationLinkBlockAttributes( - linkSuggestion, - setAttributes - ); + updateAttributes( linkSuggestion, setAttributes ); expect( setAttributes ).toHaveBeenCalledWith( { opensInNewTab: false, label: 'Custom Title', @@ -408,11 +357,7 @@ describe( 'edit', () => { type: 'post', }; - updateNavigationLinkBlockAttributes( - linkSuggestion, - setAttributes, - mockState - ); + updateAttributes( linkSuggestion, setAttributes, mockState ); expect( mockState ).toEqual( { id: 1337, label: 'Menu Test', @@ -422,7 +367,7 @@ describe( 'edit', () => { url: 'https://wordpress.local/menu-test/', } ); // Click on the existing link control, and toggle opens new tab. - updateNavigationLinkBlockAttributes( + updateAttributes( { url: 'https://wordpress.local/menu-test/', opensInNewTab: true, @@ -453,11 +398,7 @@ describe( 'edit', () => { type: 'post', }; - updateNavigationLinkBlockAttributes( - linkSuggestion, - setAttributes, - mockState - ); + updateAttributes( linkSuggestion, setAttributes, mockState ); expect( mockState ).toEqual( { id: 1337, label: 'Menu Test', @@ -467,7 +408,7 @@ describe( 'edit', () => { url: 'https://wordpress.local/menu-test/', } ); // Click on the existing link control, and toggle opens new tab. - updateNavigationLinkBlockAttributes( + updateAttributes( { url: 'https://wordpress.local/foo/', opensInNewTab: false, diff --git a/packages/block-library/src/navigation-link/transforms.js b/packages/block-library/src/navigation-link/transforms.js index 7b213a48051065..fb450a13a02dc4 100644 --- a/packages/block-library/src/navigation-link/transforms.js +++ b/packages/block-library/src/navigation-link/transforms.js @@ -40,6 +40,13 @@ const transforms = { return createBlock( 'core/navigation-link' ); }, }, + { + type: 'block', + blocks: [ 'core/page-list' ], + transform: () => { + return createBlock( 'core/navigation-link' ); + }, + }, ], to: [ { @@ -91,6 +98,13 @@ const transforms = { } ); }, }, + { + type: 'block', + blocks: [ 'core/page-list' ], + transform: () => { + return createBlock( 'core/page-list' ); + }, + }, ], }; diff --git a/packages/block-library/src/navigation-link/update-attributes.js b/packages/block-library/src/navigation-link/update-attributes.js new file mode 100644 index 00000000000000..4b7ee45ac99111 --- /dev/null +++ b/packages/block-library/src/navigation-link/update-attributes.js @@ -0,0 +1,99 @@ +/** + * WordPress dependencies + */ +import { safeDecodeURI } from '@wordpress/url'; +import { escapeHTML } from '@wordpress/escape-html'; + +/** + * @typedef {'post-type'|'custom'|'taxonomy'|'post-type-archive'} WPNavigationLinkKind + */ +/** + * Navigation Link Block Attributes + * + * @typedef {Object} WPNavigationLinkBlockAttributes + * + * @property {string} [label] Link text. + * @property {WPNavigationLinkKind} [kind] Kind is used to differentiate between term and post ids to check post draft status. + * @property {string} [type] The type such as post, page, tag, category and other custom types. + * @property {string} [rel] The relationship of the linked URL. + * @property {number} [id] A post or term id. + * @property {boolean} [opensInNewTab] Sets link target to _blank when true. + * @property {string} [url] Link href. + * @property {string} [title] Link title attribute. + */ +/** + * Link Control onChange handler that updates block attributes when a setting is changed. + * + * @param {Object} updatedValue New block attributes to update. + * @param {Function} setAttributes Block attribute update function. + * @param {WPNavigationLinkBlockAttributes} blockAttributes Current block attributes. + * + */ + +export const updateAttributes = ( + updatedValue = {}, + setAttributes, + blockAttributes = {} +) => { + const { + label: originalLabel = '', + kind: originalKind = '', + type: originalType = '', + } = blockAttributes; + + const { + title: newLabel = '', // the title of any provided Post. + url: newUrl = '', + opensInNewTab, + id, + kind: newKind = originalKind, + type: newType = originalType, + } = updatedValue; + + const newLabelWithoutHttp = newLabel.replace( /http(s?):\/\//gi, '' ); + const newUrlWithoutHttp = newUrl.replace( /http(s?):\/\//gi, '' ); + + const useNewLabel = + newLabel && + newLabel !== originalLabel && + // LinkControl without the title field relies + // on the check below. Specifically, it assumes that + // the URL is the same as a title. + // This logic a) looks suspicious and b) should really + // live in the LinkControl and not here. It's a great + // candidate for future refactoring. + newLabelWithoutHttp !== newUrlWithoutHttp; + + // Unfortunately this causes the escaping model to be inverted. + // The escaped content is stored in the block attributes (and ultimately in the database), + // and then the raw data is "recovered" when outputting into the DOM. + // It would be preferable to store the **raw** data in the block attributes and escape it in JS. + // Why? Because there isn't one way to escape data. Depending on the context, you need to do + // different transforms. It doesn't make sense to me to choose one of them for the purposes of storage. + // See also: + // - https://github.com/WordPress/gutenberg/pull/41063 + // - https://github.com/WordPress/gutenberg/pull/18617. + const label = useNewLabel + ? escapeHTML( newLabel ) + : originalLabel || escapeHTML( newUrlWithoutHttp ); + + // In https://github.com/WordPress/gutenberg/pull/24670 we decided to use "tag" in favor of "post_tag" + const type = newType === 'post_tag' ? 'tag' : newType.replace( '-', '_' ); + + const isBuiltInType = + [ 'post', 'page', 'tag', 'category' ].indexOf( type ) > -1; + + const isCustomLink = + ( ! newKind && ! isBuiltInType ) || newKind === 'custom'; + const kind = isCustomLink ? 'custom' : newKind; + + setAttributes( { + // Passed `url` may already be encoded. To prevent double encoding, decodeURI is executed to revert to the original string. + ...( newUrl && { url: encodeURI( safeDecodeURI( newUrl ) ) } ), + ...( label && { label } ), + ...( undefined !== opensInNewTab && { opensInNewTab } ), + ...( id && Number.isInteger( id ) && { id } ), + ...( kind && { kind } ), + ...( type && type !== 'URL' && { type } ), + } ); +}; diff --git a/packages/block-library/src/navigation-submenu/edit.js b/packages/block-library/src/navigation-submenu/edit.js index c1dd94a0c10343..47a222bd57bb66 100644 --- a/packages/block-library/src/navigation-submenu/edit.js +++ b/packages/block-library/src/navigation-submenu/edit.js @@ -2,7 +2,6 @@ * External dependencies */ import classnames from 'classnames'; -import escapeHtml from 'escape-html'; /** * WordPress dependencies @@ -10,50 +9,45 @@ import escapeHtml from 'escape-html'; import { useSelect, useDispatch } from '@wordpress/data'; import { PanelBody, - Popover, TextControl, TextareaControl, ToolbarButton, ToolbarGroup, } from '@wordpress/components'; import { displayShortcut, isKeyboardEvent } from '@wordpress/keycodes'; -import { __, sprintf } from '@wordpress/i18n'; +import { __ } from '@wordpress/i18n'; import { BlockControls, InnerBlocks, useInnerBlocksProps, InspectorControls, RichText, - __experimentalLinkControl as LinkControl, useBlockProps, store as blockEditorStore, getColorClassName, } from '@wordpress/block-editor'; -import { isURL, prependHTTP, safeDecodeURI } from '@wordpress/url'; -import { - Fragment, - useState, - useEffect, - useRef, - createInterpolateElement, -} from '@wordpress/element'; +import { isURL, prependHTTP } from '@wordpress/url'; +import { Fragment, useState, useEffect, useRef } from '@wordpress/element'; import { placeCaretAtHorizontalEdge } from '@wordpress/dom'; import { link as linkIcon, removeSubmenu } from '@wordpress/icons'; -import { - useResourcePermissions, - store as coreStore, -} from '@wordpress/core-data'; +import { useResourcePermissions } from '@wordpress/core-data'; import { speak } from '@wordpress/a11y'; import { createBlock } from '@wordpress/blocks'; -import { useMergeRefs } from '@wordpress/compose'; +import { useMergeRefs, usePrevious } from '@wordpress/compose'; /** * Internal dependencies */ import { ItemSubmenuIcon } from './icons'; import { name } from './block.json'; +import { LinkUI } from '../navigation-link/link-ui'; +import { updateAttributes } from '../navigation-link/update-attributes'; -const ALLOWED_BLOCKS = [ 'core/navigation-link', 'core/navigation-submenu' ]; +const ALLOWED_BLOCKS = [ + 'core/navigation-link', + 'core/navigation-submenu', + 'core/page-list', +]; const DEFAULT_BLOCK = { name: 'core/navigation-link', @@ -110,36 +104,6 @@ const useIsDraggingWithin = ( elementRef ) => { return isDraggingWithin; }; -/** - * Given the Link block's type attribute, return the query params to give to - * /wp/v2/search. - * - * @param {string} type Link block's type attribute. - * @param {string} kind Link block's entity of kind (post-type|taxonomy) - * @return {{ type?: string, subtype?: string }} Search query params. - */ -function getSuggestionsQuery( type, kind ) { - switch ( type ) { - case 'post': - case 'page': - return { type: 'post', subtype: type }; - case 'category': - return { type: 'term', subtype: 'category' }; - case 'tag': - return { type: 'term', subtype: 'post_tag' }; - case 'post_format': - return { type: 'post-format' }; - default: - if ( kind === 'taxonomy' ) { - return { type: 'term', subtype: type }; - } - if ( kind === 'post-type' ) { - return { type: 'post', subtype: type }; - } - return {}; - } -} - /** * Determine the colors for a menu. * @@ -214,64 +178,6 @@ function getColors( context, isSubMenu ) { * @property {string} [title] Link title attribute. */ -/** - * Link Control onChange handler that updates block attributes when a setting is changed. - * - * @param {Object} updatedValue New block attributes to update. - * @param {Function} setAttributes Block attribute update function. - * @param {WPNavigationLinkBlockAttributes} blockAttributes Current block attributes. - * - */ -export const updateNavigationLinkBlockAttributes = ( - updatedValue = {}, - setAttributes, - blockAttributes = {} -) => { - const { - label: originalLabel = '', - kind: originalKind = '', - type: originalType = '', - } = blockAttributes; - const { - title = '', - url = '', - opensInNewTab, - id, - kind: newKind = originalKind, - type: newType = originalType, - } = updatedValue; - - const normalizedTitle = title.replace( /http(s?):\/\//gi, '' ); - const normalizedURL = url.replace( /http(s?):\/\//gi, '' ); - const escapeTitle = - title !== '' && - normalizedTitle !== normalizedURL && - originalLabel !== title; - const label = escapeTitle - ? escapeHtml( title ) - : originalLabel || escapeHtml( normalizedURL ); - - // In https://github.com/WordPress/gutenberg/pull/24670 we decided to use "tag" in favor of "post_tag" - const type = newType === 'post_tag' ? 'tag' : newType.replace( '-', '_' ); - - const isBuiltInType = - [ 'post', 'page', 'tag', 'category' ].indexOf( type ) > -1; - - const isCustomLink = - ( ! newKind && ! isBuiltInType ) || newKind === 'custom'; - const kind = isCustomLink ? 'custom' : newKind; - - setAttributes( { - // Passed `url` may already be encoded. To prevent double encoding, decodeURI is executed to revert to the original string. - ...( url && { url: encodeURI( safeDecodeURI( url ) ) } ), - ...( label && { label } ), - ...( undefined !== opensInNewTab && { opensInNewTab } ), - ...( id && Number.isInteger( id ) && { id } ), - ...( kind && { kind } ), - ...( type && type !== 'URL' && { type } ), - } ); -}; - export default function NavigationSubmenuEdit( { attributes, isSelected, @@ -281,14 +187,9 @@ export default function NavigationSubmenuEdit( { context, clientId, } ) { - const { label, type, opensInNewTab, url, description, rel, title, kind } = - attributes; - const link = { - url, - opensInNewTab, - }; + const { label, type, url, description, rel, title } = attributes; + const { showSubmenuIcon, maxNestingLevel, openSubmenusOnClick } = context; - const { saveEntityRecord } = useDispatch( coreStore ); const { __unstableMarkNextChangeAsNotPersistent, replaceBlock } = useDispatch( blockEditorStore ); @@ -362,6 +263,8 @@ export default function NavigationSubmenuEdit( { [ clientId ] ); + const prevHasChildren = usePrevious( hasChildren ); + // Show the LinkControl on mount if the URL is empty // ( When adding a new menu item) // This can't be done in the useState call because it conflicts @@ -431,23 +334,6 @@ export default function NavigationSubmenuEdit( { userCanCreate = postsPermissions.canCreate; } - async function handleCreate( pageTitle ) { - const postType = type || 'page'; - - const page = await saveEntityRecord( 'postType', postType, { - title: pageTitle, - status: 'draft', - } ); - - return { - id: page.id, - type: postType, - title: page.title.rendered, - url: page.link, - kind: 'post-type', - }; - } - const { textColor, customTextColor, @@ -541,6 +427,13 @@ export default function NavigationSubmenuEdit( { replaceBlock( clientId, newLinkBlock ); } + useEffect( () => { + // If block becomes empty, transform to Navigation Link. + if ( ! hasChildren && prevHasChildren ) { + transformToLink(); + } + }, [ hasChildren, prevHasChildren ] ); + const canConvertToLink = ! selectedBlockHasChildren || onlyDescendantIsEmptyLink; @@ -571,6 +464,14 @@ export default function NavigationSubmenuEdit( { { /* Warning, this duplicated in packages/block-library/src/navigation-link/edit.js */ } + { + setAttributes( { label: labelValue } ); + } } + label={ __( 'Label' ) } + autoComplete="off" + /> { @@ -641,55 +542,25 @@ export default function NavigationSubmenuEdit( { /> } { ! openSubmenusOnClick && isLinkOpen && ( - setIsLinkOpen( false ) } anchor={ popoverAnchor } - shift - > - { - let format; - if ( type === 'post' ) { - /* translators: %s: search term. */ - format = __( - 'Create draft post: %s' - ); - } else { - /* translators: %s: search term. */ - format = __( - 'Create draft page: %s' - ); - } - return createInterpolateElement( - sprintf( format, searchTerm ), - { mark: } - ); - } } - noDirectEntry={ !! type } - noURLSuggestion={ !! type } - suggestionsQuery={ getSuggestionsQuery( - type, - kind - ) } - onChange={ ( updatedValue ) => - updateNavigationLinkBlockAttributes( - updatedValue, - setAttributes, - attributes - ) - } - onRemove={ () => { - setAttributes( { url: '' } ); - speak( __( 'Link removed.' ), 'assertive' ); - } } - /> - + hasCreateSuggestion={ userCanCreate } + onRemove={ () => { + setAttributes( { url: '' } ); + speak( __( 'Link removed.' ), 'assertive' ); + } } + onChange={ ( updatedValue ) => { + updateAttributes( + updatedValue, + setAttributes, + attributes + ); + } } + /> ) } { ( showSubmenuIcon || openSubmenusOnClick ) && ( diff --git a/packages/block-library/src/navigation-submenu/index.php b/packages/block-library/src/navigation-submenu/index.php index 87c7c91481cddb..c1c8006039f555 100644 --- a/packages/block-library/src/navigation-submenu/index.php +++ b/packages/block-library/src/navigation-submenu/index.php @@ -255,6 +255,14 @@ function render_block_core_navigation_submenu( $attributes, $content, $block ) { $inner_blocks_html .= $inner_block->render(); } + 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' ) ) ) { + $tag_processor->add_class( 'current-menu-ancestor' ); + } + $html = $tag_processor->get_updated_html(); + } + $html .= sprintf( '
      %s
    ', $inner_blocks_html @@ -281,3 +289,67 @@ function register_block_core_navigation_submenu() { ); } add_action( 'init', 'register_block_core_navigation_submenu' ); + +/** + * Disables display of block inspector tabs for the Navigation Submenu block. + * + * This is only a temporary measure until we have a TabPanel and mechanism that + * will allow the Navigation Submenu to programmatically select a tab when + * edited via a specific context. + * + * See: + * - https://github.com/WordPress/gutenberg/issues/45951 + * - https://github.com/WordPress/gutenberg/pull/46321 + * - https://github.com/WordPress/gutenberg/pull/46271 + * + * @param array $settings Default editor settings. + * @return array Filtered editor settings. + */ +function gutenberg_disable_tabs_for_navigation_submenu_block( $settings ) { + $current_tab_settings = _wp_array_get( + $settings, + array( '__experimentalBlockInspectorTabs' ), + array() + ); + + $settings['__experimentalBlockInspectorTabs'] = array_merge( + $current_tab_settings, + array( 'core/navigation-submenu' => false ) + ); + + return $settings; +} + +add_filter( 'block_editor_settings_all', 'gutenberg_disable_tabs_for_navigation_submenu_block' ); + +/** + * Enables animation of the block inspector for the Navigation Submenu block. + * + * See: + * - https://github.com/WordPress/gutenberg/pull/46342 + * - https://github.com/WordPress/gutenberg/issues/45884 + * + * @param array $settings Default editor settings. + * @return array Filtered editor settings. + */ +function gutenberg_enable_animation_for_navigation_submenu_inspector( $settings ) { + $current_animation_settings = _wp_array_get( + $settings, + array( '__experimentalBlockInspectorAnimation' ), + array() + ); + + $settings['__experimentalBlockInspectorAnimation'] = array_merge( + $current_animation_settings, + array( + 'core/navigation-submenu' => + array( + 'enterDirection' => 'rightToLeft', + ), + ) + ); + + return $settings; +} + +add_filter( 'block_editor_settings_all', 'gutenberg_enable_animation_for_navigation_submenu_inspector' ); diff --git a/packages/block-library/src/navigation/edit/are-blocks-dirty.js b/packages/block-library/src/navigation/edit/are-blocks-dirty.js new file mode 100644 index 00000000000000..1d7fa8a7286f26 --- /dev/null +++ b/packages/block-library/src/navigation/edit/are-blocks-dirty.js @@ -0,0 +1,51 @@ +export function areBlocksDirty( originalBlocks, blocks ) { + return ! isDeepEqual( originalBlocks, blocks, ( prop, x ) => { + // Skip inner blocks of page list during comparison as they + // are **always** controlled and may be updated async due to + // syncing with entity records. Left unchecked this would + // inadvertently trigger the dirty state. + if ( x?.name === 'core/page-list' && prop === 'innerBlocks' ) { + return true; + } + } ); +} + +/** + * Conditionally compares two candidates for deep equality. + * Provides an option to skip a given property of an object during comparison. + * + * @param {*} x 1st candidate for comparison + * @param {*} y 2nd candidate for comparison + * @param {Function|undefined} shouldSkip a function which can be used to skip a given property of an object. + * @return {boolean} whether the two candidates are deeply equal. + */ +const isDeepEqual = ( x, y, shouldSkip ) => { + if ( x === y ) { + return true; + } else if ( + typeof x === 'object' && + x !== null && + x !== undefined && + typeof y === 'object' && + y !== null && + y !== undefined + ) { + if ( Object.keys( x ).length !== Object.keys( y ).length ) return false; + + for ( const prop in x ) { + if ( y.hasOwnProperty( prop ) ) { + // Afford skipping a given property of an object. + if ( shouldSkip && shouldSkip( prop, x ) ) { + return true; + } + + if ( ! isDeepEqual( x[ prop ], y[ prop ], shouldSkip ) ) + return false; + } else return false; + } + + return true; + } + + return false; +}; diff --git a/packages/block-library/src/navigation/edit/index.js b/packages/block-library/src/navigation/edit/index.js index 588f4397274bd9..d94e0022d15ea3 100644 --- a/packages/block-library/src/navigation/edit/index.js +++ b/packages/block-library/src/navigation/edit/index.js @@ -20,11 +20,12 @@ import { __experimentalUseHasRecursion as useHasRecursion, store as blockEditorStore, withColors, - PanelColorSettings, ContrastChecker, getColorClassName, Warning, + __experimentalColorGradientSettingsDropdown as ColorGradientSettingsDropdown, __experimentalUseBlockOverlayActive as useBlockOverlayActive, + __experimentalUseMultipleOriginColorsAndGradients as useMultipleOriginColorsAndGradients, } from '@wordpress/block-editor'; import { EntityProvider, store as coreStore } from '@wordpress/core-data'; @@ -283,18 +284,37 @@ function Navigation( { ! hasResolvedNavigationMenus || isConvertingClassicMenu || fallbackNavigationMenus?.length > 0 || - classicMenus?.length !== 1 + ! classicMenus?.length ) { return; } // If there's non fallback navigation menus and - // only one classic menu then create a new navigation menu based on it. - convertClassicMenu( - classicMenus[ 0 ].id, - classicMenus[ 0 ].name, - 'publish' + // a classic menu with a `primary` location or slug, + // then create a new navigation menu based on it. + // Otherwise, use the most recently created classic menu. + const primaryMenus = classicMenus.filter( + ( classicMenu ) => + classicMenu.locations.includes( 'primary' ) || + classicMenu.slug === 'primary' ); + + if ( primaryMenus.length ) { + convertClassicMenu( + primaryMenus[ 0 ].id, + primaryMenus[ 0 ].name, + 'publish' + ); + } else { + classicMenus.sort( ( a, b ) => { + return b.id - a.id; + } ); + convertClassicMenu( + classicMenus[ 0 ].id, + classicMenus[ 0 ].name, + 'publish' + ); + } }, [ hasResolvedNavigationMenus ] ); const navRef = useRef(); @@ -393,6 +413,23 @@ function Navigation( { } }; + const onSelectClassicMenu = async ( classicMenu ) => { + const navMenu = await convertClassicMenu( + classicMenu.id, + classicMenu.name, + 'draft' + ); + if ( navMenu ) { + handleUpdateMenu( navMenu.id, { + focusNavigationBlock: true, + } ); + } + }; + + const onSelectNavigationMenu = ( menuId ) => { + handleUpdateMenu( menuId ); + }; + useEffect( () => { hideClassicMenuConversionNotice(); if ( classicMenuConversionStatus === CLASSIC_MENU_CONVERSION_PENDING ) { @@ -512,144 +549,157 @@ function Navigation( { } ); }, [ isDraftNavigationMenu, navigationMenu ] ); + const colorGradientSettings = useMultipleOriginColorsAndGradients(); const stylingInspectorControls = ( - - { hasSubmenuIndicatorSetting && ( - - { isResponsive && ( - <> - + { overlayMenuPreview && ( + ) } - - { overlayMenuPreview && ( - - ) } - - ) } -

    { __( 'Overlay Menu' ) }

    - ) } - onChange={ ( value ) => - setAttributes( { overlayMenu: value } ) - } - isBlock - hideLabelFromVision - > - - - - - { hasSubmenus && ( - <> -

    { __( 'Submenus' ) }

    - { - setAttributes( { - openSubmenusOnClick: value, - ...( value && { - showSubmenuIcon: true, - } ), // Make sure arrows are shown when we toggle this on. - } ); - } } - label={ __( 'Open on click' ) } +

    { __( 'Overlay Menu' ) }

    + + setAttributes( { overlayMenu: value } ) + } + isBlock + hideLabelFromVision + > + - - { - setAttributes( { - showSubmenuIcon: value, - } ); - } } - disabled={ attributes.openSubmenusOnClick } - label={ __( 'Show arrow' ) } + - - ) } -
    - ) } - { hasColorSettings && ( - - { enableContrastChecking && ( - <> - - - - ) } - - ) } -
    + + { hasSubmenus && ( + <> +

    { __( 'Submenus' ) }

    + { + setAttributes( { + openSubmenusOnClick: value, + ...( value && { + showSubmenuIcon: true, + } ), // Make sure arrows are shown when we toggle this on. + } ); + } } + label={ __( 'Open on click' ) } + /> + + { + setAttributes( { + showSubmenuIcon: value, + } ); + } } + disabled={ attributes.openSubmenusOnClick } + label={ __( 'Show arrow' ) } + /> + + ) } +
    + ) } +
    + + { hasColorSettings && ( + <> + setTextColor(), + }, + { + colorValue: backgroundColor.color, + label: __( 'Background' ), + onColorChange: setBackgroundColor, + resetAllFilter: () => setBackgroundColor(), + }, + { + colorValue: overlayTextColor.color, + label: __( 'Submenu & overlay text' ), + onColorChange: setOverlayTextColor, + resetAllFilter: () => setOverlayTextColor(), + }, + { + colorValue: overlayBackgroundColor.color, + label: __( 'Submenu & overlay background' ), + onColorChange: setOverlayBackgroundColor, + resetAllFilter: () => + setOverlayBackgroundColor(), + }, + ] } + panelId={ clientId } + { ...colorGradientSettings } + gradients={ [] } + disableCustomGradients={ true } + /> + { enableContrastChecking && ( + <> + + + + ) } + + ) } + + ); // If the block has inner blocks, but no menu id, then these blocks are either: @@ -668,17 +718,16 @@ function Navigation( { { stylingInspectorControls } { __( @@ -772,21 +820,8 @@ function Navigation( { isResolvingCanUserCreateNavigationMenu={ isResolvingCanUserCreateNavigationMenu } - onSelectNavigationMenu={ ( menuId ) => { - handleUpdateMenu( menuId ); - } } - onSelectClassicMenu={ async ( classicMenu ) => { - const navMenu = await convertClassicMenu( - classicMenu.id, - classicMenu.name, - 'draft' - ); - if ( navMenu ) { - handleUpdateMenu( navMenu.id, { - focusNavigationBlock: true, - } ); - } - } } + onSelectNavigationMenu={ onSelectNavigationMenu } + onSelectClassicMenu={ onSelectClassicMenu } onCreateEmpty={ createUntitledEmptyNavigationMenu } /> @@ -798,17 +833,16 @@ function Navigation( { { stylingInspectorControls } { isEntityAvailable && ( diff --git a/packages/block-library/src/navigation/edit/menu-inspector-controls.js b/packages/block-library/src/navigation/edit/menu-inspector-controls.js index 330812ef3e981c..5d70d1b1654317 100644 --- a/packages/block-library/src/navigation/edit/menu-inspector-controls.js +++ b/packages/block-library/src/navigation/edit/menu-inspector-controls.js @@ -4,12 +4,10 @@ import { __experimentalOffCanvasEditor as OffCanvasEditor, InspectorControls, + store as blockEditorStore, } from '@wordpress/block-editor'; -import { - PanelBody, - __experimentalHStack as HStack, - __experimentalHeading as Heading, -} from '@wordpress/components'; +import { PanelBody, VisuallyHidden } from '@wordpress/components'; +import { useSelect } from '@wordpress/data'; import { __ } from '@wordpress/i18n'; /** @@ -18,57 +16,33 @@ import { __ } from '@wordpress/i18n'; import ManageMenusButton from './manage-menus-button'; import NavigationMenuSelector from './navigation-menu-selector'; -const WrappedNavigationMenuSelector = ( { - clientId, - currentMenuId, - handleUpdateMenu, - convertClassicMenu, - onCreateNew, - createNavigationMenuIsSuccess, - createNavigationMenuIsError, -} ) => ( - { - handleUpdateMenu( menuId ); - } } - onSelectClassicMenu={ async ( classicMenu ) => { - const navMenu = await convertClassicMenu( - classicMenu.id, - classicMenu.name, - 'draft' - ); - if ( navMenu ) { - handleUpdateMenu( navMenu.id, { - focusNavigationBlock: true, - } ); - } - } } - onCreateNew={ onCreateNew } - createNavigationMenuIsSuccess={ createNavigationMenuIsSuccess } - createNavigationMenuIsError={ createNavigationMenuIsError } - /* translators: %s: The name of a menu. */ - actionLabel={ __( "Switch to '%s'" ) } - /> -); const MenuInspectorControls = ( { clientId, - convertClassicMenu, createNavigationMenuIsSuccess, createNavigationMenuIsError, currentMenuId = null, - handleUpdateMenu, - isNavigationMenuMissing, - innerBlocks, isManageMenusButtonDisabled, onCreateNew, + onSelectClassicMenu, + onSelectNavigationMenu, } ) => { const isOffCanvasNavigationEditorEnabled = window?.__experimentalEnableOffCanvasNavigationEditor === true; const menuControlsSlot = window?.__experimentalEnableBlockInspectorTabs ? 'list' : undefined; + /* translators: %s: The name of a menu. */ + const actionLabel = __( "Switch to '%s'" ); + + // Provide a hierarchy of clientIds for the given Navigation block (clientId). + // This is required else the list view will display the entire block tree. + const clientIdsTree = useSelect( + ( select ) => { + const { __unstableGetClientIdsTree } = select( blockEditorStore ); + return __unstableGetClientIdsTree( clientId ); + }, + [ clientId ] + ); return ( @@ -77,59 +51,37 @@ const MenuInspectorControls = ( { isOffCanvasNavigationEditorEnabled ? null : __( 'Menu' ) } > - { isOffCanvasNavigationEditorEnabled ? ( - <> - - - { __( 'Menu' ) } - - - - { currentMenuId && isNavigationMenuMissing ? ( -

    { __( 'Select or create a menu' ) }

    - ) : ( - - ) } - - ) : ( - <> - + { isOffCanvasNavigationEditorEnabled && ( + + { __( 'Menu' ) } + + ) } + + { isOffCanvasNavigationEditorEnabled ? ( + + ) : ( - - ) } + ) } +
    ); 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 9817934c3a2b16..2d8594093bf105 100644 --- a/packages/block-library/src/navigation/edit/navigation-menu-selector.js +++ b/packages/block-library/src/navigation/edit/navigation-menu-selector.js @@ -10,7 +10,7 @@ import { VisuallyHidden, } from '@wordpress/components'; import { useEntityProp } from '@wordpress/core-data'; -import { Icon, chevronUp, chevronDown, moreVertical } from '@wordpress/icons'; +import { Icon, chevronUp, chevronDown } from '@wordpress/icons'; import { __, sprintf } from '@wordpress/i18n'; import { decodeEntities } from '@wordpress/html-entities'; import { useEffect, useMemo, useState } from '@wordpress/element'; @@ -31,9 +31,6 @@ function NavigationMenuSelector( { createNavigationMenuIsError, toggleProps = {}, } ) { - const isOffCanvasNavigationEditorEnabled = - window?.__experimentalEnableOffCanvasNavigationEditor === true; - /* translators: %s: The name of a menu. */ const createActionLabel = __( "Create from '%s'" ); @@ -164,23 +161,15 @@ function NavigationMenuSelector( { return ( - { isOffCanvasNavigationEditorEnabled ? '' : selectorLabel } + { selectorLabel } } - icon={ isOffCanvasNavigationEditorEnabled ? moreVertical : null } - toggleProps={ - isOffCanvasNavigationEditorEnabled - ? { isSmall: true } - : toggleProps - } + icon={ null } + toggleProps={ toggleProps } > { ( { onClose } ) => ( <> diff --git a/packages/block-library/src/navigation/edit/test/are-blocks-dirty.js b/packages/block-library/src/navigation/edit/test/are-blocks-dirty.js new file mode 100644 index 00000000000000..dc2b9a3f31f29b --- /dev/null +++ b/packages/block-library/src/navigation/edit/test/are-blocks-dirty.js @@ -0,0 +1,121 @@ +/** + * Internal dependencies + */ +import { areBlocksDirty } from '../are-blocks-dirty'; + +describe( 'areBlocksDirty', () => { + it( 'should be clean if the blocks are the same', () => { + expect( + areBlocksDirty( + [ { name: 'core/paragraph', content: 'I am not dirty.' } ], + [ { name: 'core/paragraph', content: 'I am not dirty.' } ] + ) + ).toBe( false ); + } ); + + it( `should be dirty if the blocks' attributes are different`, () => { + expect( + areBlocksDirty( + [ { name: 'core/paragraph', content: 'I am not dirty.' } ], + [ { name: 'core/paragraph', content: 'I am actually dirty.' } ] + ) + ).toBe( true ); + } ); + + it( `should be dirty if the blocks' attributes don't match`, () => { + expect( + areBlocksDirty( + [ { name: 'core/paragraph' }, { dropCap: false } ], + [ + { name: 'core/paragraph' }, + { content: 'I am actually dirty.' }, + ] + ) + ).toBe( true ); + } ); + + it( `should be dirty if the blocks' inner blocks are dirty`, () => { + expect( + areBlocksDirty( + [ + { + name: 'core/social-links', + innerBlocks: [ + { + name: 'core/social-link', + url: 'www.wordpress.org', + }, + ], + }, + ], + [ + { + name: 'core/social-links', + innerBlocks: [ + { + name: 'core/social-link', + service: 'wordpress', + url: 'www.wordpress.org', + }, + { + name: 'core/social-link', + service: 'wordpress', + url: 'make.wordpress.org', + }, + ], + }, + ] + ) + ).toBe( true ); + } ); + + describe( 'Controlled Page List block specific exceptions', () => { + it( 'should be clean if only page list inner blocks have changed', () => { + expect( + areBlocksDirty( + [ + { name: 'core/paragraph' }, + { + name: 'core/page-list', + innerBlocks: [], + }, + ], + [ + { name: 'core/paragraph' }, + { + name: 'core/page-list', + innerBlocks: [ { name: 'core/page-list-item' } ], + }, + ] + ) + ).toBe( false ); + } ); + + it( 'should be dirty if other blocks have changed alongside page list inner blocks', () => { + expect( + areBlocksDirty( + [ + { + name: 'core/paragraph', + content: 'This is some text', + }, + { + name: 'core/page-list', + innerBlocks: [], + }, + ], + [ + { + name: 'core/paragraph', + content: 'This is some text that has changed', + }, + { + name: 'core/page-list', + innerBlocks: [ { name: 'core/page-list-item' } ], + }, + ] + ) + ).toBe( true ); + } ); + } ); +} ); diff --git a/packages/block-library/src/navigation/edit/unsaved-inner-blocks.js b/packages/block-library/src/navigation/edit/unsaved-inner-blocks.js index 0fa2a119446f36..1017b6594fb4a2 100644 --- a/packages/block-library/src/navigation/edit/unsaved-inner-blocks.js +++ b/packages/block-library/src/navigation/edit/unsaved-inner-blocks.js @@ -11,6 +11,7 @@ import { useContext, useEffect, useRef, useMemo } from '@wordpress/element'; * Internal dependencies */ import useNavigationMenu from '../use-navigation-menu'; +import { areBlocksDirty } from './are-blocks-dirty'; const EMPTY_OBJECT = {}; const DRAFT_MENU_PARAMS = [ @@ -51,13 +52,17 @@ export default function UnsavedInnerBlocks( { } }, [ blocks ] ); - // If the current inner blocks object is different in any way - // from the original inner blocks from the post content then the - // user has made changes to the inner blocks. At this point the inner - // blocks can be considered "dirty". - // We also make sure the current innerBlocks had a chance to be set. - const innerBlocksAreDirty = - !! originalBlocks.current && blocks !== originalBlocks.current; + // If the current inner blocks are different from the original inner blocks + // from the post content then the user has made changes to the inner blocks. + // At this point the inner blocks can be considered "dirty". + // Note: referential equality is not sufficient for comparison as the inner blocks + // of the page list are controlled and may be updated async due to syncing with + // entity records. As a result we need to perform a deep equality check skipping + // the page list's inner blocks. + const innerBlocksAreDirty = areBlocksDirty( + originalBlocks?.current, + blocks + ); const shouldDirectInsert = useMemo( () => diff --git a/packages/block-library/src/navigation/index.php b/packages/block-library/src/navigation/index.php index 87ea817270a391..971a5f500e7741 100644 --- a/packages/block-library/src/navigation/index.php +++ b/packages/block-library/src/navigation/index.php @@ -257,10 +257,32 @@ function block_core_navigation_get_classic_menu_fallback() { $classic_nav_menus = wp_get_nav_menus(); // If menus exist. - if ( $classic_nav_menus && ! is_wp_error( $classic_nav_menus ) && count( $classic_nav_menus ) === 1 ) { - // Use the first classic menu only. Handles simple use case where user has a single - // classic menu and switches to a block theme. In future this maybe expanded to - // determine the most appropriate classic menu to be used based on location. + if ( $classic_nav_menus && ! is_wp_error( $classic_nav_menus ) ) { + // Handles simple use case where user has a classic menu and switches to a block theme. + + // Returns the menu assigned to location `primary`. + $locations = get_nav_menu_locations(); + if ( isset( $locations['primary'] ) ) { + $primary_menu = wp_get_nav_menu_object( $locations['primary'] ); + if ( $primary_menu ) { + return $primary_menu; + } + } + + // Returns a menu if `primary` is its slug. + foreach ( $classic_nav_menus as $classic_nav_menu ) { + if ( 'primary' === $classic_nav_menu->slug ) { + return $classic_nav_menu; + } + } + + // Otherwise return the most recently created classic menu. + usort( + $classic_nav_menus, + function( $a, $b ) { + return $b->term_id - $a->term_id; + } + ); return $classic_nav_menus[0]; } } @@ -489,6 +511,7 @@ function block_core_navigation_from_block_get_post_ids( $block ) { function render_block_core_navigation( $attributes, $content, $block ) { static $seen_menu_names = array(); + static $seen_ref = array(); // Flag used to indicate whether the rendered output is considered to be // a fallback (i.e. the block has no menu associated with it). @@ -559,6 +582,11 @@ function render_block_core_navigation( $attributes, $content, $block ) { // Load inner blocks from the navigation post. if ( array_key_exists( 'ref', $attributes ) ) { + if ( in_array( $attributes['ref'], $seen_ref, true ) ) { + return ''; + } + $seen_ref[] = $attributes['ref']; + $navigation_post = get_post( $attributes['ref'] ); if ( ! isset( $navigation_post ) ) { return ''; @@ -651,22 +679,41 @@ function render_block_core_navigation( $attributes, $content, $block ) { _prime_post_caches( $post_ids, false, false ); } + $list_item_nav_blocks = array( + 'core/navigation-link', + 'core/home-link', + 'core/site-title', + 'core/site-logo', + 'core/navigation-submenu', + ); + + $needs_list_item_wrapper = array( + 'core/site-title', + 'core/site-logo', + ); + $inner_blocks_html = ''; $is_list_open = false; foreach ( $inner_blocks as $inner_block ) { - if ( ( 'core/navigation-link' === $inner_block->name || 'core/home-link' === $inner_block->name || 'core/site-title' === $inner_block->name || 'core/site-logo' === $inner_block->name || 'core/navigation-submenu' === $inner_block->name ) && ! $is_list_open ) { + $is_list_item = in_array( $inner_block->name, $list_item_nav_blocks, true ); + + if ( $is_list_item && ! $is_list_open ) { $is_list_open = true; $inner_blocks_html .= '
      '; } - if ( 'core/navigation-link' !== $inner_block->name && 'core/home-link' !== $inner_block->name && 'core/site-title' !== $inner_block->name && 'core/site-logo' !== $inner_block->name && 'core/navigation-submenu' !== $inner_block->name && $is_list_open ) { + + if ( ! $is_list_item && $is_list_open ) { $is_list_open = false; $inner_blocks_html .= '
    '; } + $inner_block_content = $inner_block->render(); - if ( 'core/site-title' === $inner_block->name || ( 'core/site-logo' === $inner_block->name && $inner_block_content ) ) { - $inner_blocks_html .= '
  • ' . $inner_block_content . '
  • '; - } else { - $inner_blocks_html .= $inner_block_content; + if ( ! empty( $inner_block_content ) ) { + if ( in_array( $inner_block->name, $needs_list_item_wrapper, true ) ) { + $inner_blocks_html .= '
  • ' . $inner_block_content . '
  • '; + } else { + $inner_blocks_html .= $inner_block_content; + } } } @@ -809,3 +856,35 @@ function block_core_navigation_typographic_presets_backcompatibility( $parsed_bl } add_filter( 'render_block_data', 'block_core_navigation_typographic_presets_backcompatibility' ); + +/** + * Enables animation of the block inspector for the Navigation block. + * + * See: + * - https://github.com/WordPress/gutenberg/pull/46342 + * - https://github.com/WordPress/gutenberg/issues/45884 + * + * @param array $settings Default editor settings. + * @return array Filtered editor settings. + */ +function gutenberg_enable_animation_for_navigation_inspector( $settings ) { + $current_animation_settings = _wp_array_get( + $settings, + array( '__experimentalBlockInspectorAnimation' ), + array() + ); + + $settings['__experimentalBlockInspectorAnimation'] = array_merge( + $current_animation_settings, + array( + 'core/navigation' => + array( + 'enterDirection' => 'leftToRight', + ), + ) + ); + + return $settings; +} + +add_filter( 'block_editor_settings_all', 'gutenberg_enable_animation_for_navigation_inspector' ); diff --git a/packages/block-library/src/page-list-item/block.json b/packages/block-library/src/page-list-item/block.json new file mode 100644 index 00000000000000..97c312d1b7960e --- /dev/null +++ b/packages/block-library/src/page-list-item/block.json @@ -0,0 +1,51 @@ +{ + "$schema": "https://schemas.wp.org/trunk/block.json", + "apiVersion": 2, + "name": "core/page-list-item", + "title": "Page List Item", + "category": "widgets", + "parent": [ "core/page-list" ], + "description": "Displays a page inside a list of all pages.", + "keywords": [ "page", "menu", "navigation" ], + "textdomain": "default", + "attributes": { + "id": { + "type": "number" + }, + "label": { + "type": "string" + }, + "title": { + "type": "string" + }, + "link": { + "type": "string" + }, + "hasChildren": { + "type": "boolean" + } + }, + "usesContext": [ + "textColor", + "customTextColor", + "backgroundColor", + "customBackgroundColor", + "overlayTextColor", + "customOverlayTextColor", + "overlayBackgroundColor", + "customOverlayBackgroundColor", + "fontSize", + "customFontSize", + "showSubmenuIcon", + "style", + "openSubmenusOnClick" + ], + "supports": { + "reusable": false, + "html": false, + "lock": false, + "inserter": false + }, + "editorStyle": "wp-block-page-list-editor", + "style": "wp-block-page-list" +} diff --git a/packages/block-library/src/page-list-item/edit.js b/packages/block-library/src/page-list-item/edit.js new file mode 100644 index 00000000000000..db8e0a5c173736 --- /dev/null +++ b/packages/block-library/src/page-list-item/edit.js @@ -0,0 +1,94 @@ +/** + * External dependencies + */ +import classnames from 'classnames'; + +/** + * WordPress dependencies + */ +import { InnerBlocks } from '@wordpress/block-editor'; +import { useSelect } from '@wordpress/data'; +import { store as coreStore } from '@wordpress/core-data'; + +/** + * Internal dependencies + */ +import { ItemSubmenuIcon } from '../navigation-link/icons'; + +function useFrontPageId() { + return useSelect( ( select ) => { + const canReadSettings = select( coreStore ).canUser( + 'read', + 'settings' + ); + if ( ! canReadSettings ) { + return undefined; + } + + const site = select( coreStore ).getEntityRecord( 'root', 'site' ); + return site?.show_on_front === 'page' && site?.page_on_front; + }, [] ); +} + +export default function PageListItemEdit( { context, attributes } ) { + const { id, label, link, hasChildren } = attributes; + const isNavigationChild = 'showSubmenuIcon' in context; + const frontPageId = useFrontPageId(); + return ( +
  • + { hasChildren && context.openSubmenusOnClick ? ( + <> + + + + + + ) : ( + + { label } + + ) } + { hasChildren && ( + <> + { ! context.openSubmenusOnClick && + context.showSubmenuIcon && ( + + ) } +
      + +
    + + ) } +
  • + ); +} diff --git a/packages/block-library/src/page-list-item/index.js b/packages/block-library/src/page-list-item/index.js new file mode 100644 index 00000000000000..18b7fe2e40e9ea --- /dev/null +++ b/packages/block-library/src/page-list-item/index.js @@ -0,0 +1,24 @@ +/** + * WordPress dependencies + */ +import { pages as icon } from '@wordpress/icons'; + +/** + * Internal dependencies + */ +import initBlock from '../utils/init-block'; +import metadata from './block.json'; +import edit from './edit.js'; + +const { name } = metadata; + +export { metadata, name }; + +export const settings = { + __experimentalLabel: ( { label } ) => label, + icon, + example: {}, + edit, +}; + +export const init = () => initBlock( { name, metadata, settings } ); diff --git a/packages/block-library/src/page-list-item/init.js b/packages/block-library/src/page-list-item/init.js new file mode 100644 index 00000000000000..79f0492c2cb2f8 --- /dev/null +++ b/packages/block-library/src/page-list-item/init.js @@ -0,0 +1,6 @@ +/** + * Internal dependencies + */ +import { init } from './'; + +export default init(); diff --git a/packages/block-library/src/page-list/block.json b/packages/block-library/src/page-list/block.json index 3068a1fb8bc008..e8ff316a9fb4f6 100644 --- a/packages/block-library/src/page-list/block.json +++ b/packages/block-library/src/page-list/block.json @@ -7,7 +7,12 @@ "description": "Display a list of all pages.", "keywords": [ "menu", "navigation" ], "textdomain": "default", - "attributes": {}, + "attributes": { + "parentPageID": { + "type": "integer", + "default": 0 + } + }, "usesContext": [ "textColor", "customTextColor", @@ -25,7 +30,20 @@ ], "supports": { "reusable": false, - "html": false + "html": false, + "typography": { + "fontSize": true, + "lineHeight": true, + "__experimentalFontFamily": true, + "__experimentalFontWeight": true, + "__experimentalFontStyle": true, + "__experimentalTextTransform": true, + "__experimentalTextDecoration": true, + "__experimentalLetterSpacing": true, + "__experimentalDefaultControls": { + "fontSize": true + } + } }, "editorStyle": "wp-block-page-list-editor", "style": "wp-block-page-list" diff --git a/packages/block-library/src/page-list/constants.js b/packages/block-library/src/page-list/constants.js new file mode 100644 index 00000000000000..9322fa345321c4 --- /dev/null +++ b/packages/block-library/src/page-list/constants.js @@ -0,0 +1,8 @@ +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; + +export const convertDescription = __( + 'This menu is automatically kept in sync with pages on your site. You can manage the menu yourself by clicking customize below.' +); diff --git a/packages/block-library/src/page-list/convert-to-links-modal.js b/packages/block-library/src/page-list/convert-to-links-modal.js index bbb3e1e44e98b1..dbc76fc77cdb1e 100644 --- a/packages/block-library/src/page-list/convert-to-links-modal.js +++ b/packages/block-library/src/page-list/convert-to-links-modal.js @@ -5,71 +5,20 @@ import { Button, Modal } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import { useDispatch } from '@wordpress/data'; import { useEntityRecords } from '@wordpress/core-data'; -import { createBlock as create } from '@wordpress/blocks'; import { store as blockEditorStore } from '@wordpress/block-editor'; +/** + * Internal dependencies + */ +import { convertToNavigationLinks } from './convert-to-navigation-links'; + +/** + * Internal dependencies + */ +import { convertDescription } from './constants'; const PAGE_FIELDS = [ 'id', 'title', 'link', 'type', 'parent' ]; const MAX_PAGE_COUNT = 100; -export const convertSelectedBlockToNavigationLinks = - ( { pages, clientId, replaceBlock, createBlock } ) => - () => { - if ( ! pages ) { - return; - } - - const linkMap = {}; - const navigationLinks = []; - pages.forEach( ( { id, title, link: url, type, parent } ) => { - // See if a placeholder exists. This is created if children appear before parents in list. - const innerBlocks = linkMap[ id ]?.innerBlocks ?? []; - linkMap[ id ] = createBlock( - 'core/navigation-link', - { - id, - label: title.rendered, - url, - type, - kind: 'post-type', - }, - innerBlocks - ); - - if ( ! parent ) { - navigationLinks.push( linkMap[ id ] ); - } else { - if ( ! linkMap[ parent ] ) { - // Use a placeholder if the child appears before parent in list. - linkMap[ parent ] = { innerBlocks: [] }; - } - const parentLinkInnerBlocks = linkMap[ parent ].innerBlocks; - parentLinkInnerBlocks.push( linkMap[ id ] ); - } - } ); - - // Transform all links with innerBlocks into Submenus. This can't be done - // sooner because page objects have no information on their children. - - const transformSubmenus = ( listOfLinks ) => { - listOfLinks.forEach( ( block, index, listOfLinksArray ) => { - const { attributes, innerBlocks } = block; - if ( innerBlocks.length !== 0 ) { - transformSubmenus( innerBlocks ); - const transformedBlock = createBlock( - 'core/navigation-submenu', - attributes, - innerBlocks - ); - listOfLinksArray[ index ] = transformedBlock; - } - } ); - }; - - transformSubmenus( navigationLinks ); - - replaceBlock( clientId, navigationLinks ); - }; - export default function ConvertToLinksModal( { onClose, clientId } ) { const { records: pages, hasResolved: pagesFinished } = useEntityRecords( 'postType', @@ -91,19 +40,12 @@ export default function ConvertToLinksModal( { onClose, clientId } ) {

    - { __( - 'To edit this navigation menu, convert it to single page links. This allows you to add, re-order, remove items, or edit their labels.' - ) } -

    -

    - { __( - "Note: if you add new pages to your site, you'll need to add them to your navigation menu." - ) } + { convertDescription }

    diff --git a/packages/block-library/src/page-list/convert-to-navigation-links.js b/packages/block-library/src/page-list/convert-to-navigation-links.js new file mode 100644 index 00000000000000..64b1dbf1c7d4d6 --- /dev/null +++ b/packages/block-library/src/page-list/convert-to-navigation-links.js @@ -0,0 +1,60 @@ +/** + * WordPress dependencies + */ +import { createBlock } from '@wordpress/blocks'; + +export const convertToNavigationLinks = ( pages ) => { + if ( ! pages ) { + return; + } + + const linkMap = {}; + const navigationLinks = []; + pages.forEach( ( { id, title, link: url, type, parent } ) => { + // See if a placeholder exists. This is created if children appear before parents in list. + const innerBlocks = linkMap[ id ]?.innerBlocks ?? []; + linkMap[ id ] = createBlock( + 'core/navigation-link', + { + id, + label: title.rendered, + url, + type, + kind: 'post-type', + }, + innerBlocks + ); + + if ( ! parent ) { + navigationLinks.push( linkMap[ id ] ); + } else { + if ( ! linkMap[ parent ] ) { + // Use a placeholder if the child appears before parent in list. + linkMap[ parent ] = { innerBlocks: [] }; + } + const parentLinkInnerBlocks = linkMap[ parent ].innerBlocks; + parentLinkInnerBlocks.push( linkMap[ id ] ); + } + } ); + + // Transform all links with innerBlocks into Submenus. This can't be done + // sooner because page objects have no information on their children. + const transformSubmenus = ( listOfLinks ) => { + listOfLinks.forEach( ( block, index, listOfLinksArray ) => { + const { attributes, innerBlocks } = block; + if ( innerBlocks.length !== 0 ) { + transformSubmenus( innerBlocks ); + const transformedBlock = createBlock( + 'core/navigation-submenu', + attributes, + innerBlocks + ); + listOfLinksArray[ index ] = transformedBlock; + } + } ); + }; + + transformSubmenus( navigationLinks ); + + return navigationLinks; +}; diff --git a/packages/block-library/src/page-list/edit.js b/packages/block-library/src/page-list/edit.js index 71cbfa437a1f9a..7372191723f466 100644 --- a/packages/block-library/src/page-list/edit.js +++ b/packages/block-library/src/page-list/edit.js @@ -6,28 +6,49 @@ import classnames from 'classnames'; /** * WordPress dependencies */ +import { createBlock } from '@wordpress/blocks'; import { + InspectorControls, BlockControls, useBlockProps, + useInnerBlocksProps, getColorClassName, + store as blockEditorStore, + Warning, } from '@wordpress/block-editor'; -import { ToolbarButton, Spinner, Notice } from '@wordpress/components'; -import { __ } from '@wordpress/i18n'; -import { useMemo, useState, memo } from '@wordpress/element'; -import { useSelect } from '@wordpress/data'; -import { store as coreStore, useEntityRecords } from '@wordpress/core-data'; +import { + PanelBody, + ToolbarButton, + Spinner, + Notice, + ComboboxControl, + Button, +} from '@wordpress/components'; +import { __, sprintf } from '@wordpress/i18n'; +import { useMemo, useState } from '@wordpress/element'; +import { useEntityRecords } from '@wordpress/core-data'; +import { useSelect, useDispatch } from '@wordpress/data'; /** * Internal dependencies */ import ConvertToLinksModal from './convert-to-links-modal'; -import { ItemSubmenuIcon } from '../navigation-link/icons'; +import { convertToNavigationLinks } from './convert-to-navigation-links'; +import { convertDescription } from './constants'; // We only show the edit option when page count is <= MAX_PAGE_COUNT // Performance of Navigation Links is not good past this value. const MAX_PAGE_COUNT = 100; +const NOOP = () => {}; -export default function PageListEdit( { context, clientId } ) { +export default function PageListEdit( { + context, + clientId, + attributes, + setAttributes, +} ) { + const { parentPageID } = attributes; + const [ pages ] = useGetPages(); const { pagesByParentId, totalPages, hasResolvedPages } = usePageData(); const isNavigationChild = 'showSubmenuIcon' in context; @@ -52,6 +73,70 @@ export default function PageListEdit( { context, clientId } ) { style: { ...context.style?.color }, } ); + const getBlockList = ( parentId = parentPageID ) => { + const childPages = pagesByParentId.get( parentId ); + + if ( ! childPages?.length ) { + return []; + } + + return childPages.reduce( ( template, page ) => { + const hasChildren = pagesByParentId.has( page.id ); + const pageProps = { + id: page.id, + label: page.title?.rendered, + title: page.title?.rendered, + link: page.url, + hasChildren, + }; + let item = null; + const children = getBlockList( page.id ); + item = createBlock( 'core/page-list-item', pageProps, children ); + template.push( item ); + + return template; + }, [] ); + }; + + const makePagesTree = ( parentId = 0, level = 0 ) => { + const childPages = pagesByParentId.get( parentId ); + + if ( ! childPages?.length ) { + return []; + } + + return childPages.reduce( ( tree, page ) => { + const hasChildren = pagesByParentId.has( page.id ); + const item = { + value: page.id, + label: '— '.repeat( level ) + page.title.rendered, + rawName: page.title.rendered, + }; + tree.push( item ); + if ( hasChildren ) { + tree.push( ...makePagesTree( page.id, level + 1 ) ); + } + return tree; + }, [] ); + }; + + const pagesTree = useMemo( makePagesTree, [ pagesByParentId ] ); + + const blockList = useMemo( getBlockList, [ + pagesByParentId, + parentPageID, + ] ); + + const innerBlocksProps = useInnerBlocksProps( blockProps, { + allowedBlocks: [ 'core/page-list-item' ], + renderAppender: false, + __unstableDisableDropZone: true, + templateLock: 'all', + onInput: NOOP, + onChange: NOOP, + value: blockList, + } ); + const getBlockContent = () => { if ( ! hasResolvedPages ) { return ( @@ -81,21 +166,86 @@ export default function PageListEdit( { context, clientId } ) { ); } - if ( totalPages > 0 ) { + if ( blockList.length === 0 ) { + const parentPageDetails = + pages && pages.find( ( page ) => page.id === parentPageID ); return ( -
      - -
    +
    + + { sprintf( + // translators: %s: Page title. + __( '"%s" page has no children.' ), + parentPageDetails.title.rendered + ) } + +
    ); } + + if ( totalPages > 0 ) { + return
      ; + } }; + const { replaceBlock, selectBlock } = useDispatch( blockEditorStore ); + + const { parentNavBlockClientId } = useSelect( ( select ) => { + const { getSelectedBlockClientId, getBlockParentsByBlockName } = + select( blockEditorStore ); + + const _selectedBlockClientId = getSelectedBlockClientId(); + + return { + parentNavBlockClientId: getBlockParentsByBlockName( + _selectedBlockClientId, + 'core/navigation', + true + )[ 0 ], + }; + }, [] ); + return ( <> - { allowConvertToLinks && ( + + { isNavigationChild && pages?.length > 0 && ( + +

      { convertDescription }

      + +
      + ) } + { pagesTree.length > 0 && ( + + + setAttributes( { parentPageID: value ?? 0 } ) + } + help={ __( + 'Choose a page to show only its subpages.' + ) } + /> + + ) } +
      + { allowConvertToLinks && totalPages > 0 && ( { __( 'Edit' ) } @@ -114,22 +264,7 @@ export default function PageListEdit( { context, clientId } ) { ); } -function useFrontPageId() { - return useSelect( ( select ) => { - const canReadSettings = select( coreStore ).canUser( - 'read', - 'settings' - ); - if ( ! canReadSettings ) { - return undefined; - } - - const site = select( coreStore ).getEntityRecord( 'root', 'site' ); - return site?.show_on_front === 'page' && site?.page_on_front; - }, [] ); -} - -function usePageData() { +function useGetPages() { const { records: pages, hasResolved: hasResolvedPages } = useEntityRecords( 'postType', 'page', @@ -142,10 +277,17 @@ function usePageData() { } ); + return [ pages, hasResolvedPages ]; +} + +function usePageData( pageId = 0 ) { + const [ pages, hasResolvedPages ] = useGetPages(); + return useMemo( () => { // TODO: Once the REST API supports passing multiple values to // 'orderby', this can be removed. // https://core.trac.wordpress.org/ticket/39037 + const sortedPages = [ ...( pages ?? [] ) ].sort( ( a, b ) => { if ( a.menu_order === b.menu_order ) { return a.title.rendered.localeCompare( b.title.rendered ); @@ -167,91 +309,5 @@ function usePageData() { hasResolvedPages, totalPages: pages?.length ?? null, }; - }, [ pages, hasResolvedPages ] ); + }, [ pageId, pages, hasResolvedPages ] ); } - -const PageItems = memo( function PageItems( { - context, - pagesByParentId, - parentId = 0, - depth = 0, -} ) { - const pages = pagesByParentId.get( parentId ); - const frontPageId = useFrontPageId(); - - if ( ! pages?.length ) { - return []; - } - - return pages.map( ( page ) => { - const hasChildren = pagesByParentId.has( page.id ); - const isNavigationChild = 'showSubmenuIcon' in context; - return ( -
    • - { hasChildren && context.openSubmenusOnClick ? ( - <> - - - - - - ) : ( - - { page.title?.rendered } - - ) } - { hasChildren && ( - <> - { ! context.openSubmenusOnClick && - context.showSubmenuIcon && ( - - ) } -
        - -
      - - ) } -
    • - ); - } ); -} ); diff --git a/packages/block-library/src/page-list/index.js b/packages/block-library/src/page-list/index.js index 7e13c23f229f2e..995e3a1c7f97ab 100644 --- a/packages/block-library/src/page-list/index.js +++ b/packages/block-library/src/page-list/index.js @@ -1,7 +1,7 @@ /** * WordPress dependencies */ -import { pages as icon } from '@wordpress/icons'; +import { pages, update } from '@wordpress/icons'; /** * Internal dependencies @@ -15,7 +15,13 @@ const { name } = metadata; export { metadata, name }; export const settings = { - icon, + icon: ( { context } ) => { + if ( context === 'list-view' ) { + return update; + } + + return pages; + }, example: {}, edit, }; diff --git a/packages/block-library/src/page-list/index.php b/packages/block-library/src/page-list/index.php index 32acdad45ed635..7944f6f3a84ad6 100644 --- a/packages/block-library/src/page-list/index.php +++ b/packages/block-library/src/page-list/index.php @@ -252,6 +252,8 @@ function render_block_core_page_list( $attributes, $content, $block ) { static $block_id = 0; ++$block_id; + $parent_page_id = $attributes['parentPageID']; + $all_pages = get_pages( array( 'sort_column' => 'menu_order,post_title', @@ -306,6 +308,13 @@ function render_block_core_page_list( $attributes, $content, $block ) { $nested_pages = block_core_page_list_nest_pages( $top_level_pages, $pages_with_children ); + if ( 0 !== $parent_page_id ) { + $nested_pages = block_core_page_list_nest_pages( + $pages_with_children[ $parent_page_id ], + $pages_with_children + ); + } + $is_navigation_child = array_key_exists( 'showSubmenuIcon', $block->context ); $open_submenus_on_click = array_key_exists( 'openSubmenusOnClick', $block->context ) ? $block->context['openSubmenusOnClick'] : false; diff --git a/packages/block-library/src/page-list/test/convert-to-links-modal.js b/packages/block-library/src/page-list/test/convert-to-links-modal.js index e9908d2b014362..4c83b64f9bb9f9 100644 --- a/packages/block-library/src/page-list/test/convert-to-links-modal.js +++ b/packages/block-library/src/page-list/test/convert-to-links-modal.js @@ -1,10 +1,28 @@ /** * Internal dependencies */ -import { convertSelectedBlockToNavigationLinks } from '../convert-to-links-modal'; + +import { convertToNavigationLinks } from '../convert-to-navigation-links'; + +// Mock createBlock to avoid creating the blocks in test environment +// as convertToNavigationLinks calls this method internally. +jest.mock( '@wordpress/blocks', () => { + const blocks = jest.requireActual( '@wordpress/blocks' ); + + return { + ...blocks, + createBlock( name, attributes, innerBlocks ) { + return { + name, + attributes, + innerBlocks, + }; + }, + }; +} ); describe( 'page list convert to links', () => { - describe( 'convertSelectedBlockToNavigationLinks', () => { + describe( 'convertToNavigationLinks', () => { it( 'Can create submenus', () => { const pages = [ { @@ -88,22 +106,10 @@ describe( 'page list convert to links', () => { type: 'page', }, ]; - const replaceBlock = jest.fn(); - const createBlock = jest.fn( - ( name, attributes, innerBlocks ) => ( { - name, - attributes, - innerBlocks, - } ) - ); - const convertLinks = convertSelectedBlockToNavigationLinks( { - pages, - clientId: 'testId', - replaceBlock, - createBlock, - } ); - convertLinks(); - expect( replaceBlock.mock.calls?.[ 0 ]?.[ 1 ] ).toEqual( [ + + const convertLinks = convertToNavigationLinks( pages ); + + expect( convertLinks ).toEqual( [ { attributes: { id: 2, @@ -280,22 +286,10 @@ describe( 'page list convert to links', () => { type: 'page', }, ]; - const replaceBlock = jest.fn(); - const createBlock = jest.fn( - ( name, attributes, innerBlocks ) => ( { - name, - attributes, - innerBlocks, - } ) - ); - const convertLinks = convertSelectedBlockToNavigationLinks( { - pages, - clientId: 'testId', - replaceBlock, - createBlock, - } ); - convertLinks(); - expect( replaceBlock.mock.calls?.[ 0 ]?.[ 1 ] ).toEqual( [ + + const convertLinks = convertToNavigationLinks( pages ); + + expect( convertLinks ).toEqual( [ { attributes: { id: 2, diff --git a/packages/block-library/src/paragraph/index.js b/packages/block-library/src/paragraph/index.js index 70ae87ccf5f36c..c9a00ccf4b33f0 100644 --- a/packages/block-library/src/paragraph/index.js +++ b/packages/block-library/src/paragraph/index.js @@ -30,12 +30,6 @@ export const settings = { content: __( 'In a village of La Mancha, the name of which I have no desire to call to mind, there lived not long since one of those gentlemen that keep a lance in the lance-rack, an old buckler, a lean hack, and a greyhound for coursing.' ), - style: { - typography: { - fontSize: 28, - }, - }, - dropCap: true, }, }, __experimentalLabel( attributes, { context } ) { diff --git a/packages/block-library/src/post-author/edit.js b/packages/block-library/src/post-author/edit.js index 1fc13ea42add9f..b0a2fb2883f79a 100644 --- a/packages/block-library/src/post-author/edit.js +++ b/packages/block-library/src/post-author/edit.js @@ -62,7 +62,7 @@ function PostAuthorEdit( { attributes; const avatarSizes = []; const authorName = authorDetails?.name || __( 'Post Author' ); - if ( authorDetails ) { + if ( authorDetails?.avatar_urls ) { Object.keys( authorDetails.avatar_urls ).forEach( ( size ) => { avatarSizes.push( { value: size, @@ -103,6 +103,7 @@ function PostAuthorEdit( { authorOptions.length && ( ( showCombobox && (
      - { showAvatar && authorDetails && ( + { showAvatar && authorDetails?.avatar_urls && (
      diff --git a/packages/block-library/src/post-template/block.json b/packages/block-library/src/post-template/block.json index 380b6d55f71fad..bc9910b47d1dc5 100644 --- a/packages/block-library/src/post-template/block.json +++ b/packages/block-library/src/post-template/block.json @@ -22,6 +22,14 @@ "__experimentalLayout": { "allowEditing": false }, + "color": { + "gradients": true, + "link": true, + "__experimentalDefaultControls": { + "background": true, + "text": true + } + }, "typography": { "fontSize": true, "lineHeight": true, diff --git a/packages/block-library/src/preformatted/transforms.js b/packages/block-library/src/preformatted/transforms.js index ef5f3324474091..068a08b8bcfc7a 100644 --- a/packages/block-library/src/preformatted/transforms.js +++ b/packages/block-library/src/preformatted/transforms.js @@ -34,7 +34,10 @@ const transforms = { type: 'block', blocks: [ 'core/paragraph' ], transform: ( attributes ) => - createBlock( 'core/paragraph', attributes ), + createBlock( 'core/paragraph', { + ...attributes, + content: attributes.content.replace( /\n/g, '
      ' ), + } ), }, { type: 'block', diff --git a/packages/block-library/src/query/block.json b/packages/block-library/src/query/block.json index cd09e22ee57f75..1974761962ec91 100644 --- a/packages/block-library/src/query/block.json +++ b/packages/block-library/src/query/block.json @@ -50,14 +50,6 @@ "supports": { "align": [ "wide", "full" ], "html": false, - "color": { - "gradients": true, - "link": true, - "__experimentalDefaultControls": { - "background": true, - "text": true - } - }, "__experimentalLayout": true }, "editorStyle": "wp-block-query-editor" diff --git a/packages/block-library/src/query/deprecated.js b/packages/block-library/src/query/deprecated.js index 42ac3dc4f1c49d..b23455d50489b5 100644 --- a/packages/block-library/src/query/deprecated.js +++ b/packages/block-library/src/query/deprecated.js @@ -1,12 +1,18 @@ /** * WordPress dependencies */ +import { createBlock } from '@wordpress/blocks'; import { InnerBlocks, useInnerBlocksProps, useBlockProps, } from '@wordpress/block-editor'; +/** + * Internal dependencies + */ +import cleanEmptyObject from '../utils/clean-empty-object'; + const migrateToTaxQuery = ( attributes ) => { const { query } = attributes; const { categoryIds, tagIds, ...newQuery } = query; @@ -25,106 +31,271 @@ const migrateToTaxQuery = ( attributes ) => { }; }; -const deprecated = [ - // Version with `categoryIds and tagIds`. - { - attributes: { - queryId: { - type: 'number', - }, - query: { - type: 'object', - default: { - perPage: null, - pages: 0, - offset: 0, - postType: 'post', - categoryIds: [], - tagIds: [], - order: 'desc', - orderBy: 'date', - author: '', - search: '', - exclude: [], - sticky: '', - inherit: true, - }, - }, - tagName: { - type: 'string', - default: 'div', +const migrateColors = ( attributes, innerBlocks ) => { + // Remove color style attributes from the Query block. + const { style, backgroundColor, gradient, textColor, ...newAttributes } = + attributes; + + const hasColorStyles = + backgroundColor || + gradient || + textColor || + style?.color || + style?.elements?.link; + + // If the query block doesn't currently have any color styles, + // nothing needs migrating. + if ( ! hasColorStyles ) { + return [ attributes, innerBlocks ]; + } + + // Clean color values from style attribute object. + if ( style ) { + newAttributes.style = cleanEmptyObject( { + ...style, + color: undefined, + elements: { + ...style.elements, + link: undefined, }, - displayLayout: { - type: 'object', - default: { - type: 'list', - }, + } ); + } + + // If the inner blocks are already wrapped in a single group + // block, add the color support styles to that group block. + if ( hasSingleInnerGroupBlock( innerBlocks ) ) { + const groupBlock = innerBlocks[ 0 ]; + + // Create new styles for the group block. + const hasStyles = + style?.color || + style?.elements?.link || + groupBlock.attributes.style; + + const newStyles = hasStyles + ? cleanEmptyObject( { + ...groupBlock.attributes.style, + color: style?.color, + elements: style?.elements?.link + ? { link: style?.elements?.link } + : undefined, + } ) + : undefined; + + // Create a new Group block from the original. + const updatedGroupBlock = createBlock( + 'core/group', + { + ...groupBlock.attributes, + backgroundColor, + gradient, + textColor, + style: newStyles, }, + groupBlock.innerBlocks + ); + + return [ newAttributes, [ updatedGroupBlock ] ]; + } + + // When we don't have a single wrapping group block for the inner + // blocks, wrap the current inner blocks in a group applying the + // color styles to that. + const newGroupBlock = createBlock( + 'core/group', + { + backgroundColor, + gradient, + textColor, + style: cleanEmptyObject( { + color: style?.color, + elements: style?.elements?.link + ? { link: style?.elements?.link } + : undefined, + } ), + }, + innerBlocks + ); + + return [ newAttributes, [ newGroupBlock ] ]; +}; + +const hasSingleInnerGroupBlock = ( innerBlocks = [] ) => + innerBlocks.length === 1 && innerBlocks[ 0 ].name === 'core/group'; + +// Version with NO wrapper `div` element. +const v1 = { + attributes: { + queryId: { + type: 'number', }, - supports: { - align: [ 'wide', 'full' ], - html: false, - color: { - gradients: true, - link: true, + query: { + type: 'object', + default: { + perPage: null, + pages: 0, + offset: 0, + postType: 'post', + categoryIds: [], + tagIds: [], + order: 'desc', + orderBy: 'date', + author: '', + search: '', + exclude: [], + sticky: '', + inherit: true, }, - __experimentalLayout: true, }, - isEligible: ( { query: { categoryIds, tagIds } = {} } ) => - categoryIds || tagIds, - migrate: migrateToTaxQuery, - save( { attributes: { tagName: Tag = 'div' } } ) { - const blockProps = useBlockProps.save(); - const innerBlocksProps = useInnerBlocksProps.save( blockProps ); - return ; + layout: { + type: 'object', + default: { + type: 'list', + }, }, }, - // Version with NO wrapper `div` element. - { - attributes: { - queryId: { - type: 'number', + supports: { + html: false, + }, + migrate( attributes ) { + const withTaxQuery = migrateToTaxQuery( attributes ); + const { layout, ...restWithTaxQuery } = withTaxQuery; + return { + ...restWithTaxQuery, + displayLayout: withTaxQuery.layout, + }; + }, + save() { + return ; + }, +}; + +// Version with `categoryIds and tagIds`. +const v2 = { + attributes: { + queryId: { + type: 'number', + }, + query: { + type: 'object', + default: { + perPage: null, + pages: 0, + offset: 0, + postType: 'post', + categoryIds: [], + tagIds: [], + order: 'desc', + orderBy: 'date', + author: '', + search: '', + exclude: [], + sticky: '', + inherit: true, }, - query: { - type: 'object', - default: { - perPage: null, - pages: 0, - offset: 0, - postType: 'post', - categoryIds: [], - tagIds: [], - order: 'desc', - orderBy: 'date', - author: '', - search: '', - exclude: [], - sticky: '', - inherit: true, - }, + }, + tagName: { + type: 'string', + default: 'div', + }, + displayLayout: { + type: 'object', + default: { + type: 'list', }, - layout: { - type: 'object', - default: { - type: 'list', - }, + }, + }, + supports: { + align: [ 'wide', 'full' ], + html: false, + color: { + gradients: true, + link: true, + }, + __experimentalLayout: true, + }, + isEligible: ( { query: { categoryIds, tagIds } = {} } ) => + categoryIds || tagIds, + migrate( attributes, innerBlocks ) { + const withTaxQuery = migrateToTaxQuery( attributes ); + return migrateColors( withTaxQuery, innerBlocks ); + }, + save( { attributes: { tagName: Tag = 'div' } } ) { + const blockProps = useBlockProps.save(); + const innerBlocksProps = useInnerBlocksProps.save( blockProps ); + return ; + }, +}; + +// Version with color support prior to moving it to the PostTemplate block. +const v3 = { + attributes: { + queryId: { + type: 'number', + }, + query: { + type: 'object', + default: { + perPage: null, + pages: 0, + offset: 0, + postType: 'post', + order: 'desc', + orderBy: 'date', + author: '', + search: '', + exclude: [], + sticky: '', + inherit: true, + taxQuery: null, + parents: [], }, }, - supports: { - html: false, + tagName: { + type: 'string', + default: 'div', + }, + displayLayout: { + type: 'object', + default: { + type: 'list', + }, }, - migrate( attributes ) { - const withTaxQuery = migrateToTaxQuery( attributes ); - const { layout, ...restWithTaxQuery } = withTaxQuery; - return { - ...restWithTaxQuery, - displayLayout: withTaxQuery.layout, - }; + namespace: { + type: 'string', }, - save() { - return ; + }, + supports: { + align: [ 'wide', 'full' ], + html: false, + color: { + gradients: true, + link: true, + __experimentalDefaultControls: { + background: true, + text: true, + }, }, + __experimentalLayout: true, + }, + isEligible( attributes ) { + const { style, backgroundColor, gradient, textColor } = attributes; + return ( + backgroundColor || + gradient || + textColor || + style?.color || + style?.elements?.link + ); }, -]; + migrate: migrateColors, + save( { attributes: { tagName: Tag = 'div' } } ) { + const blockProps = useBlockProps.save(); + const innerBlocksProps = useInnerBlocksProps.save( blockProps ); + return ; + }, +}; + +const deprecated = [ v3, v2, v1 ]; export default deprecated; 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 ffe19060682d3f..13cc70e9dc5428 100644 --- a/packages/block-library/src/query/edit/inspector-controls/index.js +++ b/packages/block-library/src/query/edit/inspector-controls/index.js @@ -141,6 +141,7 @@ export default function QueryInspectorControls( { { showColumnsControl && ( <> diff --git a/packages/block-library/src/query/edit/query-placeholder.js b/packages/block-library/src/query/edit/query-placeholder.js index 8e1debe5341a61..fa1ef818e2f1f7 100644 --- a/packages/block-library/src/query/edit/query-placeholder.js +++ b/packages/block-library/src/query/edit/query-placeholder.js @@ -104,20 +104,8 @@ function QueryVariationPicker( { icon, label, } ) { - const { defaultVariation, scopeVariations } = useSelect( - ( select ) => { - const { - getBlockVariations, - getBlockType, - getDefaultBlockVariation, - } = select( blocksStore ); - - return { - blockType: getBlockType( name ), - defaultVariation: getDefaultBlockVariation( name, 'block' ), - scopeVariations: getBlockVariations( name, 'block' ), - }; - }, + const variations = useSelect( + ( select ) => select( blocksStore ).getBlockVariations( name, 'block' ), [ name ] ); const { replaceInnerBlocks } = useDispatch( blockEditorStore ); @@ -127,8 +115,8 @@ function QueryVariationPicker( { <__experimentalBlockVariationPicker icon={ icon } label={ label } - variations={ scopeVariations } - onSelect={ ( nextVariation = defaultVariation ) => { + variations={ variations } + onSelect={ ( nextVariation ) => { if ( nextVariation.attributes ) { setAttributes( { ...nextVariation.attributes, diff --git a/packages/block-library/src/rss/edit.js b/packages/block-library/src/rss/edit.js index f72bf1c77e6741..f7689b8c38ca1c 100644 --- a/packages/block-library/src/rss/edit.js +++ b/packages/block-library/src/rss/edit.js @@ -111,6 +111,7 @@ export default function RSSEdit( { attributes, setAttributes } ) { @@ -137,6 +138,7 @@ export default function RSSEdit( { attributes, setAttributes } ) { /> { displayExcerpt && ( @@ -149,6 +151,7 @@ export default function RSSEdit( { attributes, setAttributes } ) { ) } { blockLayout === 'grid' && ( diff --git a/packages/block-library/src/search/edit.js b/packages/block-library/src/search/edit.js index a3db69287f8f73..78ff685ff01fe1 100644 --- a/packages/block-library/src/search/edit.js +++ b/packages/block-library/src/search/edit.js @@ -114,10 +114,10 @@ export default function SearchEdit( { } const colorProps = useColorProps( attributes ); - const fluidTypographyEnabled = useSetting( 'typography.fluid' ); + const fluidTypographySettings = useSetting( 'typography.fluid' ); const typographyProps = useTypographyProps( attributes, - fluidTypographyEnabled + fluidTypographySettings ); const unitControlInstanceId = useInstanceId( UnitControl ); const unitControlInputId = `wp-block-search__width-${ unitControlInstanceId }`; diff --git a/packages/block-library/src/search/edit.native.js b/packages/block-library/src/search/edit.native.js index 8b315bb21f4a9f..c5f5fb4cf23547 100644 --- a/packages/block-library/src/search/edit.native.js +++ b/packages/block-library/src/search/edit.native.js @@ -42,6 +42,36 @@ const BUTTON_OPTIONS = [ { value: 'no-button', label: __( 'No button' ) }, ]; +function useIsScreenReaderEnabled() { + const [ isScreenReaderEnabled, setIsScreenReaderEnabled ] = + useState( false ); + + useEffect( () => { + let mounted = true; + + const changeListener = AccessibilityInfo.addEventListener( + 'screenReaderChanged', + ( enabled ) => setIsScreenReaderEnabled( enabled ) + ); + + AccessibilityInfo.isScreenReaderEnabled().then( + ( screenReaderEnabled ) => { + if ( mounted && screenReaderEnabled ) { + setIsScreenReaderEnabled( screenReaderEnabled ); + } + } + ); + + return () => { + mounted = false; + + changeListener.remove(); + }; + }, [] ); + + return isScreenReaderEnabled; +} + export default function SearchEdit( { onFocus, isSelected, @@ -57,8 +87,7 @@ export default function SearchEdit( { useState( false ); const [ isLongButton, setIsLongButton ] = useState( false ); const [ buttonWidth, setButtonWidth ] = useState( MIN_BUTTON_WIDTH ); - const [ isScreenReaderEnabled, setIsScreenReaderEnabled ] = - useState( false ); + const isScreenReaderEnabled = useIsScreenReaderEnabled(); const textInputRef = useRef( null ); @@ -71,31 +100,6 @@ export default function SearchEdit( { buttonText, } = attributes; - /* - * Check if screenreader is enabled and save to state. This is important for - * properly creating accessibilityLabel text. - */ - useEffect( () => { - const a11yInfoChangeSubscription = AccessibilityInfo.addEventListener( - 'screenReaderChanged', - handleScreenReaderToggled - ); - - AccessibilityInfo.isScreenReaderEnabled().then( - ( screenReaderEnabled ) => { - setIsScreenReaderEnabled( screenReaderEnabled ); - } - ); - - return () => { - a11yInfoChangeSubscription.remove(); - }; - }, [] ); - - const handleScreenReaderToggled = ( screenReaderEnabled ) => { - setIsScreenReaderEnabled( screenReaderEnabled ); - }; - /* * Called when the value of isSelected changes. Blurs the PlainText component * used by the placeholder when this block loses focus. diff --git a/packages/block-library/src/search/index.js b/packages/block-library/src/search/index.js index 441336316415b9..85770a23268cba 100644 --- a/packages/block-library/src/search/index.js +++ b/packages/block-library/src/search/index.js @@ -1,6 +1,7 @@ /** * WordPress dependencies */ +import { __ } from '@wordpress/i18n'; import { search as icon } from '@wordpress/icons'; /** @@ -17,7 +18,10 @@ export { metadata, name }; export const settings = { icon, - example: {}, + example: { + attributes: { buttonText: __( 'Search' ), label: __( 'Search' ) }, + viewportWidth: 400, + }, variations, edit, }; diff --git a/packages/block-library/src/separator/test/__snapshots__/edit.native.js.snap b/packages/block-library/src/separator/test/__snapshots__/edit.native.js.snap new file mode 100644 index 00000000000000..5ab1ac00a0481f --- /dev/null +++ b/packages/block-library/src/separator/test/__snapshots__/edit.native.js.snap @@ -0,0 +1,7 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Separator block inserts block 1`] = ` +" +
      +" +`; diff --git a/packages/block-library/src/separator/test/edit.native.js b/packages/block-library/src/separator/test/edit.native.js new file mode 100644 index 00000000000000..0346cb3b2e6bfc --- /dev/null +++ b/packages/block-library/src/separator/test/edit.native.js @@ -0,0 +1,41 @@ +/** + * External dependencies + */ +import { + addBlock, + getEditorHtml, + initializeEditor, + getBlock, +} from 'test/helpers'; + +/** + * WordPress dependencies + */ +import { getBlockTypes, unregisterBlockType } from '@wordpress/blocks'; +import { registerCoreBlocks } from '@wordpress/block-library'; + +beforeAll( () => { + // Register all core blocks + registerCoreBlocks(); +} ); + +afterAll( () => { + // Clean up registered blocks + getBlockTypes().forEach( ( block ) => { + unregisterBlockType( block.name ); + } ); +} ); + +describe( 'Separator block', () => { + it( 'inserts block', async () => { + const screen = await initializeEditor(); + + // Add block + await addBlock( screen, 'Separator' ); + + // Get block + const separatorBlock = await getBlock( screen, 'Separator' ); + expect( separatorBlock ).toBeVisible(); + expect( getEditorHtml() ).toMatchSnapshot(); + } ); +} ); diff --git a/packages/block-library/src/shortcode/test/edit.native.js b/packages/block-library/src/shortcode/test/edit.native.js index 46c7dbddef2c97..372956a2242a0d 100644 --- a/packages/block-library/src/shortcode/test/edit.native.js +++ b/packages/block-library/src/shortcode/test/edit.native.js @@ -1,12 +1,7 @@ /** * External dependencies */ -import { - getEditorHtml, - initializeEditor, - fireEvent, - waitFor, -} from 'test/helpers'; +import { getEditorHtml, initializeEditor, fireEvent } from 'test/helpers'; /** * WordPress dependencies @@ -28,12 +23,11 @@ afterAll( () => { describe( 'Shortcode block', () => { it( 'inserts block', async () => { - const { getByLabelText, getByTestId, getByText } = - await initializeEditor(); + const screen = await initializeEditor(); - fireEvent.press( getByLabelText( 'Add block' ) ); + fireEvent.press( screen.getByLabelText( 'Add block' ) ); - const blockList = getByTestId( 'InserterUI-Blocks' ); + const blockList = screen.getByTestId( 'InserterUI-Blocks' ); // onScroll event used to force the FlatList to render all items fireEvent.scroll( blockList, { nativeEvent: { @@ -43,22 +37,25 @@ describe( 'Shortcode block', () => { }, } ); - fireEvent.press( await waitFor( () => getByText( 'Shortcode' ) ) ); + fireEvent.press( await screen.findByText( 'Shortcode' ) ); - expect( getByLabelText( /Shortcode Block\. Row 1/ ) ).toBeVisible(); + const [ shortcodeBlock ] = screen.getAllByLabelText( + /Shortcode Block\. Row 1/ + ); + expect( shortcodeBlock ).toBeVisible(); expect( getEditorHtml() ).toMatchSnapshot(); } ); it( 'edits content', async () => { - const { getByLabelText, getByPlaceholderText } = await initializeEditor( - { - initialHtml: '', - } + const screen = await initializeEditor( { + initialHtml: '', + } ); + const [ shortcodeBlock ] = screen.getAllByLabelText( + /Shortcode Block\. Row 1/ ); - const shortcodeBlock = getByLabelText( /Shortcode Block\. Row 1/ ); fireEvent.press( shortcodeBlock ); - const textField = getByPlaceholderText( 'Add a shortcode…' ); + const textField = screen.getByPlaceholderText( 'Add a shortcode…' ); fireEvent( textField, 'focus' ); fireEvent( textField, 'onChange', { nativeEvent: { diff --git a/packages/block-library/src/site-logo/edit.js b/packages/block-library/src/site-logo/edit.js index 89b9e3f6eba01e..d84b5e6fbd6837 100644 --- a/packages/block-library/src/site-logo/edit.js +++ b/packages/block-library/src/site-logo/edit.js @@ -2,7 +2,6 @@ * External dependencies */ import classnames from 'classnames'; -import { pick } from 'lodash'; /** * WordPress dependencies @@ -87,7 +86,11 @@ const SiteLogo = ( { ); return { title: siteEntities?.name, - ...pick( getSettings(), [ 'imageEditing', 'maxWidth' ] ), + ...Object.fromEntries( + Object.entries( getSettings() ).filter( ( [ key ] ) => + [ 'imageEditing', 'maxWidth' ].includes( key ) + ) + ), }; }, [] ); @@ -120,9 +123,10 @@ const SiteLogo = ( { src={ logoUrl } alt={ alt } onLoad={ ( event ) => { - setNaturalSize( - pick( event.target, [ 'naturalWidth', 'naturalHeight' ] ) - ); + setNaturalSize( { + naturalWidth: event.target.naturalWidth, + naturalHeight: event.target.naturalHeight, + } ); } } /> ); @@ -291,6 +295,7 @@ const SiteLogo = ( { setAttributes( { width: newWidth } ) @@ -512,6 +517,9 @@ export default function LogoEdit( { className={ placeholderClassName } preview={ logoImage } withIllustration={ true } + style={ { + width, + } } > { content } diff --git a/packages/block-library/src/site-logo/index.js b/packages/block-library/src/site-logo/index.js index 68d8eb6f8b9300..fc10df08e17dee 100644 --- a/packages/block-library/src/site-logo/index.js +++ b/packages/block-library/src/site-logo/index.js @@ -16,6 +16,7 @@ export { metadata, name }; export const settings = { icon, + example: {}, edit, transforms, }; diff --git a/packages/block-library/src/site-title/index.js b/packages/block-library/src/site-title/index.js index 01fe15598d6f1b..87934888ce4380 100644 --- a/packages/block-library/src/site-title/index.js +++ b/packages/block-library/src/site-title/index.js @@ -17,6 +17,7 @@ export { metadata, name }; export const settings = { icon, + example: {}, edit, transforms, deprecated, diff --git a/packages/block-library/src/social-link/social-list.js b/packages/block-library/src/social-link/social-list.js index 9ac527dccd0d39..38c1ef91f99384 100644 --- a/packages/block-library/src/social-link/social-list.js +++ b/packages/block-library/src/social-link/social-list.js @@ -1,8 +1,3 @@ -/** - * External dependencies - */ -import { find } from 'lodash'; - /** * WordPress dependencies */ @@ -22,7 +17,7 @@ import { ChainIcon } from './icons'; * @return {WPComponent} Icon component for social service. */ export const getIconBySite = ( name ) => { - const variation = find( variations, { name } ); + const variation = variations.find( ( v ) => v.name === name ); return variation ? variation.icon : ChainIcon; }; @@ -34,6 +29,6 @@ export const getIconBySite = ( name ) => { * @return {string} Display name for social service */ export const getNameBySite = ( name ) => { - const variation = find( variations, { name } ); + const variation = variations.find( ( v ) => v.name === name ); return variation ? variation.title : __( 'Social Icon' ); }; diff --git a/packages/block-library/src/social-link/test/index.native.js b/packages/block-library/src/social-link/test/index.native.js index 603776e939998f..4ad7611e72045d 100644 --- a/packages/block-library/src/social-link/test/index.native.js +++ b/packages/block-library/src/social-link/test/index.native.js @@ -42,16 +42,13 @@ describe( '', () => { 'social icons' ); fireEvent.press( - await waitFor( () => - subject.getByLabelText( 'Social Icons block' ) - ) + await subject.findByLabelText( 'Social Icons block' ) + ); + const [ socialIconsBlock ] = subject.getAllByLabelText( + /Social Icons Block. Row 1/ ); fireEvent( - await waitFor( () => - within( - subject.getByLabelText( /Social Icons Block. Row 1/ ) - ).getByTestId( 'block-list-wrapper' ) - ), + within( socialIconsBlock ).getByTestId( 'block-list-wrapper' ), 'layout', { nativeEvent: { layout: { width: 100 } } } ); @@ -99,16 +96,13 @@ describe( '', () => { 'social icons' ); fireEvent.press( - await waitFor( () => - subject.getByLabelText( 'Social Icons block' ) - ) + await subject.findByLabelText( 'Social Icons block' ) + ); + const [ socialIconsBlock ] = subject.getAllByLabelText( + /Social Icons Block. Row 1/ ); fireEvent( - await waitFor( () => - within( - subject.getByLabelText( /Social Icons Block. Row 1/ ) - ).getByTestId( 'block-list-wrapper' ) - ), + within( socialIconsBlock ).getByTestId( 'block-list-wrapper' ), 'layout', { nativeEvent: { layout: { width: 100 } } } ); diff --git a/packages/block-library/src/social-links/test/edit.native.js b/packages/block-library/src/social-links/test/edit.native.js index ade94321278e95..48fefaadaec8c0 100644 --- a/packages/block-library/src/social-links/test/edit.native.js +++ b/packages/block-library/src/social-links/test/edit.native.js @@ -104,7 +104,6 @@ describe( 'Social links block', () => { it( 'shows the social links bottom sheet when tapping on the inline appender', async () => { const screen = await initializeEditor(); - const { getByTestId, getByText } = screen; // Add block await addBlock( screen, 'Social Icons' ); @@ -131,7 +130,7 @@ describe( 'Social links block', () => { fireEvent.press( appenderButton ); // Find a social link in the inserter - const blockList = getByTestId( 'InserterUI-Blocks' ); + const blockList = screen.getByTestId( 'InserterUI-Blocks' ); // onScroll event used to force the FlatList to render all items fireEvent.scroll( blockList, { @@ -143,11 +142,13 @@ describe( 'Social links block', () => { } ); // Add the Amazon link - const amazonBlock = await waitFor( () => getByText( 'Amazon' ) ); + const amazonBlock = await screen.findByText( 'Amazon' ); expect( amazonBlock ).toBeVisible(); fireEvent.press( amazonBlock ); + await screen.findByTestId( 'navigation-screen-LinkSettingsScreen' ); + expect( getEditorHtml() ).toMatchSnapshot(); } ); diff --git a/packages/block-library/src/spacer/test/index.native.js b/packages/block-library/src/spacer/test/index.native.js index 7b6ed2ba3f371c..5d5229033d19ae 100644 --- a/packages/block-library/src/spacer/test/index.native.js +++ b/packages/block-library/src/spacer/test/index.native.js @@ -28,12 +28,11 @@ afterAll( () => { describe( 'Spacer block', () => { it( 'inserts block', async () => { - const { getByLabelText, getByTestId, getByText } = - await initializeEditor(); + const screen = await initializeEditor(); - fireEvent.press( getByLabelText( 'Add block' ) ); + fireEvent.press( screen.getByLabelText( 'Add block' ) ); - const blockList = getByTestId( 'InserterUI-Blocks' ); + const blockList = screen.getByTestId( 'InserterUI-Blocks' ); // onScroll event used to force the FlatList to render all items fireEvent.scroll( blockList, { nativeEvent: { @@ -43,9 +42,11 @@ describe( 'Spacer block', () => { }, } ); - fireEvent.press( await waitFor( () => getByText( 'Spacer' ) ) ); + fireEvent.press( screen.getByText( 'Spacer' ) ); - expect( getByLabelText( /Spacer Block\. Row 1/ ) ).toBeVisible(); + const [ spacerBlock ] = + screen.getAllByLabelText( /Spacer Block\. Row 1/ ); + expect( spacerBlock ).toBeVisible(); expect( getEditorHtml() ).toMatchSnapshot(); } ); @@ -53,24 +54,24 @@ describe( 'Spacer block', () => { const initialHtml = ` `; - const { getByLabelText, getByDisplayValue, getByTestId, getByText } = - await initializeEditor( { - initialHtml, - } ); + const screen = await initializeEditor( { + initialHtml, + } ); // Select Spacer block - const spacerBlock = getByLabelText( /Spacer Block\. Row 1/ ); + const [ spacerBlock ] = + screen.getAllByLabelText( /Spacer Block\. Row 1/ ); fireEvent.press( spacerBlock ); // Open block settings - fireEvent.press( getByLabelText( 'Open Settings' ) ); + fireEvent.press( screen.getByLabelText( 'Open Settings' ) ); await waitFor( - () => getByTestId( 'block-settings-modal' ).props.isVisible + () => screen.getByTestId( 'block-settings-modal' ).props.isVisible ); // Update height attribute - fireEvent.press( getByText( '100' ) ); - const heightTextInput = getByDisplayValue( '100' ); + fireEvent.press( screen.getByText( '100' ) ); + const heightTextInput = screen.getByDisplayValue( '100' ); fireEvent.changeText( heightTextInput, '50' ); expect( getEditorHtml() ).toMatchSnapshot(); @@ -80,28 +81,28 @@ describe( 'Spacer block', () => { const initialHtml = ` `; - const { getByLabelText, getByDisplayValue, getByTestId, getByText } = - await initializeEditor( { - initialHtml, - } ); + const screen = await initializeEditor( { + initialHtml, + } ); // Select Spacer block - const spacerBlock = getByLabelText( /Spacer Block\. Row 1/ ); + const [ spacerBlock ] = + screen.getAllByLabelText( /Spacer Block\. Row 1/ ); fireEvent.press( spacerBlock ); // Open block settings - fireEvent.press( getByLabelText( 'Open Settings' ) ); + fireEvent.press( screen.getByLabelText( 'Open Settings' ) ); await waitFor( - () => getByTestId( 'block-settings-modal' ).props.isVisible + () => screen.getByTestId( 'block-settings-modal' ).props.isVisible ); // Set vh unit - fireEvent.press( getByText( 'px' ) ); - fireEvent.press( getByText( 'Viewport height (vh)' ) ); + fireEvent.press( screen.getByText( 'px' ) ); + fireEvent.press( screen.getByText( 'Viewport height (vh)' ) ); // Update height attribute - fireEvent.press( getByText( '100' ) ); - const heightTextInput = getByDisplayValue( '100' ); + fireEvent.press( screen.getByText( '100' ) ); + const heightTextInput = screen.getByDisplayValue( '100' ); fireEvent.changeText( heightTextInput, '25' ); expect( getEditorHtml() ).toMatchSnapshot(); @@ -111,23 +112,24 @@ describe( 'Spacer block', () => { const initialHtml = ` `; - const { getByLabelText, getByTestId } = await initializeEditor( { + const screen = await initializeEditor( { initialHtml, } ); // Select Spacer block - const spacerBlock = getByLabelText( /Spacer Block\. Row 1/ ); + const [ spacerBlock ] = + screen.getAllByLabelText( /Spacer Block\. Row 1/ ); fireEvent.press( spacerBlock ); // Open block settings - fireEvent.press( getByLabelText( 'Open Settings' ) ); + fireEvent.press( screen.getByLabelText( 'Open Settings' ) ); await waitFor( - () => getByTestId( 'block-settings-modal' ).props.isVisible + () => screen.getByTestId( 'block-settings-modal' ).props.isVisible ); // Increment height fireEvent( - getByLabelText( /Height\. Value is 100 Pixels \(px\)/ ), + screen.getByLabelText( /Height\. Value is 100 Pixels \(px\)/ ), 'accessibilityAction', { nativeEvent: { actionName: 'increment' }, @@ -141,23 +143,24 @@ describe( 'Spacer block', () => { const initialHtml = ` `; - const { getByLabelText, getByTestId } = await initializeEditor( { + const screen = await initializeEditor( { initialHtml, } ); // Select Spacer block - const spacerBlock = getByLabelText( /Spacer Block\. Row 1/ ); + const [ spacerBlock ] = + screen.getAllByLabelText( /Spacer Block\. Row 1/ ); fireEvent.press( spacerBlock ); // Open block settings - fireEvent.press( getByLabelText( 'Open Settings' ) ); + fireEvent.press( screen.getByLabelText( 'Open Settings' ) ); await waitFor( - () => getByTestId( 'block-settings-modal' ).props.isVisible + () => screen.getByTestId( 'block-settings-modal' ).props.isVisible ); // Increment height fireEvent( - getByLabelText( /Height\. Value is 100 Pixels \(px\)/ ), + screen.getByLabelText( /Height\. Value is 100 Pixels \(px\)/ ), 'accessibilityAction', { nativeEvent: { actionName: 'decrement' }, diff --git a/packages/block-library/src/table-of-contents/edit.js b/packages/block-library/src/table-of-contents/edit.js index 92f2d39dd98cf5..dab35fdd0ce3d9 100644 --- a/packages/block-library/src/table-of-contents/edit.js +++ b/packages/block-library/src/table-of-contents/edit.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { isEqual } from 'lodash'; +import fastDeepEqual from 'fast-deep-equal/es6'; /** * WordPress dependencies @@ -204,7 +204,7 @@ export default function TableOfContentsEdit( { } } - if ( isEqual( headings, _latestHeadings ) ) { + if ( fastDeepEqual( headings, _latestHeadings ) ) { return null; } return _latestHeadings; diff --git a/packages/block-library/src/table/block.json b/packages/block-library/src/table/block.json index 3de088e0879b79..5e21c1f07f8ba2 100644 --- a/packages/block-library/src/table/block.json +++ b/packages/block-library/src/table/block.json @@ -47,6 +47,11 @@ "type": "string", "source": "attribute", "attribute": "data-align" + }, + "colspan": { + "type": "string", + "source": "attribute", + "attribute": "colspan" } } } @@ -82,6 +87,11 @@ "type": "string", "source": "attribute", "attribute": "data-align" + }, + "colspan": { + "type": "string", + "source": "attribute", + "attribute": "colspan" } } } @@ -117,6 +127,11 @@ "type": "string", "source": "attribute", "attribute": "data-align" + }, + "colspan": { + "type": "string", + "source": "attribute", + "attribute": "colspan" } } } diff --git a/packages/block-library/src/table/edit.js b/packages/block-library/src/table/edit.js index abb6438cb56785..2ca9cb6c42a984 100644 --- a/packages/block-library/src/table/edit.js +++ b/packages/block-library/src/table/edit.js @@ -404,7 +404,7 @@ function TableEdit( { { cells.map( ( - { content, tag: CellTag, scope, align }, + { content, tag: CellTag, scope, align, colspan }, columnIndex ) => ( { diff --git a/packages/block-library/src/table/editor.scss b/packages/block-library/src/table/editor.scss index f1fbae335748c6..b019f871c06f62 100644 --- a/packages/block-library/src/table/editor.scss +++ b/packages/block-library/src/table/editor.scss @@ -41,10 +41,6 @@ border-style: double; } - figcaption { - @include caption-style-theme(); - } - // This is only required in the editor to overcome the fact the editor // rewrites border width styles into shorthand. table.has-individual-borders { diff --git a/packages/block-library/src/table/index.js b/packages/block-library/src/table/index.js index 5839abc4c19f25..dea09dd7c98298 100644 --- a/packages/block-library/src/table/index.js +++ b/packages/block-library/src/table/index.js @@ -91,6 +91,7 @@ export const settings = { }, ], }, + viewportWidth: 450, }, transforms, edit, diff --git a/packages/block-library/src/table/save.js b/packages/block-library/src/table/save.js index 4e0c4f1956cc19..d3393267b754df 100644 --- a/packages/block-library/src/table/save.js +++ b/packages/block-library/src/table/save.js @@ -43,7 +43,10 @@ export default function save( { attributes } ) { { rows.map( ( { cells }, rowIndex ) => ( { cells.map( - ( { content, tag, scope, align }, cellIndex ) => { + ( + { content, tag, scope, align, colspan }, + cellIndex + ) => { const cellClasses = classnames( { [ `has-text-align-${ align }` ]: align, } ); @@ -62,6 +65,7 @@ export default function save( { attributes } ) { scope={ tag === 'th' ? scope : undefined } + colSpan={ colspan } /> ); } diff --git a/packages/block-library/src/table/state.js b/packages/block-library/src/table/state.js index 68fee2d0bda9b5..a1aafc31459365 100644 --- a/packages/block-library/src/table/state.js +++ b/packages/block-library/src/table/state.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { get, mapValues, pick } from 'lodash'; +import { get, mapValues } from 'lodash'; const INHERITED_COLUMN_ATTRIBUTES = [ 'align' ]; @@ -78,7 +78,11 @@ export function updateSelectedCell( state, selection, updateCell ) { return state; } - const tableSections = pick( state, [ 'head', 'body', 'foot' ] ); + const tableSections = Object.fromEntries( + Object.entries( state ).filter( ( [ key ] ) => + [ 'head', 'body', 'foot' ].includes( key ) + ) + ); const { sectionName: selectionSectionName, rowIndex: selectionRowIndex } = selection; @@ -174,9 +178,12 @@ export function insertRow( state, { sectionName, rowIndex, columnCount } ) { [ 'cells', index ], {} ); - const inheritedAttributes = pick( - firstCellInColumn, - INHERITED_COLUMN_ATTRIBUTES + + const inheritedAttributes = Object.fromEntries( + Object.entries( firstCellInColumn ).filter( + ( [ key ] ) => + INHERITED_COLUMN_ATTRIBUTES.includes( key ) + ) ); return { @@ -220,7 +227,11 @@ export function deleteRow( state, { sectionName, rowIndex } ) { * @return {Object} New table state. */ export function insertColumn( state, { columnIndex } ) { - const tableSections = pick( state, [ 'head', 'body', 'foot' ] ); + const tableSections = Object.fromEntries( + Object.entries( state ).filter( ( [ key ] ) => + [ 'head', 'body', 'foot' ].includes( key ) + ) + ); return mapValues( tableSections, ( section, sectionName ) => { // Bail early if the table section is empty. @@ -259,7 +270,11 @@ export function insertColumn( state, { columnIndex } ) { * @return {Object} New table state. */ export function deleteColumn( state, { columnIndex } ) { - const tableSections = pick( state, [ 'head', 'body', 'foot' ] ); + const tableSections = Object.fromEntries( + Object.entries( state ).filter( ( [ key ] ) => + [ 'head', 'body', 'foot' ].includes( key ) + ) + ); return mapValues( tableSections, ( section ) => { // Bail early if the table section is empty. diff --git a/packages/block-library/src/table/transforms.js b/packages/block-library/src/table/transforms.js index 15ee7dff1178f4..0651c3bc64c415 100644 --- a/packages/block-library/src/table/transforms.js +++ b/packages/block-library/src/table/transforms.js @@ -5,11 +5,12 @@ const tableContentPasteSchema = ( { phrasingContentSchema } ) => ( { th: { allowEmpty: true, children: phrasingContentSchema, - attributes: [ 'scope' ], + attributes: [ 'scope', 'colspan' ], }, td: { allowEmpty: true, children: phrasingContentSchema, + attributes: [ 'colspan' ], }, }, }, diff --git a/packages/block-library/src/tag-cloud/edit.js b/packages/block-library/src/tag-cloud/edit.js index 8cb286630d8eb7..dd669a7e840b3f 100644 --- a/packages/block-library/src/tag-cloud/edit.js +++ b/packages/block-library/src/tag-cloud/edit.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { map, filter } from 'lodash'; +import { map } from 'lodash'; /** * WordPress dependencies @@ -69,7 +69,7 @@ function TagCloudEdit( { attributes, setAttributes, taxonomies } ) { disabled: true, }; const taxonomyOptions = map( - filter( taxonomies, 'show_cloud' ), + taxonomies?.filter( ( tax ) => !! tax.show_cloud ), ( item ) => { return { value: item.slug, @@ -124,6 +124,7 @@ function TagCloudEdit( { attributes, setAttributes, taxonomies } ) { } /> 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 d57d6b2f631807..ca42726b5655fa 100644 --- a/packages/block-library/src/template-part/edit/advanced-controls.js +++ b/packages/block-library/src/template-part/edit/advanced-controls.js @@ -7,12 +7,18 @@ import { sprintf, __ } from '@wordpress/i18n'; import { InspectorControls } from '@wordpress/block-editor'; import { useSelect } from '@wordpress/data'; +/** + * Internal dependencies + */ +import { TemplatePartImportControls } from './import-controls'; + export function TemplatePartAdvancedControls( { tagName, setAttributes, isEntityAvailable, templatePartId, defaultWrapper, + hasInnerBlocks, } ) { const [ area, setArea ] = useEntityProp( 'postType', @@ -87,6 +93,12 @@ export function TemplatePartAdvancedControls( { value={ tagName || '' } onChange={ ( value ) => setAttributes( { tagName: value } ) } /> + { ! hasInnerBlocks && ( + + ) } ); } diff --git a/packages/block-library/src/template-part/edit/import-controls.js b/packages/block-library/src/template-part/edit/import-controls.js new file mode 100644 index 00000000000000..1512cd936f02cb --- /dev/null +++ b/packages/block-library/src/template-part/edit/import-controls.js @@ -0,0 +1,180 @@ +/** + * WordPress dependencies + */ +import { __, sprintf } from '@wordpress/i18n'; +import { useMemo, useState } from '@wordpress/element'; +import { useDispatch, useSelect, useRegistry } from '@wordpress/data'; +import { + Button, + FlexBlock, + FlexItem, + SelectControl, + __experimentalHStack as HStack, + __experimentalSpacer as Spacer, +} from '@wordpress/components'; +import { + switchToBlockType, + getPossibleBlockTransformations, +} from '@wordpress/blocks'; +import { store as coreStore } from '@wordpress/core-data'; +import { store as noticesStore } from '@wordpress/notices'; + +/** + * Internal dependencies + */ +import { useCreateTemplatePartFromBlocks } from './utils/hooks'; +import { transformWidgetToBlock } from './utils/transformers'; + +export function TemplatePartImportControls( { area, setAttributes } ) { + const [ selectedSidebar, setSelectedSidebar ] = useState( '' ); + const [ isBusy, setIsBusy ] = useState( false ); + + const registry = useRegistry(); + const sidebars = useSelect( ( select ) => { + return select( coreStore ).getSidebars( { + per_page: -1, + _fields: 'id,name,description,status,widgets', + } ); + }, [] ); + const { createErrorNotice } = useDispatch( noticesStore ); + + const createFromBlocks = useCreateTemplatePartFromBlocks( + area, + setAttributes + ); + + const options = useMemo( () => { + const sidebarOptions = ( sidebars ?? [] ) + .filter( + ( widgetArea ) => + widgetArea.id !== 'wp_inactive_widgets' && + widgetArea.widgets.length > 0 + ) + .map( ( widgetArea ) => { + return { + value: widgetArea.id, + label: widgetArea.name, + }; + } ); + + if ( ! sidebarOptions.length ) { + return []; + } + + return [ + { value: '', label: __( 'Select widget area' ) }, + ...sidebarOptions, + ]; + }, [ sidebars ] ); + + async function createFromWidgets( event ) { + event.preventDefault(); + + if ( isBusy || ! selectedSidebar ) { + return; + } + + setIsBusy( true ); + + const sidebar = options.find( + ( { value } ) => value === selectedSidebar + ); + const { getWidgets } = registry.resolveSelect( coreStore ); + + // The widgets API always returns a successful response. + const widgets = await getWidgets( { + sidebar: sidebar.value, + _embed: 'about', + } ); + + const skippedWidgets = new Set(); + const blocks = widgets.flatMap( ( widget ) => { + const block = transformWidgetToBlock( widget ); + + if ( block.name !== 'core/legacy-widget' ) { + return block; + } + + const transforms = getPossibleBlockTransformations( [ + block, + ] ).filter( ( item ) => { + // The block without any transformations can't be a wildcard. + if ( ! item.transforms ) { + return true; + } + + const hasWildCardFrom = item.transforms?.from?.find( + ( from ) => from.blocks && from.blocks.includes( '*' ) + ); + const hasWildCardTo = item.transforms?.to?.find( + ( to ) => to.blocks && to.blocks.includes( '*' ) + ); + + return ! hasWildCardFrom && ! hasWildCardTo; + } ); + + // Skip the block if we have no matching transformations. + if ( ! transforms.length ) { + skippedWidgets.add( widget.id_base ); + return []; + } + + // Try transforming the Legacy Widget into a first matching block. + return switchToBlockType( block, transforms[ 0 ].name ); + } ); + + await createFromBlocks( + blocks, + /* translators: %s: name of the widget area */ + sprintf( __( 'Widget area: %s' ), sidebar.label ) + ); + + if ( skippedWidgets.size ) { + createErrorNotice( + sprintf( + /* translators: %s: the list of widgets */ + __( 'Unable to import the following widgets: %s.' ), + Array.from( skippedWidgets ).join( ', ' ) + ), + { + type: 'snackbar', + } + ); + } + + setIsBusy( false ); + } + + return ( + + + + setSelectedSidebar( value ) } + disabled={ ! options.length } + __next36pxDefaultSize + __nextHasNoMarginBottom + /> + + + + + + + ); +} diff --git a/packages/block-library/src/template-part/edit/index.js b/packages/block-library/src/template-part/edit/index.js index 83b1a8d450b6ca..502bd3d00feef6 100644 --- a/packages/block-library/src/template-part/edit/index.js +++ b/packages/block-library/src/template-part/edit/index.js @@ -141,6 +141,7 @@ export default function TemplatePartEdit( { isEntityAvailable={ isEntityAvailable } templatePartId={ templatePartId } defaultWrapper={ areaObject.tagName } + hasInnerBlocks={ innerBlocks.length > 0 } /> { isPlaceholder && ( diff --git a/packages/block-library/src/template-part/edit/selection-modal.js b/packages/block-library/src/template-part/edit/selection-modal.js index f979df799c6be6..9a83914ddb4241 100644 --- a/packages/block-library/src/template-part/edit/selection-modal.js +++ b/packages/block-library/src/template-part/edit/selection-modal.js @@ -1,7 +1,7 @@ /** * WordPress dependencies */ -import { useCallback, useMemo, useState } from '@wordpress/element'; +import { useMemo, useState } from '@wordpress/element'; import { __, sprintf } from '@wordpress/i18n'; import { store as noticesStore } from '@wordpress/notices'; import { useDispatch } from '@wordpress/data'; @@ -57,7 +57,7 @@ export default function TemplatePartSelectionModal( { const { createSuccessNotice } = useDispatch( noticesStore ); - const onTemplatePartSelect = useCallback( ( templatePart ) => { + const onTemplatePartSelect = ( templatePart ) => { setAttributes( { slug: templatePart.slug, theme: templatePart.theme, @@ -74,7 +74,7 @@ export default function TemplatePartSelectionModal( { } ); onClose(); - }, [] ); + }; const createFromBlocks = useCreateTemplatePartFromBlocks( area, @@ -88,6 +88,7 @@ export default function TemplatePartSelectionModal( {
      definedArea.area === area + ); + const defaultArea = definedAreas.find( + ( definedArea ) => definedArea.area === 'uncategorized' + ); return { icon: selectedArea?.icon || defaultArea?.icon, diff --git a/packages/block-library/src/template-part/edit/utils/transformers.js b/packages/block-library/src/template-part/edit/utils/transformers.js new file mode 100644 index 00000000000000..fdef84d785b909 --- /dev/null +++ b/packages/block-library/src/template-part/edit/utils/transformers.js @@ -0,0 +1,37 @@ +/** + * WordPress dependencies + */ +import { createBlock, parse } from '@wordpress/blocks'; + +/** + * Converts a widget entity record into a block. + * + * @param {Object} widget The widget entity record. + * @return {Object} a block (converted from the entity record). + */ +export function transformWidgetToBlock( widget ) { + if ( widget.id_base === 'block' ) { + const parsedBlocks = parse( widget.instance.raw.content, { + __unstableSkipAutop: true, + } ); + if ( ! parsedBlocks.length ) { + return createBlock( 'core/paragraph', {}, [] ); + } + + return parsedBlocks[ 0 ]; + } + + let attributes; + if ( widget._embedded.about[ 0 ].is_multi ) { + attributes = { + idBase: widget.id_base, + instance: widget.instance, + }; + } else { + attributes = { + id: widget.id, + }; + } + + return createBlock( 'core/legacy-widget', attributes, [] ); +} diff --git a/packages/block-library/src/template-part/editor.scss b/packages/block-library/src/template-part/editor.scss index c17d37a6078e2d..a847ecec01f94d 100644 --- a/packages/block-library/src/template-part/editor.scss +++ b/packages/block-library/src/template-part/editor.scss @@ -5,8 +5,8 @@ // and height are used instead of max-(width/height). .components-modal__frame { @include break-small() { - width: calc(100% - #{ $grid-unit-20 * 2 }); - height: calc(100% - #{ $header-height * 2 }); + width: calc(100% - #{$grid-unit-20 * 2}); + height: calc(100% - #{$header-height * 2}); } @include break-medium() { width: $break-medium - $grid-unit-20 * 2; @@ -24,3 +24,21 @@ padding: $grid-unit-20 0; z-index: z-index(".block-library-template-part__selection-search"); } + +.is-outline-mode .block-editor-block-list__block:not(.remove-outline).wp-block-template-part, +.is-outline-mode .block-editor-block-list__block:not(.remove-outline).is-reusable { + &.is-highlighted, + &.is-selected { + box-shadow: inset 0 0 0 var(--wp-admin-border-width-focus) var(--wp-block-synced-color); + } + + &.block-editor-block-list__block:not([contenteditable]):focus { + &::after { + box-shadow: 0 0 0 var(--wp-admin-border-width-focus) var(--wp-block-synced-color); + // Show a light color for dark themes. + .is-dark-theme & { + box-shadow: 0 0 0 var(--wp-admin-border-width-focus) $dark-theme-focus; + } + } + } +} diff --git a/packages/block-library/src/template-part/index.php b/packages/block-library/src/template-part/index.php index bef49341d7bb56..f320362c32f1d4 100644 --- a/packages/block-library/src/template-part/index.php +++ b/packages/block-library/src/template-part/index.php @@ -105,8 +105,7 @@ function render_block_core_template_part( $attributes ) { // WP_DEBUG_DISPLAY must only be honored when WP_DEBUG. This precedent // is set in `wp_debug_mode()`. - $is_debug = defined( 'WP_DEBUG' ) && WP_DEBUG && - defined( 'WP_DEBUG_DISPLAY' ) && WP_DEBUG_DISPLAY; + $is_debug = WP_DEBUG && WP_DEBUG_DISPLAY; if ( is_null( $content ) && $is_debug ) { if ( ! isset( $attributes['slug'] ) ) { diff --git a/packages/block-library/src/text-columns/edit.js b/packages/block-library/src/text-columns/edit.js index 180c786226c803..993e5da0c80e39 100644 --- a/packages/block-library/src/text-columns/edit.js +++ b/packages/block-library/src/text-columns/edit.js @@ -39,6 +39,7 @@ export default function TextColumnsEdit( { attributes, setAttributes } ) { diff --git a/packages/block-library/src/utils/transformation-categories.native.js b/packages/block-library/src/utils/transformation-categories.native.js index 3ddf60d2a4c227..e001f9b67c785d 100644 --- a/packages/block-library/src/utils/transformation-categories.native.js +++ b/packages/block-library/src/utils/transformation-categories.native.js @@ -3,6 +3,7 @@ const transformationCategories = { 'core/paragraph', 'core/heading', 'core/list', + 'core/list-item', 'core/quote', 'core/pullquote', 'core/preformatted', diff --git a/packages/block-library/src/verse/test/__snapshots__/edit.native.js.snap b/packages/block-library/src/verse/test/__snapshots__/edit.native.js.snap new file mode 100644 index 00000000000000..b28fe2ddf67a53 --- /dev/null +++ b/packages/block-library/src/verse/test/__snapshots__/edit.native.js.snap @@ -0,0 +1,13 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Verse block inserts block 1`] = ` +" +
      
      +"
      +`;
      +
      +exports[`Verse block renders block text set as initial content 1`] = `
      +"
      +
      Sample text
      +" +`; diff --git a/packages/block-library/src/verse/test/edit.native.js b/packages/block-library/src/verse/test/edit.native.js index 99cc10bc44abec..96bc431be4bbd7 100644 --- a/packages/block-library/src/verse/test/edit.native.js +++ b/packages/block-library/src/verse/test/edit.native.js @@ -1,47 +1,54 @@ /** * External dependencies */ -import { render } from 'test/helpers'; - -/** - * Internal dependencies - */ -import { metadata, settings, name } from '../index'; +import { + addBlock, + getEditorHtml, + initializeEditor, + getBlock, +} from 'test/helpers'; /** * WordPress dependencies */ -import { BlockEdit } from '@wordpress/block-editor'; -import { registerBlockType, unregisterBlockType } from '@wordpress/blocks'; - -const Verse = ( { clientId, ...props } ) => ( - -); - -describe( 'Verse Block', () => { - beforeAll( () => { - registerBlockType( name, { - ...metadata, - ...settings, - } ); - } ); +import { getBlockTypes, unregisterBlockType } from '@wordpress/blocks'; +import { registerCoreBlocks } from '@wordpress/block-library'; + +beforeAll( () => { + // Register all core blocks + registerCoreBlocks(); +} ); - afterAll( () => { - unregisterBlockType( name ); +afterAll( () => { + // Clean up registered blocks + getBlockTypes().forEach( ( block ) => { + unregisterBlockType( block.name ); } ); +} ); + +describe( 'Verse block', () => { + it( 'inserts block', async () => { + const screen = await initializeEditor(); - it( 'renders without crashing', () => { - const component = render( ); - const rendered = component.toJSON(); - expect( rendered ).toBeTruthy(); + // Add block + await addBlock( screen, 'Verse' ); + + // Get block + const verseBlock = await getBlock( screen, 'Verse' ); + expect( verseBlock ).toBeVisible(); + expect( getEditorHtml() ).toMatchSnapshot(); } ); - it( 'renders given text without crashing', () => { - const component = render( - - ); - expect( - component.getByDisplayValue( '
      sample text
      ' ) - ).toBeTruthy(); + it( 'renders block text set as initial content', async () => { + const screen = await initializeEditor( { + initialHtml: ` +
      Sample text
      + `, + } ); + + // Get block + const verseBlock = await getBlock( screen, 'Verse' ); + expect( verseBlock ).toBeVisible(); + expect( getEditorHtml() ).toMatchSnapshot(); } ); } ); diff --git a/packages/block-library/src/video/edit.js b/packages/block-library/src/video/edit.js index e79f8a9774482c..f09f7dca49b51e 100644 --- a/packages/block-library/src/video/edit.js +++ b/packages/block-library/src/video/edit.js @@ -161,7 +161,7 @@ function VideoEdit( { const embedBlock = createUpgradedEmbedBlock( { attributes: { url: newSrc }, } ); - if ( undefined !== embedBlock ) { + if ( undefined !== embedBlock && onReplace ) { onReplace( embedBlock ); return; } diff --git a/packages/block-library/src/video/edit.native.js b/packages/block-library/src/video/edit.native.js index fb4937ce5da733..99225ba5d5c569 100644 --- a/packages/block-library/src/video/edit.native.js +++ b/packages/block-library/src/video/edit.native.js @@ -2,7 +2,6 @@ * External dependencies */ import { View, TouchableWithoutFeedback, Text } from 'react-native'; -import { isEmpty } from 'lodash'; /** * WordPress dependencies @@ -367,7 +366,7 @@ class VideoEdit extends Component { - isEmpty( caption ) + ! caption ? /* translators: accessibility text. Empty video caption. */ __( 'Video caption. Empty' ) : sprintf( diff --git a/packages/block-library/test/babel-plugin.js b/packages/block-library/test/babel-plugin.js index bb79d12f60f22c..7c26b0f7d117ce 100644 --- a/packages/block-library/test/babel-plugin.js +++ b/packages/block-library/test/babel-plugin.js @@ -12,56 +12,56 @@ function join( ...strings ) { return strings.join( '\n' ); } -function compare( input, output, isExperimental, options = {} ) { +function transformCode( input, isExperimental, options = {} ) { const blockLibraryPlugin = createBabelPlugin( isExperimental, false ); const { code } = transform( input, { configFile: false, plugins: [ [ blockLibraryPlugin, options ] ], } ); - expect( code ).toEqual( output ); + return code; } describe( 'babel-plugin', () => { it( 'should ignore stable blocks', () => { - compare( - join( - 'import * as experimentalBlock from "./experimental-block";', - 'const blocks = [ experimentalBlock ];' - ), - join( - 'import * as experimentalBlock from "./experimental-block";', - 'const blocks = [experimentalBlock];' - ), - () => false + const input = join( + 'import * as experimentalBlock from "./experimental-block";', + 'const blocks = [ experimentalBlock ];' ); + const expected = join( + 'import * as experimentalBlock from "./experimental-block";', + 'const blocks = [experimentalBlock];' + ); + + expect( transformCode( input, () => false ) ).toEqual( expected ); } ); + it( 'should transform experimental blocks', () => { - compare( - join( - 'import * as experimentalBlock from "./experimental-block";', - 'const blocks = [ experimentalBlock ];' - ), - join( - 'const experimentalBlock = null;', - 'const blocks = [experimentalBlock];' - ), - () => true + const input = join( + 'import * as experimentalBlock from "./experimental-block";', + 'const blocks = [ experimentalBlock ];' + ); + const expected = join( + 'const experimentalBlock = null;', + 'const blocks = [experimentalBlock];' ); + + expect( transformCode( input, () => true ) ).toEqual( expected ); } ); + it( 'should work with mixed imports blocks', () => { - compare( - join( - 'import * as stableBlock from "./stable-block";', - 'import * as experimentalBlock from "./experimental-block";', - 'const blocks = [ stableBlock, experimentalBlock ];' - ), - join( - 'import * as stableBlock from "./stable-block";', - 'const experimentalBlock = null;', - 'const blocks = [stableBlock, experimentalBlock];' - ), - ( path ) => - path.node.specifiers[ 0 ].local.name === 'experimentalBlock' + const input = join( + 'import * as stableBlock from "./stable-block";', + 'import * as experimentalBlock from "./experimental-block";', + 'const blocks = [ stableBlock, experimentalBlock ];' ); + const expected = join( + 'import * as stableBlock from "./stable-block";', + 'const experimentalBlock = null;', + 'const blocks = [stableBlock, experimentalBlock];' + ); + const isExperimental = ( path ) => + path.node.specifiers[ 0 ].local.name === 'experimentalBlock'; + + expect( transformCode( input, isExperimental ) ).toEqual( expected ); } ); } ); diff --git a/packages/blocks/CHANGELOG.md b/packages/blocks/CHANGELOG.md index f1c2317d7791b0..5d1f797d7063d5 100644 --- a/packages/blocks/CHANGELOG.md +++ b/packages/blocks/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### Breaking Changes + +- Updated dependencies to require React 18 ([45235](https://github.com/WordPress/gutenberg/pull/45235)) + ## 11.21.0 (2022-11-16) ## 11.20.0 (2022-11-02) diff --git a/packages/blocks/README.md b/packages/blocks/README.md index f78783f7283392..a03fddf71fe1cc 100644 --- a/packages/blocks/README.md +++ b/packages/blocks/README.md @@ -529,10 +529,9 @@ _Returns_ - `boolean`: Whether the given block is a template part. -### isUnmodifiedDefaultBlock +### isUnmodifiedBlock -Determines whether the block is a default block -and its attributes are equal to the default attributes +Determines whether the block's attributes are equal to the default attributes which means the block is unmodified. _Parameters_ @@ -541,7 +540,20 @@ _Parameters_ _Returns_ -- `boolean`: Whether the block is an unmodified default block +- `boolean`: Whether the block is an unmodified block. + +### isUnmodifiedDefaultBlock + +Determines whether the block is a default block and its attributes are equal +to the default attributes which means the block is unmodified. + +_Parameters_ + +- _block_ `WPBlock`: Block Object + +_Returns_ + +- `boolean`: Whether the block is an unmodified default block. ### isValidBlockContent diff --git a/packages/blocks/package.json b/packages/blocks/package.json index b67f0e6266e056..028f2eb47af905 100644 --- a/packages/blocks/package.json +++ b/packages/blocks/package.json @@ -44,6 +44,7 @@ "@wordpress/shortcode": "file:../shortcode", "change-case": "^4.1.2", "colord": "^2.7.0", + "fast-deep-equal": "^3.1.3", "hpq": "^1.3.0", "is-plain-object": "^5.0.0", "lodash": "^4.17.21", @@ -55,7 +56,7 @@ "uuid": "^8.3.0" }, "peerDependencies": { - "react": "^17.0.0" + "react": "^18.0.0" }, "publishConfig": { "access": "public" diff --git a/packages/blocks/src/api/index.js b/packages/blocks/src/api/index.js index afa11f82c6b6ba..2ddeb3a60f0abb 100644 --- a/packages/blocks/src/api/index.js +++ b/packages/blocks/src/api/index.js @@ -137,6 +137,7 @@ export { unregisterBlockVariation, } from './registration'; export { + isUnmodifiedBlock, isUnmodifiedDefaultBlock, normalizeIconObject, isValidIcon, diff --git a/packages/blocks/src/api/parser/get-block-attributes.js b/packages/blocks/src/api/parser/get-block-attributes.js index b9fd3dcf675082..ffa67c11250c2f 100644 --- a/packages/blocks/src/api/parser/get-block-attributes.js +++ b/packages/blocks/src/api/parser/get-block-attributes.js @@ -102,18 +102,20 @@ export function isOfTypes( value, types ) { * commentAttributes returns the attribute value depending on its source * definition of the given attribute key. * - * @param {string} attributeKey Attribute key. - * @param {Object} attributeSchema Attribute's schema. - * @param {string|Node} innerHTML Block's raw content. - * @param {Object} commentAttributes Block's comment attributes. + * @param {string} attributeKey Attribute key. + * @param {Object} attributeSchema Attribute's schema. + * @param {Node} innerDOM Parsed DOM of block's inner HTML. + * @param {Object} commentAttributes Block's comment attributes. + * @param {string} innerHTML Raw HTML from block node's innerHTML property. * * @return {*} Attribute value. */ export function getBlockAttribute( attributeKey, attributeSchema, - innerHTML, - commentAttributes + innerDOM, + commentAttributes, + innerHTML ) { let value; @@ -125,6 +127,10 @@ export function getBlockAttribute( ? commentAttributes[ attributeKey ] : undefined; break; + // raw source means that it's the original raw block content. + case 'raw': + value = innerHTML; + break; case 'attribute': case 'property': case 'html': @@ -133,7 +139,7 @@ export function getBlockAttribute( case 'node': case 'query': case 'tag': - value = parseWithAttributeSchema( innerHTML, attributeSchema ); + value = parseWithAttributeSchema( innerDOM, attributeSchema ); break; } @@ -270,7 +276,7 @@ export function getBlockAttributes( const blockType = normalizeBlockType( blockTypeOrName ); const blockAttributes = mapValues( blockType.attributes, ( schema, key ) => - getBlockAttribute( key, schema, doc, attributes ) + getBlockAttribute( key, schema, doc, attributes, innerHTML ) ); return applyFilters( diff --git a/packages/blocks/src/api/raw-handling/paste-handler.js b/packages/blocks/src/api/raw-handling/paste-handler.js index d0193bc87eb74c..c4ad40e0b1f509 100644 --- a/packages/blocks/src/api/raw-handling/paste-handler.js +++ b/packages/blocks/src/api/raw-handling/paste-handler.js @@ -47,6 +47,7 @@ const { console } = window; */ function filterInlineHTML( HTML, preserveWhiteSpace ) { HTML = deepFilterHTML( HTML, [ + headRemover, googleDocsUIDRemover, phrasingContentReducer, commentRemover, diff --git a/packages/blocks/src/api/raw-handling/test/paste-handler.js b/packages/blocks/src/api/raw-handling/test/paste-handler.js new file mode 100644 index 00000000000000..378189b10e0ea0 --- /dev/null +++ b/packages/blocks/src/api/raw-handling/test/paste-handler.js @@ -0,0 +1,131 @@ +/** + * WordPress dependencies + */ +import { pasteHandler } from '@wordpress/blocks'; +/** + * Internal dependencies + */ +import { init as initAndRegisterTableBlock } from '../../../../../block-library/src/table'; + +const tableWithHeaderFooterAndBodyUsingColspan = ` + + + + + + + + + + + + + + + + + + + +
      Colspan 2Header Cell
      Footer CellFooter Cell
      Colspan 2Cell Data
      `; + +const googleDocsTableWithColspan = ` + +
      + + + + + + + + + + +
      +

      Test colspan +

      +
      +

      +`; + +describe( 'pasteHandler', () => { + beforeAll( () => { + initAndRegisterTableBlock(); + } ); + + it( 'can handle a table with thead, tbody and tfoot using colspan', () => { + const [ result ] = pasteHandler( { + HTML: tableWithHeaderFooterAndBodyUsingColspan, + tagName: 'p', + preserveWhiteSpace: false, + } ); + + expect( console ).toHaveLogged(); + + expect( result.attributes ).toEqual( { + hasFixedLayout: false, + caption: '', + head: [ + { + cells: [ + { content: 'Colspan 2', tag: 'th', colspan: '2' }, + { content: 'Header Cell', tag: 'th' }, + ], + }, + ], + body: [ + { + cells: [ + { content: 'Colspan 2', tag: 'td', colspan: '2' }, + { content: 'Cell Data', tag: 'td' }, + ], + }, + ], + foot: [ + { + cells: [ + { content: 'Footer Cell', tag: 'th', colspan: '2' }, + { content: 'Footer Cell', tag: 'th' }, + ], + }, + ], + } ); + expect( result.name ).toEqual( 'core/table' ); + expect( result.isValid ).toBeTruthy(); + } ); + + it( 'can handle a google docs table with colspan', () => { + const [ result ] = pasteHandler( { + HTML: googleDocsTableWithColspan, + tagName: 'p', + preserveWhiteSpace: false, + } ); + + expect( console ).toHaveLogged(); + + expect( result.attributes ).toEqual( { + body: [ + { + cells: [ + { + align: undefined, + colspan: '2', + content: 'Test colspan', + scope: undefined, + tag: 'td', + }, + ], + }, + ], + caption: '', + foot: [], + hasFixedLayout: false, + head: [], + } ); + expect( result.name ).toEqual( 'core/table' ); + expect( result.isValid ).toBeTruthy(); + } ); +} ); diff --git a/packages/blocks/src/api/registration.js b/packages/blocks/src/api/registration.js index f8f5ac996173b6..6c34fea8d95a8d 100644 --- a/packages/blocks/src/api/registration.js +++ b/packages/blocks/src/api/registration.js @@ -618,7 +618,7 @@ export function isReusableBlock( blockOrType ) { * @return {boolean} Whether the given block is a template part. */ export function isTemplatePart( blockOrType ) { - return blockOrType.name === 'core/template-part'; + return blockOrType?.name === 'core/template-part'; } /** diff --git a/packages/blocks/src/api/utils.js b/packages/blocks/src/api/utils.js index bc68ccd189537d..c43445c6272264 100644 --- a/packages/blocks/src/api/utils.js +++ b/packages/blocks/src/api/utils.js @@ -30,37 +30,40 @@ extend( [ namesPlugin, a11yPlugin ] ); const ICON_COLORS = [ '#191e23', '#f8f9f9' ]; /** - * Determines whether the block is a default block - * and its attributes are equal to the default attributes + * Determines whether the block's attributes are equal to the default attributes * which means the block is unmodified. * * @param {WPBlock} block Block Object * - * @return {boolean} Whether the block is an unmodified default block + * @return {boolean} Whether the block is an unmodified block. */ -export function isUnmodifiedDefaultBlock( block ) { - const defaultBlockName = getDefaultBlockName(); - if ( block.name !== defaultBlockName ) { - return false; - } - +export function isUnmodifiedBlock( block ) { // Cache a created default block if no cache exists or the default block // name changed. - if ( - ! isUnmodifiedDefaultBlock.block || - isUnmodifiedDefaultBlock.block.name !== defaultBlockName - ) { - isUnmodifiedDefaultBlock.block = createBlock( defaultBlockName ); + if ( ! isUnmodifiedBlock[ block.name ] ) { + isUnmodifiedBlock[ block.name ] = createBlock( block.name ); } - const newDefaultBlock = isUnmodifiedDefaultBlock.block; - const blockType = getBlockType( defaultBlockName ); + const newBlock = isUnmodifiedBlock[ block.name ]; + const blockType = getBlockType( block.name ); return Object.keys( blockType?.attributes ?? {} ).every( - ( key ) => newDefaultBlock.attributes[ key ] === block.attributes[ key ] + ( key ) => newBlock.attributes[ key ] === block.attributes[ key ] ); } +/** + * Determines whether the block is a default block and its attributes are equal + * to the default attributes which means the block is unmodified. + * + * @param {WPBlock} block Block Object + * + * @return {boolean} Whether the block is an unmodified default block. + */ +export function isUnmodifiedDefaultBlock( block ) { + return block.name === getDefaultBlockName() && isUnmodifiedBlock( block ); +} + /** * Function that checks if the parameter is a valid icon. * diff --git a/packages/blocks/src/api/validation/index.js b/packages/blocks/src/api/validation/index.js index 6f64c5f3923abe..abea733fe44715 100644 --- a/packages/blocks/src/api/validation/index.js +++ b/packages/blocks/src/api/validation/index.js @@ -2,7 +2,7 @@ * External dependencies */ import { Tokenizer } from 'simple-html-tokenizer'; -import { isEqual } from 'lodash'; +import fastDeepEqual from 'fast-deep-equal/es6'; /** * WordPress dependencies @@ -423,7 +423,9 @@ export const isEqualAttributesOfName = { return actualDiff.length === 0 && expectedDiff.length === 0; }, style: ( actual, expected ) => { - return isEqual( ...[ actual, expected ].map( getStyleProperties ) ); + return fastDeepEqual( + ...[ actual, expected ].map( getStyleProperties ) + ); }, // For each boolean attribute, mere presence of attribute in both is enough // to assume equivalence. diff --git a/packages/blocks/src/store/actions.js b/packages/blocks/src/store/actions.js index 76cf631e81448c..f8ba8ac83769ff 100644 --- a/packages/blocks/src/store/actions.js +++ b/packages/blocks/src/store/actions.js @@ -2,7 +2,6 @@ * External dependencies */ import { isPlainObject } from 'is-plain-object'; -import { pick } from 'lodash'; /** * WordPress dependencies @@ -72,23 +71,24 @@ const processBlockType = ( blockType, { select } ) => { if ( settings.deprecated ) { settings.deprecated = settings.deprecated.map( ( deprecation ) => - pick( - // 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, - }, - name, - deprecation - ), - DEPRECATED_ENTRY_KEYS + 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, + }, + name, + deprecation + ) + ).filter( ( [ key ] ) => DEPRECATED_ENTRY_KEYS.includes( key ) ) ) ); } diff --git a/packages/blocks/src/store/reducer.js b/packages/blocks/src/store/reducer.js index 6770dd6b67f26b..16f95127137a52 100644 --- a/packages/blocks/src/store/reducer.js +++ b/packages/blocks/src/store/reducer.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { filter, find, get, isEmpty, map, mapValues } from 'lodash'; +import { get, isEmpty, map, mapValues } from 'lodash'; /** * WordPress dependencies @@ -144,8 +144,11 @@ export function blockStyles( state = {}, action ) { case 'REMOVE_BLOCK_STYLES': return { ...state, - [ action.blockName ]: filter( - get( state, [ action.blockName ], [] ), + [ action.blockName ]: get( + state, + [ action.blockName ], + [] + ).filter( ( style ) => action.styleNames.indexOf( style.name ) === -1 ), }; @@ -195,8 +198,11 @@ export function blockVariations( state = {}, action ) { case 'REMOVE_BLOCK_VARIATIONS': return { ...state, - [ action.blockName ]: filter( - get( state, [ action.blockName ], [] ), + [ action.blockName ]: get( + state, + [ action.blockName ], + [] + ).filter( ( variation ) => action.variationNames.indexOf( variation.name ) === -1 ), @@ -259,7 +265,9 @@ export function categories( state = DEFAULT_CATEGORIES, action ) { if ( ! action.category || isEmpty( action.category ) ) { return state; } - const categoryToChange = find( state, [ 'slug', action.slug ] ); + const categoryToChange = state.find( + ( { slug } ) => slug === action.slug + ); if ( categoryToChange ) { return map( state, ( category ) => { if ( category.slug === action.slug ) { diff --git a/packages/blocks/src/store/selectors.js b/packages/blocks/src/store/selectors.js index 298d7fc12141aa..27cacef4085820 100644 --- a/packages/blocks/src/store/selectors.js +++ b/packages/blocks/src/store/selectors.js @@ -3,7 +3,7 @@ */ import createSelector from 'rememo'; import removeAccents from 'remove-accents'; -import { filter, get, map } from 'lodash'; +import { get, map } from 'lodash'; /** * WordPress dependencies @@ -554,7 +554,7 @@ export function getGroupingBlockName( state ) { export const getChildBlockNames = createSelector( ( state, blockName ) => { return map( - filter( state.blockTypes, ( blockType ) => { + getBlockTypes( state ).filter( ( blockType ) => { return blockType.parent?.includes( blockName ); } ), ( { name } ) => name diff --git a/packages/blocks/src/store/test/selectors.js b/packages/blocks/src/store/test/selectors.js index 8793e1e354e44f..1fda11d72311a3 100644 --- a/packages/blocks/src/store/test/selectors.js +++ b/packages/blocks/src/store/test/selectors.js @@ -86,26 +86,28 @@ describe( 'selectors', () => { describe( 'getChildBlockNames', () => { it( 'should return an empty array if state is empty', () => { - const state = {}; + const state = { + blockTypes: {}, + }; expect( getChildBlockNames( state, 'parent1' ) ).toHaveLength( 0 ); } ); it( 'should return an empty array if no children exist', () => { const state = { - blockTypes: [ - { + blockTypes: { + child1: { name: 'child1', parent: [ 'parent1' ], }, - { + child2: { name: 'child2', parent: [ 'parent2' ], }, - { + parent3: { name: 'parent3', }, - ], + }, }; expect( getChildBlockNames( state, 'parent3' ) ).toHaveLength( 0 ); @@ -113,15 +115,15 @@ describe( 'selectors', () => { it( 'should return an empty array if the parent block is not found', () => { const state = { - blockTypes: [ - { + blockTypes: { + child1: { name: 'child1', parent: [ 'parent1' ], }, - { + parent1: { name: 'parent1', }, - ], + }, }; expect( getChildBlockNames( state, 'parent3' ) ).toHaveLength( 0 ); @@ -129,29 +131,29 @@ describe( 'selectors', () => { it( 'should return an array with the child block names', () => { const state = { - blockTypes: [ - { + blockTypes: { + child1: { name: 'child1', parent: [ 'parent1' ], }, - { + child2: { name: 'child2', parent: [ 'parent2' ], }, - { + child3: { name: 'child3', parent: [ 'parent1' ], }, - { + child4: { name: 'child4', }, - { + parent1: { name: 'parent1', }, - { + parent2: { name: 'parent2', }, - ], + }, }; expect( getChildBlockNames( state, 'parent1' ) ).toEqual( [ @@ -162,25 +164,25 @@ describe( 'selectors', () => { it( 'should return an array with the child block names even if only one child exists', () => { const state = { - blockTypes: [ - { + blockTypes: { + child1: { name: 'child1', parent: [ 'parent1' ], }, - { + child2: { name: 'child2', parent: [ 'parent2' ], }, - { + child4: { name: 'child4', }, - { + parent1: { name: 'parent1', }, - { + parent2: { name: 'parent2', }, - ], + }, }; expect( getChildBlockNames( state, 'parent1' ) ).toEqual( [ @@ -190,29 +192,29 @@ describe( 'selectors', () => { it( 'should return an array with the child block names even if children have multiple parents', () => { const state = { - blockTypes: [ - { + blockTypes: { + child1: { name: 'child1', parent: [ 'parent1' ], }, - { + child2: { name: 'child2', parent: [ 'parent1', 'parent2' ], }, - { + child3: { name: 'child3', parent: [ 'parent1' ], }, - { + child4: { name: 'child4', }, - { + parent1: { name: 'parent1', }, - { + parent2: { name: 'parent2', }, - ], + }, }; expect( getChildBlockNames( state, 'parent1' ) ).toEqual( [ diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index 2e47db6c778689..f459061f109e19 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -2,9 +2,57 @@ ## Unreleased +### Breaking Changes + +- Updated dependencies to require React 18 ([45235](https://github.com/WordPress/gutenberg/pull/45235)) + +### New Feature + +- `TabPanel`: support manual tab activation ([#46004](https://github.com/WordPress/gutenberg/pull/46004)). +- `BaseControl`: Add `useBaseControlProps` hook to help generate id-releated props ([#46170](https://github.com/WordPress/gutenberg/pull/46170)). + +### Bug Fix + +- `ColorPalette`: show "Clear" button even when colors array is empty ([#46001](https://github.com/WordPress/gutenberg/pull/46001)). +- `InputControl`: Fix internal `Flex` wrapper usage that could add an unintended `height: 100%` ([#46213](https://github.com/WordPress/gutenberg/pull/46213)). +- `Navigator`: Allow calling `goTo` and `goBack` twice in one render cycle ([#46391](https://github.com/WordPress/gutenberg/pull/46391)). +- `Modal`: Fix unexpected modal closing in IME Composition ([#46453](https://github.com/WordPress/gutenberg/pull/46453)). + ### Enhancements +- `TabPanel`: Simplify tab-focus style. ([#46276](https://github.com/WordPress/gutenberg/pull/46276)). - `TabPanel`: Add ability to set icon only tab buttons ([#45005](https://github.com/WordPress/gutenberg/pull/45005)). +- `InputControl`, `NumberControl`, `UnitControl`: Add `help` prop for additional description ([#45931](https://github.com/WordPress/gutenberg/pull/45931)). +- `BorderControl`, `ColorPicker` & `QueryControls`: Replace bottom margin overrides with `__nextHasNoMarginBottom` ([#45985](https://github.com/WordPress/gutenberg/pull/45985)). +- `CustomSelectControl`, `UnitControl`: Add `onFocus` and `onBlur` props ([#46096](https://github.com/WordPress/gutenberg/pull/46096)). +- `ResizableBox`: Prevent unnecessary paint on resize handles ([#46196](https://github.com/WordPress/gutenberg/pull/46196)). +- `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)). + +### Experimental + +- `TextControl`: Restrict `type` prop to `email`, `number`, `password`, `tel`, `text`, `search` or `url` ([#45433](https://github.com/WordPress/gutenberg/pull/45433/)). + +### Internal + +- `useControlledValue`: let TypeScript infer the return type ([#46164](https://github.com/WordPress/gutenberg/pull/46164)). +- `LinkedButton`: remove unnecessary `span` tag ([#46063](https://github.com/WordPress/gutenberg/pull/46063)). +- NumberControl: refactor styles/tests/stories to TypeScript, replace fireEvent with user-event ([#45990](https://github.com/WordPress/gutenberg/pull/45990)). +- `useBaseField`: Convert to TypeScript ([#45712](https://github.com/WordPress/gutenberg/pull/45712)). +- `Dashicon`: Convert to TypeScript ([#45924](https://github.com/WordPress/gutenberg/pull/45924)). +- `PaletteEdit`: add follow up changelog for #45681 and tests [#46095](https://github.com/WordPress/gutenberg/pull/46095). +- `AlignmentMatrixControl`: Convert to TypeScript ([#46162](https://github.com/WordPress/gutenberg/pull/46162)). +- `Autocomplete`: Refactor away from `_.find()` ([#46537](https://github.com/WordPress/gutenberg/pull/46537)). +- `TabPanel`: Refactor away from `_.find()` ([#46537](https://github.com/WordPress/gutenberg/pull/46537)). +- `BottomSheetPickerCell`: Refactor away from `_.find()` for mobile ([#46537](https://github.com/WordPress/gutenberg/pull/46537)). +- Refactor global styles context away from `_.find()` for mobile ([#46537](https://github.com/WordPress/gutenberg/pull/46537)). + +### Documentation + +- `Tooltip`: Add readme and unit tests for `shortcut` prop ([#46092](https://github.com/WordPress/gutenberg/pull/46092)). ## 22.1.0 (2022-11-16) @@ -13,6 +61,9 @@ - `ColorPalette`, `BorderBox`, `BorderBoxControl`: polish and DRY prop types, add default values ([#45463](https://github.com/WordPress/gutenberg/pull/45463)). - `TabPanel`: Add ability to set icon only tab buttons ([#45005](https://github.com/WordPress/gutenberg/pull/45005)). +### Internal +- `AnglePickerControl`: remove `:focus-visible' outline on `CircleOutlineWrapper` ([#45758](https://github.com/WordPress/gutenberg/pull/45758)) + ### Bug Fix - `FormTokenField`: Fix duplicate input in IME composition ([#45607](https://github.com/WordPress/gutenberg/pull/45607)). @@ -21,6 +72,7 @@ - `Icon`: Making size prop work for icon components using dash icon strings ([#45593](https://github.com/WordPress/gutenberg/pull/45593)) - `ToolsPanelItem`: Prevent unintended calls to onDeselect when parent panel is remounted and item is rendered via SlotFill ([#45673](https://github.com/WordPress/gutenberg/pull/45673)) - `ColorPicker`: Prevent all number fields from becoming "0" when one of them is an empty string ([#45649](https://github.com/WordPress/gutenberg/pull/45649)). +- `ToggleControl`: Fix toggle control label text overflow ([#45962](https://github.com/WordPress/gutenberg/pull/45962)). ### Internal @@ -41,6 +93,7 @@ ### Experimental - `ToggleGroupControl`: Only show enclosing border when `isBlock` and not `isDeselectable` ([#45492](https://github.com/WordPress/gutenberg/pull/45492)). +- `Theme`: Add support for custom `background` color ([#45466](https://github.com/WordPress/gutenberg/pull/45466)). ## 22.0.0 (2022-11-02) diff --git a/packages/components/package.json b/packages/components/package.json index 5afcbf72040c8d..ca77fb07450e79 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -59,7 +59,8 @@ "date-fns": "^2.28.0", "dom-scroll-into-view": "^1.2.1", "downshift": "^6.0.15", - "framer-motion": "^6.2.8", + "fast-deep-equal": "^3.1.3", + "framer-motion": "^7.6.1", "gradient-parser": "^0.1.5", "highlight-words-core": "^1.2.2", "lodash": "^4.17.21", @@ -73,8 +74,8 @@ "valtio": "^1.7.0" }, "peerDependencies": { - "react": "^17.0.0", - "react-dom": "^17.0.0" + "react": "^18.0.0", + "react-dom": "^18.0.0" }, "publishConfig": { "access": "public" diff --git a/packages/components/src/alignment-matrix-control/README.md b/packages/components/src/alignment-matrix-control/README.md index 16b7333c09db74..576fe77c1f25a4 100644 --- a/packages/components/src/alignment-matrix-control/README.md +++ b/packages/components/src/alignment-matrix-control/README.md @@ -30,8 +30,7 @@ The component accepts the following props: ### className -The class that will be added to the classes of the wrapper component. - +The class that will be added to the classes of the underlying `grid` widget. - Type: `string` - Required: No @@ -44,7 +43,7 @@ Unique ID for the component. ### label -Accessible label. If provided, sets the `aria-label` attribute of the underlying component. +Accessible label. If provided, sets the `aria-label` attribute of the underlying `grid` widget. - Type: `string` - Required: No @@ -54,26 +53,27 @@ Accessible label. If provided, sets the `aria-label` attribute of the underlying If provided, sets the default alignment value. -- Type: `string` +- Type: `AlignmentMatrixControlValue` - Required: No - Default: `center center` ### value The current alignment value. -- Type: `string` + +- Type: `AlignmentMatrixControlValue` - Required: No ### onChange A function that receives the updated alignment value. -- Type: `( nextValue: string ) => void` +- Type: `( newValue: AlignmentMatrixControlValue ) => void` - Required: No ### width -If provided, sets the width of the wrapper component. +If provided, sets the width of the control. - Type: `number` - Required: No diff --git a/packages/components/src/alignment-matrix-control/cell.js b/packages/components/src/alignment-matrix-control/cell.tsx similarity index 74% rename from packages/components/src/alignment-matrix-control/cell.js rename to packages/components/src/alignment-matrix-control/cell.tsx index 2aeb839831c0c0..b6e19c56022915 100644 --- a/packages/components/src/alignment-matrix-control/cell.js +++ b/packages/components/src/alignment-matrix-control/cell.tsx @@ -13,8 +13,14 @@ import { Cell as CellView, Point, } from './styles/alignment-matrix-control-styles'; +import type { AlignmentMatrixControlCellProps } from './types'; +import type { WordPressComponentProps } from '../ui/context'; -export default function Cell( { isActive = false, value, ...props } ) { +export default function Cell( { + isActive = false, + value, + ...props +}: WordPressComponentProps< AlignmentMatrixControlCellProps, 'span', false > ) { const tooltipText = ALIGNMENT_LABEL[ value ]; return ( diff --git a/packages/components/src/alignment-matrix-control/icon.js b/packages/components/src/alignment-matrix-control/icon.tsx similarity index 77% rename from packages/components/src/alignment-matrix-control/icon.js rename to packages/components/src/alignment-matrix-control/icon.tsx index e712b7471ff237..b294bc7551e19d 100644 --- a/packages/components/src/alignment-matrix-control/icon.js +++ b/packages/components/src/alignment-matrix-control/icon.tsx @@ -12,17 +12,19 @@ import { Cell, Point, } from './styles/alignment-matrix-control-icon-styles'; +import type { AlignmentMatrixControlIconProps } from './types'; +import type { WordPressComponentProps } from '../ui/context'; const BASE_SIZE = 24; -export default function AlignmentMatrixControlIcon( { +function AlignmentMatrixControlIcon( { className, disablePointerEvents = true, size = BASE_SIZE, style = {}, value = 'center', ...props -} ) { +}: WordPressComponentProps< AlignmentMatrixControlIconProps, 'div', false > ) { const alignIndex = getAlignmentIndex( value ); const scale = ( size / BASE_SIZE ).toFixed( 2 ); @@ -42,7 +44,6 @@ export default function AlignmentMatrixControlIcon( { className={ classes } disablePointerEvents={ disablePointerEvents } role="presentation" - size={ size } style={ styles } > { ALIGNMENTS.map( ( align, index ) => { @@ -57,3 +58,5 @@ export default function AlignmentMatrixControlIcon( { ); } + +export default AlignmentMatrixControlIcon; diff --git a/packages/components/src/alignment-matrix-control/index.js b/packages/components/src/alignment-matrix-control/index.tsx similarity index 70% rename from packages/components/src/alignment-matrix-control/index.js rename to packages/components/src/alignment-matrix-control/index.tsx index 44dbe33d95a94d..0e34e4052346da 100644 --- a/packages/components/src/alignment-matrix-control/index.js +++ b/packages/components/src/alignment-matrix-control/index.tsx @@ -18,10 +18,15 @@ import { Composite, CompositeGroup, useCompositeState } from '../composite'; import { Root, Row } from './styles/alignment-matrix-control-styles'; import AlignmentMatrixControlIcon from './icon'; import { GRID, getItemId } from './utils'; +import type { WordPressComponentProps } from '../ui/context'; +import type { + AlignmentMatrixControlProps, + AlignmentMatrixControlValue, +} from './types'; const noop = () => {}; -function useBaseId( id ) { +function useBaseId( id?: string ) { const instanceId = useInstanceId( AlignmentMatrixControl, 'alignment-matrix-control' @@ -30,7 +35,27 @@ function useBaseId( id ) { return id || instanceId; } -export default function AlignmentMatrixControl( { +/** + * + * AlignmentMatrixControl components enable adjustments to horizontal and vertical alignments for UI. + * + * ```jsx + * import { __experimentalAlignmentMatrixControl as AlignmentMatrixControl } from '@wordpress/components'; + * import { useState } from '@wordpress/element'; + * + * const Example = () => { + * const [ alignment, setAlignment ] = useState( 'center center' ); + * + * return ( + * + * ); + * }; + * ``` + */ +export function AlignmentMatrixControl( { className, id, label = __( 'Alignment Matrix Control' ), @@ -39,7 +64,7 @@ export default function AlignmentMatrixControl( { onChange = noop, width = 92, ...props -} ) { +}: WordPressComponentProps< AlignmentMatrixControlProps, 'div', false > ) { const [ immutableDefaultValue ] = useState( value ?? defaultValue ); const baseId = useBaseId( id ); const initialCurrentId = getItemId( baseId, immutableDefaultValue ); @@ -50,7 +75,7 @@ export default function AlignmentMatrixControl( { rtl: isRTL(), } ); - const handleOnChange = ( nextValue ) => { + const handleOnChange = ( nextValue: AlignmentMatrixControlValue ) => { onChange( nextValue ); }; @@ -107,3 +132,5 @@ export default function AlignmentMatrixControl( { } AlignmentMatrixControl.Icon = AlignmentMatrixControlIcon; + +export default AlignmentMatrixControl; diff --git a/packages/components/src/alignment-matrix-control/stories/index.js b/packages/components/src/alignment-matrix-control/stories/index.tsx similarity index 70% rename from packages/components/src/alignment-matrix-control/stories/index.js rename to packages/components/src/alignment-matrix-control/stories/index.tsx index babdd47c1e95f4..6401907f921693 100644 --- a/packages/components/src/alignment-matrix-control/stories/index.js +++ b/packages/components/src/alignment-matrix-control/stories/index.tsx @@ -1,3 +1,8 @@ +/** + * External dependencies + */ +import type { ComponentMeta, ComponentStory } from '@storybook/react'; + /** * WordPress dependencies */ @@ -7,21 +12,18 @@ import { Icon } from '@wordpress/icons'; /** * Internal dependencies */ -import AlignmentMatrixControl from '../'; -import { ALIGNMENTS } from '../utils'; +import AlignmentMatrixControl from '..'; import { HStack } from '../../h-stack'; +import type { AlignmentMatrixControlProps } from '../types'; -export default { +const meta: ComponentMeta< typeof AlignmentMatrixControl > = { title: 'Components (Experimental)/AlignmentMatrixControl', component: AlignmentMatrixControl, subcomponents: { 'AlignmentMatrixControl.Icon': AlignmentMatrixControl.Icon, }, argTypes: { - defaultValue: { options: ALIGNMENTS }, onChange: { action: 'onChange', control: { type: null } }, - label: { control: { type: 'text' } }, - width: { control: { type: 'number' } }, value: { control: { type: null } }, }, parameters: { @@ -29,9 +31,15 @@ export default { docs: { source: { state: 'open' } }, }, }; +export default meta; -const Template = ( { defaultValue, onChange, ...props } ) => { - const [ value, setValue ] = useState(); +const Template: ComponentStory< typeof AlignmentMatrixControl > = ( { + defaultValue, + onChange, + ...props +} ) => { + const [ value, setValue ] = + useState< AlignmentMatrixControlProps[ 'value' ] >(); // Convenience handler for Canvas view so changes are reflected useEffect( () => { @@ -43,7 +51,7 @@ const Template = ( { defaultValue, onChange, ...props } ) => { { ...props } onChange={ ( ...changeArgs ) => { setValue( ...changeArgs ); - onChange( ...changeArgs ); + onChange?.( ...changeArgs ); } } value={ value } /> diff --git a/packages/components/src/alignment-matrix-control/styles/alignment-matrix-control-icon-styles.js b/packages/components/src/alignment-matrix-control/styles/alignment-matrix-control-icon-styles.ts similarity index 72% rename from packages/components/src/alignment-matrix-control/styles/alignment-matrix-control-icon-styles.js rename to packages/components/src/alignment-matrix-control/styles/alignment-matrix-control-icon-styles.ts index 5333e9b775625b..a14894633e05d1 100644 --- a/packages/components/src/alignment-matrix-control/styles/alignment-matrix-control-icon-styles.js +++ b/packages/components/src/alignment-matrix-control/styles/alignment-matrix-control-icon-styles.ts @@ -12,6 +12,10 @@ import { pointBase, Cell as CellBase, } from './alignment-matrix-control-styles'; +import type { + AlignmentMatrixControlIconProps, + AlignmentMatrixControlCellProps, +} from '../types'; const rootSize = () => { const padding = 1.5; @@ -25,9 +29,11 @@ const rootSize = () => { } ); }; -const rootPointerEvents = ( { disablePointerEvents } ) => { +const rootPointerEvents = ( { + disablePointerEvents, +}: Pick< AlignmentMatrixControlIconProps, 'disablePointerEvents' > ) => { return css( { - pointerEvents: disablePointerEvents ? 'none' : null, + pointerEvents: disablePointerEvents ? 'none' : undefined, } ); }; @@ -46,7 +52,9 @@ export const Root = styled.div` ${ rootPointerEvents }; `; -const pointActive = ( { isActive } ) => { +const pointActive = ( { + isActive, +}: Pick< AlignmentMatrixControlCellProps, 'isActive' > ) => { const boxShadow = isActive ? `0 0 0 1px currentColor` : null; return css` diff --git a/packages/components/src/alignment-matrix-control/styles/alignment-matrix-control-styles.js b/packages/components/src/alignment-matrix-control/styles/alignment-matrix-control-styles.ts similarity index 81% rename from packages/components/src/alignment-matrix-control/styles/alignment-matrix-control-styles.js rename to packages/components/src/alignment-matrix-control/styles/alignment-matrix-control-styles.ts index f16ec53827403f..5adde6002ba3b4 100644 --- a/packages/components/src/alignment-matrix-control/styles/alignment-matrix-control-styles.js +++ b/packages/components/src/alignment-matrix-control/styles/alignment-matrix-control-styles.ts @@ -8,6 +8,10 @@ import { css } from '@emotion/react'; * Internal dependencies */ import { COLORS, reduceMotion } from '../../utils'; +import type { + AlignmentMatrixControlProps, + AlignmentMatrixControlCellProps, +} from '../types'; export const rootBase = () => { return css` @@ -27,7 +31,9 @@ const rootSize = ( { size = 92 } ) => { `; }; -export const Root = styled.div` +export const Root = styled.div< { + size: AlignmentMatrixControlProps[ 'width' ]; +} >` ${ rootBase }; border: 1px solid transparent; @@ -43,7 +49,9 @@ export const Row = styled.div` grid-template-columns: repeat( 3, 1fr ); `; -const pointActive = ( { isActive } ) => { +const pointActive = ( { + isActive, +}: Pick< AlignmentMatrixControlCellProps, 'isActive' > ) => { const boxShadow = isActive ? `0 0 0 2px ${ COLORS.gray[ 900 ] }` : null; const pointColor = isActive ? COLORS.gray[ 900 ] : COLORS.gray[ 400 ]; const pointColorHover = isActive ? COLORS.gray[ 900 ] : COLORS.ui.theme; @@ -58,7 +66,9 @@ const pointActive = ( { isActive } ) => { `; }; -export const pointBase = ( props ) => { +export const pointBase = ( + props: Pick< AlignmentMatrixControlCellProps, 'isActive' > +) => { return css` background: currentColor; box-sizing: border-box; diff --git a/packages/components/src/alignment-matrix-control/test/index.js b/packages/components/src/alignment-matrix-control/test/index.tsx similarity index 77% rename from packages/components/src/alignment-matrix-control/test/index.js rename to packages/components/src/alignment-matrix-control/test/index.tsx index 638d8acb96ad58..0e9661fe595b09 100644 --- a/packages/components/src/alignment-matrix-control/test/index.js +++ b/packages/components/src/alignment-matrix-control/test/index.tsx @@ -1,18 +1,18 @@ /** * External dependencies */ -import { render, screen, within } from '@testing-library/react'; +import { act, render, screen, within } from '@testing-library/react'; /** * Internal dependencies */ -import AlignmentMatrixControl from '../'; +import AlignmentMatrixControl from '..'; const getControl = () => { return screen.getByRole( 'grid' ); }; -const getCell = ( name ) => { +const getCell = ( name: string ) => { return within( getControl() ).getByRole( 'gridcell', { name } ); }; @@ -30,14 +30,14 @@ describe( 'AlignmentMatrixControl', () => { it.each( alignments )( 'should change value on %s cell click', - ( alignment ) => { + async ( alignment ) => { const spy = jest.fn(); render( ); - getCell( alignment ).focus(); + await act( () => getCell( alignment ).focus() ); expect( spy ).toHaveBeenCalledWith( alignment ); } diff --git a/packages/components/src/alignment-matrix-control/types.ts b/packages/components/src/alignment-matrix-control/types.ts new file mode 100644 index 00000000000000..892781234e694c --- /dev/null +++ b/packages/components/src/alignment-matrix-control/types.ts @@ -0,0 +1,54 @@ +export type AlignmentMatrixControlValue = + | 'top left' + | 'top center' + | 'top right' + | 'center left' + | 'center' + | 'center center' + | 'center right' + | 'bottom left' + | 'bottom center' + | 'bottom right'; + +export type AlignmentMatrixControlProps = { + /** + * Accessible label. If provided, sets the `aria-label` attribute of the + * underlying `grid` widget. + * + * @default 'Alignment Matrix Control' + */ + label?: string; + /** + * If provided, sets the default alignment value. + * + * @default 'center center' + */ + defaultValue?: AlignmentMatrixControlValue; + /** + * The current alignment value. + */ + value?: AlignmentMatrixControlValue; + /** + * A function that receives the updated alignment value. + */ + onChange?: ( newValue: AlignmentMatrixControlValue ) => void; + /** + * If provided, sets the width of the control. + * + * @default 92 + */ + width?: number; +}; + +export type AlignmentMatrixControlIconProps = Pick< + AlignmentMatrixControlProps, + 'value' +> & { + disablePointerEvents?: boolean; + size?: number; +}; + +export type AlignmentMatrixControlCellProps = { + isActive?: boolean; + value: NonNullable< AlignmentMatrixControlProps[ 'value' ] >; +}; diff --git a/packages/components/src/alignment-matrix-control/utils.js b/packages/components/src/alignment-matrix-control/utils.tsx similarity index 58% rename from packages/components/src/alignment-matrix-control/utils.js rename to packages/components/src/alignment-matrix-control/utils.tsx index 4934e422d196cb..a9fa7e57d67dbf 100644 --- a/packages/components/src/alignment-matrix-control/utils.js +++ b/packages/components/src/alignment-matrix-control/utils.tsx @@ -2,20 +2,25 @@ * WordPress dependencies */ import { __ } from '@wordpress/i18n'; +/** + * Internal dependencies + */ +import type { AlignmentMatrixControlValue } from './types'; -export const GRID = [ +export const GRID: AlignmentMatrixControlValue[][] = [ [ 'top left', 'top center', 'top right' ], [ 'center left', 'center center', 'center right' ], [ 'bottom left', 'bottom center', 'bottom right' ], ]; // Stored as map as i18n __() only accepts strings (not variables) -export const ALIGNMENT_LABEL = { +export const ALIGNMENT_LABEL: Record< AlignmentMatrixControlValue, string > = { 'top left': __( 'Top Left' ), 'top center': __( 'Top Center' ), 'top right': __( 'Top Right' ), 'center left': __( 'Center Left' ), 'center center': __( 'Center Center' ), + center: __( 'Center Center' ), 'center right': __( 'Center Right' ), 'bottom left': __( 'Bottom Left' ), 'bottom center': __( 'Bottom Center' ), @@ -28,25 +33,28 @@ export const ALIGNMENTS = GRID.flat(); /** * Parses and transforms an incoming value to better match the alignment values * - * @param {string} value An alignment value to parse. + * @param value An alignment value to parse. * - * @return {string} The parsed value. + * @return The parsed value. */ -export function transformValue( value ) { +export function transformValue( value: AlignmentMatrixControlValue ) { const nextValue = value === 'center' ? 'center center' : value; - return nextValue.replace( '-', ' ' ); + return nextValue.replace( '-', ' ' ) as AlignmentMatrixControlValue; } /** * Creates an item ID based on a prefix ID and an alignment value. * - * @param {string} prefixId An ID to prefix. - * @param {string} value An alignment value. + * @param prefixId An ID to prefix. + * @param value An alignment value. * - * @return {string} The item id. + * @return The item id. */ -export function getItemId( prefixId, value ) { +export function getItemId( + prefixId: string, + value: AlignmentMatrixControlValue +) { const valueId = transformValue( value ).replace( ' ', '-' ); return `${ prefixId }-${ valueId }`; @@ -55,12 +63,14 @@ export function getItemId( prefixId, value ) { /** * Retrieves the alignment index from a value. * - * @param {string} alignment Value to check. + * @param alignment Value to check. * - * @return {number} The index of a matching alignment. + * @return The index of a matching alignment. */ -export function getAlignmentIndex( alignment = 'center' ) { - const item = transformValue( alignment ).replace( '-', ' ' ); +export function getAlignmentIndex( + alignment: AlignmentMatrixControlValue = 'center' +) { + const item = transformValue( alignment ); const index = ALIGNMENTS.indexOf( item ); return index > -1 ? index : undefined; diff --git a/packages/components/src/angle-picker-control/styles/angle-picker-control-styles.js b/packages/components/src/angle-picker-control/styles/angle-picker-control-styles.js index 1a7ed713d72f18..29b38850c055f4 100644 --- a/packages/components/src/angle-picker-control/styles/angle-picker-control-styles.js +++ b/packages/components/src/angle-picker-control/styles/angle-picker-control-styles.js @@ -42,6 +42,10 @@ export const CircleIndicatorWrapper = styled.div` position: relative; width: 100%; height: 100%; + + :focus-visible { + outline: none; + } `; export const CircleIndicator = styled.div` diff --git a/packages/components/src/autocomplete/index.js b/packages/components/src/autocomplete/index.js index f96bd01750c01d..13c27d49b81b15 100644 --- a/packages/components/src/autocomplete/index.js +++ b/packages/components/src/autocomplete/index.js @@ -1,7 +1,6 @@ /** * External dependencies */ -import { find } from 'lodash'; import removeAccents from 'remove-accents'; /** @@ -290,8 +289,7 @@ function useAutocomplete( { const textAfterSelection = getTextContent( slice( record, undefined, getTextContent( record ).length ) ); - const completer = find( - completers, + const completer = completers?.find( ( { triggerPrefix, allowContext } ) => { const index = text.lastIndexOf( triggerPrefix ); @@ -415,26 +413,57 @@ function useAutocomplete( { } export function useAutocompleteProps( options ) { + const [ isVisible, setIsVisible ] = useState( false ); const ref = useRef(); + const recordAfterInput = useRef(); const onKeyDownRef = useRef(); const { popover, listBoxId, activeId, onKeyDown } = useAutocomplete( { ...options, contentRef: ref, } ); onKeyDownRef.current = onKeyDown; + + useEffect( () => { + if ( isVisible ) { + if ( ! recordAfterInput.current ) { + recordAfterInput.current = options.record; + } else if ( + recordAfterInput.current.start !== options.record.start || + recordAfterInput.current.end !== options.record.end + ) { + setIsVisible( false ); + recordAfterInput.current = null; + } + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [ options.record ] ); + + const mergedRefs = useMergeRefs( [ + ref, + useRefEffect( ( element ) => { + function _onKeyDown( event ) { + onKeyDownRef.current( event ); + } + function _onInput() { + // Only show auto complete UI if the user is inputting text. + setIsVisible( true ); + recordAfterInput.current = null; + } + element.addEventListener( 'keydown', _onKeyDown ); + element.addEventListener( 'input', _onInput ); + return () => { + element.removeEventListener( 'keydown', _onKeyDown ); + element.removeEventListener( 'input', _onInput ); + }; + }, [] ), + ] ); + + if ( ! isVisible ) { + return { ref: mergedRefs }; + } + return { - ref: useMergeRefs( [ - ref, - useRefEffect( ( element ) => { - function _onKeyDown( event ) { - onKeyDownRef.current( event ); - } - element.addEventListener( 'keydown', _onKeyDown ); - return () => { - element.removeEventListener( 'keydown', _onKeyDown ); - }; - }, [] ), - ] ), + ref: mergedRefs, children: popover, 'aria-autocomplete': listBoxId ? 'list' : undefined, 'aria-owns': listBoxId, diff --git a/packages/components/src/base-control/README.md b/packages/components/src/base-control/README.md index d940a7bc2fdc24..c2f3dc3e0108fd 100644 --- a/packages/components/src/base-control/README.md +++ b/packages/components/src/base-control/README.md @@ -4,16 +4,23 @@ ## Usage -Render a `BaseControl` for a textarea input: - ```jsx -import { BaseControl } from '@wordpress/components'; - -// The `id` prop is necessary to accessibly associate the label with the textarea -const MyBaseControl = () => ( - - + + ); ); ``` @@ -23,7 +30,9 @@ The component accepts the following props: ### id -The HTML `id` of the 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 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 @@ -44,9 +53,9 @@ If true, the label will only be visible to screen readers. ### help -If this property is added, a help text will be generated using help property as the content. +Additional description for the control. It is preferable to use plain text for `help`, as it can be accessibly associated with the control using `aria-describedby`. When the `help` contains links, or otherwise non-plain text content, it will be associated with the control using `aria-details`. -- Type: `String|WPElement` +- Type: `ReactNode` - Required: No ### className diff --git a/packages/components/src/base-control/hooks.ts b/packages/components/src/base-control/hooks.ts new file mode 100644 index 00000000000000..400b15834fead5 --- /dev/null +++ b/packages/components/src/base-control/hooks.ts @@ -0,0 +1,45 @@ +/** + * WordPress dependencies + */ +import { useInstanceId } from '@wordpress/compose'; + +/** + * Internal dependencies + */ +import BaseControl from '.'; +import type { BaseControlProps } from './types'; + +/** + * Generate props for the `BaseControl` and the inner control itself. + * + * Namely, it takes care of generating a unique `id`, properly associating it with the `label` and `help` elements. + * + * @param props + */ +export function useBaseControlProps( + props: Omit< BaseControlProps, 'children' > +) { + const { help, id: preferredId, ...restProps } = props; + + const uniqueId = useInstanceId( + BaseControl, + 'wp-components-base-control', + preferredId + ); + + // ARIA descriptions can only contain plain text, so fall back to aria-details if not. + const helpPropName = + typeof help === 'string' ? 'aria-describedby' : 'aria-details'; + + return { + baseControlProps: { + id: uniqueId, + help, + ...restProps, + }, + controlProps: { + id: uniqueId, + ...( !! help ? { [ helpPropName ]: `${ uniqueId }__help` } : {} ), + }, + }; +} diff --git a/packages/components/src/base-control/index.tsx b/packages/components/src/base-control/index.tsx index 122577fdeba62c..fa2df39bddd458 100644 --- a/packages/components/src/base-control/index.tsx +++ b/packages/components/src/base-control/index.tsx @@ -17,19 +17,30 @@ import { } from './styles/base-control-styles'; import type { WordPressComponentProps } from '../ui/context'; +export { useBaseControlProps } from './hooks'; + /** * `BaseControl` is a component used to generate labels and help text for components handling user inputs. * - * @example + * ```jsx + * import { BaseControl, useBaseControlProps } from '@wordpress/components'; + * * // Render a `BaseControl` for a textarea input - * import { BaseControl } from '@wordpress/components'; + * const MyCustomTextareaControl = ({ children, ...baseProps }) => ( + * // `useBaseControlProps` is a convenience hook to get the props for the `BaseControl` + * // and the inner control itself. Namely, it takes care of generating a unique `id`, + * // properly associating it with the `label` and `help` elements. + * const { baseControlProps, controlProps } = useBaseControlProps( baseProps ); * - * // The `id` prop is necessary to accessibly associate the label with the textarea - * const MyBaseControl = () => ( - * - * + * + * ); * ); + * ``` */ export const BaseControl = ( { __nextHasNoMarginBottom = false, diff --git a/packages/components/src/base-control/stories/index.tsx b/packages/components/src/base-control/stories/index.tsx index edc850f760745f..b1b64caec0c3b0 100644 --- a/packages/components/src/base-control/stories/index.tsx +++ b/packages/components/src/base-control/stories/index.tsx @@ -6,7 +6,7 @@ import type { ComponentMeta, ComponentStory } from '@storybook/react'; /** * Internal dependencies */ -import BaseControl from '..'; +import BaseControl, { useBaseControlProps } from '..'; import Button from '../../button'; const meta: ComponentMeta< typeof BaseControl > = { @@ -24,13 +24,14 @@ const meta: ComponentMeta< typeof BaseControl > = { }; export default meta; -const BaseControlWithTextarea: ComponentStory< typeof BaseControl > = ( { - id, - ...props -} ) => { +const BaseControlWithTextarea: ComponentStory< typeof BaseControl > = ( + props +) => { + const { baseControlProps, controlProps } = useBaseControlProps( props ); + return ( - -