From f9ba10d76006583c81bb5394ce144f33b2e52bb4 Mon Sep 17 00:00:00 2001 From: Gutenberg Repository Automation Date: Mon, 29 Aug 2022 15:27:11 +0000 Subject: [PATCH] Merge changes published in the Gutenberg plugin "trunk" branch --- .eslintrc.js | 6 +- .github/workflows/stale-issue-flaky-test.yml | 19 + .github/workflows/stale-issue-mark-stale.yml | 1 + .stylelintignore | 3 + bin/plugin/commands/changelog.js | 13 +- changelog.txt | 415 ++++ docs/how-to-guides/format-api.md | 44 +- docs/reference-guides/core-blocks.md | 14 +- docs/reference-guides/data/data-core.md | 122 +- .../theme-json-reference/theme-json-living.md | 9 +- gutenberg.php | 2 +- lib/block-supports/border.php | 14 +- lib/block-supports/colors.php | 5 +- lib/compat/wordpress-6.0/block-patterns.php | 4 - .../wordpress-6.1/block-template-utils.php | 15 +- .../wordpress-6.1/class-wp-theme-json-6-1.php | 13 +- lib/compat/wordpress-6.1/rest-api.php | 32 + .../block-editor-settings-mobile.php | 4 +- lib/experimental/blocks.php | 27 - lib/experiments-page.php | 12 - lib/load.php | 1 - package-lock.json | 74 +- package.json | 2 +- packages/a11y/CHANGELOG.md | 2 + packages/a11y/package.json | 2 +- packages/annotations/CHANGELOG.md | 2 + packages/annotations/package.json | 2 +- packages/api-fetch/CHANGELOG.md | 2 + packages/api-fetch/package.json | 2 +- packages/autop/CHANGELOG.md | 2 + packages/autop/package.json | 2 +- .../CHANGELOG.md | 2 +- .../package.json | 2 +- packages/babel-plugin-makepot/CHANGELOG.md | 2 +- packages/babel-plugin-makepot/package.json | 2 +- packages/babel-preset-default/CHANGELOG.md | 2 +- packages/babel-preset-default/package.json | 2 +- packages/base-styles/_mixins.scss | 8 +- packages/base-styles/package.json | 2 +- packages/blob/CHANGELOG.md | 2 + packages/blob/package.json | 2 +- packages/block-directory/CHANGELOG.md | 2 + packages/block-directory/package.json | 2 +- packages/block-editor/CHANGELOG.md | 2 + packages/block-editor/package.json | 2 +- .../block-list/block-list-compact.native.js | 62 + .../src/components/block-list/style.scss | 35 +- .../use-focus-first-element.js | 2 +- .../src/components/block-popover/index.js | 37 +- .../components/block-switcher/test/index.js | 3 +- .../block-tools/selected-block-popover.js | 7 + .../use-block-toolbar-popover-props.js | 123 + .../button-block-appender/style.scss | 23 + .../components/colors-gradients/control.js | 1 + .../components/inner-blocks/index.native.js | 6 +- .../src/components/rich-text/index.js | 9 + .../spacing-input-control.js | 5 + .../spacing-sizes-control/test/utils.js | 26 + .../components/spacing-sizes-control/utils.js | 45 +- .../text-decoration-control/index.js | 71 +- .../text-decoration-control/stories/index.js | 37 + .../text-decoration-control/style.scss | 18 - .../text-transform-control/index.js | 54 +- .../text-transform-control/stories/index.js | 37 + .../text-transform-control/style.scss | 18 - .../src/components/writing-flow/index.js | 2 + .../components/writing-flow/use-select-all.js | 23 +- packages/block-editor/src/hooks/align.js | 4 +- packages/block-editor/src/hooks/anchor.js | 7 +- packages/block-editor/src/hooks/dimensions.js | 15 +- .../block-editor/src/hooks/font-appearance.js | 1 + .../block-editor/src/hooks/font-family.js | 2 + packages/block-editor/src/hooks/font-size.js | 2 + packages/block-editor/src/hooks/gap.js | 68 +- packages/block-editor/src/hooks/layout.js | 4 +- .../block-editor/src/hooks/letter-spacing.js | 1 + .../block-editor/src/hooks/line-height.js | 1 + packages/block-editor/src/hooks/lock.js | 7 +- packages/block-editor/src/hooks/test/gap.js | 38 +- .../block-editor/src/hooks/text-decoration.js | 1 + .../block-editor/src/hooks/text-transform.js | 1 + .../block-editor/src/hooks/typography.scss | 6 - .../block-editor/src/layouts/constrained.js | 21 +- packages/block-editor/src/layouts/flow.js | 31 +- packages/block-editor/src/style.scss | 2 - packages/block-library/CHANGELOG.md | 2 +- packages/block-library/package.json | 2 +- packages/block-library/src/button/style.scss | 9 +- packages/block-library/src/code/editor.scss | 3 + .../block-library/src/columns/transforms.js | 20 +- .../src/cover/edit/block-controls.js | 18 +- packages/block-library/src/cover/editor.scss | 4 - packages/block-library/src/editor.scss | 1 + .../block-library/src/gallery/gap-styles.js | 16 +- packages/block-library/src/gallery/index.php | 25 +- packages/block-library/src/group/editor.scss | 29 +- .../block-library/src/home-link/block.json | 15 +- packages/block-library/src/image/edit.js | 6 +- .../src/image/use-client-width.js | 2 +- packages/block-library/src/index.js | 5 +- packages/block-library/src/index.native.js | 15 +- .../block-library/src/latest-posts/block.json | 15 +- .../block-library/src/list-item/block.json | 1 - .../src/list-item/edit.native.js | 34 +- .../src/list-item/hooks/use-copy.js | 2 + .../src/list-item/hooks/use-space.js | 12 +- .../src/list-item/style.native.scss | 8 + packages/block-library/src/list/block.json | 4 + packages/block-library/src/list/deprecated.js | 72 +- packages/block-library/src/list/edit.js | 279 +-- packages/block-library/src/list/index.js | 49 +- packages/block-library/src/list/save.js | 7 +- .../src/list/{v2 => }/tag-name.js | 0 .../src/list/{v2 => }/tag-name.native.js | 0 .../test/__snapshots__/edit.native.js.snap | 16 +- .../src/list/test/edit.native.js | 154 -- .../block-library/src/list/test/migrate.js | 158 -- packages/block-library/src/list/transforms.js | 168 +- .../src/list/{v2/migrate.js => utils.js} | 4 +- .../block-library/src/list/v2/deprecated.js | 89 - packages/block-library/src/list/v2/edit.js | 192 -- packages/block-library/src/list/v2/index.js | 22 - packages/block-library/src/list/v2/save.js | 18 - .../block-library/src/list/v2/transforms.js | 120 - .../block-library/src/media-text/block.json | 4 + .../block-library/src/media-text/style.scss | 2 + .../block-library/src/navigation/block.json | 1 + .../src/navigation/edit/index.js | 18 +- .../src/navigation/edit/responsive-wrapper.js | 11 +- .../navigation/edit/unsaved-inner-blocks.js | 38 +- .../block-library/src/navigation/editor.scss | 25 +- .../block-library/src/navigation/index.php | 21 +- .../block-library/src/navigation/style.scss | 13 +- .../navigation/test/use-navigation-menu.js | 19 +- .../block-library/src/paragraph/block.json | 2 + .../block-library/src/post-excerpt/block.json | 6 +- packages/block-library/src/post-terms/edit.js | 1 - .../src/post-terms/use-post-terms.js | 25 +- .../src/query-no-results/block.json | 13 + .../src/query-pagination-numbers/block.json | 6 +- .../block-library/src/query-title/block.json | 1 + packages/block-library/src/quote/block.json | 6 +- .../block-library/src/social-links/block.json | 9 + .../block-library/src/social-links/edit.js | 67 +- .../src/table-of-contents/block.json | 13 + .../src/term-description/block.json | 6 + packages/block-library/src/verse/block.json | 1 + .../CHANGELOG.md | 2 + .../package.json | 2 +- .../CHANGELOG.md | 2 + .../package.json | 2 +- packages/blocks/CHANGELOG.md | 6 + packages/blocks/package.json | 4 +- packages/blocks/src/api/constants.js | 1 + packages/blocks/src/api/factory.js | 52 +- .../raw-handling/figure-content-reducer.js | 9 +- .../api/raw-handling/get-raw-transforms.js | 13 +- .../src/api/raw-handling/paste-handler.js | 5 +- .../raw-handling/phrasing-content-reducer.js | 7 +- .../api/raw-handling/shortcode-converter.js | 35 +- packages/blocks/src/api/registration.js | 20 +- packages/blocks/src/api/serializer.js | 20 +- packages/blocks/src/api/templates.js | 29 +- packages/blocks/src/api/utils.js | 4 +- packages/blocks/src/store/actions.js | 2 +- packages/browserslist-config/CHANGELOG.md | 2 +- packages/browserslist-config/package.json | 2 +- packages/components/CHANGELOG.md | 28 +- packages/components/CONTRIBUTING.md | 24 +- packages/components/package.json | 2 +- .../styles/alignment-matrix-control-styles.js | 1 + .../src/autocomplete/get-default-use-items.js | 7 +- packages/components/src/autocomplete/index.js | 3 +- .../card-body/{component.js => component.tsx} | 22 +- .../src/card/card-body/{hook.js => hook.ts} | 10 +- .../src/card/card-body/{index.js => index.ts} | 0 .../{component.js => component.tsx} | 26 +- .../card/card-divider/{hook.js => hook.ts} | 10 +- .../card/card-divider/{index.js => index.ts} | 0 .../{component.js => component.tsx} | 22 +- .../src/card/card-footer/{hook.js => hook.ts} | 10 +- .../card/card-footer/{index.js => index.ts} | 0 .../{component.js => component.tsx} | 22 +- .../src/card/card-header/{hook.js => hook.ts} | 10 +- .../card/card-header/{index.js => index.ts} | 0 .../{component.js => component.tsx} | 21 +- .../src/card/card-media/{hook.js => hook.ts} | 10 +- .../card/card-media/{index.js => index.ts} | 0 .../card/card/{component.js => component.tsx} | 22 +- .../src/card/card/{hook.js => hook.ts} | 22 +- .../src/card/card/{index.js => index.ts} | 0 .../src/card/{context.js => context.ts} | 0 .../src/card/{index.js => index.ts} | 0 packages/components/src/card/stories/index.js | 209 -- .../components/src/card/stories/index.tsx | 75 + .../src/card/{styles.js => styles.ts} | 0 .../{index.js.snap => index.tsx.snap} | 0 .../src/card/test/{index.js => index.tsx} | 6 +- packages/components/src/card/types.ts | 9 +- .../components/src/clipboard-button/index.js | 13 + .../components/src/color-palette/index.js | 13 +- .../components/src/color-palette/style.scss | 14 - .../test/__snapshots__/index.js.snap | 15 +- .../src/custom-gradient-picker/index.js | 12 + .../custom-gradient-picker/stories/index.js | 3 + .../src/date-time/date-time/index.tsx | 7 +- .../src/date-time/date-time/styles.ts | 9 + .../components/src/date-time/date/index.tsx | 32 +- .../components/src/date-time/date/styles.ts | 6 + .../src/date-time/date/test/index.tsx | 8 +- .../components/src/date-time/time/styles.ts | 1 + packages/components/src/drop-zone/index.js | 5 +- .../components/src/dropdown-menu/index.js | 3 +- .../src/dropdown-menu/index.native.js | 13 - .../src/dropdown-menu/test/index.js | 112 +- .../src/focal-point-picker/README.md | 9 +- .../src/focal-point-picker/controls.js | 8 +- .../src/focal-point-picker/focal-point.js | 10 +- .../components/src/focal-point-picker/grid.js | 46 +- .../src/focal-point-picker/index.js | 464 ++-- .../src/focal-point-picker/media.js | 32 +- .../styles/focal-point-picker-style.js | 13 +- .../src/focal-point-picker/test/index.js | 2 +- .../src/focal-point-picker/utils.js | 8 +- .../components/src/focusable-iframe/index.js | 5 + .../components/src/form-token-field/index.tsx | 40 +- .../src/form-token-field/test/index.js | 442 ---- .../src/form-token-field/test/index.tsx | 2106 +++++++++++++++++ .../src/form-token-field/test/lib/fixtures.js | 89 - .../test/lib/token-field-wrapper.tsx | 71 - .../components/src/gradient-picker/README.md | 9 + .../components/src/gradient-picker/index.js | 9 + .../src/gradient-picker/stories/index.js | 1 + packages/components/src/guide/index.js | 9 +- packages/components/src/guide/test/index.js | 139 +- .../components/src/guide/test/page-control.js | 40 - .../with-constrained-tabbing/index.js | 2 +- .../with-spoken-messages/index.js | 2 + .../src/isolated-event-container/index.js | 3 + .../global-styles-context/utils.native.js | 9 +- .../src/navigable-container/menu.js | 10 +- .../src/navigation/menu/menu-title-search.js | 3 +- packages/components/src/palette-edit/index.js | 24 +- .../components/src/palette-edit/style.scss | 14 +- .../components/src/placeholder/style.scss | 5 +- packages/components/src/popover/index.js | 52 +- .../components/src/popover/stories/index.js | 1 - .../components/src/text-highlight/index.tsx | 6 +- .../test/__snapshots__/index.tsx.snap | 106 +- .../toggle-group-control/component.tsx | 5 +- .../toggle-group-control/styles.ts | 5 + packages/components/src/tooltip/index.js | 6 +- packages/components/src/tree-grid/index.js | 13 +- .../src/ui/__storybook-utils/example-grid.js | 61 - .../src/ui/__storybook-utils/index.js | 1 - .../src/ui/__storybook-utils/page.js | 29 - packages/components/src/utils/keyboard.js | 28 - packages/components/src/utils/strings.ts | 11 + .../components/src/utils/test/keyboard.js | 34 - packages/components/tsconfig.json | 121 +- packages/compose/CHANGELOG.md | 2 + packages/compose/package.json | 2 +- packages/core-data/CHANGELOG.md | 6 +- packages/core-data/README.md | 134 +- packages/core-data/package.json | 3 +- packages/core-data/src/entities.js | 325 +++ packages/core-data/src/entities.ts | 550 ----- .../core-data/src/entity-types/attachment.ts | 7 +- .../core-data/src/entity-types/comment.ts | 7 +- .../core-data/src/entity-types/entities.ts | 130 - packages/core-data/src/entity-types/index.ts | 119 +- .../src/entity-types/menu-location.ts | 7 +- .../src/entity-types/nav-menu-item.ts | 7 +- .../core-data/src/entity-types/nav-menu.ts | 6 +- packages/core-data/src/entity-types/page.ts | 6 +- packages/core-data/src/entity-types/plugin.ts | 6 +- packages/core-data/src/entity-types/post.ts | 6 +- .../core-data/src/entity-types/settings.ts | 6 +- .../core-data/src/entity-types/sidebar.ts | 7 +- .../core-data/src/entity-types/taxonomy.ts | 7 +- packages/core-data/src/entity-types/theme.ts | 6 +- packages/core-data/src/entity-types/type.ts | 6 +- packages/core-data/src/entity-types/user.ts | 6 +- .../core-data/src/entity-types/widget-type.ts | 7 +- packages/core-data/src/entity-types/widget.ts | 6 +- .../src/entity-types/wp-template-part.ts | 7 +- .../core-data/src/entity-types/wp-template.ts | 7 +- .../src/hooks/test/use-entity-record.js | 42 +- .../hooks/test/use-resource-permissions.js | 4 + .../core-data/src/hooks/use-entity-record.ts | 14 +- .../core-data/src/hooks/use-entity-records.ts | 2 + .../core-data/src/hooks/use-query-select.ts | 5 +- .../src/hooks/use-resource-permissions.ts | 34 +- packages/core-data/src/index.js | 2 - packages/core-data/src/selectors.ts | 543 ++--- packages/core-data/tsconfig.json | 21 + .../CHANGELOG.md | 6 + .../block-templates/index.js.mustache | 1 + .../block-templates/template.php.mustache | 2 +- .../package.json | 2 +- .../plugin-templates/$slug.php.mustache | 15 +- packages/create-block/CHANGELOG.md | 4 +- packages/create-block/lib/index.js | 58 +- packages/create-block/lib/prompts.js | 8 - packages/create-block/lib/scaffold.js | 8 +- packages/create-block/lib/templates.js | 57 +- .../lib/templates/block/index.js.mustache | 1 + .../lib/templates/es5/$slug.php.mustache | 20 +- .../lib/templates/es5/index.js.mustache | 4 +- .../lib/templates/plugin/$slug.php.mustache | 15 +- packages/create-block/package.json | 2 +- .../package.json | 2 +- packages/customize-widgets/CHANGELOG.md | 2 + packages/customize-widgets/package.json | 2 +- packages/data-controls/CHANGELOG.md | 2 + packages/data-controls/package.json | 2 +- packages/data/CHANGELOG.md | 6 +- packages/data/README.md | 8 +- packages/data/package.json | 4 +- .../components/use-dispatch/use-dispatch.js | 22 +- packages/data/src/index.js | 5 +- .../data/src/plugins/persistence/index.js | 2 +- .../data/src/redux-store/metadata/reducer.ts | 4 +- packages/data/src/types.ts | 32 +- packages/data/tsconfig.json | 4 +- packages/date/CHANGELOG.md | 2 + packages/date/package.json | 2 +- packages/date/src/index.js | 3 +- .../CHANGELOG.md | 2 +- .../package.json | 2 +- packages/deprecated/CHANGELOG.md | 2 + packages/deprecated/package.json | 2 +- packages/docgen/lib/markdown/formatter.js | 19 + packages/docgen/package.json | 2 +- packages/docgen/test/get-jsdoc-from-token.js | 3 +- packages/dom-ready/CHANGELOG.md | 2 + packages/dom-ready/package.json | 2 +- packages/dom/CHANGELOG.md | 2 + packages/dom/package.json | 4 +- .../editor/open-document-settings-sidebar.ts | 22 +- .../src/request-utils/index.ts | 3 + .../src/request-utils/users.ts | 121 + packages/e2e-test-utils/CHANGELOG.md | 2 +- packages/e2e-test-utils/package.json | 2 +- packages/e2e-tests/CHANGELOG.md | 2 +- packages/e2e-tests/package.json | 2 +- .../specs/editor/blocks/navigation.test.js | 10 +- .../__snapshots__/cpt-locking.test.js.snap | 4 +- .../inner-blocks-allowed-blocks.test.js | 3 + .../plugins/register-block-type-hooks.test.js | 32 - .../__snapshots__/block-deletion.test.js.snap | 4 +- .../duplicating-blocks.test.js.snap | 21 - ...ep-styles-on-block-transforms.test.js.snap | 6 - .../multi-block-selection.test.js.snap | 26 +- .../__snapshots__/rich-text.test.js.snap | 24 +- .../__snapshots__/writing-flow.test.js.snap | 14 +- .../editor/various/block-switcher.test.js | 14 +- .../editor/various/duplicating-blocks.test.js | 47 - .../editor/various/inserting-blocks.test.js | 4 + .../keep-styles-on-block-transforms.test.js | 17 - .../various/multi-block-selection.test.js | 8 + .../specs/editor/various/rich-text.test.js | 15 +- .../editor/various/splitting-merging.test.js | 9 +- .../various/toolbar-roving-tabindex.test.js | 3 +- .../specs/editor/various/writing-flow.test.js | 12 +- packages/edit-post/CHANGELOG.md | 2 + packages/edit-post/package.json | 2 +- packages/edit-site/CHANGELOG.md | 2 + packages/edit-site/package.json | 2 +- .../add-new-template/new-template.js | 2 + .../src/components/add-new-template/utils.js | 102 +- .../global-styles/dimensions-panel.js | 83 +- .../src/components/global-styles/hooks.js | 8 +- .../src/components/global-styles/preview.js | 4 +- .../src/components/global-styles/utils.js | 2 +- packages/edit-widgets/CHANGELOG.md | 2 + packages/edit-widgets/package.json | 2 +- packages/editor/CHANGELOG.md | 2 + packages/editor/package.json | 2 +- .../components/post-featured-image/index.js | 6 +- .../editor/src/components/post-url/label.js | 2 +- .../theme-support-check/test/index.js | 28 +- packages/element/CHANGELOG.md | 6 + packages/element/package.json | 4 +- packages/element/src/serialize.js | 2 +- packages/element/src/test/platform.js | 10 +- packages/env/package.json | 2 +- packages/escape-html/CHANGELOG.md | 2 + packages/escape-html/package.json | 2 +- packages/eslint-plugin/CHANGELOG.md | 2 +- packages/eslint-plugin/package.json | 2 +- packages/format-library/CHANGELOG.md | 2 + packages/format-library/package.json | 2 +- packages/hooks/CHANGELOG.md | 2 + packages/hooks/package.json | 2 +- packages/html-entities/CHANGELOG.md | 2 + packages/html-entities/package.json | 2 +- packages/i18n/CHANGELOG.md | 2 + packages/i18n/package.json | 3 +- packages/i18n/tools/pot-to-php.js | 15 +- packages/icons/CHANGELOG.md | 2 + packages/icons/package.json | 2 +- packages/interface/CHANGELOG.md | 2 + packages/interface/package.json | 2 +- packages/is-shallow-equal/CHANGELOG.md | 2 + packages/is-shallow-equal/package.json | 2 +- packages/jest-console/CHANGELOG.md | 2 +- packages/jest-console/package.json | 2 +- packages/jest-preset-default/CHANGELOG.md | 6 +- packages/jest-preset-default/jest-preset.js | 1 - packages/jest-preset-default/package.json | 2 +- packages/jest-puppeteer-axe/CHANGELOG.md | 2 +- packages/jest-puppeteer-axe/package.json | 2 +- packages/keyboard-shortcuts/CHANGELOG.md | 2 + packages/keyboard-shortcuts/package.json | 2 +- packages/keycodes/CHANGELOG.md | 2 + packages/keycodes/package.json | 2 +- packages/lazy-import/package.json | 2 +- .../package.json | 2 +- packages/list-reusable-blocks/CHANGELOG.md | 2 + packages/list-reusable-blocks/package.json | 2 +- packages/media-utils/CHANGELOG.md | 2 + packages/media-utils/package.json | 2 +- packages/notices/CHANGELOG.md | 2 + packages/notices/package.json | 2 +- .../npm-package-json-lint-config/CHANGELOG.md | 6 + .../npm-package-json-lint-config/package.json | 2 +- .../test/index.test.js | 11 +- packages/nux/CHANGELOG.md | 2 + packages/nux/package.json | 2 +- packages/plugins/CHANGELOG.md | 2 + packages/plugins/package.json | 2 +- packages/postcss-plugins-preset/CHANGELOG.md | 2 +- packages/postcss-plugins-preset/package.json | 2 +- packages/postcss-themes/CHANGELOG.md | 2 +- packages/postcss-themes/package.json | 2 +- packages/preferences-persistence/CHANGELOG.md | 2 + packages/preferences-persistence/package.json | 2 +- packages/preferences/CHANGELOG.md | 2 + packages/preferences/package.json | 2 +- packages/prettier-config/CHANGELOG.md | 6 +- packages/prettier-config/package.json | 2 +- packages/prettier-config/test/index.js | 8 +- packages/primitives/CHANGELOG.md | 2 + packages/primitives/package.json | 2 +- packages/priority-queue/CHANGELOG.md | 2 + packages/priority-queue/package.json | 2 +- .../package.json | 2 +- packages/react-i18n/CHANGELOG.md | 2 + packages/react-i18n/package.json | 2 +- packages/react-native-aztec/package.json | 2 +- packages/react-native-aztec/src/AztecView.js | 34 +- packages/react-native-bridge/package.json | 2 +- packages/react-native-editor/CHANGELOG.md | 7 + .../gutenberg-editor-lists-@canary.test.js | 4 +- .../gutenberg-editor-lists-end.test.js | 2 +- .../gutenberg-editor-lists.test.js | 2 +- .../__device-tests__/helpers/test-data.js | 4 +- packages/react-native-editor/ios/Podfile.lock | 8 +- packages/react-native-editor/package.json | 2 +- .../react-native-editor/src/initial-html.js | 190 +- packages/react-native-editor/src/setup.js | 9 +- .../src/test/index.test.js | 2 - .../CHANGELOG.md | 2 +- .../package.json | 2 +- packages/redux-routine/CHANGELOG.md | 6 + packages/redux-routine/package.json | 4 +- packages/redux-routine/src/is-action.js | 2 +- packages/reusable-blocks/CHANGELOG.md | 2 + packages/reusable-blocks/package.json | 2 +- packages/rich-text/CHANGELOG.md | 2 + packages/rich-text/package.json | 2 +- .../rich-text/src/can-indent-list-items.js | 31 - .../rich-text/src/can-outdent-list-items.js | 20 - packages/rich-text/src/change-list-type.js | 65 - packages/rich-text/src/component/index.js | 6 - .../use-indent-list-item-on-space.js | 63 - .../rich-text/src/get-last-child-index.js | 42 - packages/rich-text/src/get-line-index.js | 28 - .../rich-text/src/get-parent-line-index.js | 35 - packages/rich-text/src/indent-list-items.js | 90 - packages/rich-text/src/index.js | 8 - packages/rich-text/src/is-active-list-type.js | 31 - .../rich-text/src/is-list-root-selected.js | 23 - packages/rich-text/src/outdent-list-items.js | 59 - .../src/test/can-indent-list-items.js | 51 - .../src/test/can-outdent-list-items.js | 39 - .../rich-text/src/test/change-list-type.js | 102 - .../src/test/get-last-child-index.js | 75 - .../src/test/get-parent-line-index.js | 63 - .../rich-text/src/test/indent-list-items.js | 163 -- .../rich-text/src/test/outdent-list-items.js | 150 -- packages/scripts/CHANGELOG.md | 6 +- packages/scripts/package.json | 2 +- packages/server-side-render/CHANGELOG.md | 2 + packages/server-side-render/package.json | 2 +- packages/shortcode/CHANGELOG.md | 2 + packages/shortcode/package.json | 2 +- packages/style-engine/CHANGELOG.md | 2 + packages/style-engine/package.json | 2 +- packages/style-engine/src/styles/index.ts | 2 + .../style-engine/src/styles/shadow/index.ts | 14 + packages/stylelint-config/CHANGELOG.md | 2 +- packages/stylelint-config/package.json | 2 +- packages/token-list/CHANGELOG.md | 2 + packages/token-list/package.json | 2 +- packages/url/CHANGELOG.md | 2 + packages/url/package.json | 2 +- packages/viewport/CHANGELOG.md | 2 + packages/viewport/package.json | 2 +- packages/warning/CHANGELOG.md | 2 + packages/warning/package.json | 2 +- packages/widgets/CHANGELOG.md | 2 + packages/widgets/package.json | 2 +- packages/wordcount/CHANGELOG.md | 2 + packages/wordcount/package.json | 2 +- schemas/json/theme.json | 15 +- test/e2e/specs/editor/blocks/avatar.spec.js | 71 + test/e2e/specs/editor/blocks/list.spec.js | 424 +++- .../plugins/register-block-type-hooks.spec.js | 29 + .../editor/various/duplicating-blocks.spec.js | 64 + .../blocks-raw-handling.test.js.snap | 16 +- test/integration/blocks-raw-handling.test.js | 32 +- .../fixtures/blocks/core__list-item.html | 3 + .../fixtures/blocks/core__list-item.json | 10 + .../blocks/core__list-item.parsed.json | 9 + .../blocks/core__list-item.serialized.html | 3 + .../blocks/core__list__deprecated-v0.html | 3 + .../blocks/core__list__deprecated-v0.json | 37 + .../core__list__deprecated-v0.parsed.json | 17 + .../core__list__deprecated-v0.serialized.html | 13 + .../blocks/core__list__deprecated-v1.html | 6 +- .../blocks/core__list__deprecated-v1.json | 54 +- .../core__list__deprecated-v1.parsed.json | 12 +- .../core__list__deprecated-v1.serialized.html | 26 +- .../fixtures/blocks/core__list__ul.html | 28 +- .../fixtures/blocks/core__list__ul.json | 53 +- .../blocks/core__list__ul.parsed.json | 67 +- .../blocks/core__list__ul.serialized.html | 24 +- .../fixtures/documents/apple-out.html | 28 +- .../fixtures/documents/evernote-out.html | 34 +- .../fixtures/documents/google-docs-out.html | 28 +- .../google-docs-with-comments-out.html | 28 +- .../fixtures/documents/markdown-out.html | 28 +- .../documents/ms-word-online-out.html | 30 +- .../fixtures/documents/ms-word-out.html | 28 +- test/native/__mocks__/styleMock.js | 3 + test/native/jest.config.js | 2 +- tools/webpack/blocks.js | 12 +- tsconfig.json | 1 + 550 files changed, 8068 insertions(+), 6764 deletions(-) create mode 100644 .github/workflows/stale-issue-flaky-test.yml delete mode 100644 lib/experimental/blocks.php create mode 100644 packages/block-editor/src/components/block-list/block-list-compact.native.js create mode 100644 packages/block-editor/src/components/block-tools/use-block-toolbar-popover-props.js create mode 100644 packages/block-editor/src/components/text-decoration-control/stories/index.js delete mode 100644 packages/block-editor/src/components/text-decoration-control/style.scss create mode 100644 packages/block-editor/src/components/text-transform-control/stories/index.js delete mode 100644 packages/block-editor/src/components/text-transform-control/style.scss create mode 100644 packages/block-library/src/code/editor.scss rename packages/block-library/src/list/{v2 => }/tag-name.js (100%) rename packages/block-library/src/list/{v2 => }/tag-name.native.js (100%) delete mode 100644 packages/block-library/src/list/test/migrate.js rename packages/block-library/src/list/{v2/migrate.js => utils.js} (94%) delete mode 100644 packages/block-library/src/list/v2/deprecated.js delete mode 100644 packages/block-library/src/list/v2/edit.js delete mode 100644 packages/block-library/src/list/v2/index.js delete mode 100644 packages/block-library/src/list/v2/save.js delete mode 100644 packages/block-library/src/list/v2/transforms.js rename packages/components/src/card/card-body/{component.js => component.tsx} (60%) rename packages/components/src/card/card-body/{hook.js => hook.ts} (78%) rename packages/components/src/card/card-body/{index.js => index.ts} (100%) rename packages/components/src/card/card-divider/{component.js => component.tsx} (51%) rename packages/components/src/card/card-divider/{hook.js => hook.ts} (72%) rename packages/components/src/card/card-divider/{index.js => index.ts} (100%) rename packages/components/src/card/card-footer/{component.js => component.tsx} (51%) rename packages/components/src/card/card-footer/{hook.js => hook.ts} (79%) rename packages/components/src/card/card-footer/{index.js => index.ts} (100%) rename packages/components/src/card/card-header/{component.js => component.tsx} (51%) rename packages/components/src/card/card-header/{hook.js => hook.ts} (79%) rename packages/components/src/card/card-header/{index.js => index.ts} (100%) rename packages/components/src/card/card-media/{component.js => component.tsx} (56%) rename packages/components/src/card/card-media/{hook.js => hook.ts} (72%) rename packages/components/src/card/card-media/{index.js => index.ts} (100%) rename packages/components/src/card/card/{component.js => component.tsx} (83%) rename packages/components/src/card/card/{hook.js => hook.ts} (72%) rename packages/components/src/card/card/{index.js => index.ts} (100%) rename packages/components/src/card/{context.js => context.ts} (100%) rename packages/components/src/card/{index.js => index.ts} (100%) delete mode 100644 packages/components/src/card/stories/index.js create mode 100644 packages/components/src/card/stories/index.tsx rename packages/components/src/card/{styles.js => styles.ts} (100%) rename packages/components/src/card/test/__snapshots__/{index.js.snap => index.tsx.snap} (100%) rename packages/components/src/card/test/{index.js => index.tsx} (98%) delete mode 100644 packages/components/src/form-token-field/test/index.js create mode 100644 packages/components/src/form-token-field/test/index.tsx delete mode 100644 packages/components/src/form-token-field/test/lib/fixtures.js delete mode 100644 packages/components/src/form-token-field/test/lib/token-field-wrapper.tsx delete mode 100644 packages/components/src/guide/test/page-control.js delete mode 100644 packages/components/src/ui/__storybook-utils/example-grid.js delete mode 100644 packages/components/src/ui/__storybook-utils/index.js delete mode 100644 packages/components/src/ui/__storybook-utils/page.js delete mode 100644 packages/components/src/utils/keyboard.js delete mode 100644 packages/components/src/utils/test/keyboard.js create mode 100644 packages/core-data/src/entities.js delete mode 100644 packages/core-data/src/entities.ts delete mode 100644 packages/core-data/src/entity-types/entities.ts create mode 100644 packages/core-data/tsconfig.json create mode 100644 packages/e2e-test-utils-playwright/src/request-utils/users.ts delete mode 100644 packages/e2e-tests/specs/editor/plugins/register-block-type-hooks.test.js delete mode 100644 packages/e2e-tests/specs/editor/various/__snapshots__/duplicating-blocks.test.js.snap delete mode 100644 packages/e2e-tests/specs/editor/various/duplicating-blocks.test.js delete mode 100644 packages/rich-text/src/can-indent-list-items.js delete mode 100644 packages/rich-text/src/can-outdent-list-items.js delete mode 100644 packages/rich-text/src/change-list-type.js delete mode 100644 packages/rich-text/src/component/use-indent-list-item-on-space.js delete mode 100644 packages/rich-text/src/get-last-child-index.js delete mode 100644 packages/rich-text/src/get-line-index.js delete mode 100644 packages/rich-text/src/get-parent-line-index.js delete mode 100644 packages/rich-text/src/indent-list-items.js delete mode 100644 packages/rich-text/src/is-active-list-type.js delete mode 100644 packages/rich-text/src/is-list-root-selected.js delete mode 100644 packages/rich-text/src/outdent-list-items.js delete mode 100644 packages/rich-text/src/test/can-indent-list-items.js delete mode 100644 packages/rich-text/src/test/can-outdent-list-items.js delete mode 100644 packages/rich-text/src/test/change-list-type.js delete mode 100644 packages/rich-text/src/test/get-last-child-index.js delete mode 100644 packages/rich-text/src/test/get-parent-line-index.js delete mode 100644 packages/rich-text/src/test/indent-list-items.js delete mode 100644 packages/rich-text/src/test/outdent-list-items.js create mode 100644 packages/style-engine/src/styles/shadow/index.ts create mode 100644 test/e2e/specs/editor/blocks/avatar.spec.js create mode 100644 test/e2e/specs/editor/plugins/register-block-type-hooks.spec.js create mode 100644 test/e2e/specs/editor/various/duplicating-blocks.spec.js create mode 100644 test/integration/fixtures/blocks/core__list-item.html create mode 100644 test/integration/fixtures/blocks/core__list-item.json create mode 100644 test/integration/fixtures/blocks/core__list-item.parsed.json create mode 100644 test/integration/fixtures/blocks/core__list-item.serialized.html create mode 100644 test/integration/fixtures/blocks/core__list__deprecated-v0.html create mode 100644 test/integration/fixtures/blocks/core__list__deprecated-v0.json create mode 100644 test/integration/fixtures/blocks/core__list__deprecated-v0.parsed.json create mode 100644 test/integration/fixtures/blocks/core__list__deprecated-v0.serialized.html diff --git a/.eslintrc.js b/.eslintrc.js index 725c6e3023708..4e685f9ab3cb3 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,7 +1,6 @@ /** * External dependencies */ -const { escapeRegExp } = require( 'lodash' ); const glob = require( 'glob' ).sync; const { join } = require( 'path' ); @@ -17,7 +16,8 @@ const { version } = require( './package' ); * @type {string} */ const majorMinorRegExp = - escapeRegExp( version.replace( /\.\d+$/, '' ) ) + '(\\.\\d+)?'; + version.replace( /\.\d+$/, '' ).replace( /[\\^$.*+?()[\]{}|]/g, '\\$&' ) + + '(\\.\\d+)?'; /** * The list of patterns matching files used only for development purposes. @@ -94,6 +94,7 @@ module.exports = { 'differenceWith', 'dropRight', 'each', + 'escapeRegExp', 'extend', 'findIndex', 'findKey', @@ -102,6 +103,7 @@ module.exports = { 'flatten', 'flattenDeep', 'fromPairs', + 'has', 'identity', 'invoke', 'isArray', diff --git a/.github/workflows/stale-issue-flaky-test.yml b/.github/workflows/stale-issue-flaky-test.yml new file mode 100644 index 0000000000000..c17935e2eb144 --- /dev/null +++ b/.github/workflows/stale-issue-flaky-test.yml @@ -0,0 +1,19 @@ +name: 'Mark old flaky tests issues as stale' +on: + schedule: + - cron: '20 1 * * *' + +jobs: + stale: + runs-on: ubuntu-latest + if: ${{ github.repository == 'WordPress/gutenberg' }} + + steps: + - uses: actions/stale@996798eb71ef485dc4c7b4d3285842d714040c4a # v3.0.17 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + stale-issue-message: 'This issue has gone 30 days without any activity.' + days-before-stale: 30 + days-before-close: 1 + only-labels: '[Type] Flaky Test' + stale-issue-label: '[Status] Stale' diff --git a/.github/workflows/stale-issue-mark-stale.yml b/.github/workflows/stale-issue-mark-stale.yml index e0ebd50ec32c0..28154b35a0b70 100644 --- a/.github/workflows/stale-issue-mark-stale.yml +++ b/.github/workflows/stale-issue-mark-stale.yml @@ -12,6 +12,7 @@ jobs: - uses: actions/stale@996798eb71ef485dc4c7b4d3285842d714040c4a # v3.0.17 with: repo-token: ${{ secrets.GITHUB_TOKEN }} + stale-issue-message: "Hi,\nThis issue has gone 30 days without any activity. This means it is time for a check-in to make sure it is still relevant. If you are still experiencing this issue with the latest versions, you can help the project by responding to confirm the problem and by providing any updated reproduction steps.\nThanks for helping out." days-before-stale: 30 days-before-close: -1 only-labels: 'Needs Testing' diff --git a/.stylelintignore b/.stylelintignore index 06bb87c852b4a..801a210d64800 100644 --- a/.stylelintignore +++ b/.stylelintignore @@ -1 +1,4 @@ +build +build-style +node_modules packages/stylelint-config/test diff --git a/bin/plugin/commands/changelog.js b/bin/plugin/commands/changelog.js index 520e9c60a29e1..7c8ad778d3cbf 100644 --- a/bin/plugin/commands/changelog.js +++ b/bin/plugin/commands/changelog.js @@ -1,7 +1,7 @@ /** * External dependencies */ -const { groupBy, escapeRegExp, flow } = require( 'lodash' ); +const { groupBy, flow } = require( 'lodash' ); const Octokit = require( '@octokit/rest' ); const { sprintf } = require( 'sprintf-js' ); const semver = require( 'semver' ); @@ -185,6 +185,17 @@ const REWORD_TERMS = { docs: 'documentation', }; +/** + * Escapes the RegExp special characters. + * + * @param {string} string Input string. + * + * @return {string} Regex-escaped string. + */ +function escapeRegExp( string ) { + return string.replace( /[\\^$.*+?()[\]{}|]/g, '\\$&' ); +} + /** * Returns candidates based on whether the given labels * are part of the allowed list. diff --git a/changelog.txt b/changelog.txt index e10d9c84a402d..094dd5f0b2ec3 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,420 @@ == Changelog == += 14.0.0-rc.2 = + + + +## Changelog + +### Enhancements + +#### Block Library +- List: Use nested blocks. ([42711](https://github.com/WordPress/gutenberg/pull/42711)) + + +## First time contributors + +The following PRs were merged by first time contributors: + + + +## Contributors + +The following contributors merged PRs in this release: + +@ellatrix + + += 14.0.0-rc.1 = + + + +## Changelog + +### Enhancements + +- Add optional capture group to URL regex in wp-env. ([43200](https://github.com/WordPress/gutenberg/pull/43200)) +- Core Data: Add canRead to useResourcePermissions. ([43484](https://github.com/WordPress/gutenberg/pull/43484)) +- Element: Remove `enzyme` from `platform` test. ([43531](https://github.com/WordPress/gutenberg/pull/43531)) +- [Create-block] Add `--variant` flag to allow creation of different block type variants. ([41289](https://github.com/WordPress/gutenberg/pull/41289)) +- Remove duplicated 'import' comments. ([43478](https://github.com/WordPress/gutenberg/pull/43478)) +- Use str_starts_with. ([43371](https://github.com/WordPress/gutenberg/pull/43371)) +- [Create Block] Adding a `--no-plugin` flag to scaffold only block files. ([41642](https://github.com/WordPress/gutenberg/pull/41642)) +- Customize widgets: Fix top contents cutoff in keyboard shortcuts. ([43391](https://github.com/WordPress/gutenberg/pull/43391)) + +#### Design Tools +- Add font family and text-decoration typography supports to paragraph blocks. ([39642](https://github.com/WordPress/gutenberg/pull/39642)) +- BlockGap: Add support for spacing presets. ([43296](https://github.com/WordPress/gutenberg/pull/43296)) +- Categories List: Add typography support. ([43254](https://github.com/WordPress/gutenberg/pull/43254)) +- Code Block: Add missing typography supports to code block. ([43255](https://github.com/WordPress/gutenberg/pull/43255)) +- Column Block: Adopt typography supports. ([43252](https://github.com/WordPress/gutenberg/pull/43252)) +- Columns Block: Add typography supports. ([43253](https://github.com/WordPress/gutenberg/pull/43253)) +- Comment Author Name: Add missing typography support. ([43256](https://github.com/WordPress/gutenberg/pull/43256)) +- Comment Date: Add missing typography support. ([43262](https://github.com/WordPress/gutenberg/pull/43262)) +- Comment Edit Link: Add missing typography support. ([43263](https://github.com/WordPress/gutenberg/pull/43263)) +- Comment Template: Adopt typography supports. ([43266](https://github.com/WordPress/gutenberg/pull/43266)) +- Comments Content: Add missing typography support. ([43257](https://github.com/WordPress/gutenberg/pull/43257)) +- Comments Pagination Next: Add missing typography supports. ([43288](https://github.com/WordPress/gutenberg/pull/43288)) +- Comments Pagination Numbers: Add typography support. ([43289](https://github.com/WordPress/gutenberg/pull/43289)) +- Comments Pagination Previous: Add missing typography supports. ([43290](https://github.com/WordPress/gutenberg/pull/43290)) +- Comments Pagination: Add typography support. ([43287](https://github.com/WordPress/gutenberg/pull/43287)) +- Comments Reply Link: Add missing typography support. ([43264](https://github.com/WordPress/gutenberg/pull/43264)) +- Comments Title: Add missing typography supports. ([43291](https://github.com/WordPress/gutenberg/pull/43291)) +- Comments: Add typography support. ([43286](https://github.com/WordPress/gutenberg/pull/43286)) +- Cover: Add typography supports. ([43298](https://github.com/WordPress/gutenberg/pull/43298)) +- Gallery: Add background color block supports. ([43294](https://github.com/WordPress/gutenberg/pull/43294)) +- Group: Add missing typography supports. ([43308](https://github.com/WordPress/gutenberg/pull/43308)) +- Heading: Add padding support. ([43454](https://github.com/WordPress/gutenberg/pull/43454)) +- List: Add spacing block supports. ([43402](https://github.com/WordPress/gutenberg/pull/43402)) +- Media & Text: Add spacing block supports. ([43456](https://github.com/WordPress/gutenberg/pull/43456)) +- Media & Text: Add typography support. ([43314](https://github.com/WordPress/gutenberg/pull/43314)) +- Navigation: Add missing typography support. ([43542](https://github.com/WordPress/gutenberg/pull/43542)) +- Paragraph: Add spacing block supports. ([43455](https://github.com/WordPress/gutenberg/pull/43455)) +- Post Author Biography: Add missing typography support. ([43318](https://github.com/WordPress/gutenberg/pull/43318)) +- Post Author Name: Add missing typography supports. ([43319](https://github.com/WordPress/gutenberg/pull/43319)) +- Post Author: Add missing typography supports. ([43317](https://github.com/WordPress/gutenberg/pull/43317)) +- Post Comments Count: Add missing typography supports. ([43321](https://github.com/WordPress/gutenberg/pull/43321)) +- Post Comments Link: Add missing typography supports. ([43338](https://github.com/WordPress/gutenberg/pull/43338)) +- Post Date: Add missing typography supports. ([43340](https://github.com/WordPress/gutenberg/pull/43340)) +- Post Date: Add spacing support. ([43406](https://github.com/WordPress/gutenberg/pull/43406)) +- Post Featured Image: Add border support applied to inner img. ([42847](https://github.com/WordPress/gutenberg/pull/42847)) +- Post Navigation Link: Add missing typography supports. ([43344](https://github.com/WordPress/gutenberg/pull/43344)) +- Post Template: Add typography supports. ([43342](https://github.com/WordPress/gutenberg/pull/43342)) +- Post Terms: Add missing typography supports. ([43343](https://github.com/WordPress/gutenberg/pull/43343)) +- Post Title: Add padding support. ([43457](https://github.com/WordPress/gutenberg/pull/43457)) +- Preformatted: Add missing typography supports. ([43345](https://github.com/WordPress/gutenberg/pull/43345)) +- Pullquote: Add missing typography supports. ([43346](https://github.com/WordPress/gutenberg/pull/43346)) +- Query Title: Add padding support. ([43458](https://github.com/WordPress/gutenberg/pull/43458)) +- Social Links: Enable alpha on color pickers. ([43453](https://github.com/WordPress/gutenberg/pull/43453)) +- Social links: Add background color block supports. ([43293](https://github.com/WordPress/gutenberg/pull/43293)) +- Spacer: Add spacing block supports. ([43366](https://github.com/WordPress/gutenberg/pull/43366)) +- Table of Contents: Add typography support. ([43509](https://github.com/WordPress/gutenberg/pull/43509)) +- Table of contents block: Add color block supports. ([43363](https://github.com/WordPress/gutenberg/pull/43363)) +- Table of contents: Add spacing supports. ([43368](https://github.com/WordPress/gutenberg/pull/43368)) +- Table: Add spacing block supports. ([43370](https://github.com/WordPress/gutenberg/pull/43370)) +- Tag cloud: Add spacing block supports. ([43367](https://github.com/WordPress/gutenberg/pull/43367)) +- Term description: Add spacing block supports. ([43372](https://github.com/WordPress/gutenberg/pull/43372)) +- Verse: Add margin support. ([43461](https://github.com/WordPress/gutenberg/pull/43461)) +- Video: Add spacing block supports. ([43365](https://github.com/WordPress/gutenberg/pull/43365)) + +#### Components +- (Custom)SelectControl: Refresh and refactor chevron. ([42962](https://github.com/WordPress/gutenberg/pull/42962)) +- Always use `screen` for `testing-library` queries. ([43152](https://github.com/WordPress/gutenberg/pull/43152)) +- Autocomplete: Use KeyboardEvent.code instead of KeyboardEvent.keyCode. ([43432](https://github.com/WordPress/gutenberg/pull/43432)) +- Card: Migrate to TypeScript. ([42941](https://github.com/WordPress/gutenberg/pull/42941)) +- ComboboxControl: Normalize hyphen-like Unicode characters to ASCII hyphens when matching search queries. ([42942](https://github.com/WordPress/gutenberg/pull/42942)) +- CustomGradientPicker: Use KeyboardEvent.code instead of KeyboardEvent.keyCode. ([43437](https://github.com/WordPress/gutenberg/pull/43437)) +- DateTimePicker: Replace react-dates and moment with useLilius and date-fns. ([43005](https://github.com/WordPress/gutenberg/pull/43005)) +- Disabled: Migrate to TypeScript. ([42708](https://github.com/WordPress/gutenberg/pull/42708)) +- FocalPointPicker: Use KeyboardEvent.code, partially refactor tests to modern RTL and user-event. ([43441](https://github.com/WordPress/gutenberg/pull/43441)) +- FontSizePicker: Add a flag to remove bottom margin. ([43062](https://github.com/WordPress/gutenberg/pull/43062)) +- FormTokenField: Add the ability to auto-select first matching suggestion for incomplete token. ([42527](https://github.com/WordPress/gutenberg/pull/42527)) +- FormTokenField: Use KeyboardEvent.code, refactor tests to model RTL and user-event. ([43442](https://github.com/WordPress/gutenberg/pull/43442)) +- Modal: Use code instead of keyCode for keyboard events. ([43429](https://github.com/WordPress/gutenberg/pull/43429)) +- Popover: Move eslint-disable comment to the correct deps array. ([43320](https://github.com/WordPress/gutenberg/pull/43320)) +- Refactor `Button` tests to `@testing-library/react`. ([42981](https://github.com/WordPress/gutenberg/pull/42981)) +- Refactor `Guide` `PageControl` tests to @testing-library/react. ([43148](https://github.com/WordPress/gutenberg/pull/43148)) +- Refactor `MenuGroup` tests to `@testing-library/react`. ([43275](https://github.com/WordPress/gutenberg/pull/43275)) +- Refactor `withSpokenMessages` tests to `@testing-library`. ([43273](https://github.com/WordPress/gutenberg/pull/43273)) +- ToggleGroupControl: Improve styling for icon options. ([43060](https://github.com/WordPress/gutenberg/pull/43060)) + +#### Block Library +- Add a setting to hide the prefix in the archive title. ([42594](https://github.com/WordPress/gutenberg/pull/42594)) +- Embed: Update Reddit icon. ([43326](https://github.com/WordPress/gutenberg/pull/43326)) +- Post date: Add option to display as the last modified date. ([42312](https://github.com/WordPress/gutenberg/pull/42312)) +- Reset focalPoint after replacing the cover image. ([42859](https://github.com/WordPress/gutenberg/pull/42859)) +- Social Link: Update Reddit icon and color to match brand guidelines. ([43325](https://github.com/WordPress/gutenberg/pull/43325)) +- Try "constrained" content width as new layout type. ([42763](https://github.com/WordPress/gutenberg/pull/42763)) +- Try: Add a clickable Group setup state. ([40664](https://github.com/WordPress/gutenberg/pull/40664)) +- Use page list instead of placeholder as fallback. ([42735](https://github.com/WordPress/gutenberg/pull/42735)) +- [Query Loop]: Honour intended post type when previewing patterns and when replacing them with patterns. ([43285](https://github.com/WordPress/gutenberg/pull/43285)) + +#### Global Styles +- BlockGap: Add axial gap option to global styles where available. ([42490](https://github.com/WordPress/gutenberg/pull/42490)) +- Pseudo-elements supports on button elements. ([43088](https://github.com/WordPress/gutenberg/pull/43088)) +- Spacing presets: Add check for 0 spacing steps. ([43105](https://github.com/WordPress/gutenberg/pull/43105)) +- Spacing presets: Add support for margins. ([43246](https://github.com/WordPress/gutenberg/pull/43246)) +- Spacing presets: Implement disabling of custom space sizes. ([43216](https://github.com/WordPress/gutenberg/pull/43216)) + +#### Post Editor +- Editor: Refactor `PostAuthorCheck` tests to `@testing-library`. ([43176](https://github.com/WordPress/gutenberg/pull/43176)) +- Editor: Refactor `ThemeSupportCheck` tests to `@testing-library/react`. ([43532](https://github.com/WordPress/gutenberg/pull/43532)) +- Editor: Refactor a few component tests to `@testing-library/react`. ([43376](https://github.com/WordPress/gutenberg/pull/43376)) + +#### Accessibility +- Block Editor: Remove `aria-selected` from `LinkPreview`. ([43279](https://github.com/WordPress/gutenberg/pull/43279)) +- Block Editor: Replace `aria-owns` with `aria-controls` in `URLInput`. ([43278](https://github.com/WordPress/gutenberg/pull/43278)) +- Separator: Disable the contrastChecker via block.json. ([43357](https://github.com/WordPress/gutenberg/pull/43357)) + +#### CSS & Styling +- Placeholder: Add blurred background to work in nested cases. ([43379](https://github.com/WordPress/gutenberg/pull/43379)) +- Placeholder: Refactor and simplify dashed placeholders used for Featured Image & Site Logo. ([43228](https://github.com/WordPress/gutenberg/pull/43228)) + +#### Testing +- Components: Refactor Placeholder tests to @testing-library/react. ([43069](https://github.com/WordPress/gutenberg/pull/43069)) +- Components: Refactor `Tooltip` tests to `@testing-library/react`. ([43061](https://github.com/WordPress/gutenberg/pull/43061)) + +#### Data Layer +- [data] Export the type for the combineReducers export. ([43516](https://github.com/WordPress/gutenberg/pull/43516)) + +#### Site Editor +- Template Part: Allow changing properties in focus mode. ([43151](https://github.com/WordPress/gutenberg/pull/43151)) + +#### Block Editor +- Refactor `LinkControl` tests to `@testing-library`. ([43147](https://github.com/WordPress/gutenberg/pull/43147)) + + +### Bug Fixes + +- Create Block: Refactor handling for template variants. ([43481](https://github.com/WordPress/gutenberg/pull/43481)) +- Docs: Fix some typos. ([43175](https://github.com/WordPress/gutenberg/pull/43175)) +- Fix no-results grammar. ([43168](https://github.com/WordPress/gutenberg/pull/43168)) +- Fix spinner causing a flash when loading site editor. ([43226](https://github.com/WordPress/gutenberg/pull/43226)) +- Image: Fix unclickable buttons. ([43361](https://github.com/WordPress/gutenberg/pull/43361)) +- Keycodes: Fix display of symbols in keyboard shortcut modal. ([43137](https://github.com/WordPress/gutenberg/pull/43137)) +- MediaReplaceFlow: Reset default LinkControl margins. ([43156](https://github.com/WordPress/gutenberg/pull/43156)) +- Post title: Fix pasting into existing content. ([43123](https://github.com/WordPress/gutenberg/pull/43123)) +- [Block Editor]: Fix block switcher label to take into account block variations. ([43309](https://github.com/WordPress/gutenberg/pull/43309)) +- [useEntityRecord] Pass the correct kind, name, and recordId to getEditedEntityRecord. ([43517](https://github.com/WordPress/gutenberg/pull/43517)) +- wp-env: Set core source to latest when null. ([43133](https://github.com/WordPress/gutenberg/pull/43133)) + +#### Block Library +- Ensure the block toolbar doesn't overlap block by modifying forcePosition and shift popover props. ([42887](https://github.com/WordPress/gutenberg/pull/42887)) +- Ensure pagination numbers have an href in block edit function. ([43354](https://github.com/WordPress/gutenberg/pull/43354)) +- Fix Post Featured Image border attributes. ([43426](https://github.com/WordPress/gutenberg/pull/43426)) +- Fix classic block converted to regular blocks when clicking new 'Edit visually' button. ([43219](https://github.com/WordPress/gutenberg/pull/43219)) +- Fix featured image being unselectable using arrow keys. ([43323](https://github.com/WordPress/gutenberg/pull/43323)) +- Fix navigation block undefined index error on frontend. ([43302](https://github.com/WordPress/gutenberg/pull/43302)) +- Gallery block: Ensure image attributes copy correctly between transforms. ([42796](https://github.com/WordPress/gutenberg/pull/42796)) +- Home Link: Fix undo trap. ([43112](https://github.com/WordPress/gutenberg/pull/43112)) +- List v2: Copy list wrapper when copying list items. ([42860](https://github.com/WordPress/gutenberg/pull/42860)) +- Navigation: Page List fix missing padding. ([43358](https://github.com/WordPress/gutenberg/pull/43358)) +- Prevent query block from looping in classic themes. ([43221](https://github.com/WordPress/gutenberg/pull/43221)) +- Pullquote block: Avoid text-align settings affecting block width and font size. ([43188](https://github.com/WordPress/gutenberg/pull/43188)) +- Pullquote block: Remove font definition from the default block styles. ([43195](https://github.com/WordPress/gutenberg/pull/43195)) +- taxonomy-controls.js: Change REST context to "view" when fetching taxonomy terms. ([43274](https://github.com/WordPress/gutenberg/pull/43274)) + +#### Components +- (Custom)SelectControl: Truncate long options. ([43301](https://github.com/WordPress/gutenberg/pull/43301)) +- AlignmentMatrixControl: Fix `width` bug. ([43482](https://github.com/WordPress/gutenberg/pull/43482)) +- ColorPalette, GradientPicker: Fix color picker popover positioning. ([42989](https://github.com/WordPress/gutenberg/pull/42989)) +- ColorPalette: Make sure "key" is unique when iterating over color entries with the same value. ([43096](https://github.com/WordPress/gutenberg/pull/43096)) +- Dropdown: Anchor popover to the dropdown wrapper (instead of the toggle). ([43377](https://github.com/WordPress/gutenberg/pull/43377)) +- Fix block toolbar offset in site editor when toggling sidebars. ([43172](https://github.com/WordPress/gutenberg/pull/43172)) +- Fix popover glitch that results in incorrect toolbar positioning in site editor. ([43267](https://github.com/WordPress/gutenberg/pull/43267)) +- Improve appearance of controls in the Global Styles Typography panel. ([43304](https://github.com/WordPress/gutenberg/pull/43304)) +- Popover: Fix and improve opening animation, use framer motion. ([43186](https://github.com/WordPress/gutenberg/pull/43186)) +- Popover: Make sure offset middleware always applies the latest frame offset values. ([43329](https://github.com/WordPress/gutenberg/pull/43329)) +- Refactor `Guide` tests to `@testing-library/react`. ([43380](https://github.com/WordPress/gutenberg/pull/43380)) + +#### Global Styles +- Check for recursive dynamic reference in the site editor. ([43166](https://github.com/WordPress/gutenberg/pull/43166)) +- Duotone: Prevent early return blocking other style generation. ([43300](https://github.com/WordPress/gutenberg/pull/43300)) +- Fix dynamic references on the site editor. ([42976](https://github.com/WordPress/gutenberg/pull/42976)) +- Fix error in handling spacing preset slugs. ([43237](https://github.com/WordPress/gutenberg/pull/43237)) +- Layout: Re-instate alignwide and alignfull in flow layout get alignments. ([43502](https://github.com/WordPress/gutenberg/pull/43502)) +- Spacing presets: Fix/minor issues noted in initial UI PR. ([43214](https://github.com/WordPress/gutenberg/pull/43214)) + +#### Block Editor +- Fix Cmd+A issue in Storybook. ([43145](https://github.com/WordPress/gutenberg/pull/43145)) +- Fix drag and drop indicator before first block and after last block. ([43135](https://github.com/WordPress/gutenberg/pull/43135)) +- Fix spinner position in URLInput component. ([43472](https://github.com/WordPress/gutenberg/pull/43472)) +- Partial select: Fix selecting into image. ([42983](https://github.com/WordPress/gutenberg/pull/42983)) + +#### Design Tools +- Border Radius: Prevent invalid css unit only styles or empty radii style attribute. ([42409](https://github.com/WordPress/gutenberg/pull/42409)) +- Border Support: Fix disabling of border style control. ([43109](https://github.com/WordPress/gutenberg/pull/43109)) +- Post Comments Count: Prevent text-decoration from affecting warning. ([43497](https://github.com/WordPress/gutenberg/pull/43497)) + +#### Site Editor +- Do not show scrollbar when toolbar overflows the editor wrapper. ([43332](https://github.com/WordPress/gutenberg/pull/43332)) +- Fix template part focus mode resizable editor height. ([43408](https://github.com/WordPress/gutenberg/pull/43408)) + +#### npm Packages +- Jest Preset: Ignore `is-plain-obj` transformation. ([43179](https://github.com/WordPress/gutenberg/pull/43179)) +- Jest Preset: Improve `is-plain-obj` transformation ignore. ([43271](https://github.com/WordPress/gutenberg/pull/43271)) + +#### Widgets Editor +- Fix legacy widget form positioning in customizer. ([43297](https://github.com/WordPress/gutenberg/pull/43297)) + +#### CSS & Styling +- Group/Stack/Row: Scope the dashed placeholder rules. ([43169](https://github.com/WordPress/gutenberg/pull/43169)) + +#### List View +- Ensure long anchors don't cause the List View to extend. ([43134](https://github.com/WordPress/gutenberg/pull/43134)) + +#### Post Editor +- Post Template: Don't fetch settings and templates for non-admin users. ([42845](https://github.com/WordPress/gutenberg/pull/42845)) + +#### Accessibility +- Fix Top toolbar buttons tooltips and style when 'Show button text labels' is enabled. ([42815](https://github.com/WordPress/gutenberg/pull/42815)) + +#### Patterns +- Fix custom placeholder not displaying on subsequent Paragraph blocks. ([42519](https://github.com/WordPress/gutenberg/pull/42519)) + + +### Performance + +Lodash is known to unnecessarily inflate the bundle size of packages, and in most cases, it can be replaced with native language functionality. See these for more information and rationale ([16938](https://github.com/WordPress/gutenberg/issues/16938#issuecomment-602837246), [17025](https://github.com/WordPress/gutenberg/issues/17025), [39495](https://github.com/WordPress/gutenberg/issues/39495)) + +- Lodash: Refactor away from `_.deburr()`. ([43118](https://github.com/WordPress/gutenberg/pull/43118)) +- Lodash: Refactor away from `_.upperFirst()`. ([43306](https://github.com/WordPress/gutenberg/pull/43306)) +- Lodash: Refactor away from `_.xor()`. ([43389](https://github.com/WordPress/gutenberg/pull/43389)) +- Lodash: Refactor pascal case usages away from Lodash. ([42466](https://github.com/WordPress/gutenberg/pull/42466)) +- Lodash: Remove completely from `@wordpress/create-block` package. ([43362](https://github.com/WordPress/gutenberg/pull/43362)) +- Lodash: Remove completely from `@wordpress/docgen` package. ([43100](https://github.com/WordPress/gutenberg/pull/43100)) +- Lodash: Remove completely from `eslint-plugin` package. ([43420](https://github.com/WordPress/gutenberg/pull/43420)) +- Lodash: Remove from `core-data` resolvers. ([43117](https://github.com/WordPress/gutenberg/pull/43117)) +- Lodash: Refactor away from `_.words()`. ([42467](https://github.com/WordPress/gutenberg/pull/42467)) +- Editor: Remove Lodash from store code. ([42502](https://github.com/WordPress/gutenberg/pull/42502)) +- Lodash: Refactor away from `_.camelCase()`. ([43220](https://github.com/WordPress/gutenberg/pull/43220)) +- Lodash: Refactor away from `_.difference()`. ([43224](https://github.com/WordPress/gutenberg/pull/43224)) +- Lodash: Refactor away from `_.mapKeys()`. ([43258](https://github.com/WordPress/gutenberg/pull/43258)) +- Lodash: Refactor away from `_.times()`. ([43374](https://github.com/WordPress/gutenberg/pull/43374)) +- Lodash: Refactor components away from `_.includes()`. ([43518](https://github.com/WordPress/gutenberg/pull/43518)) +- Lodash: Remove `_.omit()` usage from components. ([43474](https://github.com/WordPress/gutenberg/pull/43474)) +- Lodash: Refactor away from `_.startCase()`. ([43229](https://github.com/WordPress/gutenberg/pull/43229)) +- Lodash: Remove `_.omit()` from deprecated blocks. ([43411](https://github.com/WordPress/gutenberg/pull/43411)) +- Lodash: Refactor away from `_.capitalize()`. ([42465](https://github.com/WordPress/gutenberg/pull/42465)) +- Lodash: Remove completely from `@wordpress/e2e-test-utils` package. ([43231](https://github.com/WordPress/gutenberg/pull/43231)) +- Lodash: Refactor away from `_.sortBy()`. ([43479](https://github.com/WordPress/gutenberg/pull/43479)) +- Lodash: Refactor away from `_.uniq()`. ([43330](https://github.com/WordPress/gutenberg/pull/43330)) +- Lodash: Refactor away from `_.uniqBy()`. ([43182](https://github.com/WordPress/gutenberg/pull/43182)) +- Lodash: Remove completely from `e2e-test-utils-playwright` package. ([43419](https://github.com/WordPress/gutenberg/pull/43419)) + +### Experiments + +#### Components +- Font size picker: Use t-shirt sizes for the ToggleGroupControl component. ([43074](https://github.com/WordPress/gutenberg/pull/43074)) + + +### Documentation + +- Add documentation for useRootPaddingAwareAlignments in theme.json. ([43463](https://github.com/WordPress/gutenberg/pull/43463)) +- Comma is missing. ([43446](https://github.com/WordPress/gutenberg/pull/43446)) +- Convert HTML to Markdown in changelog for 13.9. ([43324](https://github.com/WordPress/gutenberg/pull/43324)) +- Handbook: Fix format API example link. ([43477](https://github.com/WordPress/gutenberg/pull/43477)) +- Stabilize the useResourcePermissions hook. ([43268](https://github.com/WordPress/gutenberg/pull/43268)) +- [Docs] Replace useState with edit in useEntityRecord usage examples. ([43270](https://github.com/WordPress/gutenberg/pull/43270)) +- Block Editor Handbook: Added missing codetabs end marker. ([43185](https://github.com/WordPress/gutenberg/pull/43185)) + + +### Code Quality + +- PHP: Use str_contains(). ([43382](https://github.com/WordPress/gutenberg/pull/43382)) +- PHP: Use str_starts_with. ([43410](https://github.com/WordPress/gutenberg/pull/43410)) +- Style engine: Pass options to CSS static methods. ([43399](https://github.com/WordPress/gutenberg/pull/43399)) +- Style engine tweaks. ([43303](https://github.com/WordPress/gutenberg/pull/43303)) +- Navigation block - minor refactor to classic menu conversion code. ([43081](https://github.com/WordPress/gutenberg/pull/43081)) +- Data: Bundle TypeScript types with the data package. ([43315](https://github.com/WordPress/gutenberg/pull/43315)) +- getTemplateInfo: Return stable reference to an empty object. ([43155](https://github.com/WordPress/gutenberg/pull/43155)) + +#### Components +- Clean up unused and duplicate `COLORS` values. ([43445](https://github.com/WordPress/gutenberg/pull/43445)) +- Packages: Ensure dependencies use version ranges. ([43355](https://github.com/WordPress/gutenberg/pull/43355)) +- Swatch: Remove component in favor of ColorIndicator. ([43068](https://github.com/WordPress/gutenberg/pull/43068)) +- Update/floating UI version. ([43206](https://github.com/WordPress/gutenberg/pull/43206)) + +#### List View +- Block list: Update block list view preferences name for consistency. ([43494](https://github.com/WordPress/gutenberg/pull/43494)) + +#### Widgets Editor +- Use useResourcePermissions in block-library and the widgets editor. ([43305](https://github.com/WordPress/gutenberg/pull/43305)) + +#### Block Editor +- Rich Text: Eliminate second scan when getting text content. ([43207](https://github.com/WordPress/gutenberg/pull/43207)) + +#### Global Styles +- Theme_JSON: Use existing append_to_selector for pseudo-elements. ([43167](https://github.com/WordPress/gutenberg/pull/43167)) +- Enable appearance tools via theme_support. ([43337](https://github.com/WordPress/gutenberg/pull/43337)) + + +### Tools + +- ESLint Plugin: Remove all rules targeting test files from recommended presets. ([43272](https://github.com/WordPress/gutenberg/pull/43272)) +- Fix 'Mark issues stale after needs testing for 30 days' workflow. ([43545](https://github.com/WordPress/gutenberg/pull/43545)) +- Ignore library CSS and built CSS in stylelint. ([42027](https://github.com/WordPress/gutenberg/pull/42027)) + +#### Testing +- Migrate wp editor meta box test to Playwright. ([41519](https://github.com/WordPress/gutenberg/pull/41519)) +- PHPCS: Exclude PHPUnit tests from file and class name sniffs (for Core parity). ([43131](https://github.com/WordPress/gutenberg/pull/43131)) +- PHPUnit: Let PHPUnit Polyfills match PHPUnit version. ([43334](https://github.com/WordPress/gutenberg/pull/43334)) +- PHPUnit: Turns on PHP notices and deprecations. ([43102](https://github.com/WordPress/gutenberg/pull/43102)) +- Update incorrect quote end-to-end test snapshot. ([43407](https://github.com/WordPress/gutenberg/pull/43407)) +- Update test fixture for performance test. ([43359](https://github.com/WordPress/gutenberg/pull/43359)) +- Quote: Stabilise flaky end-to-end test. ([43460](https://github.com/WordPress/gutenberg/pull/43460)) + +#### Build Tooling +- Build Tools: Fix typo in performance tests workflow. ([43153](https://github.com/WordPress/gutenberg/pull/43153)) +- Packages: Update the minimum required Node.js version to 14 for tools. ([43141](https://github.com/WordPress/gutenberg/pull/43141)) + +#### npm Packages +- Packages: Replace `is-plain-obj` with `is-plain-object`. ([43511](https://github.com/WordPress/gutenberg/pull/43511)) + + +#### Components +- (Custom)GradientPicker: Add flag to remove margins. ([43387](https://github.com/WordPress/gutenberg/pull/43387)) +- AlignmentMatrixControl: Improve stories. ([43438](https://github.com/WordPress/gutenberg/pull/43438)) +- AnglePickerControl: Add flag to remove bottom margin. ([43160](https://github.com/WordPress/gutenberg/pull/43160)) +- ComboboxControl: Add flag to remove bottom margin. ([43165](https://github.com/WordPress/gutenberg/pull/43165)) +- CustomSelectControl: Deprecate constrained width style. ([43230](https://github.com/WordPress/gutenberg/pull/43230)) +- DuotonePicker/DuotoneSwatch: Add stories. ([43225](https://github.com/WordPress/gutenberg/pull/43225)) +- Storybook: Add margin checker tool. ([43223](https://github.com/WordPress/gutenberg/pull/43223)) +- ToggleGroupControl: Improve stories for documentation view. ([43265](https://github.com/WordPress/gutenberg/pull/43265)) +- ToolsPanel: Tighten grid gaps. ([43424](https://github.com/WordPress/gutenberg/pull/43424)) + +#### Block Library +- Buttons: Update selectors to work better with button elements. ([43022](https://github.com/WordPress/gutenberg/pull/43022)) +- Comments block: Remove empty block wrapper. ([43383](https://github.com/WordPress/gutenberg/pull/43383)) +- Group block: Update description to remove "layout." ([43498](https://github.com/WordPress/gutenberg/pull/43498)) +- Image: Try different resting state for placeholder, alternate version. ([43180](https://github.com/WordPress/gutenberg/pull/43180)) +- Navigation: Try to improve the appender in an empty block. ([43115](https://github.com/WordPress/gutenberg/pull/43115)) +- Polish placeholder radius and enable duotone on image setup state. ([43425](https://github.com/WordPress/gutenberg/pull/43425)) +- Pullquote: Use inline rich text instead of multiline. ([43210](https://github.com/WordPress/gutenberg/pull/43210)) +- [Blocks] Paragraph and Heading: Add gradient support. ([43119](https://github.com/WordPress/gutenberg/pull/43119)) + +#### Patterns +- Bundle new collection of Header and Footer block patterns. ([43157](https://github.com/WordPress/gutenberg/pull/43157)) +- Mark which attributes of the image should be considered content. ([43280](https://github.com/WordPress/gutenberg/pull/43280)) +- Prefer _x() for i18n context in core patterns. ([43409](https://github.com/WordPress/gutenberg/pull/43409)) + +#### Design Tools +- Add margin and padding supports to Audio block. ([43351](https://github.com/WordPress/gutenberg/pull/43351)) +- Add margin/padding support to Archives block. ([43350](https://github.com/WordPress/gutenberg/pull/43350)) + +#### Global Styles +- Add documentation about spacing presets. ([43349](https://github.com/WordPress/gutenberg/pull/43349)) +- Spacing presets: Add editor UI support. ([42173](https://github.com/WordPress/gutenberg/pull/42173)) + +#### Site Editor +- [Site Editor]: Add success notice upon template creation. ([43430](https://github.com/WordPress/gutenberg/pull/43430)) + +#### CSS & Styling +- Style engine: Use style engine for block supports CSS in editor. ([43055](https://github.com/WordPress/gutenberg/pull/43055)) +- Style engine: Remove `enqueue` flag. ([43103](https://github.com/WordPress/gutenberg/pull/43103)) + +#### Block Editor +- Merging blocks: Allow x to be merged into wrapper blocks (quote, list, group...). ([42780](https://github.com/WordPress/gutenberg/pull/42780)) + + +## First time contributors + +The following PRs were merged by first time contributors: + +- @drzraf: taxonomy-controls.js: Change REST context to "view" when fetching taxonomy terms. ([43274](https://github.com/WordPress/gutenberg/pull/43274)) +- @markbiek: ComboboxControl: Normalize hyphen-like Unicode characters to ASCII hyphens when matching search queries. ([42942](https://github.com/WordPress/gutenberg/pull/42942)) +- @randhirexpresstech: Add font family and text-decoration typography supports to paragraph blocks. ([39642](https://github.com/WordPress/gutenberg/pull/39642)) +- @Rink9: Migrate wp editor meta box test to Playwright. ([41519](https://github.com/WordPress/gutenberg/pull/41519)) +- @titusdmoore: Add optional capture group to URL regex in wp-env. ([43200](https://github.com/WordPress/gutenberg/pull/43200)) + + +## Contributors + +The following contributors merged PRs in this release: + +@aaronrobertshaw @adamziel @afercia @andrewserong @aristath @awps @carolinan @ciampo @derekblank @dinhtungdu @dmsnell @draganescu @drzraf @ellatrix @geriux @glendaviesnz @gziolo @hellofromtonya @hz-tyfoon @jasmussen @jostnes @kdevnel @MaggieCabrera @Mamaduka @markbiek @matiasbenedetto @mcsf @mirka @ndiego @noahtallen @noisysocks @ntsekouras @oandregal @ockham @paulopmt1 @pbking @ramonjd @randhirexpresstech @Rink9 @ryanwelcher @scruffian @SiobhyB @Soean @t-hamano @talldan @tellthemachines @titusdmoore @torounit @tyxla @walbo + + = 13.9.0 = ## Changelog diff --git a/docs/how-to-guides/format-api.md b/docs/how-to-guides/format-api.md index 96793b843a13d..dc395cbcbf39d 100644 --- a/docs/how-to-guides/format-api.md +++ b/docs/how-to-guides/format-api.md @@ -78,7 +78,7 @@ registerFormatType( 'my-custom-format/sample-output', { } ); ``` -Let's check that everything is working as expected. Build and reload and then select a text block. Confirm the new button was added to the format toolbar. +Let's check that everything is working as expected. Build and reload and then select any block containing text like for example the paragraph block. Confirm the new button was added to the format toolbar. ![Toolbar with custom button](https://developer.wordpress.org/files/2021/12/format-api-toolbar.png) @@ -125,13 +125,13 @@ registerFormatType( 'my-custom-format/sample-output', { Confirm it is working: first build and reload, then make a text selection and click the button. Your browser will likely display that selection differently than the surrounding text. -You can also confirm by switching to HTML view (Code editor Ctrl+Shift+Alt+M) and see the text selection wrapped with `` HTML tags. +You can also confirm by switching to HTML view (Code editor `Ctrl+Shift+Alt+M`) and see the text selection wrapped with `` HTML tags. Use the `className` option when registering to add your own custom class to the tag. You can use that class and custom CSS to target that element and style as you wish. ### Step 4: Show the button only for specific blocks (Optional) -By default, the button is rendered on every rich text toolbar (image captions, buttons, paragraphs, etc). You can render the button only on blocks of a certain type by using `wp.data.withSelect` together with `wp.compose.ifCondition`. +By default, the button is rendered on every rich text toolbar (image captions, buttons, paragraphs, etc). You can render the button only on blocks of a certain type by using [the data API](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-data). Here is an example that only shows the button for Paragraph blocks: @@ -173,6 +173,44 @@ registerFormatType( 'my-custom-format/sample-output', { } ); ``` +### Step5: Add a button outside of the dropdown (Optional) + +Using the `RichTextToolbarButton` component, the button is added to the default dropdown menu. You can add the button directly to the toolbar by using the `BlockControls` component. + +```js +import { registerFormatType, toggleFormat } from '@wordpress/rich-text'; +import { BlockControls } from '@wordpress/block-editor'; +import { ToolbarGroup, ToolbarButton } from '@wordpress/components'; + +const MyCustomButton = ( { isActive, onChange, value } ) => { + return ( + + + { + onChange( + toggleFormat( value, { + type: 'my-custom-format/sample-output', + } ) + ); + } } + isActive={ isActive } + /> + + + ); +}; + +registerFormatType( 'my-custom-format/sample-output', { + title: 'Sample output', + tagName: 'samp', + className: null, + edit: MyCustomButton, +} ); +``` + ## Troubleshooting If you run into errors: diff --git a/docs/reference-guides/core-blocks.md b/docs/reference-guides/core-blocks.md index 3449d35fe06fe..3d2f9a11f91a2 100644 --- a/docs/reference-guides/core-blocks.md +++ b/docs/reference-guides/core-blocks.md @@ -293,7 +293,7 @@ Create a link that always points to the homepage of the site. Usually not necess - **Name:** core/home-link - **Category:** design -- **Supports:** ~~html~~, ~~reusable~~ +- **Supports:** typography (fontSize, lineHeight), ~~html~~, ~~reusable~~ - **Attributes:** label ## Custom HTML @@ -329,7 +329,7 @@ Display a list of your most recent posts. ([Source](https://github.com/WordPress - **Name:** core/latest-posts - **Category:** widgets -- **Supports:** align, ~~html~~ +- **Supports:** align, typography (fontSize, lineHeight), ~~html~~ - **Attributes:** addLinkToFeaturedImage, categories, columns, displayAuthor, displayFeaturedImage, displayPostContent, displayPostContentRadio, displayPostDate, excerptLength, featuredImageAlign, featuredImageSizeHeight, featuredImageSizeSlug, featuredImageSizeWidth, order, orderBy, postLayout, postsToShow, selectedAuthor ## List @@ -338,7 +338,7 @@ Create a bulleted or numbered list. ([Source](https://github.com/WordPress/guten - **Name:** core/list - **Category:** text -- **Supports:** __unstablePasteTextInline, anchor, color (background, gradients, link, text), typography (fontSize, lineHeight), ~~className~~ +- **Supports:** __unstablePasteTextInline, anchor, color (background, gradients, link, text), spacing (margin, padding), typography (fontSize, lineHeight), ~~className~~ - **Attributes:** ordered, placeholder, reversed, start, type, values ## List item @@ -365,7 +365,7 @@ Set media and words side-by-side for a richer layout. ([Source](https://github.c - **Name:** core/media-text - **Category:** media -- **Supports:** align (full, wide), anchor, color (background, gradients, link, text), typography (fontSize, lineHeight), ~~html~~ +- **Supports:** align (full, wide), anchor, color (background, gradients, link, text), spacing (margin, padding), typography (fontSize, lineHeight), ~~html~~ - **Attributes:** align, focalPoint, href, imageFill, isStackedOnMobile, linkClass, linkDestination, linkTarget, mediaAlt, mediaId, mediaLink, mediaPosition, mediaSizeSlug, mediaType, mediaUrl, mediaWidth, rel, verticalAlignment ## Unsupported @@ -617,7 +617,7 @@ Contains the block elements used to render content when no query results are fou - **Name:** core/query-no-results - **Category:** theme -- **Supports:** align, color (background, gradients, link, text), ~~html~~, ~~reusable~~ +- **Supports:** align, color (background, gradients, link, text), typography (fontSize, lineHeight), ~~html~~, ~~reusable~~ - **Attributes:** ## Pagination @@ -761,7 +761,7 @@ Display icons linking to your social media profiles or sites. ([Source](https:// - **Name:** core/social-links - **Category:** widgets -- **Supports:** align (center, left, right), anchor, spacing (blockGap, margin, units) +- **Supports:** align (center, left, right), anchor, color (background, gradients, ~~enableContrastChecker~~, ~~text~~), spacing (blockGap, margin, units) - **Attributes:** customIconBackgroundColor, customIconColor, iconBackgroundColor, iconBackgroundColorValue, iconColor, iconColorValue, openInNewTab, showLabels, size ## Spacer @@ -788,7 +788,7 @@ Summarize your post with a list of headings. Add HTML anchors to Heading blocks - **Name:** core/table-of-contents - **Category:** layout -- **Supports:** color (background, gradients, link, text), spacing (margin, padding), ~~html~~ +- **Supports:** color (background, gradients, link, text), spacing (margin, padding), typography (fontSize, lineHeight), ~~html~~ - **Attributes:** headings, onlyIncludeCurrentPage ## Tag Cloud diff --git a/docs/reference-guides/data/data-core.md b/docs/reference-guides/data/data-core.md index 61d0f8bd30f26..ba500d10c0038 100644 --- a/docs/reference-guides/data/data-core.md +++ b/docs/reference-guides/data/data-core.md @@ -21,7 +21,7 @@ _Parameters_ - _state_ `State`: Data state. - _action_ `string`: Action to check. One of: 'create', 'read', 'update', 'delete'. - _resource_ `string`: REST resource to check, e.g. 'media' or 'posts'. -- _id_ `GenericRecordKey`: Optional ID of the rest resource to check. +- _id_ `EntityRecordKey`: Optional ID of the rest resource to check. _Returns_ @@ -39,9 +39,9 @@ Calling this may trigger an OPTIONS request to the REST API via the _Parameters_ - _state_ `State`: Data state. -- _kind_ `Kind`: Entity kind. -- _name_ `Name`: Entity name. -- _recordId_ `GenericRecordKey`: Record's id. +- _kind_ `string`: Entity kind. +- _name_ `string`: Entity name. +- _recordId_ `EntityRecordKey`: Record's id. _Returns_ @@ -56,11 +56,11 @@ Returns all available authors. _Parameters_ - _state_ `State`: Data state. -- _query_ `EntityQuery< any >`: Optional object of query parameters to include with request. +- _query_ `GetRecordsHttpQuery`: Optional object of query parameters to include with request. _Returns_ -- `User< 'edit' >[]`: Authors list. +- `ET.User[]`: Authors list. ### getAutosave @@ -70,8 +70,8 @@ _Parameters_ - _state_ `State`: State tree. - _postType_ `string`: The type of the parent post. -- _postId_ `GenericRecordKey`: The id of the parent post. -- _authorId_ `GenericRecordKey`: The id of the author. +- _postId_ `EntityRecordKey`: The id of the parent post. +- _authorId_ `EntityRecordKey`: The id of the author. _Returns_ @@ -88,7 +88,7 @@ _Parameters_ - _state_ `State`: State tree. - _postType_ `string`: The type of the parent post. -- _postId_ `GenericRecordKey`: The id of the parent post. +- _postId_ `EntityRecordKey`: The id of the parent post. _Returns_ @@ -140,7 +140,7 @@ _Parameters_ _Returns_ -- `User< 'edit' >`: Current user object. +- `undefined< 'edit' >`: Current user object. ### getEditedEntityRecord @@ -149,13 +149,13 @@ Returns the specified entity record, merged with its edits. _Parameters_ - _state_ `State`: State tree. -- _kind_ `K`: Entity kind. -- _name_ `N`: Entity name. -- _recordId_ `KeyOf< K, N >`: Record ID. +- _kind_ `string`: Entity kind. +- _name_ `string`: Entity name. +- _recordId_ `EntityRecordKey`: Record ID. _Returns_ -- `EntityRecord | undefined`: The entity record, merged with its edits. +- `undefined< EntityRecord > | undefined`: The entity record, merged with its edits. ### getEmbedPreview @@ -179,7 +179,7 @@ Returns the loaded entities for the given kind. _Parameters_ - _state_ `State`: Data state. -- _kind_ `Kind`: Entity kind. +- _kind_ `string`: Entity kind. _Returns_ @@ -192,7 +192,7 @@ Returns the loaded entities for the given kind. _Parameters_ - _state_ `State`: Data state. -- _kind_ `Kind`: Entity kind. +- _kind_ `string`: Entity kind. _Returns_ @@ -207,8 +207,8 @@ Returns the entity config given its kind and name. _Parameters_ - _state_ `State`: Data state. -- _kind_ `Kind`: Entity kind. -- _name_ `Name`: Entity name. +- _kind_ `string`: Entity kind. +- _name_ `string`: Entity name. _Returns_ @@ -221,8 +221,8 @@ Returns the entity config given its kind and name. _Parameters_ - _state_ `State`: Data state. -- _kind_ `Kind`: Entity kind. -- _name_ `Name`: Entity name. +- _kind_ `string`: Entity kind. +- _name_ `string`: Entity name. _Returns_ @@ -237,14 +237,14 @@ entity object if it exists and is received. _Parameters_ - _state_ `State`: State tree -- _kind_ `K`: Entity kind. -- _name_ `N`: Entity name. -- _key_ `KeyOf< R >`: Record's key -- _query_ Optional query. If requesting specific fields, fields must always include the ID. +- _kind_ `string`: Entity kind. +- _name_ `string`: Entity name. +- _key_ `EntityRecordKey`: Record's key +- _query_ `GetRecordsHttpQuery`: Optional query. If requesting specific fields, fields must always include the ID. _Returns_ -- Record. +- `EntityRecord | undefined`: Record. ### getEntityRecordEdits @@ -253,9 +253,9 @@ Returns the specified entity record's edits. _Parameters_ - _state_ `State`: State tree. -- _kind_ `K`: Entity kind. -- _name_ `N`: Entity name. -- _recordId_ `KeyOf< K, N >`: Record ID. +- _kind_ `string`: Entity kind. +- _name_ `string`: Entity name. +- _recordId_ `EntityRecordKey`: Record ID. _Returns_ @@ -272,9 +272,9 @@ They are defined in the entity's config. _Parameters_ - _state_ `State`: State tree. -- _kind_ `K`: Entity kind. -- _name_ `N`: Entity name. -- _recordId_ `KeyOf< K, N >`: Record ID. +- _kind_ `string`: Entity kind. +- _name_ `string`: Entity name. +- _recordId_ `EntityRecordKey`: Record ID. _Returns_ @@ -287,13 +287,13 @@ Returns the Entity's records. _Parameters_ - _state_ `State`: State tree -- _kind_ `K`: Entity kind. -- _name_ `N`: Entity name. -- _query_ Optional terms query. If requesting specific fields, fields must always include the ID. +- _kind_ `string`: Entity kind. +- _name_ `string`: Entity name. +- _query_ `GetRecordsHttpQuery`: Optional terms query. If requesting specific fields, fields must always include the ID. _Returns_ -- Records. +- `EntityRecord[] | null`: Records. ### getLastEntityDeleteError @@ -302,9 +302,9 @@ Returns the specified entity record's last delete error. _Parameters_ - _state_ `State`: State tree. -- _kind_ `Kind`: Entity kind. -- _name_ `Name`: Entity name. -- _recordId_ `GenericRecordKey`: Record ID. +- _kind_ `string`: Entity kind. +- _name_ `string`: Entity name. +- _recordId_ `EntityRecordKey`: Record ID. _Returns_ @@ -317,9 +317,9 @@ Returns the specified entity record's last save error. _Parameters_ - _state_ `State`: State tree. -- _kind_ `Kind`: Entity kind. -- _name_ `Name`: Entity name. -- _recordId_ `GenericRecordKey`: Record ID. +- _kind_ `string`: Entity kind. +- _name_ `string`: Entity name. +- _recordId_ `EntityRecordKey`: Record ID. _Returns_ @@ -333,9 +333,9 @@ with its attributes mapped to their raw values. _Parameters_ - _state_ `State`: State tree. -- _kind_ `K`: Entity kind. -- _name_ `N`: Entity name. -- _key_ `KeyOf< K, N >`: Record's key. +- _kind_ `string`: Entity kind. +- _name_ `string`: Entity name. +- _key_ `EntityRecordKey`: Record's key. _Returns_ @@ -411,7 +411,7 @@ _Parameters_ _Returns_ -- `User< 'edit' >[]`: Users list. +- `undefined< 'edit' >[]`: Users list. ### hasEditsForEntityRecord @@ -421,9 +421,9 @@ and false otherwise. _Parameters_ - _state_ `State`: State tree. -- _kind_ `K`: Entity kind. -- _name_ `N`: Entity name. -- _recordId_ `KeyOf< K, N >`: Record ID. +- _kind_ `string`: Entity kind. +- _name_ `string`: Entity name. +- _recordId_ `EntityRecordKey`: Record ID. _Returns_ @@ -437,9 +437,9 @@ or false otherwise. _Parameters_ - _state_ `State`: State tree -- _kind_ `K`: Entity kind. -- _name_ `N`: Entity name. -- _query_ `EntityQuery< C >`: Optional terms query. +- _kind_ `string`: Entity kind. +- _name_ `string`: Entity name. +- _query_ `GetRecordsHttpQuery`: Optional terms query. _Returns_ @@ -453,7 +453,7 @@ _Parameters_ - _state_ `State`: State tree. - _postType_ `string`: The type of the parent post. -- _postId_ `GenericRecordKey`: The id of the parent post. +- _postId_ `EntityRecordKey`: The id of the parent post. _Returns_ @@ -492,9 +492,9 @@ Returns true if the specified entity record is autosaving, and false otherwise. _Parameters_ - _state_ `State`: State tree. -- _kind_ `Kind`: Entity kind. -- _name_ `Name`: Entity name. -- _recordId_ `GenericRecordKey`: Record ID. +- _kind_ `string`: Entity kind. +- _name_ `string`: Entity name. +- _recordId_ `EntityRecordKey`: Record ID. _Returns_ @@ -507,9 +507,9 @@ Returns true if the specified entity record is deleting, and false otherwise. _Parameters_ - _state_ `State`: State tree. -- _kind_ `Kind`: Entity kind. -- _name_ `Name`: Entity name. -- _recordId_ `GenericRecordKey`: Record ID. +- _kind_ `string`: Entity kind. +- _name_ `string`: Entity name. +- _recordId_ `EntityRecordKey`: Record ID. _Returns_ @@ -553,9 +553,9 @@ Returns true if the specified entity record is saving, and false otherwise. _Parameters_ - _state_ `State`: State tree. -- _kind_ `K`: Entity kind. -- _name_ `N`: Entity name. -- _recordId_ `KeyOf< K, N >`: Record ID. +- _kind_ `string`: Entity kind. +- _name_ `string`: Entity name. +- _recordId_ `EntityRecordKey`: Record ID. _Returns_ 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 7c9b7f75da5d4..0096a41be04ba 100644 --- a/docs/reference-guides/theme-json-reference/theme-json-living.md +++ b/docs/reference-guides/theme-json-reference/theme-json-living.md @@ -4,7 +4,7 @@ > > There're related documents you may be interested in: the [theme.json v1](/docs/reference-guides/theme-json-reference/theme-json-v1.md) specification and the [reference to migrate from theme.json v1 to v2](/docs/reference-guides/theme-json-reference/theme-json-migrations.md). -This reference guide lists the settings and style properties defined in the theme.json schema. See the [theme.json how to guide](/docs/how-to-guides/themes/theme-json.md) for examples and guide on how to use the theme.json file in your theme. +This reference guide lists the settings and style properties defined in the theme.json schema. See the [theme.json how to guide](/docs/how-to-guides/themes/theme-json.md) for examples and guide on how to use the theme.json file in your theme. ## Schema @@ -195,6 +195,13 @@ CSS and SVG filter styles. | --- | --- |--- | | duotone | string | | +--- + +### shadow + +Box shadow styles. + + --- diff --git a/gutenberg.php b/gutenberg.php index 8f631ed25f241..b649e3841489e 100644 --- a/gutenberg.php +++ b/gutenberg.php @@ -5,7 +5,7 @@ * Description: Printing since 1440. This is the development plugin for the new block editor in core. * Requires at least: 5.9 * Requires PHP: 5.6 - * Version: 13.9.0 + * Version: 14.0.0-rc.2 * Author: Gutenberg Team * Text Domain: gutenberg * diff --git a/lib/block-supports/border.php b/lib/block-supports/border.php index cfdbac9c47e3f..09e8727743342 100644 --- a/lib/block-supports/border.php +++ b/lib/block-supports/border.php @@ -12,22 +12,18 @@ * @param WP_Block_Type $block_type Block Type. */ function gutenberg_register_border_support( $block_type ) { - // Determine if any border related features are supported. - $has_border_support = block_has_support( $block_type, array( '__experimentalBorder' ) ); - $has_border_color_support = gutenberg_has_border_feature_support( $block_type, 'color' ); - // Setup attributes and styles within that if needed. if ( ! $block_type->attributes ) { $block_type->attributes = array(); } - if ( $has_border_support && ! array_key_exists( 'style', $block_type->attributes ) ) { + if ( block_has_support( $block_type, array( '__experimentalBorder' ) ) && ! array_key_exists( 'style', $block_type->attributes ) ) { $block_type->attributes['style'] = array( 'type' => 'object', ); } - if ( $has_border_color_support && ! array_key_exists( 'borderColor', $block_type->attributes ) ) { + if ( gutenberg_has_border_feature_support( $block_type, 'color' ) && ! array_key_exists( 'borderColor', $block_type->attributes ) ) { $block_type->attributes['borderColor'] = array( 'type' => 'string', ); @@ -48,9 +44,7 @@ function gutenberg_apply_border_support( $block_type, $block_attributes ) { return array(); } - $sides = array( 'top', 'right', 'bottom', 'left' ); - $border_block_styles = array(); - + $border_block_styles = array(); $has_border_color_support = gutenberg_has_border_feature_support( $block_type, 'color' ); $has_border_width_support = gutenberg_has_border_feature_support( $block_type, 'width' ); @@ -106,7 +100,7 @@ function gutenberg_apply_border_support( $block_type, $block_attributes ) { // Generate styles for individual border sides. if ( $has_border_color_support || $has_border_width_support ) { - foreach ( $sides as $side ) { + foreach ( array( 'top', 'right', 'bottom', 'left' ) as $side ) { $border = _wp_array_get( $block_attributes, array( 'style', 'border', $side ), null ); $border_side_values = array( 'width' => isset( $border['width'] ) && ! gutenberg_should_skip_block_supports_serialization( $block_type, '__experimentalBorder', 'width' ) ? $border['width'] : null, diff --git a/lib/block-supports/colors.php b/lib/block-supports/colors.php index ab4e9fe661004..8b9702ae44533 100644 --- a/lib/block-supports/colors.php +++ b/lib/block-supports/colors.php @@ -11,10 +11,7 @@ * @param WP_Block_Type $block_type Block Type. */ function gutenberg_register_colors_support( $block_type ) { - $color_support = false; - if ( property_exists( $block_type, 'supports' ) ) { - $color_support = _wp_array_get( $block_type->supports, array( 'color' ), false ); - } + $color_support = property_exists( $block_type, 'supports' ) ? _wp_array_get( $block_type->supports, array( 'color' ), false ) : false; $has_text_colors_support = true === $color_support || ( is_array( $color_support ) && _wp_array_get( $color_support, array( 'text' ), true ) ); $has_background_colors_support = true === $color_support || ( is_array( $color_support ) && _wp_array_get( $color_support, array( 'background' ), true ) ); $has_gradients_support = _wp_array_get( $color_support, array( 'gradients' ), false ); diff --git a/lib/compat/wordpress-6.0/block-patterns.php b/lib/compat/wordpress-6.0/block-patterns.php index 5ccdccb77bde2..6ba5a15035530 100644 --- a/lib/compat/wordpress-6.0/block-patterns.php +++ b/lib/compat/wordpress-6.0/block-patterns.php @@ -11,10 +11,6 @@ * `theme.json` file. */ function _register_remote_theme_patterns() { - if ( ! get_theme_support( 'core-block-patterns' ) ) { - return; - } - if ( ! apply_filters( 'should_load_remote_block_patterns', true ) ) { return; } diff --git a/lib/compat/wordpress-6.1/block-template-utils.php b/lib/compat/wordpress-6.1/block-template-utils.php index 03b93c8393746..ed1f105c0e576 100644 --- a/lib/compat/wordpress-6.1/block-template-utils.php +++ b/lib/compat/wordpress-6.1/block-template-utils.php @@ -121,6 +121,13 @@ function gutenberg_get_block_templates( $query = array(), $template_type = 'wp_t continue; } + if ( $post_type && + isset( $template->post_types ) && + ! in_array( $post_type, $template->post_types, true ) + ) { + continue; + } + $query_result[] = $template; } if ( ! isset( $query['wp_id'] ) ) { @@ -264,8 +271,8 @@ function gutenberg_build_block_template_result_from_post( $post ) { $is_wp_suggestion = get_post_meta( $post->ID, 'is_wp_suggestion', true ); $theme = $terms[0]->name; - $has_theme_file = wp_get_theme()->get_stylesheet() === $theme && - null !== _get_block_template_file( $post->post_type, $post->post_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; $template = new WP_Block_Template(); $template->wp_id = $post->ID; @@ -288,6 +295,10 @@ function gutenberg_build_block_template_result_from_post( $post ) { $template->is_custom = false; } + if ( 'wp_template' === $post->post_type && $has_theme_file && isset( $template_file['postTypes'] ) ) { + $template->post_types = $template_file['postTypes']; + } + if ( 'wp_template_part' === $post->post_type ) { $type_terms = get_the_terms( $post, 'wp_template_part_area' ); if ( ! is_wp_error( $type_terms ) && false !== $type_terms ) { diff --git a/lib/compat/wordpress-6.1/class-wp-theme-json-6-1.php b/lib/compat/wordpress-6.1/class-wp-theme-json-6-1.php index f1371c04cacd3..4f9265657ab10 100644 --- a/lib/compat/wordpress-6.1/class-wp-theme-json-6-1.php +++ b/lib/compat/wordpress-6.1/class-wp-theme-json-6-1.php @@ -79,6 +79,7 @@ class WP_Theme_JSON_6_1 extends WP_Theme_JSON_6_0 { 'text-decoration' => array( 'typography', 'textDecoration' ), 'text-transform' => array( 'typography', 'textTransform' ), 'filter' => array( 'filter', 'duotone' ), + 'box-shadow' => array( 'shadow' ), ); /** @@ -97,6 +98,7 @@ class WP_Theme_JSON_6_1 extends WP_Theme_JSON_6_0 { '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( @@ -153,9 +155,11 @@ protected static function sanitize( $input, $valid_block_names, $valid_element_n // 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 ) { - 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 ] ); + 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 ] ); + } } } } @@ -314,6 +318,7 @@ public static function remove_insecure_properties( $theme_json ) { 'gradient' => null, 'text' => null, ), + 'shadow' => null, 'filter' => array( 'duotone' => null, ), @@ -487,7 +492,7 @@ protected static function get_style_nodes( $theme_json, $selectors = array() ) { if ( isset( $theme_json['styles']['elements'] ) ) { foreach ( self::ELEMENTS as $element => $selector ) { - if ( ! isset( $theme_json['styles']['elements'][ $element ] ) ) { + if ( ! isset( $theme_json['styles']['elements'][ $element ] ) || ! array_key_exists( $element, static::ELEMENTS ) ) { continue; } diff --git a/lib/compat/wordpress-6.1/rest-api.php b/lib/compat/wordpress-6.1/rest-api.php index 88b6d3bbb9a4d..f3391389d9e38 100644 --- a/lib/compat/wordpress-6.1/rest-api.php +++ b/lib/compat/wordpress-6.1/rest-api.php @@ -61,3 +61,35 @@ function gutenberg_add_site_icon_url_to_index( WP_REST_Response $response ) { return $response; } add_action( 'rest_index', 'gutenberg_add_site_icon_url_to_index' ); + +/** + * Returns the has_archive post type field. + * + * @param array $type The response data. + * @param string $field_name The field name. The function handles field has_archive. + */ +function gutenberg_get_post_type_has_archive_field( $type, $field_name ) { + if ( ! empty( $type ) && ! empty( $type['slug'] ) && 'has_archive' === $field_name ) { + $post_type_object = get_post_type_object( $type['slug'] ); + return $post_type_object->has_archive; + } +} + +/** + * Registers the has_archive post type REST API field. + */ +function gutenberg_register_has_archive_on_post_types_endpoint() { + register_rest_field( + 'type', + 'has_archive', + array( + 'get_callback' => 'gutenberg_get_post_type_has_archive_field', + 'schema' => array( + 'description' => __( 'If the value is a string, the value will be used as the archive slug. If the value is false the post type has no archive.', 'gutenberg' ), + 'type' => array( 'string', 'boolean' ), + 'context' => array( 'view', 'edit' ), + ), + ) + ); +} +add_action( 'rest_api_init', 'gutenberg_register_has_archive_on_post_types_endpoint' ); diff --git a/lib/experimental/block-editor-settings-mobile.php b/lib/experimental/block-editor-settings-mobile.php index 4319f8766352c..b0da17e929667 100644 --- a/lib/experimental/block-editor-settings-mobile.php +++ b/lib/experimental/block-editor-settings-mobile.php @@ -29,8 +29,8 @@ function gutenberg_get_block_editor_settings_mobile( $settings ) { // To tell mobile that the site uses quote v2 (inner blocks). // See https://github.com/WordPress/gutenberg/pull/25892. $settings['__experimentalEnableQuoteBlockV2'] = true; - // To be set to true when the web makes quote v2 (inner blocks) the default. - $settings['__experimentalEnableListBlockV2'] = gutenberg_is_list_v2_enabled(); + // To tell mobile that the site uses list v2 (inner blocks). + $settings['__experimentalEnableListBlockV2'] = true; } return $settings; diff --git a/lib/experimental/blocks.php b/lib/experimental/blocks.php deleted file mode 100644 index 80691e5d917ab..0000000000000 --- a/lib/experimental/blocks.php +++ /dev/null @@ -1,27 +0,0 @@ - __( 'Test a new list block that uses nested list item blocks (Warning: The new block is not ready. You may experience content loss, avoid using it on production sites)', 'gutenberg' ), - 'id' => 'gutenberg-list-v2', - ) - ); register_setting( 'gutenberg-experiments', 'gutenberg-experiments' diff --git a/lib/load.php b/lib/load.php index 9600d3100d3b7..cb2ad7721c320 100644 --- a/lib/load.php +++ b/lib/load.php @@ -103,7 +103,6 @@ function gutenberg_is_experiment_enabled( $name ) { require __DIR__ . '/experimental/class-wp-webfonts-provider.php'; require __DIR__ . '/experimental/class-wp-webfonts-provider-local.php'; require __DIR__ . '/experimental/webfonts.php'; -require __DIR__ . '/experimental/blocks.php'; require __DIR__ . '/experimental/navigation-theme-opt-in.php'; require __DIR__ . '/experimental/navigation-page.php'; diff --git a/package-lock.json b/package-lock.json index e82a20afbbc67..96b15cb29fbe1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "gutenberg", - "version": "13.9.0", + "version": "14.0.0-rc.2", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -16656,7 +16656,7 @@ "change-case": "^4.1.2", "colord": "^2.7.0", "hpq": "^1.3.0", - "is-plain-obj": "^4.1.0", + "is-plain-object": "^5.0.0", "lodash": "^4.17.21", "memize": "^1.1.0", "rememo": "^4.0.0", @@ -16891,7 +16891,7 @@ "@wordpress/priority-queue": "file:packages/priority-queue", "@wordpress/redux-routine": "file:packages/redux-routine", "equivalent-key-map": "^0.2.2", - "is-plain-obj": "^4.1.0", + "is-plain-object": "^5.0.0", "is-promise": "^4.0.0", "lodash": "^4.17.21", "redux": "^4.1.2", @@ -17181,7 +17181,7 @@ "@types/react-dom": "^17.0.11", "@wordpress/escape-html": "file:packages/escape-html", "change-case": "^4.1.2", - "is-plain-obj": "^4.1.0", + "is-plain-object": "^5.0.0", "react": "^17.0.2", "react-dom": "^17.0.2" } @@ -17454,7 +17454,6 @@ "@babel/runtime": "^7.16.0", "@wordpress/hooks": "file:packages/hooks", "gettext-parser": "^1.3.1", - "lodash": "^4.17.21", "memize": "^1.1.0", "sprintf-js": "^1.1.1", "tannin": "^1.2.0" @@ -17747,7 +17746,7 @@ "version": "file:packages/redux-routine", "requires": { "@babel/runtime": "^7.16.0", - "is-plain-obj": "^4.1.0", + "is-plain-object": "^5.0.0", "is-promise": "^4.0.0", "lodash": "^4.17.21", "rungen": "^0.3.2" @@ -28928,6 +28927,16 @@ "is-plain-object": "^2.0.4", "kind-of": "^6.0.2", "shallow-clone": "^3.0.0" + }, + "dependencies": { + "is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "requires": { + "isobject": "^3.0.1" + } + } } }, "clone-regexp": { @@ -34447,6 +34456,16 @@ "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", "requires": { "is-plain-object": "^2.0.4" + }, + "dependencies": { + "is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "requires": { + "isobject": "^3.0.1" + } + } } } } @@ -38366,18 +38385,10 @@ } } }, - "is-plain-obj": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", - "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==" - }, "is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "requires": { - "isobject": "^3.0.1" - } + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==" }, "is-potential-custom-element-name": { "version": "1.0.1", @@ -44262,6 +44273,17 @@ "kind-of": "^3.0.2", "lazy-cache": "^1.0.3", "shallow-clone": "^0.1.2" + }, + "dependencies": { + "is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "requires": { + "isobject": "^3.0.1" + } + } } }, "kind-of": { @@ -45581,6 +45603,16 @@ "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", "requires": { "is-plain-object": "^2.0.4" + }, + "dependencies": { + "is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "requires": { + "isobject": "^3.0.1" + } + } } } } @@ -52829,6 +52861,14 @@ "requires": { "is-extendable": "^0.1.0" } + }, + "is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "requires": { + "isobject": "^3.0.1" + } } } }, diff --git a/package.json b/package.json index e075a83646ef6..ee933ef6610c5 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gutenberg", - "version": "13.9.0", + "version": "14.0.0-rc.2", "private": true, "description": "A new WordPress editor experience.", "author": "The WordPress Contributors", diff --git a/packages/a11y/CHANGELOG.md b/packages/a11y/CHANGELOG.md index ebeadb135ab3e..2f8accb0079f4 100644 --- a/packages/a11y/CHANGELOG.md +++ b/packages/a11y/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 3.16.0 (2022-08-24) + ## 3.15.0 (2022-08-10) ## 3.14.0 (2022-07-27) diff --git a/packages/a11y/package.json b/packages/a11y/package.json index cded52bd9feb1..7b46ac2902c86 100644 --- a/packages/a11y/package.json +++ b/packages/a11y/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/a11y", - "version": "3.15.1-next.d6164808d3.0", + "version": "3.16.0", "description": "Accessibility (a11y) utilities for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/annotations/CHANGELOG.md b/packages/annotations/CHANGELOG.md index 9ccf226dff40b..3400f90fb856b 100644 --- a/packages/annotations/CHANGELOG.md +++ b/packages/annotations/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 2.16.0 (2022-08-24) + ## 2.15.0 (2022-08-10) ## 2.14.0 (2022-07-27) diff --git a/packages/annotations/package.json b/packages/annotations/package.json index 28df27a47a464..8efe29f06a659 100644 --- a/packages/annotations/package.json +++ b/packages/annotations/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/annotations", - "version": "2.15.1-next.d6164808d3.0", + "version": "2.16.0", "description": "Annotate content in the Gutenberg editor.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/api-fetch/CHANGELOG.md b/packages/api-fetch/CHANGELOG.md index 577bfa5ee087d..7e9afb241fbae 100644 --- a/packages/api-fetch/CHANGELOG.md +++ b/packages/api-fetch/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 6.13.0 (2022-08-24) + ## 6.12.0 (2022-08-10) ## 6.11.0 (2022-07-27) diff --git a/packages/api-fetch/package.json b/packages/api-fetch/package.json index 072b4768a30e4..4b6ea159c897e 100644 --- a/packages/api-fetch/package.json +++ b/packages/api-fetch/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/api-fetch", - "version": "6.12.1-next.d6164808d3.0", + "version": "6.13.0", "description": "Utility to make WordPress REST API requests.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/autop/CHANGELOG.md b/packages/autop/CHANGELOG.md index 2e3e69ac18abf..fda951777833f 100644 --- a/packages/autop/CHANGELOG.md +++ b/packages/autop/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 3.16.0 (2022-08-24) + ## 3.15.0 (2022-08-10) ## 3.14.0 (2022-07-27) diff --git a/packages/autop/package.json b/packages/autop/package.json index e45042ad9ed11..4813462cd26c3 100644 --- a/packages/autop/package.json +++ b/packages/autop/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/autop", - "version": "3.15.1-next.d6164808d3.0", + "version": "3.16.0", "description": "WordPress's automatic paragraph functions `autop` and `removep`.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/babel-plugin-import-jsx-pragma/CHANGELOG.md b/packages/babel-plugin-import-jsx-pragma/CHANGELOG.md index 649c890035f8f..6419cefb1328c 100644 --- a/packages/babel-plugin-import-jsx-pragma/CHANGELOG.md +++ b/packages/babel-plugin-import-jsx-pragma/CHANGELOG.md @@ -2,7 +2,7 @@ ## Unreleased -## 4.0.0-next.0 (2022-08-23) +## 4.0.0 (2022-08-24) ### Breaking Change diff --git a/packages/babel-plugin-import-jsx-pragma/package.json b/packages/babel-plugin-import-jsx-pragma/package.json index 9ad6cf3e60639..297378e3a8707 100644 --- a/packages/babel-plugin-import-jsx-pragma/package.json +++ b/packages/babel-plugin-import-jsx-pragma/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/babel-plugin-import-jsx-pragma", - "version": "4.0.1-next.d6164808d3.0", + "version": "4.0.0", "description": "Babel transform plugin for automatically injecting an import to be used as the pragma for the React JSX Transform plugin.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/babel-plugin-makepot/CHANGELOG.md b/packages/babel-plugin-makepot/CHANGELOG.md index 6629de8ae652e..26ea6ae682756 100644 --- a/packages/babel-plugin-makepot/CHANGELOG.md +++ b/packages/babel-plugin-makepot/CHANGELOG.md @@ -2,7 +2,7 @@ ## Unreleased -## 5.0.0-next.0 (2022-08-23) +## 5.0.0 (2022-08-24) ### Breaking Change diff --git a/packages/babel-plugin-makepot/package.json b/packages/babel-plugin-makepot/package.json index 28f33be87ea46..869b7778a0f84 100644 --- a/packages/babel-plugin-makepot/package.json +++ b/packages/babel-plugin-makepot/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/babel-plugin-makepot", - "version": "5.0.1-next.d6164808d3.0", + "version": "5.0.0", "description": "WordPress Babel internationalization (i18n) plugin.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/babel-preset-default/CHANGELOG.md b/packages/babel-preset-default/CHANGELOG.md index 6be1a291d5717..cc5d8b64dde95 100644 --- a/packages/babel-preset-default/CHANGELOG.md +++ b/packages/babel-preset-default/CHANGELOG.md @@ -2,7 +2,7 @@ ## Unreleased -## 7.0.0-next.0 (2022-08-23) +## 7.0.0 (2022-08-24) ### Breaking Change diff --git a/packages/babel-preset-default/package.json b/packages/babel-preset-default/package.json index e7e0c2521b706..fa16d9df66de5 100644 --- a/packages/babel-preset-default/package.json +++ b/packages/babel-preset-default/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/babel-preset-default", - "version": "7.0.1-next.d6164808d3.0", + "version": "7.0.0", "description": "Default Babel preset for WordPress development.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/base-styles/_mixins.scss b/packages/base-styles/_mixins.scss index 7b3d3faea6117..95373ddd92b1f 100644 --- a/packages/base-styles/_mixins.scss +++ b/packages/base-styles/_mixins.scss @@ -202,6 +202,11 @@ } } +@mixin placeholder-style() { + border: $border-width dashed currentColor; + border-radius: $radius-block-ui; +} + /** * Allows users to opt-out of animations via OS-level preferences. */ @@ -226,7 +231,6 @@ animation-delay: 0s; } } - } @mixin input-control { @@ -371,6 +375,7 @@ * Reset default styles for JavaScript UI based pages. * This is a WP-admin agnostic reset */ + @mixin reset { box-sizing: border-box; @@ -384,6 +389,7 @@ /** * Reset the WP Admin page styles for Gutenberg-like pages. */ + @mixin wp-admin-reset( $content-container ) { background: $white; diff --git a/packages/base-styles/package.json b/packages/base-styles/package.json index da3592345a32d..add5536dc70d7 100644 --- a/packages/base-styles/package.json +++ b/packages/base-styles/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/base-styles", - "version": "4.7.1-next.d6164808d3.0", + "version": "4.7.0", "description": "Base SCSS utilities and variables for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/blob/CHANGELOG.md b/packages/blob/CHANGELOG.md index 281562dd1ab68..cf0c2ce540b06 100644 --- a/packages/blob/CHANGELOG.md +++ b/packages/blob/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 3.16.0 (2022-08-24) + ## 3.15.0 (2022-08-10) ## 3.14.0 (2022-07-27) diff --git a/packages/blob/package.json b/packages/blob/package.json index c524cf8c04dd3..630e06d10ed00 100644 --- a/packages/blob/package.json +++ b/packages/blob/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/blob", - "version": "3.15.1-next.d6164808d3.0", + "version": "3.16.0", "description": "Blob utilities for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/block-directory/CHANGELOG.md b/packages/block-directory/CHANGELOG.md index da24e0ccfe1ea..d469c8a77759a 100644 --- a/packages/block-directory/CHANGELOG.md +++ b/packages/block-directory/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 3.14.0 (2022-08-24) + ## 3.13.0 (2022-08-10) ## 3.12.0 (2022-07-27) diff --git a/packages/block-directory/package.json b/packages/block-directory/package.json index 685958641f9ad..b1783a8029e0a 100644 --- a/packages/block-directory/package.json +++ b/packages/block-directory/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/block-directory", - "version": "3.13.1-next.d6164808d3.0", + "version": "3.14.0", "description": "Extend editor with block directory features to search, download and install blocks.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/block-editor/CHANGELOG.md b/packages/block-editor/CHANGELOG.md index 4005c3e45e7a2..4e261a8a5fba9 100644 --- a/packages/block-editor/CHANGELOG.md +++ b/packages/block-editor/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 9.8.0 (2022-08-24) + ## 9.7.0 (2022-08-10) ## 9.6.0 (2022-07-27) diff --git a/packages/block-editor/package.json b/packages/block-editor/package.json index 3844dd10a0a9e..3f312a4543ac7 100644 --- a/packages/block-editor/package.json +++ b/packages/block-editor/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/block-editor", - "version": "9.7.1-next.d6164808d3.0", + "version": "9.8.0", "description": "Generic block editor.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/block-editor/src/components/block-list/block-list-compact.native.js b/packages/block-editor/src/components/block-list/block-list-compact.native.js new file mode 100644 index 0000000000000..1b2740d5bd45d --- /dev/null +++ b/packages/block-editor/src/components/block-list/block-list-compact.native.js @@ -0,0 +1,62 @@ +/** + * External dependencies + */ +import { View } from 'react-native'; + +/** + * WordPress dependencies + */ +import { store as blockEditorStore } from '@wordpress/block-editor'; +import { useSelect } from '@wordpress/data'; + +/** + * Internal dependencies + */ +import styles from './style.scss'; +import BlockListBlock from './block'; + +/** + * NOTE: This is a component currently used by the List block (V2) + * It only passes the needed props for this block, if other blocks will use it + * make sure you pass other props that might be required coming from: + * components/inner-blocks/index.native.js + */ + +function BlockListCompact( props ) { + const { + marginHorizontal = styles.defaultBlock.marginLeft, + marginVertical = styles.defaultBlock.marginTop, + rootClientId, + } = props; + const { blockClientIds } = useSelect( + ( select ) => { + const { getBlockOrder } = select( blockEditorStore ); + const blockOrder = getBlockOrder( rootClientId ); + + return { + blockClientIds: blockOrder, + }; + }, + [ rootClientId ] + ); + + const containerStyle = { + marginVertical: -marginVertical, + marginHorizontal: -marginHorizontal, + }; + + return ( + + { blockClientIds.map( ( currentClientId ) => ( + + ) ) } + + ); +} + +export default BlockListCompact; diff --git a/packages/block-editor/src/components/block-list/style.scss b/packages/block-editor/src/components/block-list/style.scss index 616864b7b5a84..860b468e70842 100644 --- a/packages/block-editor/src/components/block-list/style.scss +++ b/packages/block-editor/src/components/block-list/style.scss @@ -4,10 +4,6 @@ margin: 0; } -/** - * Notices & Block Selected/Hover Styles. - */ - /** * Cross-Block Selection */ @@ -104,8 +100,20 @@ border-color: var(--wp-admin-theme-color); } } -} + // Ensure an accurate partial text selection. + // To do this, we disable text selection on the main container, then re-enable it only on the + // elements that actually get selected. + // To keep in mind: user-select is currently inherited to all nodes inside. + .has-multi-selection & { + user-select: none; + } + + // Re-enable it on components inside. + [class^="components-"] { + user-select: text; + } +} .is-block-moving-mode.block-editor-block-list__block-selection-button { // Should be invisible but not unfocusable. @@ -118,6 +126,19 @@ .block-editor-block-list__layout .block-editor-block-list__block { position: relative; + // Re-enable text-selection on editable blocks. + user-select: text; + + // Hide the select style pseudo element as it interferes with the style. + &.is-partially-selected::after { + height: 0; + } + + &.is-highlighted::after, + &.is-highlighted ~ .is-multi-selected::after { + height: auto; + } + // Break long strings of text without spaces so they don't overflow the block. overflow-wrap: break-word; @@ -192,7 +213,9 @@ bottom: 0; left: 0; border-radius: $radius-block-ui; - box-shadow: 0 0 0 var(--wp-admin-border-width-focus) transparent; + box-shadow: 0 0 0 0 transparent; + transition: box-shadow 0.1s linear; + @include reduce-motion("transition"); } // Warnings diff --git a/packages/block-editor/src/components/block-list/use-block-props/use-focus-first-element.js b/packages/block-editor/src/components/block-list/use-block-props/use-focus-first-element.js index 8b95f19f3c9ff..492555935dadf 100644 --- a/packages/block-editor/src/components/block-list/use-block-props/use-focus-first-element.js +++ b/packages/block-editor/src/components/block-list/use-block-props/use-focus-first-element.js @@ -85,7 +85,7 @@ export function useFocusFirstElement( clientId ) { const { ownerDocument } = ref.current; // Do not focus the block if it already contains the active element. - if ( ref.current.contains( ownerDocument.activeElement ) ) { + if ( isInsideRootBlock( ref.current, ownerDocument.activeElement ) ) { return; } diff --git a/packages/block-editor/src/components/block-popover/index.js b/packages/block-editor/src/components/block-popover/index.js index 370959d1b145c..15be18b0a8c82 100644 --- a/packages/block-editor/src/components/block-popover/index.js +++ b/packages/block-editor/src/components/block-popover/index.js @@ -6,8 +6,9 @@ import classnames from 'classnames'; /** * WordPress dependencies */ +import { useMergeRefs } from '@wordpress/compose'; import { Popover } from '@wordpress/components'; -import { useMemo } from '@wordpress/element'; +import { forwardRef, useMemo } from '@wordpress/element'; /** * Internal dependencies @@ -15,19 +16,25 @@ import { useMemo } from '@wordpress/element'; import { __unstableUseBlockElement as useBlockElement } from '../block-list/use-block-props/use-block-refs'; import usePopoverScroll from './use-popover-scroll'; -export default function BlockPopover( { - clientId, - bottomClientId, - children, - __unstableRefreshSize, - __unstableCoverTarget = false, - __unstablePopoverSlot, - __unstableContentRef, - ...props -} ) { +function BlockPopover( + { + clientId, + bottomClientId, + children, + __unstableRefreshSize, + __unstableCoverTarget = false, + __unstablePopoverSlot, + __unstableContentRef, + ...props + }, + ref +) { const selectedElement = useBlockElement( clientId ); const lastSelectedElement = useBlockElement( bottomClientId ?? clientId ); - const popoverScrollRef = usePopoverScroll( __unstableContentRef ); + const mergedRefs = useMergeRefs( [ + ref, + usePopoverScroll( __unstableContentRef ), + ] ); const style = useMemo( () => { if ( ! selectedElement || lastSelectedElement !== selectedElement ) { return {}; @@ -51,7 +58,7 @@ export default function BlockPopover( { return ( ); } + +export default forwardRef( BlockPopover ); 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 3cdb022dc361f..ae073d74d07a6 100644 --- a/packages/block-editor/src/components/block-switcher/test/index.js +++ b/packages/block-editor/src/components/block-switcher/test/index.js @@ -8,7 +8,6 @@ import { shallow, mount } from 'enzyme'; */ import { useSelect } from '@wordpress/data'; import { registerBlockType, unregisterBlockType } from '@wordpress/blocks'; -import { DOWN } from '@wordpress/keycodes'; import { Button } from '@wordpress/components'; import { copy } from '@wordpress/icons'; @@ -180,7 +179,7 @@ describe( 'BlockSwitcherDropdownMenu', () => { const onToggleStub = jest.fn(); const mockKeyDown = { preventDefault: () => {}, - keyCode: DOWN, + code: 'ArrowDown', }; afterEach( () => { 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 962063ba284b6..652c04efe6aed 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 @@ -20,6 +20,7 @@ import BlockSelectionButton from './block-selection-button'; import BlockContextualToolbar from './block-contextual-toolbar'; import { store as blockEditorStore } from '../../store'; import BlockPopover from '../block-popover'; +import useBlockToolbarPopoverProps from './use-block-toolbar-popover-props'; function selector( select ) { const { @@ -113,6 +114,11 @@ function SelectedBlockPopover( { // to it when re-mounting. const initialToolbarItemIndexRef = useRef(); + const popoverProps = useBlockToolbarPopoverProps( { + contentElement: __unstableContentRef?.current, + clientId, + } ); + if ( ! shouldShowBreadcrumb && ! shouldShowContextualToolbar ) { return null; } @@ -126,6 +132,7 @@ function SelectedBlockPopover( { } ) } __unstablePopoverSlot={ __unstablePopoverSlot } __unstableContentRef={ __unstableContentRef } + { ...popoverProps } > { shouldShowContextualToolbar && ( toolbarHeight ) { + return DEFAULT_PROPS; + } + + return RESTRICTED_HEIGHT_PROPS; +} + +/** + * Determines the desired popover positioning behavior, returning a set of appropriate props. + * + * @param {Object} elements + * @param {Element} elements.contentElement The DOM element that represents the editor content or canvas. + * @param {string} elements.clientId The clientId of the first selected block. + * + * @return {Object} The popover props used to determine the position of the toolbar. + */ +export default function useBlockToolbarPopoverProps( { + contentElement, + clientId, +} ) { + const selectedBlockElement = useBlockElement( clientId ); + const [ toolbarHeight, setToolbarHeight ] = useState( 0 ); + const [ props, setProps ] = useState( () => + getProps( contentElement, selectedBlockElement, toolbarHeight ) + ); + const blockIndex = useSelect( + ( select ) => select( blockEditorStore ).getBlockIndex( clientId ), + [ clientId ] + ); + + const popoverRef = useRefEffect( ( popoverNode ) => { + setToolbarHeight( popoverNode.offsetHeight ); + }, [] ); + + const updateProps = useCallback( + () => + setProps( + getProps( contentElement, selectedBlockElement, toolbarHeight ) + ), + [ contentElement, selectedBlockElement, toolbarHeight ] + ); + + // Update props when the block is moved. This also ensures the props are + // correct on initial mount, and when the selected block or content element + // changes (since the callback ref will update). + useLayoutEffect( updateProps, [ blockIndex, updateProps ] ); + + // Update props when the viewport is resized or the block is resized. + useLayoutEffect( () => { + if ( ! contentElement || ! selectedBlockElement ) { + return; + } + + // Update the toolbar props on viewport resize. + const contentView = contentElement?.ownerDocument?.defaultView; + contentView?.addEventHandler?.( 'resize', updateProps ); + + // Update the toolbar props on block resize. + let resizeObserver; + const blockView = selectedBlockElement?.ownerDocument?.defaultView; + if ( blockView.ResizeObserver ) { + resizeObserver = new blockView.ResizeObserver( updateProps ); + resizeObserver.observe( selectedBlockElement ); + } + + return () => { + contentView?.removeEventHandler?.( 'resize', updateProps ); + + if ( resizeObserver ) { + resizeObserver.disconnect(); + } + }; + }, [ updateProps, contentElement, selectedBlockElement ] ); + + return { + ...props, + ref: popoverRef, + }; +} diff --git a/packages/block-editor/src/components/button-block-appender/style.scss b/packages/block-editor/src/components/button-block-appender/style.scss index 6de8718d85533..dfeb3241c7bb7 100644 --- a/packages/block-editor/src/components/button-block-appender/style.scss +++ b/packages/block-editor/src/components/button-block-appender/style.scss @@ -31,3 +31,26 @@ color: $black; } } + +// When the appender shows up in empty container blocks, such as Group and Columns, add an extra click state. +.block-list-appender:only-child { + .is-layout-constrained.block-editor-block-list__block:not(.is-selected) > &, + .is-layout-flow.block-editor-block-list__block:not(.is-selected) > & { + pointer-events: none; + + &::after { + content: ""; + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + pointer-events: none; + @include placeholder-style(); + } + + .block-editor-inserter { + visibility: hidden; + } + } +} diff --git a/packages/block-editor/src/components/colors-gradients/control.js b/packages/block-editor/src/components/colors-gradients/control.js index 241da40d5aa3a..34e4f334e3c58 100644 --- a/packages/block-editor/src/components/colors-gradients/control.js +++ b/packages/block-editor/src/components/colors-gradients/control.js @@ -92,6 +92,7 @@ function ColorGradientControlInner( { ), [ TAB_GRADIENT.value ]: ( { } ); } ); +describe( 'getPresetValueFromCustomValue', () => { + const spacingSizes = [ { name: 'Small', slug: 20, size: '8px' } ]; + it( 'should return original value if a string in spacing presets var format', () => { + expect( + getPresetValueFromCustomValue( + 'var:preset|spacing|80', + spacingSizes + ) + ).toBe( 'var:preset|spacing|80' ); + } ); + it( 'should return value constructed from matching spacingSizes array entry if value matches sizes', () => { + expect( getPresetValueFromCustomValue( '8px', spacingSizes ) ).toBe( + 'var:preset|spacing|20' + ); + } ); + it( 'should return values as-is if no matching preset in spacingSizes array', () => { + expect( + getPresetValueFromCustomValue( '1.125rem', spacingSizes ) + ).toBe( '1.125rem' ); + } ); +} ); + describe( 'getSpacingPresetCssVar', () => { it( 'should return original value if not a string in spacing presets var format', () => { expect( getSpacingPresetCssVar( '20px' ) ).toBe( '20px' ); @@ -144,6 +167,9 @@ describe( 'isValuesDefined', () => { it( 'should return false if values are not defined', () => { expect( isValuesDefined( undefinedValues ) ).toBe( false ); } ); + it( 'should return false if values is passed in as null', () => { + expect( isValuesDefined( null ) ).toBe( false ); + } ); const definedValues = { top: 'var:preset|spacing|30', bottom: 'var:preset|spacing|20', diff --git a/packages/block-editor/src/components/spacing-sizes-control/utils.js b/packages/block-editor/src/components/spacing-sizes-control/utils.js index 2f824e25cacf6..8236d743ab32f 100644 --- a/packages/block-editor/src/components/spacing-sizes-control/utils.js +++ b/packages/block-editor/src/components/spacing-sizes-control/utils.js @@ -43,6 +43,33 @@ export function getCustomValueFromPreset( value, spacingSizes ) { return spacingSize?.size; } +/** + * Converts a custom value to preset value if one can be found. + * + * Returns value as-is if no match is found. + * + * @param {string} value Value to convert + * @param {Array} spacingSizes Array of the current spacing preset objects + * + * @return {string} The preset value if it can be found. + */ +export function getPresetValueFromCustomValue( value, spacingSizes ) { + // Return value as-is if it is already a preset; + if ( isValueSpacingPreset( value ) ) { + return value; + } + + const spacingMatch = spacingSizes.find( + ( size ) => String( size.size ) === String( value ) + ); + + if ( spacingMatch?.slug ) { + return `var:preset|spacing|${ spacingMatch.slug }`; + } + + return value; +} + /** * Converts a spacing preset into a custom value. * @@ -181,15 +208,15 @@ export function isValuesMixed( values = {}, sides = ALL_SIDES ) { * @return {boolean} Whether values are defined. */ export function isValuesDefined( values ) { - return ( - values !== undefined && - ! isEmpty( - Object.values( values ).filter( - // Switching units when input is empty causes values only - // containing units. This gives false positive on mixed values - // unless filtered. - ( value ) => !! value && /\d/.test( value ) - ) + if ( values === undefined || values === null ) { + return false; + } + return ! isEmpty( + Object.values( values ).filter( + // Switching units when input is empty causes values only + // containing units. This gives false positive on mixed values + // unless filtered. + ( value ) => !! value && /\d/.test( value ) ) ); } diff --git a/packages/block-editor/src/components/text-decoration-control/index.js b/packages/block-editor/src/components/text-decoration-control/index.js index 9edfb9b4a0195..44635602c947a 100644 --- a/packages/block-editor/src/components/text-decoration-control/index.js +++ b/packages/block-editor/src/components/text-decoration-control/index.js @@ -1,7 +1,15 @@ +/** + * External dependencies + */ +import classnames from 'classnames'; + /** * WordPress dependencies */ -import { BaseControl, Button } from '@wordpress/components'; +import { + __experimentalToggleGroupControl as ToggleGroupControl, + __experimentalToggleGroupControlOptionIcon as ToggleGroupControlOptionIcon, +} from '@wordpress/components'; import { formatStrikethrough, formatUnderline } from '@wordpress/icons'; import { __ } from '@wordpress/i18n'; @@ -21,38 +29,41 @@ const TEXT_DECORATIONS = [ /** * Control to facilitate text decoration selections. * - * @param {Object} props Component props. - * @param {string} props.value Currently selected text decoration. - * @param {Function} props.onChange Handles change in text decoration selection. + * @param {Object} props Component props. + * @param {string} props.value Currently selected text decoration. + * @param {Function} props.onChange Handles change in text decoration selection. + * @param {string} [props.className] Additional class name to apply. * * @return {WPElement} Text decoration control. */ -export default function TextDecorationControl( { value, onChange } ) { +export default function TextDecorationControl( { + value, + onChange, + className, + ...props +} ) { return ( -
- - { __( 'Decoration' ) } - -
- { TEXT_DECORATIONS.map( ( textDecoration ) => { - return ( -
-
+ + { TEXT_DECORATIONS.map( ( textDecoration ) => { + return ( + + ); + } ) } + ); } diff --git a/packages/block-editor/src/components/text-decoration-control/stories/index.js b/packages/block-editor/src/components/text-decoration-control/stories/index.js new file mode 100644 index 0000000000000..cb50e4cd6c6f7 --- /dev/null +++ b/packages/block-editor/src/components/text-decoration-control/stories/index.js @@ -0,0 +1,37 @@ +/** + * WordPress dependencies + */ +import { useState } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import TextDecorationControl from '../'; + +export default { + title: 'BlockEditor/TextDecorationControl', + component: TextDecorationControl, + argTypes: { + onChange: { action: 'onChange' }, + size: { + options: [ 'default', '__unstable-large' ], + control: { type: 'radio' }, + }, + }, +}; + +const Template = ( { onChange, ...args } ) => { + const [ value, setValue ] = useState(); + return ( + { + onChange( ...changeArgs ); + setValue( ...changeArgs ); + } } + value={ value } + /> + ); +}; + +export const Default = Template.bind( {} ); diff --git a/packages/block-editor/src/components/text-decoration-control/style.scss b/packages/block-editor/src/components/text-decoration-control/style.scss deleted file mode 100644 index f5d5848d3d303..0000000000000 --- a/packages/block-editor/src/components/text-decoration-control/style.scss +++ /dev/null @@ -1,18 +0,0 @@ -.block-editor-text-decoration-control { - flex: 0 0 50%; - - legend { - margin-bottom: 8px; - } - - .block-editor-text-decoration-control__buttons { - display: inline-flex; - margin-bottom: 24px; - - .components-button.has-icon { - min-width: 24px; - padding: 0; - margin-right: 4px; - } - } -} diff --git a/packages/block-editor/src/components/text-transform-control/index.js b/packages/block-editor/src/components/text-transform-control/index.js index 6d85ae7b421b7..c71c85993f5ac 100644 --- a/packages/block-editor/src/components/text-transform-control/index.js +++ b/packages/block-editor/src/components/text-transform-control/index.js @@ -1,7 +1,13 @@ +/** + * External dependencies + */ /** * WordPress dependencies */ -import { BaseControl, Button } from '@wordpress/components'; +import { + __experimentalToggleGroupControl as ToggleGroupControl, + __experimentalToggleGroupControlOptionIcon as ToggleGroupControlOptionIcon, +} from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import { formatCapitalize, @@ -36,32 +42,26 @@ const TEXT_TRANSFORMS = [ * * @return {WPElement} Text transform control. */ -export default function TextTransformControl( { value, onChange } ) { +export default function TextTransformControl( { value, onChange, ...props } ) { return ( -
- - { __( 'Letter case' ) } - -
- { TEXT_TRANSFORMS.map( ( textTransform ) => { - return ( -
-
+ + { TEXT_TRANSFORMS.map( ( textTransform ) => { + return ( + + ); + } ) } + ); } diff --git a/packages/block-editor/src/components/text-transform-control/stories/index.js b/packages/block-editor/src/components/text-transform-control/stories/index.js new file mode 100644 index 0000000000000..3219d714257e8 --- /dev/null +++ b/packages/block-editor/src/components/text-transform-control/stories/index.js @@ -0,0 +1,37 @@ +/** + * WordPress dependencies + */ +import { useState } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import TextTransformControl from '../'; + +export default { + title: 'BlockEditor/TextTransformControl', + component: TextTransformControl, + argTypes: { + onChange: { action: 'onChange' }, + size: { + options: [ 'default', '__unstable-large' ], + control: { type: 'radio' }, + }, + }, +}; + +const Template = ( { onChange, ...args } ) => { + const [ value, setValue ] = useState(); + return ( + { + onChange( ...changeArgs ); + setValue( ...changeArgs ); + } } + value={ value } + /> + ); +}; + +export const Default = Template.bind( {} ); diff --git a/packages/block-editor/src/components/text-transform-control/style.scss b/packages/block-editor/src/components/text-transform-control/style.scss deleted file mode 100644 index 09280029a971a..0000000000000 --- a/packages/block-editor/src/components/text-transform-control/style.scss +++ /dev/null @@ -1,18 +0,0 @@ -.block-editor-text-transform-control { - flex: 0 0 50%; - - legend { - margin-bottom: 8px; - } - - .block-editor-text-transform-control__buttons { - display: inline-flex; - margin-bottom: 24px; - - .components-button.has-icon { - min-width: 24px; - padding: 0; - margin-right: 4px; - } - } -} diff --git a/packages/block-editor/src/components/writing-flow/index.js b/packages/block-editor/src/components/writing-flow/index.js index dfd44e828c7f8..b28f065c559c5 100644 --- a/packages/block-editor/src/components/writing-flow/index.js +++ b/packages/block-editor/src/components/writing-flow/index.js @@ -51,12 +51,14 @@ export function useWritingFlow() { return; } + node.classList.add( 'has-multi-selection' ); node.setAttribute( 'aria-label', __( 'Multiple selected blocks' ) ); return () => { + node.classList.remove( 'has-multi-selection' ); node.removeAttribute( 'aria-label' ); }; }, diff --git a/packages/block-editor/src/components/writing-flow/use-select-all.js b/packages/block-editor/src/components/writing-flow/use-select-all.js index 15473e3a201c7..93c55c185b8c7 100644 --- a/packages/block-editor/src/components/writing-flow/use-select-all.js +++ b/packages/block-editor/src/components/writing-flow/use-select-all.js @@ -37,28 +37,25 @@ export default function useSelectAll() { return; } + event.preventDefault(); + const [ firstSelectedClientId ] = selectedClientIds; const rootClientId = getBlockRootClientId( firstSelectedClientId ); - let blockClientIds = getBlockOrder( rootClientId ); + const blockClientIds = getBlockOrder( rootClientId ); // If we have selected all sibling nested blocks, try selecting up a // level. See: https://github.com/WordPress/gutenberg/pull/31859/ if ( selectedClientIds.length === blockClientIds.length ) { - blockClientIds = getBlockOrder( - getBlockRootClientId( rootClientId ) - ); - } - - const firstClientId = first( blockClientIds ); - const lastClientId = last( blockClientIds ); - - if ( firstClientId === lastClientId ) { - selectBlock( firstClientId ); + if ( rootClientId ) { + node.ownerDocument.defaultView + .getSelection() + .removeAllRanges(); + selectBlock( rootClientId ); + } return; } - multiSelect( firstClientId, lastClientId ); - event.preventDefault(); + multiSelect( first( blockClientIds ), last( blockClientIds ) ); } node.addEventListener( 'keydown', onKeyDown ); diff --git a/packages/block-editor/src/hooks/align.js b/packages/block-editor/src/hooks/align.js index c790a1cce20e0..4d3a22340b1ee 100644 --- a/packages/block-editor/src/hooks/align.js +++ b/packages/block-editor/src/hooks/align.js @@ -2,7 +2,7 @@ * External dependencies */ import classnames from 'classnames'; -import { has, without } from 'lodash'; +import { without } from 'lodash'; /** * WordPress dependencies @@ -87,7 +87,7 @@ export function getValidAlignments( */ export function addAttribute( settings ) { // Allow blocks to specify their own attribute definition with default values if needed. - if ( has( settings.attributes, [ 'align', 'type' ] ) ) { + if ( 'type' in ( settings.attributes?.align ?? {} ) ) { return settings; } if ( hasBlockSupport( settings, 'align' ) ) { diff --git a/packages/block-editor/src/hooks/anchor.js b/packages/block-editor/src/hooks/anchor.js index 4be1719ec9092..804375da6e2d0 100644 --- a/packages/block-editor/src/hooks/anchor.js +++ b/packages/block-editor/src/hooks/anchor.js @@ -1,8 +1,3 @@ -/** - * External dependencies - */ -import { has } from 'lodash'; - /** * WordPress dependencies */ @@ -42,7 +37,7 @@ const ANCHOR_SCHEMA = { */ export function addAttribute( settings ) { // Allow blocks to specify their own attribute definition with default values if needed. - if ( has( settings.attributes, [ 'anchor', 'type' ] ) ) { + if ( 'type' in ( settings.attributes?.anchor ?? {} ) ) { return settings; } if ( hasBlockSupport( settings, 'anchor' ) ) { diff --git a/packages/block-editor/src/hooks/dimensions.js b/packages/block-editor/src/hooks/dimensions.js index 6140e756c1d79..3eded40fc4a0a 100644 --- a/packages/block-editor/src/hooks/dimensions.js +++ b/packages/block-editor/src/hooks/dimensions.js @@ -79,15 +79,16 @@ export function DimensionsPanel( props ) { }, } ); + const spacingClassnames = classnames( { + 'tools-panel-item-spacing': spacingSizes && spacingSizes.length > 0, + } ); + return ( <> { ! isPaddingDisabled && ( 0, - } ) } + className={ spacingClassnames } hasValue={ () => hasPaddingValue( props ) } label={ __( 'Padding' ) } onDeselect={ () => resetPadding( props ) } @@ -100,10 +101,7 @@ export function DimensionsPanel( props ) { ) } { ! isMarginDisabled && ( 0, - } ) } + className={ spacingClassnames } hasValue={ () => hasMarginValue( props ) } label={ __( 'Margin' ) } onDeselect={ () => resetMargin( props ) } @@ -116,6 +114,7 @@ export function DimensionsPanel( props ) { ) } { ! isGapDisabled && ( hasGapValue( props ) } label={ __( 'Block spacing' ) } onDeselect={ () => resetGap( props ) } diff --git a/packages/block-editor/src/hooks/font-appearance.js b/packages/block-editor/src/hooks/font-appearance.js index b398ef58dc28c..af25daed0d30b 100644 --- a/packages/block-editor/src/hooks/font-appearance.js +++ b/packages/block-editor/src/hooks/font-appearance.js @@ -58,6 +58,7 @@ export function FontAppearanceEdit( props ) { hasFontStyles={ hasFontStyles } hasFontWeights={ hasFontWeights } value={ { fontStyle, fontWeight } } + size="__unstable-large" /> ); } diff --git a/packages/block-editor/src/hooks/font-family.js b/packages/block-editor/src/hooks/font-family.js index 12c36eac6882b..fe8693f64421b 100644 --- a/packages/block-editor/src/hooks/font-family.js +++ b/packages/block-editor/src/hooks/font-family.js @@ -132,6 +132,8 @@ export function FontFamilyEdit( { fontFamilies={ fontFamilies } value={ value } onChange={ onChange } + size="__unstable-large" + __nextHasNoMarginBottom /> ); } diff --git a/packages/block-editor/src/hooks/font-size.js b/packages/block-editor/src/hooks/font-size.js index f0b7315d02330..40abdd8f8eb30 100644 --- a/packages/block-editor/src/hooks/font-size.js +++ b/packages/block-editor/src/hooks/font-size.js @@ -147,6 +147,8 @@ export function FontSizeEdit( props ) { onChange={ onChange } value={ fontSizeValue } withReset={ false } + size="__unstable-large" + __nextHasNoMarginBottom /> ); } diff --git a/packages/block-editor/src/hooks/gap.js b/packages/block-editor/src/hooks/gap.js index 9bb30261cd315..018872eb84032 100644 --- a/packages/block-editor/src/hooks/gap.js +++ b/packages/block-editor/src/hooks/gap.js @@ -15,6 +15,7 @@ import { */ import { __unstableUseBlockRef as useBlockRef } from '../components/block-list/use-block-props/use-block-refs'; import { getSpacingPresetCssVar } from '../components/spacing-sizes-control/utils'; +import SpacingSizesControl from '../components/spacing-sizes-control'; import useSetting from '../components/use-setting'; import { AXIAL_SIDES, SPACING_SUPPORT_KEY, useCustomSides } from './dimensions'; import { cleanEmptyObject } from './utils'; @@ -55,12 +56,8 @@ export function getGapBoxControlValueFromStyle( blockGapValue ) { const isValueString = typeof blockGapValue === 'string'; return { - top: isValueString - ? getSpacingPresetCssVar( blockGapValue ) - : getSpacingPresetCssVar( blockGapValue?.top ), - left: isValueString - ? getSpacingPresetCssVar( blockGapValue ) - : getSpacingPresetCssVar( blockGapValue?.left ), + top: isValueString ? blockGapValue : blockGapValue?.top, + left: isValueString ? blockGapValue : blockGapValue?.left, }; } @@ -78,8 +75,10 @@ export function getGapCSSValue( blockGapValue, defaultValue = '0' ) { return null; } - const row = blockGapBoxControlValue?.top || defaultValue; - const column = blockGapBoxControlValue?.left || defaultValue; + const row = + getSpacingPresetCssVar( blockGapBoxControlValue?.top ) || defaultValue; + const column = + getSpacingPresetCssVar( blockGapBoxControlValue?.left ) || defaultValue; return row === column ? row : `${ row } ${ column }`; } @@ -132,6 +131,8 @@ export function GapEdit( props ) { setAttributes, } = props; + const spacingSizes = useSetting( 'spacing.spacingSizes' ); + const units = useCustomUnits( { availableUnits: useSetting( 'spacing.units' ) || [ '%', @@ -157,6 +158,9 @@ export function GapEdit( props ) { // If splitOnAxis activated we need to return a BoxControl object to the BoxControl component. if ( !! next && splitOnAxis ) { blockGap = { ...getGapBoxControlValueFromStyle( next ) }; + } else if ( next?.hasOwnProperty( 'top' ) ) { + // If splitOnAxis is not enabled, treat the 'top' value as the shorthand gap value. + blockGap = next.top; } const newStyle = { @@ -195,32 +199,46 @@ export function GapEdit( props ) { right: gapValue?.left, bottom: gapValue?.top, } - : gapValue?.top; + : { + top: gapValue?.top, + }; return Platform.select( { web: ( <> - { splitOnAxis ? ( - + ) : ( + + ) ) } + { spacingSizes?.length > 0 && ( + - ) : ( - ) } ), diff --git a/packages/block-editor/src/hooks/layout.js b/packages/block-editor/src/hooks/layout.js index 30a5b265ca10a..c4d4af0b95d4c 100644 --- a/packages/block-editor/src/hooks/layout.js +++ b/packages/block-editor/src/hooks/layout.js @@ -2,7 +2,7 @@ * External dependencies */ import classnames from 'classnames'; -import { has, kebabCase } from 'lodash'; +import { kebabCase } from 'lodash'; /** * WordPress dependencies @@ -246,7 +246,7 @@ function LayoutTypeSwitcher( { type, onChange } ) { * @return {Object} Filtered block settings. */ export function addAttribute( settings ) { - if ( has( settings.attributes, [ 'layout', 'type' ] ) ) { + if ( 'type' in ( settings.attributes?.layout ?? {} ) ) { return settings; } if ( hasBlockSupport( settings, layoutBlockSupportKey ) ) { diff --git a/packages/block-editor/src/hooks/letter-spacing.js b/packages/block-editor/src/hooks/letter-spacing.js index 3dc0d82b7b9a7..9e214fd07d792 100644 --- a/packages/block-editor/src/hooks/letter-spacing.js +++ b/packages/block-editor/src/hooks/letter-spacing.js @@ -46,6 +46,7 @@ export function LetterSpacingEdit( props ) { value={ style?.typography?.letterSpacing } onChange={ onChange } __unstableInputWidth={ '100%' } + size="__unstable-large" /> ); } diff --git a/packages/block-editor/src/hooks/line-height.js b/packages/block-editor/src/hooks/line-height.js index 7f4c2d5a4aa01..c8397d850a1e5 100644 --- a/packages/block-editor/src/hooks/line-height.js +++ b/packages/block-editor/src/hooks/line-height.js @@ -42,6 +42,7 @@ export function LineHeightEdit( props ) { __nextHasNoMarginBottom={ true } value={ style?.typography?.lineHeight } onChange={ onChange } + size="__unstable-large" /> ); } diff --git a/packages/block-editor/src/hooks/lock.js b/packages/block-editor/src/hooks/lock.js index 24ac0feeaa2ef..7e9423edcc4bb 100644 --- a/packages/block-editor/src/hooks/lock.js +++ b/packages/block-editor/src/hooks/lock.js @@ -1,8 +1,3 @@ -/** - * External dependencies - */ -import { has } from 'lodash'; - /** * WordPress dependencies */ @@ -17,7 +12,7 @@ import { addFilter } from '@wordpress/hooks'; */ export function addAttribute( settings ) { // Allow blocks to specify their own attribute definition with default values if needed. - if ( has( settings.attributes, [ 'lock', 'type' ] ) ) { + if ( 'type' in ( settings.attributes?.lock ?? {} ) ) { return settings; } // Gracefully handle if settings.attributes is undefined. diff --git a/packages/block-editor/src/hooks/test/gap.js b/packages/block-editor/src/hooks/test/gap.js index 1e9d76079cb00..a08e5a6759b2a 100644 --- a/packages/block-editor/src/hooks/test/gap.js +++ b/packages/block-editor/src/hooks/test/gap.js @@ -27,28 +27,6 @@ describe( 'gap', () => { ...blockGapValue, } ); } ); - it( 'should unwrap var: values from a string into a CSS var() function', () => { - const expectedValue = { - top: 'var(--wp--preset--spacing--60)', - left: 'var(--wp--preset--spacing--60)', - }; - expect( - getGapBoxControlValueFromStyle( 'var:preset|spacing|60' ) - ).toEqual( expectedValue ); - } ); - it( 'should unwrap var: values from an object into a CSS var() function', () => { - const expectedValue = { - top: 'var(--wp--preset--spacing--20)', - left: 'var(--wp--preset--spacing--60)', - }; - const blockGapValue = { - top: 'var:preset|spacing|20', - left: 'var:preset|spacing|60', - }; - expect( getGapBoxControlValueFromStyle( blockGapValue ) ).toEqual( - expectedValue - ); - } ); } ); describe( 'getGapCSSValue()', () => { it( 'should return `null` if argument is falsey', () => { @@ -84,5 +62,21 @@ describe( 'gap', () => { '88px 1px' ); } ); + + it( 'should unwrap var: values from a string into a CSS var() function', () => { + expect( getGapCSSValue( 'var:preset|spacing|60' ) ).toEqual( + 'var(--wp--preset--spacing--60)' + ); + } ); + + it( 'should unwrap var: values from an object into a CSS var() function and return shorthand values', () => { + const blockGapValue = { + top: 'var:preset|spacing|20', + left: 'var:preset|spacing|60', + }; + expect( getGapCSSValue( blockGapValue ) ).toEqual( + 'var(--wp--preset--spacing--20) var(--wp--preset--spacing--60)' + ); + } ); } ); } ); diff --git a/packages/block-editor/src/hooks/text-decoration.js b/packages/block-editor/src/hooks/text-decoration.js index 65f0aadf77d1e..17ba9ee73f698 100644 --- a/packages/block-editor/src/hooks/text-decoration.js +++ b/packages/block-editor/src/hooks/text-decoration.js @@ -46,6 +46,7 @@ export function TextDecorationEdit( props ) { ); } diff --git a/packages/block-editor/src/hooks/text-transform.js b/packages/block-editor/src/hooks/text-transform.js index b2710b5f654b5..588327633ecb8 100644 --- a/packages/block-editor/src/hooks/text-transform.js +++ b/packages/block-editor/src/hooks/text-transform.js @@ -46,6 +46,7 @@ export function TextTransformEdit( props ) { ); } diff --git a/packages/block-editor/src/hooks/typography.scss b/packages/block-editor/src/hooks/typography.scss index 7d7d44c24b4e2..ea939116dfba3 100644 --- a/packages/block-editor/src/hooks/typography.scss +++ b/packages/block-editor/src/hooks/typography.scss @@ -1,10 +1,4 @@ .typography-block-support-panel { - .components-font-size-picker__controls, - .block-editor-text-decoration-control__buttons, - .block-editor-text-transform-control__buttons { - margin-bottom: 0; - } - .single-column { grid-column: span 1; } diff --git a/packages/block-editor/src/layouts/constrained.js b/packages/block-editor/src/layouts/constrained.js index 8517382defcf8..8e6e5fd264f3f 100644 --- a/packages/block-editor/src/layouts/constrained.js +++ b/packages/block-editor/src/layouts/constrained.js @@ -15,7 +15,7 @@ import { getCSSRules } from '@wordpress/style-engine'; */ import useSetting from '../components/use-setting'; import { appendSelectors, getBlockGapCSS, getAlignmentsInfo } from './utils'; -import { getGapBoxControlValueFromStyle } from '../hooks/gap'; +import { getGapCSSValue } from '../hooks/gap'; import { shouldSkipSerialization } from '../hooks/utils'; export default { @@ -117,16 +117,19 @@ export default { layoutDefinitions, } ) { const { contentSize, wideSize } = layout; - const blockGapStyleValue = getGapBoxControlValueFromStyle( - style?.spacing?.blockGap - ); + const blockGapStyleValue = getGapCSSValue( style?.spacing?.blockGap ); + // If a block's block.json skips serialization for spacing or // spacing.blockGap, don't apply the user-defined value to the styles. - const blockGapValue = - blockGapStyleValue?.top && - ! shouldSkipSerialization( blockName, 'spacing', 'blockGap' ) - ? blockGapStyleValue?.top - : ''; + let blockGapValue = ''; + if ( ! shouldSkipSerialization( blockName, 'spacing', 'blockGap' ) ) { + // If an object is provided only use the 'top' value for this kind of gap. + if ( blockGapStyleValue?.top ) { + blockGapValue = getGapCSSValue( blockGapStyleValue?.top ); + } else if ( typeof blockGapStyleValue === 'string' ) { + blockGapValue = getGapCSSValue( blockGapStyleValue ); + } + } let output = !! contentSize || !! wideSize diff --git a/packages/block-editor/src/layouts/flow.js b/packages/block-editor/src/layouts/flow.js index a41ced61cb6ab..ecc6779b9128d 100644 --- a/packages/block-editor/src/layouts/flow.js +++ b/packages/block-editor/src/layouts/flow.js @@ -6,9 +6,8 @@ import { __ } from '@wordpress/i18n'; /** * Internal dependencies */ - import { getBlockGapCSS, getAlignmentsInfo } from './utils'; -import { getGapBoxControlValueFromStyle } from '../hooks/gap'; +import { getGapCSSValue } from '../hooks/gap'; import { shouldSkipSerialization } from '../hooks/utils'; export default { @@ -27,16 +26,19 @@ export default { hasBlockGapSupport, layoutDefinitions, } ) { - const blockGapStyleValue = getGapBoxControlValueFromStyle( - style?.spacing?.blockGap - ); + const blockGapStyleValue = getGapCSSValue( style?.spacing?.blockGap ); + // If a block's block.json skips serialization for spacing or // spacing.blockGap, don't apply the user-defined value to the styles. - const blockGapValue = - blockGapStyleValue?.top && - ! shouldSkipSerialization( blockName, 'spacing', 'blockGap' ) - ? blockGapStyleValue?.top - : ''; + let blockGapValue = ''; + if ( ! shouldSkipSerialization( blockName, 'spacing', 'blockGap' ) ) { + // If an object is provided only use the 'top' value for this kind of gap. + if ( blockGapStyleValue?.top ) { + blockGapValue = getGapCSSValue( blockGapStyleValue?.top ); + } else if ( typeof blockGapStyleValue === 'string' ) { + blockGapValue = getGapCSSValue( blockGapStyleValue ); + } + } let output = ''; @@ -65,6 +67,7 @@ export default { info: alignmentInfo[ alignment ], } ) ); } + const { contentSize, wideSize } = layout; const alignments = [ { name: 'left' }, @@ -72,6 +75,14 @@ export default { { name: 'right' }, ]; + if ( contentSize ) { + alignments.unshift( { name: 'full' } ); + } + + if ( wideSize ) { + alignments.unshift( { name: 'wide', info: alignmentInfo.wide } ); + } + alignments.unshift( { name: 'none', info: alignmentInfo.none } ); return alignments; diff --git a/packages/block-editor/src/style.scss b/packages/block-editor/src/style.scss index 614f611cec2af..b2c08654ebea6 100644 --- a/packages/block-editor/src/style.scss +++ b/packages/block-editor/src/style.scss @@ -46,8 +46,6 @@ @import "./components/responsive-block-control/style.scss"; @import "./components/rich-text/style.scss"; @import "./components/skip-to-selected-block/style.scss"; -@import "./components/text-transform-control/style.scss"; -@import "./components/text-decoration-control/style.scss"; @import "./components/tool-selector/style.scss"; @import "./components/url-input/style.scss"; @import "./components/url-popover/style.scss"; diff --git a/packages/block-library/CHANGELOG.md b/packages/block-library/CHANGELOG.md index d276a5b215943..13d42304fbc8e 100644 --- a/packages/block-library/CHANGELOG.md +++ b/packages/block-library/CHANGELOG.md @@ -2,7 +2,7 @@ ## Unreleased -## 7.12.1-next.0 (2022-08-23) +## 7.13.0 (2022-08-24) ### Bug Fix diff --git a/packages/block-library/package.json b/packages/block-library/package.json index 65ca557bb9848..38bea5efd407b 100644 --- a/packages/block-library/package.json +++ b/packages/block-library/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/block-library", - "version": "7.12.2-next.d6164808d3.0", + "version": "7.13.0", "description": "Block library for the WordPress editor.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/block-library/src/button/style.scss b/packages/block-library/src/button/style.scss index 984027ddbd93a..58fe01e12e99f 100644 --- a/packages/block-library/src/button/style.scss +++ b/packages/block-library/src/button/style.scss @@ -4,20 +4,12 @@ $blocks-block__margin: 0.5em; // Prefer the link selector instead of the regular button classname // to support the previous markup in addition to the new one. .wp-block-button__link { - box-shadow: none; cursor: pointer; display: inline-block; text-align: center; word-break: break-word; // overflow-wrap doesn't work well if a link is wrapped in the div, so use word-break here. box-sizing: border-box; - &:hover, - &:focus, - &:active, - &:visited { - color: $white; - } - &.aligncenter { text-align: center; } @@ -32,6 +24,7 @@ $blocks-block__margin: 0.5em; // They are needed for backwards compatibility. :where(.wp-block-button__link) { // This needs a low specificity so it won't override the rules from the button element if defined in theme.json. + box-shadow: none; text-decoration: none; // 100% causes an oval, but any explicit but really high value retains the pill shape. diff --git a/packages/block-library/src/code/editor.scss b/packages/block-library/src/code/editor.scss new file mode 100644 index 0000000000000..3e6d6ba3d80af --- /dev/null +++ b/packages/block-library/src/code/editor.scss @@ -0,0 +1,3 @@ +.wp-block-code code { + background: none; +} diff --git a/packages/block-library/src/columns/transforms.js b/packages/block-library/src/columns/transforms.js index cef6bd32794e0..9fb80e49e4295 100644 --- a/packages/block-library/src/columns/transforms.js +++ b/packages/block-library/src/columns/transforms.js @@ -29,9 +29,23 @@ const transforms = { createBlocksFromInnerBlocksTemplate( innerBlocksTemplate ) ); }, - isMatch: ( { length: selectedBlocksLength } ) => - selectedBlocksLength && - selectedBlocksLength <= MAXIMUM_SELECTED_BLOCKS, + isMatch: ( { length: selectedBlocksLength }, blocks ) => { + // If a user is trying to transform a single Columns block, skip + // the transformation. Enabling this functiontionality creates + // nested Columns blocks resulting in an unintuitive user experience. + // Multiple Columns blocks can still be transformed. + if ( + blocks.length === 1 && + blocks[ 0 ].name === 'core/columns' + ) { + return false; + } + + return ( + selectedBlocksLength && + selectedBlocksLength <= MAXIMUM_SELECTED_BLOCKS + ); + }, }, { type: 'block', diff --git a/packages/block-library/src/cover/edit/block-controls.js b/packages/block-library/src/cover/edit/block-controls.js index 59aaaaffe77d7..65b6f3765765f 100644 --- a/packages/block-library/src/cover/edit/block-controls.js +++ b/packages/block-library/src/cover/edit/block-controls.js @@ -9,7 +9,7 @@ import { __experimentalBlockAlignmentMatrixControl as BlockAlignmentMatrixControl, __experimentalBlockFullHeightAligmentControl as FullHeightAlignmentControl, } from '@wordpress/block-editor'; -import { __ } from '@wordpress/i18n'; +import { __, isRTL } from '@wordpress/i18n'; /** * Internal dependencies @@ -58,15 +58,27 @@ export default function CoverBlockControls( { } ); }; + // Flip value horizontally to match the physical direction indicated by + // AlignmentMatrixControl with the logical direction indicated by cover + // block in RTL languages. + const flipHorizontalPosition = ( ltrContentPosition ) => { + return isRTL() + ? ltrContentPosition.replace( /left|right/, ( match ) => + match === 'left' ? 'right' : 'left' + ) + : ltrContentPosition; + }; + return ( <> setAttributes( { - contentPosition: nextPosition, + contentPosition: + flipHorizontalPosition( nextPosition ), } ) } isDisabled={ ! hasInnerBlocks } diff --git a/packages/block-library/src/cover/editor.scss b/packages/block-library/src/cover/editor.scss index cd421ddb66848..9e770b6278f34 100644 --- a/packages/block-library/src/cover/editor.scss +++ b/packages/block-library/src/cover/editor.scss @@ -47,10 +47,6 @@ margin: 0; } - .block-editor-block-list__layout { - width: 100%; - } - // The .wp-block-cover class is used just to increase selector specificity. .wp-block-cover__inner-container { // Avoid text align inherit from cover image align. diff --git a/packages/block-library/src/editor.scss b/packages/block-library/src/editor.scss index 6bb4a8d2d2cb8..edb66416af88c 100644 --- a/packages/block-library/src/editor.scss +++ b/packages/block-library/src/editor.scss @@ -5,6 +5,7 @@ @import "./button/editor.scss"; @import "./buttons/editor.scss"; @import "./categories/editor.scss"; +@import "./code/editor.scss"; @import "./columns/editor.scss"; @import "./comments/editor.scss"; @import "./comment-author-avatar/editor.scss"; diff --git a/packages/block-library/src/gallery/gap-styles.js b/packages/block-library/src/gallery/gap-styles.js index 9c5b217d18ba9..7bd0932d62f5b 100644 --- a/packages/block-library/src/gallery/gap-styles.js +++ b/packages/block-library/src/gallery/gap-styles.js @@ -1,7 +1,10 @@ /** * WordPress dependencies */ -import { BlockList } from '@wordpress/block-editor'; +import { + BlockList, + __experimentalGetGapCSSValue as getGapCSSValue, +} from '@wordpress/block-editor'; import { useContext, createPortal } from '@wordpress/element'; export default function GapStyles( { blockGap, clientId } ) { @@ -17,17 +20,18 @@ export default function GapStyles( { blockGap, clientId } ) { if ( !! blockGap ) { row = typeof blockGap === 'string' - ? blockGap - : blockGap?.top || fallbackValue; + ? getGapCSSValue( blockGap ) + : getGapCSSValue( blockGap?.top ) || fallbackValue; column = typeof blockGap === 'string' - ? blockGap - : blockGap?.left || fallbackValue; + ? getGapCSSValue( blockGap ) + : getGapCSSValue( blockGap?.left ) || fallbackValue; gapValue = row === column ? row : `${ row } ${ column }`; } + // The unstable gallery gap calculation requires a real value (such as `0px`) and not `0`. const gap = `#block-${ clientId } { - --wp--style--unstable-gallery-gap: ${ column }; + --wp--style--unstable-gallery-gap: ${ column === '0' ? '0px' : column }; gap: ${ gapValue } }`; diff --git a/packages/block-library/src/gallery/index.php b/packages/block-library/src/gallery/index.php index e6eecb7dda412..9f29184ffed07 100644 --- a/packages/block-library/src/gallery/index.php +++ b/packages/block-library/src/gallery/index.php @@ -51,13 +51,29 @@ function block_core_gallery_render( $attributes, $content ) { if ( is_array( $gap ) ) { foreach ( $gap as $key => $value ) { // Make sure $value is a string to avoid PHP 8.1 deprecation error in preg_match() when the value is null. - $value = is_string( $value ) ? $value : ''; - $gap[ $key ] = $value && preg_match( '%[\\\(&=}]|/\*%', $value ) ? null : $value; + $value = is_string( $value ) ? $value : ''; + $value = $value && preg_match( '%[\\\(&=}]|/\*%', $value ) ? null : $value; + + // Get spacing CSS variable from preset value if provided. + if ( is_string( $value ) && str_contains( $value, 'var:preset|spacing|' ) ) { + $index_to_splice = strrpos( $value, '|' ) + 1; + $slug = _wp_to_kebab_case( substr( $value, $index_to_splice ) ); + $value = "var(--wp--preset--spacing--$slug)"; + } + + $gap[ $key ] = $value; } } else { // Make sure $gap is a string to avoid PHP 8.1 deprecation error in preg_match() when the value is null. $gap = is_string( $gap ) ? $gap : ''; $gap = $gap && preg_match( '%[\\\(&=}]|/\*%', $gap ) ? null : $gap; + + // Get spacing CSS variable from preset value if provided. + if ( is_string( $gap ) && str_contains( $gap, 'var:preset|spacing|' ) ) { + $index_to_splice = strrpos( $gap, '|' ) + 1; + $slug = _wp_to_kebab_case( substr( $gap, $index_to_splice ) ); + $gap = "var(--wp--preset--spacing--$slug)"; + } } $class = wp_unique_id( 'wp-block-gallery-' ); @@ -80,6 +96,11 @@ function block_core_gallery_render( $attributes, $content ) { $gap_value = $gap_row === $gap_column ? $gap_row : $gap_row . ' ' . $gap_column; } + // The unstable gallery gap calculation requires a real value (such as `0px`) and not `0`. + if ( '0' === $gap_column ) { + $gap_column = '0px'; + } + // Set the CSS variable to the column value, and the `gap` property to the combined gap value. $style = '.wp-block-gallery.' . $class . '{ --wp--style--unstable-gallery-gap: ' . $gap_column . '; gap: ' . $gap_value . '}'; diff --git a/packages/block-library/src/group/editor.scss b/packages/block-library/src/group/editor.scss index cbc0af62acdd6..a743b0abd2644 100644 --- a/packages/block-library/src/group/editor.scss +++ b/packages/block-library/src/group/editor.scss @@ -39,12 +39,10 @@ &::after { content: ""; display: flex; - border: $border-width dashed currentColor; - opacity: 0.4; - border-radius: $radius-block-ui; flex: 1 0 $grid-unit-60; pointer-events: none; min-height: $grid-unit-60 - $border-width - $border-width; + @include placeholder-style(); } // Let the parent be selectable in the placeholder area. @@ -53,28 +51,3 @@ pointer-events: all; } } - -// Show an unselected empty group button as a dashed outline instead of the appender button. -// This effectively adds a selectable-to-delete state. -.is-layout-flow.block-editor-block-list__block:not(.is-selected) { - > .block-list-appender:only-child { - pointer-events: none; - - &::after { - content: ""; - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - border: $border-width dashed currentColor; - opacity: 0.4; - border-radius: $radius-block-ui; - pointer-events: none; - } - - .block-editor-inserter { - visibility: hidden; - } - } -} diff --git a/packages/block-library/src/home-link/block.json b/packages/block-library/src/home-link/block.json index 3597aa9d5554a..567bdf8c27ba7 100644 --- a/packages/block-library/src/home-link/block.json +++ b/packages/block-library/src/home-link/block.json @@ -23,7 +23,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-home-link-editor", "style": "wp-block-home-link" diff --git a/packages/block-library/src/image/edit.js b/packages/block-library/src/image/edit.js index 7e45b27e546c6..bfc72d7cde476 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, has, isEmpty, omit, pick } from 'lodash'; +import { get, isEmpty, omit, pick } from 'lodash'; /** * WordPress dependencies @@ -98,8 +98,8 @@ export const isExternalImage = ( id, url ) => url && ! id && ! isBlobURL( url ); */ function hasDefaultSize( image, defaultSize ) { return ( - has( image, [ 'sizes', defaultSize, 'url' ] ) || - has( image, [ 'media_details', 'sizes', defaultSize, 'source_url' ] ) + 'url' in ( image?.sizes?.[ defaultSize ] ?? {} ) || + 'source_url' in ( image?.media_details?.sizes?.[ defaultSize ] ?? {} ) ); } diff --git a/packages/block-library/src/image/use-client-width.js b/packages/block-library/src/image/use-client-width.js index 738bfb0373f88..ddbc53536bbff 100644 --- a/packages/block-library/src/image/use-client-width.js +++ b/packages/block-library/src/image/use-client-width.js @@ -7,7 +7,7 @@ export default function useClientWidth( ref, dependencies ) { const [ clientWidth, setClientWidth ] = useState(); function calculateClientWidth() { - setClientWidth( ref.current.clientWidth ); + setClientWidth( ref.current?.clientWidth ); } useEffect( calculateClientWidth, dependencies ); diff --git a/packages/block-library/src/index.js b/packages/block-library/src/index.js index f0d5924d7248d..e71ee9326a4b8 100644 --- a/packages/block-library/src/index.js +++ b/packages/block-library/src/index.js @@ -290,10 +290,7 @@ export const registerCoreBlocks = ( export const __experimentalRegisterExperimentalCoreBlocks = process.env .IS_GUTENBERG_PLUGIN ? ( { enableFSEBlocks } = {} ) => { - const enabledExperiments = [ - window.__experimentalEnableListBlockV2 ? 'list-v2' : null, - enableFSEBlocks ? 'fse' : null, - ]; + const enabledExperiments = [ enableFSEBlocks ? 'fse' : null ]; getAllBlocks() .filter( ( { metadata } ) => isBlockMetadataExperimental( metadata ) diff --git a/packages/block-library/src/index.native.js b/packages/block-library/src/index.native.js index 6285ad2f91377..1acc90c0116c6 100644 --- a/packages/block-library/src/index.native.js +++ b/packages/block-library/src/index.native.js @@ -186,14 +186,6 @@ const devOnly = ( block ) => ( !! __DEV__ ? block : null ); const iOSOnly = ( block ) => Platform.OS === 'ios' ? block : devOnly( block ); -// To be removed once List V2 is released on the web editor. -function listCheck( listBlock, blocksFlags ) { - if ( blocksFlags?.__experimentalEnableListBlockV2 ) { - listBlock.settings = listBlock?.settingsV2; - } - return listBlock; -} - // Hide the Classic block and SocialLink block addFilter( 'blocks.registerBlockType', @@ -245,11 +237,8 @@ addFilter( * * registerCoreBlocks(); * ``` - * @param {Object} [blocksFlags] Experimental flags - * - * */ -export const registerCoreBlocks = ( blocksFlags ) => { +export const registerCoreBlocks = () => { // When adding new blocks to this list please also consider updating /src/block-support/supported-blocks.json in the Gutenberg-Mobile repo [ paragraph, @@ -261,7 +250,7 @@ export const registerCoreBlocks = ( blocksFlags ) => { video, nextpage, separator, - listCheck( list, blocksFlags ), + list, listItem, quote, mediaText, diff --git a/packages/block-library/src/latest-posts/block.json b/packages/block-library/src/latest-posts/block.json index 6dcffe37a6c6a..b423b09d900cb 100644 --- a/packages/block-library/src/latest-posts/block.json +++ b/packages/block-library/src/latest-posts/block.json @@ -84,7 +84,20 @@ }, "supports": { "align": true, - "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-latest-posts-editor", "style": "wp-block-latest-posts" diff --git a/packages/block-library/src/list-item/block.json b/packages/block-library/src/list-item/block.json index 5b8ad8456944f..674cf381aa99c 100644 --- a/packages/block-library/src/list-item/block.json +++ b/packages/block-library/src/list-item/block.json @@ -1,7 +1,6 @@ { "$schema": "https://schemas.wp.org/trunk/block.json", "apiVersion": 2, - "__experimental": "list-v2", "name": "core/list-item", "title": "List item", "category": "text", diff --git a/packages/block-library/src/list-item/edit.native.js b/packages/block-library/src/list-item/edit.native.js index 2762c28c6024d..5326cbd79b0e4 100644 --- a/packages/block-library/src/list-item/edit.native.js +++ b/packages/block-library/src/list-item/edit.native.js @@ -14,6 +14,7 @@ import { store as blockEditorStore, } from '@wordpress/block-editor'; import { __ } from '@wordpress/i18n'; +import { usePreferredColorSchemeStyle } from '@wordpress/compose'; import { useSelect } from '@wordpress/data'; import { useState, useCallback } from '@wordpress/element'; @@ -26,6 +27,8 @@ import { IndentUI } from './edit.js'; import styles from './style.scss'; import ListStyleType from './list-style-type'; +const OPACITY = '9e'; + export default function ListItemEdit( { attributes, setAttributes, @@ -84,11 +87,35 @@ export default function ListItemEdit( { const blockProps = useBlockProps( { ...( hasInnerBlocks && styles[ 'wp-block-list-item__nested-blocks' ] ), } ); + const innerBlocksProps = useInnerBlocksProps( blockProps, { allowedBlocks: [ 'core/list' ], - renderAppender: false, + useCompactList: true, } ); + // Set default placeholder text color from light/dark scheme or base colors + const defaultPlaceholderFromScheme = usePreferredColorSchemeStyle( + styles[ 'wp-block-list-item__list-item-placeholder' ], + styles[ 'wp-block-list-item__list-item-placeholder--dark' ] + ); + + const currentTextColor = style?.color || style?.baseColors?.color?.text; + + const defaultPlaceholderTextColor = currentTextColor + ? currentTextColor + : defaultPlaceholderFromScheme?.color; + + // Add hex opacity to default placeholder text color and style object + const defaultPlaceholderTextColorWithOpacity = + defaultPlaceholderTextColor + OPACITY; + + const styleWithPlaceholderOpacity = { + ...style, + ...( style?.color && { + placeholderColor: style.color + OPACITY, + } ), + }; + const onSplit = useSplit( clientId ); const onMerge = useMerge( clientId ); const onLayout = useCallback( ( { nativeEvent } ) => { @@ -128,12 +155,15 @@ export default function ListItemEdit( { } value={ content } placeholder={ placeholder || __( 'List' ) } + placeholderTextColor={ + defaultPlaceholderTextColorWithOpacity + } onSplit={ onSplit } onMerge={ onMerge } onReplace={ ( blocks, ...args ) => { onReplace( convertToListItems( blocks ), ...args ); } } - style={ style } + style={ styleWithPlaceholderOpacity } deleteEnter={ true } containerWidth={ contentWidth } /> diff --git a/packages/block-library/src/list-item/hooks/use-copy.js b/packages/block-library/src/list-item/hooks/use-copy.js index ed9e285ca8714..7a76019ad11a4 100644 --- a/packages/block-library/src/list-item/hooks/use-copy.js +++ b/packages/block-library/src/list-item/hooks/use-copy.js @@ -29,8 +29,10 @@ export default function useCopy( clientId ) { } node.addEventListener( 'copy', onCopy ); + node.addEventListener( 'cut', onCopy ); return () => { node.removeEventListener( 'copy', onCopy ); + node.removeEventListener( 'cut', onCopy ); }; }, [] ); } diff --git a/packages/block-library/src/list-item/hooks/use-space.js b/packages/block-library/src/list-item/hooks/use-space.js index 3fc6d1157eb2e..6079b2c5edb28 100644 --- a/packages/block-library/src/list-item/hooks/use-space.js +++ b/packages/block-library/src/list-item/hooks/use-space.js @@ -19,13 +19,21 @@ export default function useSpace( clientId ) { return useRefEffect( ( element ) => { function onKeyDown( event ) { + const { keyCode, shiftKey, altKey, metaKey, ctrlKey } = event; + if ( event.defaultPrevented || - event.keyCode !== SPACE || - ! canIndent + ! canIndent || + keyCode !== SPACE || + // Only override when no modifiers are pressed. + shiftKey || + altKey || + metaKey || + ctrlKey ) { return; } + const selectionStart = getSelectionStart(); const selectionEnd = getSelectionEnd(); if ( diff --git a/packages/block-library/src/list-item/style.native.scss b/packages/block-library/src/list-item/style.native.scss index f4946ff39c7cd..b5cdd3e8919ca 100644 --- a/packages/block-library/src/list-item/style.native.scss +++ b/packages/block-library/src/list-item/style.native.scss @@ -47,3 +47,11 @@ .wp-block-list-item__list-item-container { margin-right: 8; } + +.wp-block-list-item__list-item-placeholder { + color: $gray; +} + +.wp-block-list-item__list-item-placeholder--dark { + color: $gray-50; +} diff --git a/packages/block-library/src/list/block.json b/packages/block-library/src/list/block.json index 0d68dbaae5d8f..0d49991f903b9 100644 --- a/packages/block-library/src/list/block.json +++ b/packages/block-library/src/list/block.json @@ -58,6 +58,10 @@ "text": true } }, + "spacing": { + "margin": true, + "padding": true + }, "__unstablePasteTextInline": true, "__experimentalSelector": "ol,ul", "__experimentalSlashInserter": true diff --git a/packages/block-library/src/list/deprecated.js b/packages/block-library/src/list/deprecated.js index a3454f5ae050c..cfd7e3039cee8 100644 --- a/packages/block-library/src/list/deprecated.js +++ b/packages/block-library/src/list/deprecated.js @@ -7,6 +7,7 @@ import { RichText, useBlockProps } from '@wordpress/block-editor'; * Internal dependencies */ import migrateFontFamily from '../utils/migrate-font-family'; +import { migrateToListV2 } from './utils'; const v0 = { attributes: { @@ -68,6 +69,75 @@ const v0 = { }, }; +const v1 = { + attributes: { + ordered: { + type: 'boolean', + default: false, + __experimentalRole: 'content', + }, + values: { + type: 'string', + source: 'html', + selector: 'ol,ul', + multiline: 'li', + __unstableMultilineWrapperTags: [ 'ol', 'ul' ], + default: '', + __experimentalRole: 'content', + }, + type: { + type: 'string', + }, + start: { + type: 'number', + }, + reversed: { + type: 'boolean', + }, + placeholder: { + type: 'string', + }, + }, + supports: { + anchor: true, + className: false, + typography: { + fontSize: true, + __experimentalFontFamily: true, + lineHeight: true, + __experimentalFontStyle: true, + __experimentalFontWeight: true, + __experimentalLetterSpacing: true, + __experimentalTextTransform: true, + __experimentalDefaultControls: { + fontSize: true, + }, + }, + color: { + gradients: true, + link: true, + __experimentalDefaultControls: { + background: true, + text: true, + }, + }, + __unstablePasteTextInline: true, + __experimentalSelector: 'ol,ul', + __experimentalSlashInserter: true, + }, + save( { attributes } ) { + const { ordered, values, type, reversed, start } = attributes; + const TagName = ordered ? 'ol' : 'ul'; + + return ( + + + + ); + }, + migrate: migrateToListV2, +}; + /** * New deprecations need to be placed first * for them to have higher priority. @@ -76,4 +146,4 @@ const v0 = { * * See block-deprecation.md */ -export default [ v0 ]; +export default [ v1, v0 ]; diff --git a/packages/block-library/src/list/edit.js b/packages/block-library/src/list/edit.js index 8d39252aee128..2cb6ab0a75586 100644 --- a/packages/block-library/src/list/edit.js +++ b/packages/block-library/src/list/edit.js @@ -1,177 +1,190 @@ +/** + * External dependencies + */ +import { last } from 'lodash'; + /** * WordPress dependencies */ -import { __, _x, isRTL } from '@wordpress/i18n'; -import { createBlock } from '@wordpress/blocks'; import { - RichText, BlockControls, - RichTextShortcut, useBlockProps, + useInnerBlocksProps, + store as blockEditorStore, } from '@wordpress/block-editor'; import { ToolbarButton } from '@wordpress/components'; -import { - __unstableCanIndentListItems as canIndentListItems, - __unstableCanOutdentListItems as canOutdentListItems, - __unstableIndentListItems as indentListItems, - __unstableOutdentListItems as outdentListItems, - __unstableChangeListType as changeListType, - __unstableIsListRootSelected as isListRootSelected, - __unstableIsActiveListType as isActiveListType, -} from '@wordpress/rich-text'; +import { useDispatch, useSelect, useRegistry } from '@wordpress/data'; +import { isRTL, __ } from '@wordpress/i18n'; import { formatListBullets, formatListBulletsRTL, formatListNumbered, formatListNumberedRTL, - formatIndent, - formatIndentRTL, formatOutdent, formatOutdentRTL, } from '@wordpress/icons'; +import { createBlock } from '@wordpress/blocks'; +import { useCallback, useEffect, Platform } from '@wordpress/element'; +import deprecated from '@wordpress/deprecated'; /** * Internal dependencies */ -import { name } from './'; import OrderedListSettings from './ordered-list-settings'; +import { migrateToListV2 } from './utils'; +import TagName from './tag-name'; -export default function ListEdit( { - attributes, - setAttributes, - mergeBlocks, - onReplace, - style, -} ) { - const { ordered, values, type, reversed, start, placeholder } = attributes; - const tagName = ordered ? 'ol' : 'ul'; +const TEMPLATE = [ [ 'core/list-item' ] ]; +const NATIVE_MARGIN_SPACING = 8; - const controls = ( { value, onChange, onFocus } ) => ( - <> - { - onChange( outdentListItems( value ) ); - } } - /> - { - onChange( indentListItems( value, { type: tagName } ) ); - } } - /> - { - onChange( indentListItems( value, { type: tagName } ) ); - } } - /> - { - onChange( outdentListItems( value ) ); - } } - /> - - { - onChange( changeListType( value, { type: 'ul' } ) ); - onFocus(); +/** + * At the moment, deprecations don't handle create blocks from attributes + * (like when using CPT templates). For this reason, this hook is necessary + * to avoid breaking templates using the old list block format. + * + * @param {Object} attributes Block attributes. + * @param {string} clientId Block client ID. + */ +function useMigrateOnLoad( attributes, clientId ) { + const registry = useRegistry(); + const { updateBlockAttributes, replaceInnerBlocks } = + useDispatch( blockEditorStore ); - if ( isListRootSelected( value ) ) { - setAttributes( { ordered: false } ); - } - } } - /> - { - onChange( changeListType( value, { type: 'ol' } ) ); - onFocus(); + useEffect( () => { + // As soon as the block is loaded, migrate it to the new version. - if ( isListRootSelected( value ) ) { - setAttributes( { ordered: true } ); - } - } } - /> - { - onChange( outdentListItems( value ) ); - onFocus(); - } } - /> - { - onChange( indentListItems( value, { type: tagName } ) ); - onFocus(); - } } - /> - + if ( ! attributes.values ) { + return; + } + + const [ newAttributes, newInnerBlocks ] = migrateToListV2( attributes ); + + deprecated( 'Value attribute on the list block', { + since: '6.0', + version: '6.5', + alternative: 'inner blocks', + } ); + + registry.batch( () => { + updateBlockAttributes( clientId, newAttributes ); + replaceInnerBlocks( clientId, newInnerBlocks ); + } ); + }, [ attributes.values ] ); +} + +function useOutdentList( clientId ) { + const { canOutdent } = useSelect( + ( innerSelect ) => { + const { getBlockRootClientId, getBlock } = + innerSelect( blockEditorStore ); + const parentId = getBlockRootClientId( clientId ); + return { + canOutdent: + !! parentId && + getBlock( parentId ).name === 'core/list-item', + }; + }, + [ clientId ] + ); + const { replaceBlocks, selectionChange } = useDispatch( blockEditorStore ); + const { getBlockRootClientId, getBlockAttributes, getBlock } = + useSelect( blockEditorStore ); + + return [ + canOutdent, + useCallback( () => { + const parentBlockId = getBlockRootClientId( clientId ); + const parentBlockAttributes = getBlockAttributes( parentBlockId ); + // Create a new parent block without the inner blocks. + const newParentBlock = createBlock( + 'core/list-item', + parentBlockAttributes + ); + const { innerBlocks } = getBlock( clientId ); + // Replace the parent block with a new parent block without inner blocks, + // and make the inner blocks siblings of the parent. + replaceBlocks( + [ parentBlockId ], + [ newParentBlock, ...innerBlocks ] + ); + // Select the last child of the list being outdent. + selectionChange( last( innerBlocks ).clientId ); + }, [ clientId ] ), + ]; +} + +function IndentUI( { clientId } ) { + const [ canOutdent, outdentList ] = useOutdentList( clientId ); + return ( + <> + ); +} +export default function Edit( { attributes, setAttributes, clientId, style } ) { const blockProps = useBlockProps( { - style, + ...( Platform.isNative && { style } ), + } ); + const innerBlocksProps = useInnerBlocksProps( blockProps, { + allowedBlocks: [ 'core/list-item' ], + template: TEMPLATE, + templateInsertUpdatesSelection: true, + ...( Platform.isNative && { + marginVertical: NATIVE_MARGIN_SPACING, + marginHorizontal: NATIVE_MARGIN_SPACING, + useCompactList: true, + } ), } ); + useMigrateOnLoad( attributes, clientId ); + const { ordered, type, reversed, start } = attributes; + + const controls = ( + + { + setAttributes( { ordered: false } ); + } } + /> + { + setAttributes( { ordered: true } ); + } } + /> + + + ); return ( <> - - setAttributes( { values: nextValues } ) - } - value={ values } - aria-label={ __( 'List text' ) } - placeholder={ placeholder || __( 'List' ) } - onMerge={ mergeBlocks } - onSplit={ ( value ) => - createBlock( name, { ...attributes, values: value } ) - } - __unstableOnSplitMiddle={ () => - createBlock( 'core/paragraph' ) - } - onReplace={ onReplace } - onRemove={ () => onReplace( [] ) } - start={ start } + - { controls } - + { ...innerBlocksProps } + /> + { controls } { ordered && ( ) } diff --git a/packages/block-library/src/list/index.js b/packages/block-library/src/list/index.js index a401d75eb1458..6df2d4ef40333 100644 --- a/packages/block-library/src/list/index.js +++ b/packages/block-library/src/list/index.js @@ -2,6 +2,7 @@ * WordPress dependencies */ import { list as icon } from '@wordpress/icons'; +import { __ } from '@wordpress/i18n'; /** * Internal dependencies @@ -11,41 +12,41 @@ import edit from './edit'; import metadata from './block.json'; import save from './save'; import transforms from './transforms'; -import settingsV2 from './v2'; const { name } = metadata; -export { metadata, name, settingsV2 }; +export { metadata, name }; -const settingsV1 = { +const settings = { icon, example: { - attributes: { - values: '
  • Alice.
  • The White Rabbit.
  • The Cheshire Cat.
  • The Mad Hatter.
  • The Queen of Hearts.
  • ', - }, + innerBlocks: [ + { + name: 'core/list-item', + attributes: { content: __( 'Alice.' ) }, + }, + { + name: 'core/list-item', + attributes: { content: __( 'The White Rabbit.' ) }, + }, + { + name: 'core/list-item', + attributes: { content: __( 'The Cheshire Cat.' ) }, + }, + { + name: 'core/list-item', + attributes: { content: __( 'The Mad Hatter.' ) }, + }, + { + name: 'core/list-item', + attributes: { content: __( 'The Queen of Hearts.' ) }, + }, + ], }, transforms, - merge( attributes, attributesToMerge ) { - const { values } = attributesToMerge; - - if ( ! values || values === '
  • ' ) { - return attributes; - } - - return { - ...attributes, - values: attributes.values + values, - }; - }, edit, save, deprecated, }; -let settings = settingsV1; -if ( process.env.IS_GUTENBERG_PLUGIN ) { - settings = window?.__experimentalEnableListBlockV2 - ? settingsV2 - : settingsV1; -} export { settings }; diff --git a/packages/block-library/src/list/save.js b/packages/block-library/src/list/save.js index 7d4f8a37fc86a..c74893f38b67b 100644 --- a/packages/block-library/src/list/save.js +++ b/packages/block-library/src/list/save.js @@ -1,15 +1,14 @@ /** * WordPress dependencies */ -import { RichText, useBlockProps } from '@wordpress/block-editor'; +import { InnerBlocks, useBlockProps } from '@wordpress/block-editor'; export default function save( { attributes } ) { - const { ordered, values, type, reversed, start } = attributes; + const { ordered, type, reversed, start } = attributes; const TagName = ordered ? 'ol' : 'ul'; - return ( - + ); } diff --git a/packages/block-library/src/list/v2/tag-name.js b/packages/block-library/src/list/tag-name.js similarity index 100% rename from packages/block-library/src/list/v2/tag-name.js rename to packages/block-library/src/list/tag-name.js diff --git a/packages/block-library/src/list/v2/tag-name.native.js b/packages/block-library/src/list/tag-name.native.js similarity index 100% rename from packages/block-library/src/list/v2/tag-name.native.js rename to packages/block-library/src/list/tag-name.native.js diff --git a/packages/block-library/src/list/test/__snapshots__/edit.native.js.snap b/packages/block-library/src/list/test/__snapshots__/edit.native.js.snap index 3fe53069b1775..a573276840bb6 100644 --- a/packages/block-library/src/list/test/__snapshots__/edit.native.js.snap +++ b/packages/block-library/src/list/test/__snapshots__/edit.native.js.snap @@ -122,12 +122,24 @@ exports[`List V2 block shows different indentation levels 1`] = ` exports[`List block inserts block 1`] = ` " -
    +
      +
    • +
    " `; exports[`List block renders a list with a few items 1`] = ` " -
    • Item 1
    • Item 2
    • Item 3
    +
      +
    • Item 1
    • + + + +
    • Item 2
    • + + + +
    • Item 3
    • +
    " `; diff --git a/packages/block-library/src/list/test/edit.native.js b/packages/block-library/src/list/test/edit.native.js index 62738da807dc4..0d3bff36b067b 100644 --- a/packages/block-library/src/list/test/edit.native.js +++ b/packages/block-library/src/list/test/edit.native.js @@ -119,20 +119,6 @@ describe( 'List V2 block', () => { // Select List block const listBlock = getByA11yLabel( /List Block\. Row 1/ ); - - fireEvent( - within( listBlock ).getByTestId( 'block-list-wrapper' ), - 'layout', - { - nativeEvent: { - layout: { - width: 100, - height: 50, - }, - }, - } - ); - fireEvent.press( listBlock ); // Select List Item block @@ -180,61 +166,18 @@ describe( 'List V2 block', () => { // Select List block const listBlock = getByA11yLabel( /List Block\. Row 1/ ); - fireEvent( - within( listBlock ).getByTestId( 'block-list-wrapper' ), - 'layout', - { - nativeEvent: { - layout: { - width: 100, - height: 50, - }, - }, - } - ); - fireEvent.press( listBlock ); // Select List Item block const firstNestedLevelBlock = within( listBlock ).getByA11yLabel( /List item Block\. Row 2/ ); - - fireEvent( - within( firstNestedLevelBlock ).getByTestId( 'block-list-wrapper' ), - 'layout', - { - nativeEvent: { - layout: { - width: 100, - height: 350, - }, - }, - } - ); - fireEvent.press( firstNestedLevelBlock ); // Select second level list const secondNestedLevelBlock = within( firstNestedLevelBlock ).getByA11yLabel( /List Block\. Row 1/ ); - - fireEvent( - within( secondNestedLevelBlock ).getByTestId( - 'block-list-wrapper' - ), - 'layout', - { - nativeEvent: { - layout: { - width: 100, - height: 50, - }, - }, - } - ); - fireEvent.press( secondNestedLevelBlock ); expect( getEditorHtml() ).toMatchSnapshot(); @@ -256,20 +199,6 @@ describe( 'List V2 block', () => { // Select List block const listBlock = getByA11yLabel( /List Block\. Row 1/ ); - - fireEvent( - within( listBlock ).getByTestId( 'block-list-wrapper' ), - 'layout', - { - nativeEvent: { - layout: { - width: 100, - height: 50, - }, - }, - } - ); - fireEvent.press( listBlock ); // Select Secont List Item block @@ -300,40 +229,12 @@ describe( 'List V2 block', () => { // Select List block const listBlock = getByA11yLabel( /List Block\. Row 1/ ); - - fireEvent( - within( listBlock ).getByTestId( 'block-list-wrapper' ), - 'layout', - { - nativeEvent: { - layout: { - width: 100, - height: 50, - }, - }, - } - ); - fireEvent.press( listBlock ); // Select List Item block const firstNestedLevelBlock = within( listBlock ).getByA11yLabel( /List item Block\. Row 1/ ); - - fireEvent( - within( firstNestedLevelBlock ).getByTestId( 'block-list-wrapper' ), - 'layout', - { - nativeEvent: { - layout: { - width: 100, - height: 350, - }, - }, - } - ); - fireEvent.press( firstNestedLevelBlock ); // Select Inner block List @@ -341,19 +242,6 @@ describe( 'List V2 block', () => { /List Block\. Row 1/ ); - fireEvent( - within( innerBlockList ).getByTestId( 'block-list-wrapper' ), - 'layout', - { - nativeEvent: { - layout: { - width: 100, - height: 50, - }, - }, - } - ); - // Select nested List Item block const listItemBlock = within( innerBlockList ).getByA11yLabel( /List item Block\. Row 1/ @@ -386,20 +274,6 @@ describe( 'List V2 block', () => { // Select List block const listBlock = getByA11yLabel( /List Block\. Row 1/ ); - - fireEvent( - within( listBlock ).getByTestId( 'block-list-wrapper' ), - 'layout', - { - nativeEvent: { - layout: { - width: 100, - height: 50, - }, - }, - } - ); - fireEvent.press( listBlock ); // Update to ordered list @@ -428,20 +302,6 @@ describe( 'List V2 block', () => { // Select List block const listBlock = getByA11yLabel( /List Block\. Row 1/ ); - - fireEvent( - within( listBlock ).getByTestId( 'block-list-wrapper' ), - 'layout', - { - nativeEvent: { - layout: { - width: 100, - height: 50, - }, - }, - } - ); - fireEvent.press( listBlock ); // Update to ordered list @@ -481,20 +341,6 @@ describe( 'List V2 block', () => { // Select List block const listBlock = getByA11yLabel( /List Block\. Row 1/ ); - - fireEvent( - within( listBlock ).getByTestId( 'block-list-wrapper' ), - 'layout', - { - nativeEvent: { - layout: { - width: 100, - height: 50, - }, - }, - } - ); - fireEvent.press( listBlock ); // Update to ordered list diff --git a/packages/block-library/src/list/test/migrate.js b/packages/block-library/src/list/test/migrate.js deleted file mode 100644 index a57693d341bd5..0000000000000 --- a/packages/block-library/src/list/test/migrate.js +++ /dev/null @@ -1,158 +0,0 @@ -/** - * WordPress dependencies - */ -import { registerBlockType, serialize } from '@wordpress/blocks'; - -/** - * Internal dependencies - */ -import { migrateToListV2 } from '../v2/migrate'; -import * as listItem from '../../list-item'; -import * as list from '../../list'; -import listV2 from '../../list/v2'; - -describe( 'Migrate list block', () => { - beforeAll( () => { - const prev = window.__experimentalEnableListBlockV2; - - // force list and list item block registration. - registerBlockType( - { name: listItem.name, ...listItem.metadata }, - listItem.settings - ); - registerBlockType( { name: list.name, ...list.metadata }, listV2 ); - - window.__experimentalEnableListBlockV2 = prev; - } ); - - it( 'should migrate the values attribute to inner blocks', () => { - const [ updatedAttributes, updatedInnerBlocks ] = migrateToListV2( { - values: '
  • test
  • test
  • test
    1. test test
    2. test est eesssss
  • ', - ordered: false, - } ); - - expect( updatedAttributes ).toEqual( { - ordered: false, - // Ideally the values attributes shouldn't be here - // but since we didn't enable v2 by default yet, - // we're keeping the old default value in block.json - values: '', - } ); - expect( serialize( updatedInnerBlocks ) ) - .toEqual( ` -
  • test
  • - - - -
  • test
  • - - - -
  • test -
      -
    1. test test
    2. - - - -
    3. test est eesssss
    4. -
    -
  • -` ); - } ); - - it( 'should handle empty space properly', () => { - const [ updatedAttributes, updatedInnerBlocks ] = migrateToListV2( { - values: `
  • Europe
  • -
  • - \tAfrica -
      -
    1. Algeria
    2. -
    - \t -
  • `, - ordered: false, - } ); - - expect( updatedAttributes ).toEqual( { - ordered: false, - // Ideally the values attributes shouldn't be here - // but since we didn't enable v2 by default yet, - // we're keeping the old default value in block.json - values: '', - } ); - expect( serialize( updatedInnerBlocks ) ) - .toEqual( ` -
  • Europe
  • - - - -
  • Africa -
      -
    1. Algeria
    2. -
    -
  • -` ); - } ); - - it( 'should handle formats properly', () => { - const [ updatedAttributes, updatedInnerBlocks ] = migrateToListV2( { - values: `
  • Europe
    • France
      • Lyon Rhones
      • Paris Ile de france
        • 1er
  • `, - ordered: false, - } ); - - expect( updatedAttributes ).toEqual( { - ordered: false, - // Ideally the values attributes shouldn't be here - // but since we didn't enable v2 by default yet, - // we're keeping the old default value in block.json - values: '', - } ); - expect( serialize( updatedInnerBlocks ) ) - .toEqual( ` -
  • Europe -
      -
    • France -
        -
      • Lyon Rhones
      • - - - -
      • Paris Ile de france -
          -
        • 1er
        • -
        -
      • -
      -
    • -
    -
  • -` ); - } ); - - it( 'should not add random space', () => { - const [ updatedAttributes, updatedInnerBlocks ] = migrateToListV2( { - values: `
  • Europe
    • France
      • Paris
  • `, - ordered: false, - } ); - - expect( updatedAttributes ).toEqual( { - ordered: false, - // Ideally the values attributes shouldn't be here - // but since we didn't enable v2 by default yet, - // we're keeping the old default value in block.json - values: '', - } ); - expect( serialize( updatedInnerBlocks ) ) - .toEqual( ` -
  • Europe -
      -
    • France -
        -
      • Paris
      • -
      -
    • -
    -
  • -` ); - } ); -} ); diff --git a/packages/block-library/src/list/transforms.js b/packages/block-library/src/list/transforms.js index 8f2c768a343b7..a6263d7ad639c 100644 --- a/packages/block-library/src/list/transforms.js +++ b/packages/block-library/src/list/transforms.js @@ -1,15 +1,13 @@ /** * WordPress dependencies */ -import { createBlock, getBlockAttributes } from '@wordpress/blocks'; -import { - __UNSTABLE_LINE_SEPARATOR, - create, - join, - replace, - split, - toHTMLString, -} from '@wordpress/rich-text'; +import { createBlock } from '@wordpress/blocks'; +import { create, split, toHTMLString } from '@wordpress/rich-text'; + +/** + * Internal dependencies + */ +import { createListBlockFromDOMElement } from './utils'; function getListContentSchema( { phrasingContentSchema } ) { const listContentSchema = { @@ -32,6 +30,15 @@ function getListContentSchema( { phrasingContentSchema } ) { return listContentSchema; } +function getListContentFlat( blocks ) { + return blocks.flatMap( ( { name, attributes, innerBlocks = [] } ) => { + if ( name === 'core/list-item' ) { + return [ attributes.content, ...getListContentFlat( innerBlocks ) ]; + } + return getListContentFlat( innerBlocks ); + } ); +} + const transforms = { from: [ { @@ -39,30 +46,28 @@ const transforms = { isMultiBlock: true, blocks: [ 'core/paragraph', 'core/heading' ], transform: ( blockAttributes ) => { - return createBlock( 'core/list', { - values: toHTMLString( { - value: join( - blockAttributes.map( ( { content } ) => { - const value = create( { html: content } ); - - if ( blockAttributes.length > 1 ) { - return value; - } - - // When converting only one block, transform - // every line to a list item. - return replace( - value, - /\n/g, - __UNSTABLE_LINE_SEPARATOR - ); - } ), - __UNSTABLE_LINE_SEPARATOR - ), - multilineTag: 'li', - } ), - anchor: blockAttributes.anchor, - } ); + let childBlocks = []; + if ( blockAttributes.length > 1 ) { + childBlocks = blockAttributes.map( ( { content } ) => { + return createBlock( 'core/list-item', { content } ); + } ); + } else if ( blockAttributes.length === 1 ) { + const value = create( { + html: blockAttributes[ 0 ].content, + } ); + childBlocks = split( value, '\n' ).map( ( result ) => { + return createBlock( 'core/list-item', { + content: toHTMLString( { value: result } ), + } ); + } ); + } + return createBlock( + 'core/list', + { + anchor: blockAttributes.anchor, + }, + childBlocks + ); }, }, { @@ -72,102 +77,43 @@ const transforms = { ol: getListContentSchema( args ).ol, ul: getListContentSchema( args ).ul, } ), - transform( node ) { - const attributes = { - ordered: node.nodeName === 'OL', - anchor: node.id === '' ? undefined : node.id, - }; - - if ( attributes.ordered ) { - const type = node.getAttribute( 'type' ); - - if ( type ) { - attributes.type = type; - } - - if ( node.getAttribute( 'reversed' ) !== null ) { - attributes.reversed = true; - } - - const start = parseInt( node.getAttribute( 'start' ), 10 ); - - if ( - ! isNaN( start ) && - // start=1 only makes sense if the list is reversed. - ( start !== 1 || attributes.reversed ) - ) { - attributes.start = start; - } - } - - return createBlock( 'core/list', { - ...getBlockAttributes( 'core/list', node.outerHTML ), - ...attributes, - } ); - }, + transform: createListBlockFromDOMElement, }, ...[ '*', '-' ].map( ( prefix ) => ( { type: 'prefix', prefix, transform( content ) { - return createBlock( 'core/list', { - values: `
  • ${ content }
  • `, - } ); + return createBlock( 'core/list', {}, [ + createBlock( 'core/list-item', { content } ), + ] ); }, } ) ), ...[ '1.', '1)' ].map( ( prefix ) => ( { type: 'prefix', prefix, transform( content ) { - return createBlock( 'core/list', { - ordered: true, - values: `
  • ${ content }
  • `, - } ); + return createBlock( + 'core/list', + { + ordered: true, + }, + [ createBlock( 'core/list-item', { content } ) ] + ); }, } ) ), ], to: [ - { + ...[ 'core/paragraph', 'core/heading' ].map( ( block ) => ( { type: 'block', - blocks: [ 'core/paragraph' ], - transform: ( { values } ) => - split( - create( { - html: values, - multilineTag: 'li', - multilineWrapperTags: [ 'ul', 'ol' ], - } ), - __UNSTABLE_LINE_SEPARATOR - ).map( ( piece ) => - createBlock( 'core/paragraph', { - content: toHTMLString( { value: piece } ), + blocks: [ block ], + transform: ( _attributes, childBlocks ) => { + return getListContentFlat( childBlocks ).map( ( content ) => + createBlock( block, { + content, } ) - ), - }, - { - type: 'block', - blocks: [ 'core/heading' ], - transform: ( { values } ) => - split( - create( { - html: values, - multilineTag: 'li', - multilineWrapperTags: [ 'ul', 'ol' ], - } ), - __UNSTABLE_LINE_SEPARATOR - ).map( ( piece ) => - createBlock( 'core/heading', { - content: toHTMLString( { value: piece } ), - } ) - ), - }, - { - type: 'block', - blocks: [ 'core/table-of-contents' ], - transform: () => { - return createBlock( 'core/table-of-contents' ); + ); }, - }, + } ) ), ], }; diff --git a/packages/block-library/src/list/v2/migrate.js b/packages/block-library/src/list/utils.js similarity index 94% rename from packages/block-library/src/list/v2/migrate.js rename to packages/block-library/src/list/utils.js index d0ed33e8f3c44..61d1cbef89234 100644 --- a/packages/block-library/src/list/v2/migrate.js +++ b/packages/block-library/src/list/utils.js @@ -11,11 +11,11 @@ import { createBlock } from '@wordpress/blocks'; export function createListBlockFromDOMElement( listElement ) { const listAttributes = { ordered: 'OL' === listElement.tagName, + anchor: listElement.id === '' ? undefined : listElement.id, start: listElement.getAttribute( 'start' ) ? parseInt( listElement.getAttribute( 'start' ), 10 ) : undefined, - reversed: - listElement.getAttribute( 'reversed' ) === true ? true : undefined, + reversed: listElement.hasAttribute( 'reversed' ) ? true : undefined, type: listElement.getAttribute( 'type' ) ?? undefined, }; diff --git a/packages/block-library/src/list/v2/deprecated.js b/packages/block-library/src/list/v2/deprecated.js deleted file mode 100644 index f177e68bcff80..0000000000000 --- a/packages/block-library/src/list/v2/deprecated.js +++ /dev/null @@ -1,89 +0,0 @@ -/** - * WordPress dependencies - */ -import { RichText, useBlockProps } from '@wordpress/block-editor'; - -/** - * Internal dependencies - */ -import initialDeprecations from '../deprecated'; -import { migrateToListV2 } from './migrate'; - -const v1 = { - attributes: { - ordered: { - type: 'boolean', - default: false, - __experimentalRole: 'content', - }, - values: { - type: 'string', - source: 'html', - selector: 'ol,ul', - multiline: 'li', - __unstableMultilineWrapperTags: [ 'ol', 'ul' ], - default: '', - __experimentalRole: 'content', - }, - type: { - type: 'string', - }, - start: { - type: 'number', - }, - reversed: { - type: 'boolean', - }, - placeholder: { - type: 'string', - }, - }, - supports: { - anchor: true, - className: false, - typography: { - fontSize: true, - __experimentalFontFamily: true, - lineHeight: true, - __experimentalFontStyle: true, - __experimentalFontWeight: true, - __experimentalLetterSpacing: true, - __experimentalTextTransform: true, - __experimentalDefaultControls: { - fontSize: true, - }, - }, - color: { - gradients: true, - link: true, - __experimentalDefaultControls: { - background: true, - text: true, - }, - }, - __unstablePasteTextInline: true, - __experimentalSelector: 'ol,ul', - __experimentalSlashInserter: true, - }, - save( { attributes } ) { - const { ordered, values, type, reversed, start } = attributes; - const TagName = ordered ? 'ol' : 'ul'; - - return ( - - - - ); - }, - migrate: migrateToListV2, -}; - -/** - * New deprecations need to be placed first - * for them to have higher priority. - * - * Old deprecations may need to be updated as well. - * - * See block-deprecation.md - */ -export default [ v1, ...initialDeprecations ]; diff --git a/packages/block-library/src/list/v2/edit.js b/packages/block-library/src/list/v2/edit.js deleted file mode 100644 index 9ef320b53b001..0000000000000 --- a/packages/block-library/src/list/v2/edit.js +++ /dev/null @@ -1,192 +0,0 @@ -/** - * External dependencies - */ -import { last } from 'lodash'; - -/** - * WordPress dependencies - */ -import { - BlockControls, - useBlockProps, - useInnerBlocksProps, - store as blockEditorStore, -} from '@wordpress/block-editor'; -import { ToolbarButton } from '@wordpress/components'; -import { useDispatch, useSelect, useRegistry } from '@wordpress/data'; -import { isRTL, __ } from '@wordpress/i18n'; -import { - formatListBullets, - formatListBulletsRTL, - formatListNumbered, - formatListNumberedRTL, - formatOutdent, - formatOutdentRTL, -} from '@wordpress/icons'; -import { createBlock } from '@wordpress/blocks'; -import { useCallback, useEffect, Platform } from '@wordpress/element'; -import deprecated from '@wordpress/deprecated'; - -/** - * Internal dependencies - */ -import OrderedListSettings from '../ordered-list-settings'; -import { migrateToListV2 } from './migrate'; -import TagName from './tag-name'; - -const TEMPLATE = [ [ 'core/list-item' ] ]; -const NATIVE_MARGIN_SPACING = 8; - -/** - * At the moment, deprecations don't handle create blocks from attributes - * (like when using CPT templates). For this reason, this hook is necessary - * to avoid breaking templates using the old list block format. - * - * @param {Object} attributes Block attributes. - * @param {string} clientId Block client ID. - */ -function useMigrateOnLoad( attributes, clientId ) { - const registry = useRegistry(); - const { updateBlockAttributes, replaceInnerBlocks } = - useDispatch( blockEditorStore ); - - useEffect( () => { - // As soon as the block is loaded, migrate it to the new version. - - if ( ! attributes.values ) { - return; - } - - const [ newAttributes, newInnerBlocks ] = migrateToListV2( attributes ); - - deprecated( 'Value attribute on the list block', { - since: '6.0', - version: '6.5', - alternative: 'inner blocks', - } ); - - registry.batch( () => { - updateBlockAttributes( clientId, newAttributes ); - replaceInnerBlocks( clientId, newInnerBlocks ); - } ); - }, [ attributes.values ] ); -} - -function useOutdentList( clientId ) { - const { canOutdent } = useSelect( - ( innerSelect ) => { - const { getBlockRootClientId, getBlock } = - innerSelect( blockEditorStore ); - const parentId = getBlockRootClientId( clientId ); - return { - canOutdent: - !! parentId && - getBlock( parentId ).name === 'core/list-item', - }; - }, - [ clientId ] - ); - const { replaceBlocks, selectionChange } = useDispatch( blockEditorStore ); - const { getBlockRootClientId, getBlockAttributes, getBlock } = - useSelect( blockEditorStore ); - - return [ - canOutdent, - useCallback( () => { - const parentBlockId = getBlockRootClientId( clientId ); - const parentBlockAttributes = getBlockAttributes( parentBlockId ); - // Create a new parent block without the inner blocks. - const newParentBlock = createBlock( - 'core/list-item', - parentBlockAttributes - ); - const { innerBlocks } = getBlock( clientId ); - // Replace the parent block with a new parent block without inner blocks, - // and make the inner blocks siblings of the parent. - replaceBlocks( - [ parentBlockId ], - [ newParentBlock, ...innerBlocks ] - ); - // Select the last child of the list being outdent. - selectionChange( last( innerBlocks ).clientId ); - }, [ clientId ] ), - ]; -} - -function IndentUI( { clientId } ) { - const [ canOutdent, outdentList ] = useOutdentList( clientId ); - return ( - <> - - - ); -} - -function Edit( { attributes, setAttributes, clientId, style } ) { - const blockProps = useBlockProps( { - ...( Platform.isNative && { style } ), - } ); - const innerBlocksProps = useInnerBlocksProps( blockProps, { - allowedBlocks: [ 'core/list-item' ], - template: TEMPLATE, - templateInsertUpdatesSelection: true, - ...( Platform.isNative && { - marginVertical: NATIVE_MARGIN_SPACING, - marginHorizontal: NATIVE_MARGIN_SPACING, - } ), - } ); - useMigrateOnLoad( attributes, clientId ); - const { ordered, reversed, start } = attributes; - - const controls = ( - - { - setAttributes( { ordered: false } ); - } } - /> - { - setAttributes( { ordered: true } ); - } } - /> - - - ); - - return ( - <> - - { controls } - { ordered && ( - - ) } - - ); -} - -export default Edit; diff --git a/packages/block-library/src/list/v2/index.js b/packages/block-library/src/list/v2/index.js deleted file mode 100644 index 33107a72ff1bf..0000000000000 --- a/packages/block-library/src/list/v2/index.js +++ /dev/null @@ -1,22 +0,0 @@ -/** - * WordPress dependencies - */ -import { list as icon } from '@wordpress/icons'; - -/** - * Internal dependencies - */ -import edit from './edit'; -import save from './save'; -import transforms from './transforms'; -import deprecated from './deprecated'; - -const settings = { - icon, - edit, - save, - transforms, - deprecated, -}; - -export default settings; diff --git a/packages/block-library/src/list/v2/save.js b/packages/block-library/src/list/v2/save.js deleted file mode 100644 index 8d187a93f38b5..0000000000000 --- a/packages/block-library/src/list/v2/save.js +++ /dev/null @@ -1,18 +0,0 @@ -/** - * WordPress dependencies - */ -import { InnerBlocks, useBlockProps } from '@wordpress/block-editor'; - -export default function save( { attributes } ) { - const { ordered, reversed, start } = attributes; - const TagName = ordered ? 'ol' : 'ul'; - return ( - - - - ); -} diff --git a/packages/block-library/src/list/v2/transforms.js b/packages/block-library/src/list/v2/transforms.js deleted file mode 100644 index ba5514815fc72..0000000000000 --- a/packages/block-library/src/list/v2/transforms.js +++ /dev/null @@ -1,120 +0,0 @@ -/** - * WordPress dependencies - */ -import { createBlock } from '@wordpress/blocks'; -import { create, split, toHTMLString } from '@wordpress/rich-text'; - -/** - * Internal dependencies - */ -import { createListBlockFromDOMElement } from './migrate'; - -function getListContentSchema( { phrasingContentSchema } ) { - const listContentSchema = { - ...phrasingContentSchema, - ul: {}, - ol: { attributes: [ 'type', 'start', 'reversed' ] }, - }; - - // Recursion is needed. - // Possible: ul > li > ul. - // Impossible: ul > ul. - [ 'ul', 'ol' ].forEach( ( tag ) => { - listContentSchema[ tag ].children = { - li: { - children: listContentSchema, - }, - }; - } ); - - return listContentSchema; -} - -function getListContentFlat( blocks ) { - return blocks.flatMap( ( { name, attributes, innerBlocks = [] } ) => { - if ( name === 'core/list-item' ) { - return [ attributes.content, ...getListContentFlat( innerBlocks ) ]; - } - return getListContentFlat( innerBlocks ); - } ); -} - -const transforms = { - from: [ - { - type: 'block', - isMultiBlock: true, - blocks: [ 'core/paragraph', 'core/heading' ], - transform: ( blockAttributes ) => { - let childBlocks = []; - if ( blockAttributes.length > 1 ) { - childBlocks = blockAttributes.map( ( { content } ) => { - return createBlock( 'core/list-item', { content } ); - } ); - } else if ( blockAttributes.length === 1 ) { - const value = create( { - html: blockAttributes[ 0 ].content, - } ); - childBlocks = split( value, '\n' ).map( ( result ) => { - return createBlock( 'core/list-item', { - content: toHTMLString( { value: result } ), - } ); - } ); - } - return createBlock( - 'core/list', - { - anchor: blockAttributes.anchor, - }, - childBlocks - ); - }, - }, - ...[ '*', '-' ].map( ( prefix ) => ( { - type: 'prefix', - prefix, - transform( content ) { - return createBlock( 'core/list', {}, [ - createBlock( 'core/list-item', { content } ), - ] ); - }, - } ) ), - ...[ '1.', '1)' ].map( ( prefix ) => ( { - type: 'prefix', - prefix, - transform( content ) { - return createBlock( - 'core/list', - { - ordered: true, - }, - [ createBlock( 'core/list-item', { content } ) ] - ); - }, - } ) ), - { - type: 'raw', - selector: 'ol,ul', - schema: ( args ) => ( { - ol: getListContentSchema( args ).ol, - ul: getListContentSchema( args ).ul, - } ), - transform: createListBlockFromDOMElement, - }, - ], - to: [ - ...[ 'core/paragraph', 'core/heading' ].map( ( block ) => ( { - type: 'block', - blocks: [ block ], - transform: ( _attributes, childBlocks ) => { - return getListContentFlat( childBlocks ).map( ( content ) => - createBlock( block, { - content, - } ) - ); - }, - } ) ), - ], -}; - -export default transforms; diff --git a/packages/block-library/src/media-text/block.json b/packages/block-library/src/media-text/block.json index 462b627b78ea7..5feaed68a5db8 100644 --- a/packages/block-library/src/media-text/block.json +++ b/packages/block-library/src/media-text/block.json @@ -98,6 +98,10 @@ "text": true } }, + "spacing": { + "margin": true, + "padding": true + }, "typography": { "fontSize": true, "lineHeight": true, diff --git a/packages/block-library/src/media-text/style.scss b/packages/block-library/src/media-text/style.scss index 4f1b6bd05c559..e43961ac89097 100644 --- a/packages/block-library/src/media-text/style.scss +++ b/packages/block-library/src/media-text/style.scss @@ -6,6 +6,8 @@ display: grid; grid-template-columns: 50% 1fr; grid-template-rows: auto; + // This block has customizable padding, border-box makes that more predictable. + box-sizing: border-box; &.has-media-on-the-right { grid-template-columns: 1fr 50%; diff --git a/packages/block-library/src/navigation/block.json b/packages/block-library/src/navigation/block.json index 9aacf07419e53..4b3825042904b 100644 --- a/packages/block-library/src/navigation/block.json +++ b/packages/block-library/src/navigation/block.json @@ -94,6 +94,7 @@ "__experimentalFontWeight": true, "__experimentalTextTransform": true, "__experimentalFontFamily": true, + "__experimentalLetterSpacing": true, "__experimentalTextDecoration": true, "__experimentalSkipSerialization": [ "textDecoration" ], "__experimentalDefaultControls": { diff --git a/packages/block-library/src/navigation/edit/index.js b/packages/block-library/src/navigation/edit/index.js index a6c66a7a9d534..bc3e879632ee7 100644 --- a/packages/block-library/src/navigation/edit/index.js +++ b/packages/block-library/src/navigation/edit/index.js @@ -36,6 +36,7 @@ import { import { __, sprintf } from '@wordpress/i18n'; import { speak } from '@wordpress/a11y'; import { createBlock } from '@wordpress/blocks'; +import { close, Icon } from '@wordpress/icons'; /** * Internal dependencies @@ -465,15 +466,25 @@ function Navigation( { setOverlayMenuPreview( ! overlayMenuPreview ); } } > - { hasIcon && } - { ! hasIcon && { __( 'Menu' ) } } + { hasIcon && ( + <> + + + + ) } + { ! hasIcon && ( + <> + { __( 'Menu' ) } + { __( 'Close' ) } + + ) } ) } { overlayMenuPreview && ( setAttributes( { hasIcon: value } ) @@ -633,7 +644,6 @@ function Navigation( { overlayTextColor={ overlayTextColor } > onToggle( true ) } > @@ -104,10 +104,15 @@ export default function ResponsiveWrapper( {
    -
    -
    -
    + <> + { isSaving && } - + ); } diff --git a/packages/block-library/src/navigation/editor.scss b/packages/block-library/src/navigation/editor.scss index b0daf5dd59b15..b8ab1463fb2e9 100644 --- a/packages/block-library/src/navigation/editor.scss +++ b/packages/block-library/src/navigation/editor.scss @@ -290,9 +290,8 @@ $color-control-label-height: 20px; right: 0; bottom: 0; left: 0; - border: $border-width dashed currentColor; - opacity: 0.4; pointer-events: none; + @include placeholder-style(); // Inherit border radius from style variations. border-radius: inherit; @@ -300,7 +299,6 @@ $color-control-label-height: 20px; > svg { fill: currentColor; - opacity: 0.4; } } @@ -568,21 +566,6 @@ body.editor-styles-wrapper padding: $grid-unit-10 $grid-unit-15; } -.wp-block-navigation__unsaved-changes { - position: relative; - - .components-spinner { - position: absolute; - top: calc(50% - #{$spinner-size} / 2); - left: calc(50% - #{$spinner-size} / 2); - - // Delay showing the saving spinner until after 2 seconds. - // This should ensure it only shows for slow connections. - opacity: 0; - animation: 0.5s linear 2s normal forwards fadein; - } -} - @keyframes fadeouthalf { 0% { opacity: 1; @@ -592,11 +575,6 @@ body.editor-styles-wrapper } } -.wp-block-navigation__unsaved-changes-overlay.is-saving { - opacity: 1; - animation: 0.5s linear 2s normal forwards fadeouthalf; -} - .wp-block-navigation-delete-menu-button { width: 100%; justify-content: center; @@ -606,6 +584,7 @@ body.editor-styles-wrapper .wp-block-navigation__overlay-menu-preview { display: flex; align-items: center; + justify-content: space-between; width: 100%; background-color: $gray-100; padding: 0 $grid-unit-30; diff --git a/packages/block-library/src/navigation/index.php b/packages/block-library/src/navigation/index.php index 310288ec9a251..6a117977a7985 100644 --- a/packages/block-library/src/navigation/index.php +++ b/packages/block-library/src/navigation/index.php @@ -610,16 +610,20 @@ function render_block_core_navigation( $attributes, $content, $block ) { $is_hidden_by_default ? 'always-shown' : '', ); - $toggle_button_icon = ''; - $should_display_icon_label = isset( $attributes['hasIcon'] ) && true === $attributes['hasIcon']; - $toggle_button_content = $should_display_icon_label ? $toggle_button_icon : 'Menu'; + $should_display_icon_label = isset( $attributes['hasIcon'] ) && true === $attributes['hasIcon']; + $toggle_button_icon = ''; + $toggle_button_content = $should_display_icon_label ? $toggle_button_icon : __( 'Menu' ); + $toggle_close_button_icon = ''; + $toggle_close_button_content = $should_display_icon_label ? $toggle_close_button_icon : __( 'Close' ); + $toggle_aria_label_open = $should_display_icon_label ? 'aria-label="' . __( 'Open menu' ) . '"' : ''; // Open button label. + $toggle_aria_label_close = $should_display_icon_label ? 'aria-label="' . __( 'Close menu' ) . '"' : ''; // Close button label. $responsive_container_markup = sprintf( - ' + '
    - +
    %2$s
    @@ -628,13 +632,14 @@ function render_block_core_navigation( $attributes, $content, $block ) {
    ', esc_attr( $modal_unique_id ), $inner_blocks_html, - __( 'Open menu' ), // Open button label. - __( 'Close menu' ), // Close button label. + $toggle_aria_label_open, + $toggle_aria_label_close, esc_attr( implode( ' ', $responsive_container_classes ) ), esc_attr( implode( ' ', $open_button_classes ) ), safecss_filter_attr( $colors['overlay_inline_styles'] ), __( 'Menu' ), - $toggle_button_content + $toggle_button_content, + $toggle_close_button_content ); return sprintf( diff --git a/packages/block-library/src/navigation/style.scss b/packages/block-library/src/navigation/style.scss index 34b930442cd8f..bfd51ffd44365 100644 --- a/packages/block-library/src/navigation/style.scss +++ b/packages/block-library/src/navigation/style.scss @@ -468,8 +468,11 @@ button.wp-block-navigation-item__content { flex-direction: column; background-color: inherit; - // Use em units to apply padding, so themes can more easily style this in various ways. - padding: 2rem; + // Try to inherit any root paddings set, so the X can align to a top-right aligned menu. + padding-top: var(--wp--style--root--padding-top, 2rem); + padding-right: var(--wp--style--root--padding-right, 2rem); + padding-bottom: var(--wp--style--root--padding-bottom, 2rem); + padding-left: var(--wp--style--root--padding-left, 2rem); // Allow modal to scroll. overflow: auto; @@ -613,6 +616,7 @@ button.wp-block-navigation-item__content { border: none; margin: 0; padding: 0; + text-transform: inherit; svg { fill: currentColor; @@ -646,6 +650,11 @@ button.wp-block-navigation-item__content { .wp-block-navigation__responsive-close { width: 100%; + // Try to inherit wide-width when defined, so the X can align to a top-right aligned menu. + max-width: var(--wp--style--global--wide-size, 100%); + margin-left: auto; + margin-right: auto; + // This element is not keyboard accessible, and is focusable only using the mouse. // It is part of the MicroModal library that adds a scrim outside of a modal dialog that is not fullscreen, // where clicking that scrim closes the overlay just like the close button. diff --git a/packages/block-library/src/navigation/test/use-navigation-menu.js b/packages/block-library/src/navigation/test/use-navigation-menu.js index 69466ae1b1bd2..d4ff99709996c 100644 --- a/packages/block-library/src/navigation/test/use-navigation-menu.js +++ b/packages/block-library/src/navigation/test/use-navigation-menu.js @@ -49,6 +49,20 @@ function resolveRecords( registry, menus ) { } ); } +function resolveReadPermission( registry, allowed ) { + const dispatch = registry.dispatch( coreStore ); + dispatch.receiveUserPermission( 'create/navigation', allowed ); + dispatch.startResolution( 'canUser', [ 'read', 'navigation' ] ); + dispatch.finishResolution( 'canUser', [ 'read', 'navigation' ] ); +} + +function resolveReadRecordPermission( registry, ref, allowed ) { + const dispatch = registry.dispatch( coreStore ); + dispatch.receiveUserPermission( 'create/navigation', allowed ); + dispatch.startResolution( 'canUser', [ 'read', 'navigation', ref ] ); + dispatch.finishResolution( 'canUser', [ 'read', 'navigation', ref ] ); +} + function resolveCreatePermission( registry, allowed ) { const dispatch = registry.dispatch( coreStore ); dispatch.receiveUserPermission( 'create/navigation', allowed ); @@ -105,9 +119,10 @@ describe( 'useNavigationMenus', () => { } ); } ); - it( 'Should return information about all menus when ref is missing', () => { + it( 'Should return information about all menus when the ref is missing', () => { resolveRecords( registry, navigationMenus ); resolveCreatePermission( registry, true ); + resolveReadPermission( registry, true ); expect( useNavigationMenu() ).toEqual( { navigationMenus, navigationMenu: undefined, @@ -171,6 +186,7 @@ describe( 'useNavigationMenus', () => { it( 'Should return correct permissions (create, update)', () => { resolveRecords( registry, navigationMenus ); resolveCreatePermission( registry, true ); + resolveReadRecordPermission( registry, 1, true ); resolveUpdatePermission( registry, 1, true ); resolveDeletePermission( registry, 1, false ); expect( useNavigationMenu( 1 ) ).toEqual( { @@ -194,6 +210,7 @@ describe( 'useNavigationMenus', () => { it( 'Should return correct permissions (delete only)', () => { resolveRecords( registry, navigationMenus ); resolveCreatePermission( registry, false ); + resolveReadRecordPermission( registry, 1, false ); resolveUpdatePermission( registry, 1, false ); resolveDeletePermission( registry, 1, true ); expect( useNavigationMenu( 1 ) ).toEqual( { diff --git a/packages/block-library/src/paragraph/block.json b/packages/block-library/src/paragraph/block.json index d1681d165368c..497a6b2c29a66 100644 --- a/packages/block-library/src/paragraph/block.json +++ b/packages/block-library/src/paragraph/block.json @@ -48,6 +48,8 @@ "typography": { "fontSize": true, "lineHeight": true, + "__experimentalFontFamily": true, + "__experimentalTextDecoration": true, "__experimentalFontStyle": true, "__experimentalFontWeight": true, "__experimentalLetterSpacing": true, diff --git a/packages/block-library/src/post-excerpt/block.json b/packages/block-library/src/post-excerpt/block.json index 2fef4f20cf8af..03107ff900e06 100644 --- a/packages/block-library/src/post-excerpt/block.json +++ b/packages/block-library/src/post-excerpt/block.json @@ -37,10 +37,12 @@ "typography": { "fontSize": true, "lineHeight": true, - "__experimentalFontStyle": true, + "__experimentalFontFamily": true, "__experimentalFontWeight": true, - "__experimentalLetterSpacing": true, + "__experimentalFontStyle": true, "__experimentalTextTransform": true, + "__experimentalTextDecoration": true, + "__experimentalLetterSpacing": true, "__experimentalDefaultControls": { "fontSize": true } diff --git a/packages/block-library/src/post-terms/edit.js b/packages/block-library/src/post-terms/edit.js index 7e7d70ec2b8f1..a85e32ff4a9df 100644 --- a/packages/block-library/src/post-terms/edit.js +++ b/packages/block-library/src/post-terms/edit.js @@ -58,7 +58,6 @@ export default function PostTermsEdit( { ); const { postTerms, hasPostTerms, isLoading } = usePostTerms( { postId, - postType, term: selectedTerm, } ); const hasPost = postId && postType; diff --git a/packages/block-library/src/post-terms/use-post-terms.js b/packages/block-library/src/post-terms/use-post-terms.js index 10fdb3ecfea55..99c9ddcfbfb46 100644 --- a/packages/block-library/src/post-terms/use-post-terms.js +++ b/packages/block-library/src/post-terms/use-post-terms.js @@ -1,12 +1,12 @@ /** * WordPress dependencies */ -import { useEntityProp, store as coreStore } from '@wordpress/core-data'; +import { store as coreStore } from '@wordpress/core-data'; import { useSelect } from '@wordpress/data'; -export default function usePostTerms( { postId, postType, term } ) { - const { rest_base: restBase, slug } = term; - const [ termIds ] = useEntityProp( 'postType', postType, restBase, postId ); +export default function usePostTerms( { postId, term } ) { + const { slug } = term; + return useSelect( ( select ) => { const visible = term?.visibility?.publicly_queryable; @@ -17,30 +17,25 @@ export default function usePostTerms( { postId, postType, term } ) { hasPostTerms: false, }; } - if ( ! termIds ) { - // Waiting for post terms to be fetched. - return { isLoading: term?.postTerms?.includes( postType ) }; - } - if ( ! termIds.length ) { - return { isLoading: false }; - } + const { getEntityRecords, isResolving } = select( coreStore ); const taxonomyArgs = [ 'taxonomy', slug, { - include: termIds, + post: postId, + per_page: -1, context: 'view', }, ]; const terms = getEntityRecords( ...taxonomyArgs ); - const _isLoading = isResolving( 'getEntityRecords', taxonomyArgs ); + return { postTerms: terms, - isLoading: _isLoading, + isLoading: isResolving( 'getEntityRecords', taxonomyArgs ), hasPostTerms: !! terms?.length, }; }, - [ termIds, term?.visibility?.publicly_queryable ] + [ postId, term?.visibility?.publicly_queryable ] ); } diff --git a/packages/block-library/src/query-no-results/block.json b/packages/block-library/src/query-no-results/block.json index 09a03821625a9..f042223f36aec 100644 --- a/packages/block-library/src/query-no-results/block.json +++ b/packages/block-library/src/query-no-results/block.json @@ -15,6 +15,19 @@ "color": { "gradients": true, "link": true + }, + "typography": { + "fontSize": true, + "lineHeight": true, + "__experimentalFontFamily": true, + "__experimentalFontWeight": true, + "__experimentalFontStyle": true, + "__experimentalTextTransform": true, + "__experimentalTextDecoration": true, + "__experimentalLetterSpacing": true, + "__experimentalDefaultControls": { + "fontSize": true + } } } } diff --git a/packages/block-library/src/query-pagination-numbers/block.json b/packages/block-library/src/query-pagination-numbers/block.json index 6084b6bfe43d4..fd28596581961 100644 --- a/packages/block-library/src/query-pagination-numbers/block.json +++ b/packages/block-library/src/query-pagination-numbers/block.json @@ -21,10 +21,12 @@ "typography": { "fontSize": true, "lineHeight": true, - "__experimentalFontStyle": true, + "__experimentalFontFamily": true, "__experimentalFontWeight": true, - "__experimentalLetterSpacing": true, + "__experimentalFontStyle": true, "__experimentalTextTransform": true, + "__experimentalTextDecoration": true, + "__experimentalLetterSpacing": true, "__experimentalDefaultControls": { "fontSize": true } diff --git a/packages/block-library/src/query-title/block.json b/packages/block-library/src/query-title/block.json index 318643bae3005..33df75866bce0 100644 --- a/packages/block-library/src/query-title/block.json +++ b/packages/block-library/src/query-title/block.json @@ -48,6 +48,7 @@ "__experimentalFontWeight": true, "__experimentalLetterSpacing": true, "__experimentalTextTransform": true, + "__experimentalTextDecoration": true, "__experimentalDefaultControls": { "fontSize": true, "fontAppearance": true, diff --git a/packages/block-library/src/quote/block.json b/packages/block-library/src/quote/block.json index c4726f3cdaa0c..3ceaf0fcf3e03 100644 --- a/packages/block-library/src/quote/block.json +++ b/packages/block-library/src/quote/block.json @@ -34,10 +34,12 @@ "typography": { "fontSize": true, "lineHeight": true, - "__experimentalFontStyle": true, + "__experimentalFontFamily": true, "__experimentalFontWeight": true, - "__experimentalLetterSpacing": true, + "__experimentalFontStyle": true, "__experimentalTextTransform": true, + "__experimentalTextDecoration": true, + "__experimentalLetterSpacing": true, "__experimentalDefaultControls": { "fontSize": true, "fontAppearance": true diff --git a/packages/block-library/src/social-links/block.json b/packages/block-library/src/social-links/block.json index 8dd7df80a2525..6ba9f32040ffc 100644 --- a/packages/block-library/src/social-links/block.json +++ b/packages/block-library/src/social-links/block.json @@ -56,6 +56,15 @@ "type": "flex" } }, + "color": { + "enableContrastChecker": false, + "background": true, + "gradients": true, + "text": false, + "__experimentalDefaultControls": { + "background": false + } + }, "spacing": { "blockGap": [ "horizontal", "vertical" ], "margin": [ "top", "bottom" ], diff --git a/packages/block-library/src/social-links/edit.js b/packages/block-library/src/social-links/edit.js index 64ce714c4fb36..eb1f83180e6e7 100644 --- a/packages/block-library/src/social-links/edit.js +++ b/packages/block-library/src/social-links/edit.js @@ -7,15 +7,16 @@ import classNames from 'classnames'; * WordPress dependencies */ import { getBlockSupport } from '@wordpress/blocks'; -import { Fragment, useEffect, useRef } from '@wordpress/element'; +import { useEffect, useRef } from '@wordpress/element'; import { BlockControls, useInnerBlocksProps, useBlockProps, InspectorControls, ContrastChecker, - PanelColorSettings, withColors, + __experimentalColorGradientSettingsDropdown as ColorGradientSettingsDropdown, + __experimentalUseMultipleOriginColorsAndGradients as useMultipleOriginColorsAndGradients, } from '@wordpress/block-editor'; import { MenuGroup, @@ -46,6 +47,7 @@ const getDefaultBlockLayout = ( blockTypeOrName ) => { export function SocialLinksEdit( props ) { const { + clientId, name, attributes, iconBackgroundColor, @@ -137,6 +139,10 @@ export function SocialLinksEdit( props ) { setAttributes( { iconColorValue: colorValue } ); }, label: __( 'Icon color' ), + resetAllFilter: () => { + setIconColor( undefined ); + setAttributes( { iconColorValue: undefined } ); + }, }, ]; @@ -152,11 +158,17 @@ export function SocialLinksEdit( props ) { } ); }, label: __( 'Icon background' ), + resetAllFilter: () => { + setIconBackgroundColor( undefined ); + setAttributes( { iconBackgroundColorValue: undefined } ); + }, } ); } + const colorGradientSettings = useMultipleOriginColorsAndGradients(); + return ( - + <> - - { ! logosOnly && ( - + + { colorSettings.map( + ( { onChange, label, value, resetAllFilter } ) => ( + - ) } - + ) + ) } + { ! logosOnly && ( + + ) }
      - + ); } diff --git a/packages/block-library/src/table-of-contents/block.json b/packages/block-library/src/table-of-contents/block.json index 7a887ca4eb8fc..f095a2e2e5cdb 100644 --- a/packages/block-library/src/table-of-contents/block.json +++ b/packages/block-library/src/table-of-contents/block.json @@ -31,6 +31,19 @@ "spacing": { "margin": true, "padding": true + }, + "typography": { + "fontSize": true, + "lineHeight": true, + "__experimentalFontFamily": true, + "__experimentalFontWeight": true, + "__experimentalFontStyle": true, + "__experimentalTextTransform": true, + "__experimentalTextDecoration": true, + "__experimentalLetterSpacing": true, + "__experimentalDefaultControls": { + "fontSize": true + } } }, "example": {} diff --git a/packages/block-library/src/term-description/block.json b/packages/block-library/src/term-description/block.json index 3e6641725940f..66eb9348a4709 100644 --- a/packages/block-library/src/term-description/block.json +++ b/packages/block-library/src/term-description/block.json @@ -28,6 +28,12 @@ "typography": { "fontSize": true, "lineHeight": true, + "__experimentalFontFamily": true, + "__experimentalFontWeight": true, + "__experimentalFontStyle": true, + "__experimentalTextTransform": true, + "__experimentalTextDecoration": true, + "__experimentalLetterSpacing": true, "__experimentalDefaultControls": { "fontSize": true } diff --git a/packages/block-library/src/verse/block.json b/packages/block-library/src/verse/block.json index 359f85ca4a534..dc0772cc9aa52 100644 --- a/packages/block-library/src/verse/block.json +++ b/packages/block-library/src/verse/block.json @@ -38,6 +38,7 @@ "__experimentalFontWeight": true, "__experimentalLetterSpacing": true, "__experimentalTextTransform": true, + "__experimentalTextDecoration": true, "__experimentalDefaultControls": { "fontSize": true, "fontAppearance": true diff --git a/packages/block-serialization-default-parser/CHANGELOG.md b/packages/block-serialization-default-parser/CHANGELOG.md index d46f540609260..3274baa0faf58 100644 --- a/packages/block-serialization-default-parser/CHANGELOG.md +++ b/packages/block-serialization-default-parser/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 4.16.0 (2022-08-24) + ## 4.15.0 (2022-08-10) ## 4.14.0 (2022-07-27) diff --git a/packages/block-serialization-default-parser/package.json b/packages/block-serialization-default-parser/package.json index 605eb1981abad..9788971fa2670 100644 --- a/packages/block-serialization-default-parser/package.json +++ b/packages/block-serialization-default-parser/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/block-serialization-default-parser", - "version": "4.15.1-next.d6164808d3.0", + "version": "4.16.0", "description": "Block serialization specification parser for WordPress posts.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/block-serialization-spec-parser/CHANGELOG.md b/packages/block-serialization-spec-parser/CHANGELOG.md index dc062ea17be7b..818bb758ec15a 100644 --- a/packages/block-serialization-spec-parser/CHANGELOG.md +++ b/packages/block-serialization-spec-parser/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 4.16.0 (2022-08-24) + ## 4.15.0 (2022-08-10) ## 4.14.0 (2022-07-27) diff --git a/packages/block-serialization-spec-parser/package.json b/packages/block-serialization-spec-parser/package.json index c8f216a9672ce..cb7b275c64a63 100644 --- a/packages/block-serialization-spec-parser/package.json +++ b/packages/block-serialization-spec-parser/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/block-serialization-spec-parser", - "version": "4.15.1-next.d6164808d3.0", + "version": "4.16.0", "description": "Block serialization specification parser for WordPress posts.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/blocks/CHANGELOG.md b/packages/blocks/CHANGELOG.md index ca6faf5c28222..d3f7a73cc99ec 100644 --- a/packages/blocks/CHANGELOG.md +++ b/packages/blocks/CHANGELOG.md @@ -2,6 +2,12 @@ ## Unreleased +## 11.15.0 (2022-08-24) + +### Bug Fix + +- Packages: Replace `is-plain-obj` with `is-plain-object` ([#43511](https://github.com/WordPress/gutenberg/pull/43511)). + ## 11.14.0 (2022-08-10) ## 11.13.0 (2022-07-27) diff --git a/packages/blocks/package.json b/packages/blocks/package.json index d6c705f0a226d..e7562b9158a8b 100644 --- a/packages/blocks/package.json +++ b/packages/blocks/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/blocks", - "version": "11.14.1-next.d6164808d3.0", + "version": "11.15.0", "description": "Block API for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -44,7 +44,7 @@ "change-case": "^4.1.2", "colord": "^2.7.0", "hpq": "^1.3.0", - "is-plain-obj": "^4.1.0", + "is-plain-object": "^5.0.0", "lodash": "^4.17.21", "memize": "^1.1.0", "rememo": "^4.0.0", diff --git a/packages/blocks/src/api/constants.js b/packages/blocks/src/api/constants.js index c06243023123f..90b6fc094276f 100644 --- a/packages/blocks/src/api/constants.js +++ b/packages/blocks/src/api/constants.js @@ -226,6 +226,7 @@ export const __EXPERIMENTAL_ELEMENTS = { button: '.wp-element-button, .wp-block-button__link', 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', }; export const __EXPERIMENTAL_PATHS_WITH_MERGE = { diff --git a/packages/blocks/src/api/factory.js b/packages/blocks/src/api/factory.js index e7e8e9f114a10..fed5071e47bd2 100644 --- a/packages/blocks/src/api/factory.js +++ b/packages/blocks/src/api/factory.js @@ -2,16 +2,6 @@ * External dependencies */ import { v4 as uuid } from 'uuid'; -import { - every, - castArray, - some, - filter, - first, - has, - isEmpty, - map, -} from 'lodash'; /** * WordPress dependencies @@ -163,14 +153,14 @@ export function cloneBlock( block, mergeAttributes = {}, newInnerBlocks ) { * @return {boolean} Is the transform possible? */ const isPossibleTransformForSource = ( transform, direction, blocks ) => { - if ( isEmpty( blocks ) ) { + if ( ! blocks.length ) { return false; } // If multiple blocks are selected, only multi block transforms // or wildcard transforms are allowed. const isMultiBlock = blocks.length > 1; - const firstBlockName = first( blocks ).name; + const firstBlockName = blocks[ 0 ].name; const isValidForMultiBlocks = isWildcardBlockTransform( transform ) || ! isMultiBlock || @@ -183,7 +173,7 @@ const isPossibleTransformForSource = ( transform, direction, blocks ) => { // for a block selection of multiple blocks of different types. if ( ! isWildcardBlockTransform( transform ) && - ! every( blocks, { name: firstBlockName } ) + ! blocks.every( ( block ) => block.name === firstBlockName ) ) { return false; } @@ -196,7 +186,7 @@ const isPossibleTransformForSource = ( transform, direction, blocks ) => { // Check if the transform's block name matches the source block (or is a wildcard) // only if this is a transform 'from'. - const sourceBlock = first( blocks ); + const sourceBlock = blocks[ 0 ]; const hasMatchingName = direction !== 'from' || transform.blocks.indexOf( sourceBlock.name ) !== -1 || @@ -241,15 +231,14 @@ const isPossibleTransformForSource = ( transform, direction, blocks ) => { * @return {Array} Block types that the blocks can be transformed into. */ const getBlockTypesForPossibleFromTransforms = ( blocks ) => { - if ( isEmpty( blocks ) ) { + if ( ! blocks.length ) { return []; } const allBlockTypes = getBlockTypes(); // filter all blocks to find those with a 'from' transform. - const blockTypesWithPossibleFromTransforms = filter( - allBlockTypes, + const blockTypesWithPossibleFromTransforms = allBlockTypes.filter( ( blockType ) => { const fromTransforms = getBlockTransforms( 'from', blockType.name ); return !! findTransform( fromTransforms, ( transform ) => { @@ -274,18 +263,18 @@ const getBlockTypesForPossibleFromTransforms = ( blocks ) => { * @return {Array} Block types that the source can be transformed into. */ const getBlockTypesForPossibleToTransforms = ( blocks ) => { - if ( isEmpty( blocks ) ) { + if ( ! blocks.length ) { return []; } - const sourceBlock = first( blocks ); + const sourceBlock = blocks[ 0 ]; const blockType = getBlockType( sourceBlock.name ); const transformsTo = blockType ? getBlockTransforms( 'to', blockType.name ) : []; // filter all 'to' transforms to find those that are possible. - const possibleTransforms = filter( transformsTo, ( transform ) => { + const possibleTransforms = transformsTo.filter( ( transform ) => { return ( transform && isPossibleTransformForSource( transform, 'to', blocks ) ); @@ -338,7 +327,7 @@ export const isContainerGroupBlock = ( name ) => * @return {Array} Block types that the blocks argument can be transformed to. */ export function getPossibleBlockTransformations( blocks ) { - if ( isEmpty( blocks ) ) { + if ( ! blocks.length ) { return []; } @@ -418,7 +407,7 @@ export function getBlockTransforms( direction, blockTypeOrName ) { transforms.supportedMobileTransforms && Array.isArray( transforms.supportedMobileTransforms ); const filteredTransforms = usingMobileTransformations - ? filter( transforms[ direction ], ( t ) => { + ? transforms[ direction ].filter( ( t ) => { if ( t.type === 'raw' ) { return true; } @@ -431,7 +420,7 @@ export function getBlockTransforms( direction, blockTypeOrName ) { return true; } - return every( t.blocks, ( transformBlockName ) => + return t.blocks.every( ( transformBlockName ) => transforms.supportedMobileTransforms.includes( transformBlockName ) @@ -459,7 +448,7 @@ function maybeCheckTransformIsMatch( transform, blocks ) { if ( typeof transform.isMatch !== 'function' ) { return true; } - const sourceBlock = first( blocks ); + const sourceBlock = blocks[ 0 ]; const attributes = transform.isMultiBlock ? blocks.map( ( block ) => block.attributes ) : sourceBlock.attributes; @@ -477,7 +466,7 @@ function maybeCheckTransformIsMatch( transform, blocks ) { * @return {?Array} Array of blocks or null. */ export function switchToBlockType( blocks, name ) { - const blocksArray = castArray( blocks ); + const blocksArray = Array.isArray( blocks ) ? blocks : [ blocks ]; const isMultiBlock = blocksArray.length > 1; const firstBlock = blocksArray[ 0 ]; const sourceName = firstBlock.name; @@ -514,7 +503,7 @@ export function switchToBlockType( blocks, name ) { let transformationResults; if ( transformation.isMultiBlock ) { - if ( has( transformation, '__experimentalConvert' ) ) { + if ( '__experimentalConvert' in transformation ) { transformationResults = transformation.__experimentalConvert( blocksArray ); } else { @@ -523,7 +512,7 @@ export function switchToBlockType( blocks, name ) { blocksArray.map( ( currentBlock ) => currentBlock.innerBlocks ) ); } - } else if ( has( transformation, '__experimentalConvert' ) ) { + } else if ( '__experimentalConvert' in transformation ) { transformationResults = transformation.__experimentalConvert( firstBlock ); } else { @@ -544,7 +533,9 @@ export function switchToBlockType( blocks, name ) { // If the transformation function returned a single object, we want to work // with an array instead. - transformationResults = castArray( transformationResults ); + transformationResults = Array.isArray( transformationResults ) + ? transformationResults + : [ transformationResults ]; // Ensure that every block object returned by the transformation has a // valid block type. @@ -562,8 +553,7 @@ export function switchToBlockType( blocks, name ) { return transformationResults; } - const hasSwitchedBlock = some( - transformationResults, + const hasSwitchedBlock = transformationResults.some( ( result ) => result.name === name ); @@ -608,7 +598,7 @@ export const getBlockFromExample = ( name, example ) => { return createBlock( name, example.attributes, - map( example.innerBlocks, ( innerBlock ) => + ( example.innerBlocks ?? [] ).map( ( innerBlock ) => getBlockFromExample( innerBlock.name, innerBlock ) ) ); diff --git a/packages/blocks/src/api/raw-handling/figure-content-reducer.js b/packages/blocks/src/api/raw-handling/figure-content-reducer.js index 47144bc9b4311..4dfb24f2c9781 100644 --- a/packages/blocks/src/api/raw-handling/figure-content-reducer.js +++ b/packages/blocks/src/api/raw-handling/figure-content-reducer.js @@ -1,8 +1,3 @@ -/** - * External dependencies - */ -import { has } from 'lodash'; - /** * WordPress dependencies */ @@ -25,7 +20,7 @@ function isFigureContent( node, schema ) { return false; } - return has( schema, [ 'figure', 'children', tag ] ); + return tag in ( schema?.figure?.children ?? {} ); } /** @@ -39,7 +34,7 @@ function isFigureContent( node, schema ) { function canHaveAnchor( node, schema ) { const tag = node.nodeName.toLowerCase(); - return has( schema, [ 'figure', 'children', 'a', 'children', tag ] ); + return tag in ( schema?.figure?.children?.a?.children ?? {} ); } /** diff --git a/packages/blocks/src/api/raw-handling/get-raw-transforms.js b/packages/blocks/src/api/raw-handling/get-raw-transforms.js index cc7d77053a910..060c72d00f847 100644 --- a/packages/blocks/src/api/raw-handling/get-raw-transforms.js +++ b/packages/blocks/src/api/raw-handling/get-raw-transforms.js @@ -1,16 +1,12 @@ -/** - * External dependencies - */ -import { filter } from 'lodash'; - /** * Internal dependencies */ import { getBlockTransforms } from '../factory'; export function getRawTransforms() { - return filter( getBlockTransforms( 'from' ), { type: 'raw' } ).map( - ( transform ) => { + return getBlockTransforms( 'from' ) + .filter( ( { type } ) => type === 'raw' ) + .map( ( transform ) => { return transform.isMatch ? transform : { @@ -19,6 +15,5 @@ export function getRawTransforms() { transform.selector && node.matches( transform.selector ), }; - } - ); + } ); } diff --git a/packages/blocks/src/api/raw-handling/paste-handler.js b/packages/blocks/src/api/raw-handling/paste-handler.js index a8edd9347d7b9..d0193bc87eb74 100644 --- a/packages/blocks/src/api/raw-handling/paste-handler.js +++ b/packages/blocks/src/api/raw-handling/paste-handler.js @@ -226,8 +226,9 @@ export function pasteHandler( { blocks.length === 1 && hasBlockSupport( blocks[ 0 ].name, '__unstablePasteTextInline', false ) ) { + const trimRegex = /^[\n]+|[\n]+$/g; // Don't catch line breaks at the start or end. - const trimmedPlainText = plainText.replace( /^[\n]+|[\n]+$/g, '' ); + const trimmedPlainText = plainText.replace( trimRegex, '' ); if ( trimmedPlainText !== '' && @@ -236,7 +237,7 @@ export function pasteHandler( { return removeInvalidHTML( getBlockInnerHTML( blocks[ 0 ] ), phrasingContentSchema - ); + ).replace( trimRegex, '' ); } } diff --git a/packages/blocks/src/api/raw-handling/phrasing-content-reducer.js b/packages/blocks/src/api/raw-handling/phrasing-content-reducer.js index d1e3a3e43439a..c72a5ffaf6771 100644 --- a/packages/blocks/src/api/raw-handling/phrasing-content-reducer.js +++ b/packages/blocks/src/api/raw-handling/phrasing-content-reducer.js @@ -1,8 +1,3 @@ -/** - * External dependencies - */ -import { includes } from 'lodash'; - /** * WordPress dependencies */ @@ -33,7 +28,7 @@ export default function phrasingContentReducer( node, doc ) { // fallback. if ( textDecorationLine === 'line-through' || - includes( textDecoration, 'line-through' ) + textDecoration.includes( 'line-through' ) ) { wrap( doc.createElement( 's' ), node ); } diff --git a/packages/blocks/src/api/raw-handling/shortcode-converter.js b/packages/blocks/src/api/raw-handling/shortcode-converter.js index aedbd055248e5..3434717553a98 100644 --- a/packages/blocks/src/api/raw-handling/shortcode-converter.js +++ b/packages/blocks/src/api/raw-handling/shortcode-converter.js @@ -1,8 +1,3 @@ -/** - * External dependencies - */ -import { some, castArray, find, mapValues, pickBy, includes } from 'lodash'; - /** * WordPress dependencies */ @@ -16,6 +11,9 @@ import { getBlockType } from '../registration'; import { getBlockAttributes } from '../parser/get-block-attributes'; import { applyBuiltInValidationFixes } from '../parser/apply-built-in-validation-fixes'; +const castArray = ( maybeArray ) => + Array.isArray( maybeArray ) ? maybeArray : [ maybeArray ]; + function segmentHTMLToShortcodeBlock( HTML, lastIndex = 0, @@ -29,7 +27,7 @@ function segmentHTMLToShortcodeBlock( ( transform ) => excludedBlockNames.indexOf( transform.blockName ) === -1 && transform.type === 'shortcode' && - some( castArray( transform.tag ), ( tag ) => + castArray( transform.tag ).some( ( tag ) => regexp( tag ).test( HTML ) ) ); @@ -39,7 +37,7 @@ function segmentHTMLToShortcodeBlock( } const transformTags = castArray( transformation.tag ); - const transformTag = find( transformTags, ( tag ) => + const transformTag = transformTags.find( ( tag ) => regexp( tag ).test( HTML ) ); @@ -56,7 +54,7 @@ function segmentHTMLToShortcodeBlock( // consider the shortcode as inline text, and thus skip conversion for // this segment. if ( - ! includes( match.shortcode.content || '', '<' ) && + ! match.shortcode.content?.includes( '<' ) && ! ( /(\n|

      )\s*$/.test( beforeHTML ) && /^\s*(\n|<\/p>)/.test( afterHTML ) @@ -102,16 +100,17 @@ function segmentHTMLToShortcodeBlock( ); } ); } else { - const attributes = mapValues( - pickBy( - transformation.attributes, - ( schema ) => schema.shortcode - ), - // Passing all of `match` as second argument is intentionally broad - // but shouldn't be too relied upon. - // - // See: https://github.com/WordPress/gutenberg/pull/3610#discussion_r152546926 - ( schema ) => schema.shortcode( match.shortcode.attrs, match ) + const attributes = Object.fromEntries( + Object.entries( transformation.attributes ) + .filter( ( [ , schema ] ) => schema.shortcode ) + // Passing all of `match` as second argument is intentionally broad + // but shouldn't be too relied upon. + // + // See: https://github.com/WordPress/gutenberg/pull/3610#discussion_r152546926 + .map( ( [ key, schema ] ) => [ + key, + schema.shortcode( match.shortcode.attrs, match ), + ] ) ); const blockType = getBlockType( transformation.blockName ); diff --git a/packages/blocks/src/api/registration.js b/packages/blocks/src/api/registration.js index 9f93b993880be..903d9e7e00e32 100644 --- a/packages/blocks/src/api/registration.js +++ b/packages/blocks/src/api/registration.js @@ -4,7 +4,6 @@ * External dependencies */ import { camelCase } from 'change-case'; -import { isEmpty, pick, pickBy } from 'lodash'; /** * WordPress dependencies @@ -174,12 +173,11 @@ export function unstable__bootstrapServerSideBlockDefinitions( definitions ) { } serverSideBlockDefinitions[ blockName ] = Object.fromEntries( - Object.entries( - pickBy( - definitions[ blockName ], - ( value ) => value !== null && value !== undefined + Object.entries( definitions[ blockName ] ) + .filter( + ( [ , value ] ) => value !== null && value !== undefined ) - ).map( ( [ key, value ] ) => [ camelCase( key ), value ] ) + .map( ( [ key, value ] ) => [ camelCase( key ), value ] ) ); } } @@ -211,7 +209,11 @@ function getBlockSettingsFromMetadata( { textdomain, ...metadata } ) { 'variations', ]; - const settings = pick( metadata, allowedFields ); + const settings = Object.fromEntries( + Object.entries( metadata ).filter( ( [ key ] ) => + allowedFields.includes( key ) + ) + ); if ( textdomain ) { Object.keys( i18nBlockSchema ).forEach( ( key ) => { @@ -321,7 +323,7 @@ function translateBlockSettingUsingI18nSchema( } if ( Array.isArray( i18nSchema ) && - ! isEmpty( i18nSchema ) && + i18nSchema.length && Array.isArray( settingValue ) ) { return settingValue.map( ( value ) => @@ -334,7 +336,7 @@ function translateBlockSettingUsingI18nSchema( } if ( isObject( i18nSchema ) && - ! isEmpty( i18nSchema ) && + Object.entries( i18nSchema ).length && isObject( settingValue ) ) { return Object.keys( settingValue ).reduce( ( accumulator, key ) => { diff --git a/packages/blocks/src/api/serializer.js b/packages/blocks/src/api/serializer.js index 09553966be587..3300e0893d245 100644 --- a/packages/blocks/src/api/serializer.js +++ b/packages/blocks/src/api/serializer.js @@ -1,8 +1,3 @@ -/** - * External dependencies - */ -import { isEmpty, reduce, castArray } from 'lodash'; - /** * WordPress dependencies */ @@ -216,9 +211,8 @@ export function getSaveContent( blockTypeOrName, attributes, innerBlocks ) { * @return {Object} Subset of attributes for comment serialization. */ export function getCommentAttributes( blockType, attributes ) { - return reduce( - blockType.attributes, - ( accumulator, attributeSchema, key ) => { + return Object.entries( blockType.attributes ?? {} ).reduce( + ( accumulator, [ key, attributeSchema ] ) => { const value = attributes[ key ]; // Ignore undefined values. if ( undefined === value ) { @@ -315,9 +309,10 @@ export function getCommentDelimitedContent( attributes, content ) { - const serializedAttributes = ! isEmpty( attributes ) - ? serializeAttributes( attributes ) + ' ' - : ''; + const serializedAttributes = + attributes && Object.entries( attributes ).length + ? serializeAttributes( attributes ) + ' ' + : ''; // Strip core blocks of their namespace prefix. const blockName = rawBlockName?.startsWith( 'core/' ) @@ -401,7 +396,8 @@ export function __unstableSerializeAndClean( blocks ) { * @return {string} The post content. */ export default function serialize( blocks, options ) { - return castArray( blocks ) + const blocksArray = Array.isArray( blocks ) ? blocks : [ blocks ]; + return blocksArray .map( ( block ) => serializeBlock( block, options ) ) .join( '\n\n' ); } diff --git a/packages/blocks/src/api/templates.js b/packages/blocks/src/api/templates.js index b4ee9fac6a588..bc76218892688 100644 --- a/packages/blocks/src/api/templates.js +++ b/packages/blocks/src/api/templates.js @@ -1,8 +1,3 @@ -/** - * External dependencies - */ -import { every, map, get, mapValues } from 'lodash'; - /** * WordPress dependencies */ @@ -26,7 +21,7 @@ import { getBlockType } from './registration'; export function doBlocksMatchTemplate( blocks = [], template = [] ) { return ( blocks.length === template.length && - every( template, ( [ name, , innerBlocksTemplate ], index ) => { + template.every( ( [ name, , innerBlocksTemplate ], index ) => { const block = blocks[ index ]; return ( name === block.name && @@ -55,8 +50,7 @@ export function synchronizeBlocksWithTemplate( blocks = [], template ) { return blocks; } - return map( - template, + return template.map( ( [ name, attributes, innerBlocksTemplate ], index ) => { const block = blocks[ index ]; @@ -74,14 +68,21 @@ export function synchronizeBlocksWithTemplate( blocks = [], template ) { const blockType = getBlockType( name ); const isHTMLAttribute = ( attributeDefinition ) => - get( attributeDefinition, [ 'source' ] ) === 'html'; + attributeDefinition?.source === 'html'; const isQueryAttribute = ( attributeDefinition ) => - get( attributeDefinition, [ 'source' ] ) === 'query'; + attributeDefinition?.source === 'query'; const normalizeAttributes = ( schema, values ) => { - return mapValues( values, ( value, key ) => { - return normalizeAttribute( schema[ key ], value ); - } ); + if ( ! values ) { + return {}; + } + + return Object.fromEntries( + Object.entries( values ).map( ( [ key, value ] ) => [ + key, + normalizeAttribute( schema[ key ], value ), + ] ) + ); }; const normalizeAttribute = ( definition, value ) => { if ( isHTMLAttribute( definition ) && Array.isArray( value ) ) { @@ -104,7 +105,7 @@ export function synchronizeBlocksWithTemplate( blocks = [], template ) { }; const normalizedAttributes = normalizeAttributes( - get( blockType, [ 'attributes' ], {} ), + blockType?.attributes ?? {}, attributes ); diff --git a/packages/blocks/src/api/utils.js b/packages/blocks/src/api/utils.js index 5cb5a6a07d5e8..391bac42245dc 100644 --- a/packages/blocks/src/api/utils.js +++ b/packages/blocks/src/api/utils.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { every, has, reduce } from 'lodash'; +import { every, reduce } from 'lodash'; import { colord, extend } from 'colord'; import namesPlugin from 'colord/plugins/names'; import a11yPlugin from 'colord/plugins/a11y'; @@ -99,7 +99,7 @@ export function normalizeIconObject( icon ) { return { src: icon }; } - if ( has( icon, [ 'background' ] ) ) { + if ( 'background' in icon ) { const colordBgColor = colord( icon.background ); const getColorContrast = ( iconColor ) => colordBgColor.contrast( iconColor ); diff --git a/packages/blocks/src/store/actions.js b/packages/blocks/src/store/actions.js index 0ef38acfc2e0c..f7d5f9b2bc330 100644 --- a/packages/blocks/src/store/actions.js +++ b/packages/blocks/src/store/actions.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import isPlainObject from 'is-plain-obj'; +import { isPlainObject } from 'is-plain-object'; import { castArray, omit, pick, some } from 'lodash'; /** diff --git a/packages/browserslist-config/CHANGELOG.md b/packages/browserslist-config/CHANGELOG.md index 5179df93f4165..dc08f8c72fab6 100644 --- a/packages/browserslist-config/CHANGELOG.md +++ b/packages/browserslist-config/CHANGELOG.md @@ -2,7 +2,7 @@ ## Unreleased -## 5.0.0-next.0 (2022-08-23) +## 5.0.0 (2022-08-24) ### Breaking Change diff --git a/packages/browserslist-config/package.json b/packages/browserslist-config/package.json index d540c001a2461..658907e49dd99 100644 --- a/packages/browserslist-config/package.json +++ b/packages/browserslist-config/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/browserslist-config", - "version": "5.0.1-next.d6164808d3.0", + "version": "5.0.0", "description": "WordPress Browserslist shared configuration.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index b179c240ad9fd..ca4cc66bc63f5 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -2,7 +2,18 @@ ## Unreleased -## 20.0.0-next.0 (2022-08-23) +### Bug Fix + +- `Popover`: enable auto-updating every animation frame ([#43617](https://github.com/WordPress/gutenberg/pull/43617)). + +### Internal + +- Remove unused `normalizeArrowKey` utility function ([#43640](https://github.com/WordPress/gutenberg/pull/43640/)). +- Refactor `FocalPointPicker` to function component ([#39168](https://github.com/WordPress/gutenberg/pull/39168)). +- `Guide`: use `code` instead of `keyCode` for keyboard events ([#43604](https://github.com/WordPress/gutenberg/pull/43604/)). +- `Navigation`: use `code` instead of `keyCode` for keyboard events ([#43644](https://github.com/WordPress/gutenberg/pull/43644/)). + +## 20.0.0 (2022-08-24) ### Breaking Changes @@ -10,8 +21,10 @@ ### Bug Fix +- `AlignmentMatrixControl`: keep the physical direction in RTL languages ([#43126](https://github.com/WordPress/gutenberg/pull/43126)). - `AlignmentMatrixControl`: Fix the `width` prop so it works as intended ([#43482](https://github.com/WordPress/gutenberg/pull/43482)). - `SelectControl`, `CustomSelectControl`: Truncate long option strings ([#43301](https://github.com/WordPress/gutenberg/pull/43301)). +- `ToggleGroupControl`: Fix minor inconsistency in label height ([#43331](https://github.com/WordPress/gutenberg/pull/43331)). - `Popover`: fix and improve opening animation ([#43186](https://github.com/WordPress/gutenberg/pull/43186)). - `Popover`: fix incorrect deps in hooks resulting in incorrect positioning after calling `update` ([#43267](https://github.com/WordPress/gutenberg/pull/43267/)). - `FontSizePicker`: Fix excessive margin between label and input ([#43304](https://github.com/WordPress/gutenberg/pull/43304)). @@ -24,6 +37,7 @@ - `CustomGradientPicker`, `GradientPicker`: Add `__nextHasNoMargin` prop for opting into the new margin-free styles ([#43387](https://github.com/WordPress/gutenberg/pull/43387)). - `ToolsPanel`: Tighten grid gaps ([#43424](https://github.com/WordPress/gutenberg/pull/43424)). +- `ColorPalette`: Make popover style consistent ([#43570](https://github.com/WordPress/gutenberg/pull/43570)). - `ToggleGroupControl`: Improve TypeScript documentation ([#43265](https://github.com/WordPress/gutenberg/pull/43265)). - `ComboboxControl`: Normalize hyphen-like characters to an ASCII hyphen ([#42942](https://github.com/WordPress/gutenberg/pull/42942)). - `FormTokenField`: Refactor away from `_.difference()` ([#43224](https://github.com/WordPress/gutenberg/pull/43224/)). @@ -33,10 +47,12 @@ ### Internal - `Tooltip`: Refactor tests to `@testing-library/react` ([#43061](https://github.com/WordPress/gutenberg/pull/43061)). +- `ClipboardButton`, `FocusableIframe`, `IsolatedEventContainer`, `withConstrainedTabbing`, `withSpokenMessages`: Improve TypeScript types ([#43579](https://github.com/WordPress/gutenberg/pull/43579)). - Clean up unused and duplicate `COLORS` values ([#43445](https://github.com/WordPress/gutenberg/pull/43445)). - Update `floating-ui` to the latest version ([#43206](https://github.com/WordPress/gutenberg/pull/43206)). - `DateTimePicker`, `TimePicker`, `DatePicker`: Switch from `moment` to `date-fns` ([#43005](https://github.com/WordPress/gutenberg/pull/43005)). - `DatePicker`: Switch from `react-dates` to `use-lilius` ([#43005](https://github.com/WordPress/gutenberg/pull/43005)). +- `DateTimePicker`: address feedback after recent refactor to `date-fns` and `use-lilius` ([#43495](https://github.com/WordPress/gutenberg/pull/43495)). - `convertLTRToRTL()`: Refactor away from `_.mapKeys()` ([#43258](https://github.com/WordPress/gutenberg/pull/43258/)). - `withSpokenMessages`: Update to use `@testing-library/react` ([#43273](https://github.com/WordPress/gutenberg/pull/43273)). - `MenuGroup`: Refactor unit tests to use `@testing-library/react` ([#43275](https://github.com/WordPress/gutenberg/pull/43275)). @@ -49,13 +65,23 @@ - `Modal`: use `KeyboardEvent.code` instead of deprecated `KeyboardEvent.keyCode`. improve unit tests ([#43429](https://github.com/WordPress/gutenberg/pull/43429/)). - `FocalPointPicker`: use `KeyboardEvent.code`, partially refactor tests to modern RTL and `user-event` ([#43441](https://github.com/WordPress/gutenberg/pull/43441/)). - `CustomGradientPicker`: use `KeyboardEvent.code` instead of `KeyboardEvent.keyCode` ([#43437](https://github.com/WordPress/gutenberg/pull/43437/)). +- `Card`: Convert to TypeScript ([#42941](https://github.com/WordPress/gutenberg/pull/42941)). - `NavigableContainer`: Refactor away from `_.omit()` ([#43474](https://github.com/WordPress/gutenberg/pull/43474/)). - `Notice`: Refactor away from `_.omit()` ([#43474](https://github.com/WordPress/gutenberg/pull/43474/)). - `Snackbar`: Refactor away from `_.omit()` ([#43474](https://github.com/WordPress/gutenberg/pull/43474/)). - `UnitControl`: Refactor away from `_.omit()` ([#43474](https://github.com/WordPress/gutenberg/pull/43474/)). - `BottomSheet`: Refactor away from `_.omit()` ([#43474](https://github.com/WordPress/gutenberg/pull/43474/)). +- `DropZone`: Refactor away from `_.includes()` ([#43518](https://github.com/WordPress/gutenberg/pull/43518/)). +- `NavigableMenu`: Refactor away from `_.includes()` ([#43518](https://github.com/WordPress/gutenberg/pull/43518/)). +- `Tooltip`: Refactor away from `_.includes()` ([#43518](https://github.com/WordPress/gutenberg/pull/43518/)). +- `TreeGrid`: Refactor away from `_.includes()` ([#43518](https://github.com/WordPress/gutenberg/pull/43518/)). +- `FormTokenField`: use `KeyboardEvent.code`, refactor tests to modern RTL and `user-event` ([#43442](https://github.com/WordPress/gutenberg/pull/43442/)). +- `DropdownMenu`: use `KeyboardEvent.code`, refactor tests to model RTL and `user-event` ([#43439](https://github.com/WordPress/gutenberg/pull/43439/)). +- `Autocomplete`: Refactor away from `_.escapeRegExp()` ([#43629](https://github.com/WordPress/gutenberg/pull/43629/)). +- `TextHighlight`: Refactor away from `_.escapeRegExp()` ([#43629](https://github.com/WordPress/gutenberg/pull/43629/)). ### Experimental + - `FormTokenField`: add `__experimentalAutoSelectFirstMatch` prop to auto select the first matching suggestion on typing ([#42527](https://github.com/WordPress/gutenberg/pull/42527/)). ## 19.17.0 (2022-08-10) diff --git a/packages/components/CONTRIBUTING.md b/packages/components/CONTRIBUTING.md index 0b86603690593..989fe32e8f491 100644 --- a/packages/components/CONTRIBUTING.md +++ b/packages/components/CONTRIBUTING.md @@ -156,8 +156,8 @@ function Example( A couple of good examples of how hooks are used for composition are: -- the `Card` component, which builds on top of the `Surface` component by [calling the `useSurface` hook inside its own hook](/packages/components/src/card/card/hook.js); -- the `HStack` component, which builds on top of the `Flex` component and [calls the `useFlex` hook inside its own hook](/packages/components/src/h-stack/hook.js). +- the `Card` component, which builds on top of the `Surface` component by [calling the `useSurface` hook inside its own hook](/packages/components/src/card/card/hook.ts); +- the `HStack` component, which builds on top of the `Flex` component and [calls the `useFlex` hook inside its own hook](/packages/components/src/h-stack/hook.tsx). ## Contributing to this package diff --git a/packages/core-data/package.json b/packages/core-data/package.json index fc6f2c6a02d4d..ebb1e8c7671a0 100644 --- a/packages/core-data/package.json +++ b/packages/core-data/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/core-data", - "version": "4.14.1-next.d6164808d3.0", + "version": "4.14.0", "description": "Access to and manipulation of core WordPress entities.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -25,6 +25,7 @@ "main": "build/index.js", "module": "build-module/index.js", "react-native": "src/index", + "types": "build-types", "sideEffects": [ "{src,build,build-module}/index.js" ], diff --git a/packages/core-data/src/entities.js b/packages/core-data/src/entities.js new file mode 100644 index 0000000000000..0b64c3362e895 --- /dev/null +++ b/packages/core-data/src/entities.js @@ -0,0 +1,325 @@ +/** + * External dependencies + */ +import { capitalCase, pascalCase } from 'change-case'; +import { map, find, get } from 'lodash'; + +/** + * WordPress dependencies + */ +import apiFetch from '@wordpress/api-fetch'; +import { __ } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import { addEntities } from './actions'; + +export const DEFAULT_ENTITY_KEY = 'id'; + +const POST_RAW_ATTRIBUTES = [ 'title', 'excerpt', 'content' ]; + +export const rootEntitiesConfig = [ + { + label: __( 'Base' ), + kind: 'root', + name: '__unstableBase', + baseURL: '/', + baseURLParams: { + _fields: [ + 'description', + 'gmt_offset', + 'home', + 'name', + 'site_icon', + 'site_icon_url', + 'site_logo', + 'timezone_string', + 'url', + ].join( ',' ), + }, + }, + { + label: __( 'Site' ), + name: 'site', + kind: 'root', + baseURL: '/wp/v2/settings', + getTitle: ( record ) => { + return get( record, [ 'title' ], __( 'Site Title' ) ); + }, + }, + { + label: __( 'Post Type' ), + name: 'postType', + kind: 'root', + key: 'slug', + baseURL: '/wp/v2/types', + baseURLParams: { context: 'edit' }, + }, + { + name: 'media', + kind: 'root', + baseURL: '/wp/v2/media', + baseURLParams: { context: 'edit' }, + plural: 'mediaItems', + label: __( 'Media' ), + rawAttributes: [ 'caption', 'title', 'description' ], + }, + { + name: 'taxonomy', + kind: 'root', + key: 'slug', + baseURL: '/wp/v2/taxonomies', + baseURLParams: { context: 'edit' }, + plural: 'taxonomies', + label: __( 'Taxonomy' ), + }, + { + name: 'sidebar', + kind: 'root', + baseURL: '/wp/v2/sidebars', + baseURLParams: { context: 'edit' }, + plural: 'sidebars', + transientEdits: { blocks: true }, + label: __( 'Widget areas' ), + }, + { + name: 'widget', + kind: 'root', + baseURL: '/wp/v2/widgets', + baseURLParams: { context: 'edit' }, + plural: 'widgets', + transientEdits: { blocks: true }, + label: __( 'Widgets' ), + }, + { + name: 'widgetType', + kind: 'root', + baseURL: '/wp/v2/widget-types', + baseURLParams: { context: 'edit' }, + plural: 'widgetTypes', + label: __( 'Widget types' ), + }, + { + label: __( 'User' ), + name: 'user', + kind: 'root', + baseURL: '/wp/v2/users', + baseURLParams: { context: 'edit' }, + plural: 'users', + }, + { + name: 'comment', + kind: 'root', + baseURL: '/wp/v2/comments', + baseURLParams: { context: 'edit' }, + plural: 'comments', + label: __( 'Comment' ), + }, + { + name: 'menu', + kind: 'root', + baseURL: '/wp/v2/menus', + baseURLParams: { context: 'edit' }, + plural: 'menus', + label: __( 'Menu' ), + }, + { + name: 'menuItem', + kind: 'root', + baseURL: '/wp/v2/menu-items', + baseURLParams: { context: 'edit' }, + plural: 'menuItems', + label: __( 'Menu Item' ), + rawAttributes: [ 'title' ], + }, + { + name: 'menuLocation', + kind: 'root', + baseURL: '/wp/v2/menu-locations', + baseURLParams: { context: 'edit' }, + plural: 'menuLocations', + label: __( 'Menu Location' ), + key: 'name', + }, + { + label: __( 'Global Styles' ), + name: 'globalStyles', + kind: 'root', + baseURL: '/wp/v2/global-styles', + baseURLParams: { context: 'edit' }, + plural: 'globalStylesVariations', // Should be different than name. + getTitle: ( record ) => record?.title?.rendered || record?.title, + }, + { + label: __( 'Themes' ), + name: 'theme', + kind: 'root', + baseURL: '/wp/v2/themes', + baseURLParams: { context: 'edit' }, + key: 'stylesheet', + }, + { + label: __( 'Plugins' ), + name: 'plugin', + kind: 'root', + baseURL: '/wp/v2/plugins', + baseURLParams: { context: 'edit' }, + key: 'plugin', + }, +]; + +export const additionalEntityConfigLoaders = [ + { kind: 'postType', loadEntities: loadPostTypeEntities }, + { kind: 'taxonomy', loadEntities: loadTaxonomyEntities }, +]; + +/** + * Returns a function to be used to retrieve extra edits to apply before persisting a post type. + * + * @param {Object} persistedRecord Already persisted Post + * @param {Object} edits Edits. + * @return {Object} Updated edits. + */ +export const prePersistPostType = ( persistedRecord, edits ) => { + const newEdits = {}; + + if ( persistedRecord?.status === 'auto-draft' ) { + // Saving an auto-draft should create a draft by default. + if ( ! edits.status && ! newEdits.status ) { + newEdits.status = 'draft'; + } + + // Fix the auto-draft default title. + if ( + ( ! edits.title || edits.title === 'Auto Draft' ) && + ! newEdits.title && + ( ! persistedRecord?.title || + persistedRecord?.title === 'Auto Draft' ) + ) { + newEdits.title = ''; + } + } + + return newEdits; +}; + +/** + * Returns the list of post type entities. + * + * @return {Promise} Entities promise + */ +async function loadPostTypeEntities() { + const postTypes = await apiFetch( { + path: '/wp/v2/types?context=view', + } ); + return map( postTypes, ( postType, name ) => { + const isTemplate = [ 'wp_template', 'wp_template_part' ].includes( + name + ); + const namespace = postType?.rest_namespace ?? 'wp/v2'; + return { + kind: 'postType', + baseURL: `/${ namespace }/${ postType.rest_base }`, + baseURLParams: { context: 'edit' }, + name, + label: postType.name, + transientEdits: { + blocks: true, + selection: true, + }, + mergedEdits: { meta: true }, + rawAttributes: POST_RAW_ATTRIBUTES, + getTitle: ( record ) => + record?.title?.rendered || + record?.title || + ( isTemplate + ? capitalCase( record.slug ?? '' ) + : String( record.id ) ), + __unstablePrePersist: isTemplate ? undefined : prePersistPostType, + __unstable_rest_base: postType.rest_base, + }; + } ); +} + +/** + * Returns the list of the taxonomies entities. + * + * @return {Promise} Entities promise + */ +async function loadTaxonomyEntities() { + const taxonomies = await apiFetch( { + path: '/wp/v2/taxonomies?context=view', + } ); + return map( taxonomies, ( taxonomy, name ) => { + const namespace = taxonomy?.rest_namespace ?? 'wp/v2'; + return { + kind: 'taxonomy', + baseURL: `/${ namespace }/${ taxonomy.rest_base }`, + baseURLParams: { context: 'edit' }, + name, + label: taxonomy.name, + }; + } ); +} + +/** + * Returns the entity's getter method name given its kind and name. + * + * @example + * ```js + * const nameSingular = getMethodName( 'root', 'theme', 'get' ); + * // nameSingular is getRootTheme + * + * const namePlural = getMethodName( 'root', 'theme', 'set' ); + * // namePlural is setRootThemes + * ``` + * + * @param {string} kind Entity kind. + * @param {string} name Entity name. + * @param {string} prefix Function prefix. + * @param {boolean} usePlural Whether to use the plural form or not. + * + * @return {string} Method name + */ +export const getMethodName = ( + kind, + name, + prefix = 'get', + usePlural = false +) => { + const entityConfig = find( rootEntitiesConfig, { kind, name } ); + const kindPrefix = kind === 'root' ? '' : pascalCase( kind ); + const nameSuffix = pascalCase( name ) + ( usePlural ? 's' : '' ); + const suffix = + usePlural && 'plural' in entityConfig && entityConfig?.plural + ? pascalCase( entityConfig.plural ) + : nameSuffix; + return `${ prefix }${ kindPrefix }${ suffix }`; +}; + +/** + * Loads the kind entities into the store. + * + * @param {string} kind Kind + * + * @return {(thunkArgs: object) => Promise} Entities + */ +export const getOrLoadEntitiesConfig = + ( kind ) => + async ( { select, dispatch } ) => { + let configs = select.getEntitiesConfig( kind ); + if ( configs && configs.length !== 0 ) { + return configs; + } + + const loader = find( additionalEntityConfigLoaders, { kind } ); + if ( ! loader ) { + return []; + } + + configs = await loader.loadEntities(); + dispatch( addEntities( configs ) ); + + return configs; + }; diff --git a/packages/core-data/src/entities.ts b/packages/core-data/src/entities.ts deleted file mode 100644 index 3637563adceaa..0000000000000 --- a/packages/core-data/src/entities.ts +++ /dev/null @@ -1,550 +0,0 @@ -/** - * External dependencies - */ -import { capitalCase, pascalCase } from 'change-case'; -import { map, find, get } from 'lodash'; - -/** - * WordPress dependencies - */ -import apiFetch from '@wordpress/api-fetch'; -import { __ } from '@wordpress/i18n'; - -/** - * Internal dependencies - */ -import { addEntities } from './actions'; -import type * as Records from './entity-types'; -import type { - EntityType, - Context, - Post, - Taxonomy, - Type, - Updatable, -} from './entity-types'; - -export const DEFAULT_ENTITY_KEY = 'id'; - -const POST_RAW_ATTRIBUTES = [ 'title', 'excerpt', 'content' ]; - -type AttachmentEntity< C extends Context = Context > = EntityType< - { - name: 'media'; - kind: 'root'; - baseURLParams: { context: 'edit' }; - }, - Records.Attachment< C >, - C ->; - -const attachmentConfig: AttachmentEntity[ 'config' ] = { - name: 'media', - kind: 'root', - baseURL: '/wp/v2/media', - baseURLParams: { context: 'edit' }, - plural: 'mediaItems', - label: __( 'Media' ), - rawAttributes: [ 'caption', 'title', 'description' ], -}; - -type SiteEntity< C extends Context = Context > = EntityType< - { - name: 'site'; - kind: 'root'; - }, - Records.Settings< C >, - C ->; - -const siteConfig: SiteEntity[ 'config' ] = { - label: __( 'Site' ), - name: 'site', - kind: 'root', - baseURL: '/wp/v2/settings', - getTitle: ( record: Records.Settings< 'edit' > ) => { - return get( record, [ 'title' ], __( 'Site Title' ) ); - }, -}; - -type PostTypeEntity< C extends Context = Context > = EntityType< - { - name: 'postType'; - kind: 'root'; - key: 'slug'; - baseURLParams: { context: 'edit' }; - }, - Records.Type< C >, - C ->; - -const postTypeConfig: PostTypeEntity[ 'config' ] = { - label: __( 'Post Type' ), - name: 'postType', - kind: 'root', - key: 'slug', - baseURL: '/wp/v2/types', - baseURLParams: { context: 'edit' }, -}; - -type TaxonomyEntity< C extends Context = Context > = EntityType< - { - name: 'taxonomy'; - kind: 'root'; - key: 'slug'; - baseURLParams: { context: 'edit' }; - }, - Records.Taxonomy< C >, - C ->; - -const taxonomyConfig: TaxonomyEntity[ 'config' ] = { - name: 'taxonomy', - kind: 'root', - key: 'slug', - baseURL: '/wp/v2/taxonomies', - baseURLParams: { context: 'edit' }, - plural: 'taxonomies', - label: __( 'Taxonomy' ), -}; - -type SidebarEntity< C extends Context = Context > = EntityType< - { - name: 'sidebar'; - kind: 'root'; - baseURLParams: { context: 'edit' }; - }, - Records.Sidebar< C >, - C ->; - -const sidebarConfig: SidebarEntity[ 'config' ] = { - name: 'sidebar', - kind: 'root', - baseURL: '/wp/v2/sidebars', - baseURLParams: { context: 'edit' }, - plural: 'sidebars', - transientEdits: { blocks: true }, - label: __( 'Widget areas' ), -}; - -type WidgetEntity< C extends Context = Context > = EntityType< - { - name: 'widget'; - kind: 'root'; - baseURLParams: { context: 'edit' }; - }, - Records.Widget< C >, - C ->; -const widgetConfig: WidgetEntity[ 'config' ] = { - name: 'widget', - kind: 'root', - baseURL: '/wp/v2/widgets', - baseURLParams: { context: 'edit' }, - plural: 'widgets', - transientEdits: { blocks: true }, - label: __( 'Widgets' ), -}; - -type WidgetTypeEntity< C extends Context = Context > = EntityType< - { - name: 'widgetType'; - kind: 'root'; - baseURLParams: { context: 'edit' }; - }, - Records.WidgetType< C >, - C ->; -const widgetTypeConfig: WidgetTypeEntity[ 'config' ] = { - name: 'widgetType', - kind: 'root', - baseURL: '/wp/v2/widget-types', - baseURLParams: { context: 'edit' }, - plural: 'widgetTypes', - label: __( 'Widget types' ), -}; - -type UserEntity< C extends Context = Context > = EntityType< - { - name: 'user'; - kind: 'root'; - baseURLParams: { context: 'edit' }; - }, - Records.User< C >, - C ->; -const userConfig: UserEntity[ 'config' ] = { - label: __( 'User' ), - name: 'user', - kind: 'root', - baseURL: '/wp/v2/users', - baseURLParams: { context: 'edit' }, - plural: 'users', -}; - -type CommentEntity< C extends Context = Context > = EntityType< - { - name: 'comment'; - kind: 'root'; - baseURLParams: { context: 'edit' }; - }, - Records.Comment< C >, - C ->; -const commentConfig: CommentEntity[ 'config' ] = { - name: 'comment', - kind: 'root', - baseURL: '/wp/v2/comments', - baseURLParams: { context: 'edit' }, - plural: 'comments', - label: __( 'Comment' ), -}; - -type NavMenuEntity< C extends Context = Context > = EntityType< - { - name: 'menu'; - kind: 'root'; - baseURLParams: { context: 'edit' }; - }, - Records.NavMenu< C >, - C ->; - -const menuConfig: NavMenuEntity[ 'config' ] = { - name: 'menu', - kind: 'root', - baseURL: '/wp/v2/menus', - baseURLParams: { context: 'edit' }, - plural: 'menus', - label: __( 'Menu' ), -}; - -type NavMenuItemEntity< C extends Context = Context > = EntityType< - { - name: 'menuItem'; - kind: 'root'; - baseURLParams: { context: 'edit' }; - }, - Records.NavMenuItem< C >, - C ->; - -const menuItemConfig: NavMenuItemEntity[ 'config' ] = { - name: 'menuItem', - kind: 'root', - baseURL: '/wp/v2/menu-items', - baseURLParams: { context: 'edit' }, - plural: 'menuItems', - label: __( 'Menu Item' ), - rawAttributes: [ 'title' ], -}; - -type MenuLocationEntity< C extends Context = Context > = EntityType< - { - name: 'menuLocation'; - kind: 'root'; - key: 'name'; - baseURLParams: { context: 'edit' }; - }, - Records.MenuLocation< C >, - C ->; - -const menuLocationConfig: MenuLocationEntity[ 'config' ] = { - name: 'menuLocation', - kind: 'root', - baseURL: '/wp/v2/menu-locations', - baseURLParams: { context: 'edit' }, - plural: 'menuLocations', - label: __( 'Menu Location' ), - key: 'name', -}; - -const globalStyleConfig = { - label: __( 'Global Styles' ), - name: 'globalStyles', - kind: 'root', - baseURL: '/wp/v2/global-styles', - baseURLParams: { context: 'edit' }, - plural: 'globalStylesVariations', // Should be different than name. - getTitle: ( record ) => record?.title?.rendered || record?.title, -}; - -type ThemeEntity< C extends Context = Context > = EntityType< - { - name: 'theme'; - kind: 'root'; - baseURLParams: { context: 'edit' }; - key: 'stylesheet'; - }, - Records.Theme< C >, - C ->; - -const themeConfig: ThemeEntity[ 'config' ] = { - label: __( 'Themes' ), - name: 'theme', - kind: 'root', - baseURL: '/wp/v2/themes', - baseURLParams: { context: 'edit' }, - key: 'stylesheet', -}; - -type PluginEntity< C extends Context = Context > = EntityType< - { - name: 'plugin'; - kind: 'root'; - baseURLParams: { context: 'edit' }; - key: 'plugin'; - }, - Records.Plugin< C >, - C ->; -const pluginConfig: PluginEntity[ 'config' ] = { - label: __( 'Plugins' ), - name: 'plugin', - kind: 'root', - baseURL: '/wp/v2/plugins', - baseURLParams: { context: 'edit' }, - key: 'plugin', -}; - -export const rootEntitiesConfig = [ - { - label: __( 'Base' ), - kind: 'root', - name: '__unstableBase', - baseURL: '/', - baseURLParams: { - _fields: [ - 'description', - 'gmt_offset', - 'home', - 'name', - 'site_icon', - 'site_icon_url', - 'site_logo', - 'timezone_string', - 'url', - ].join( ',' ), - }, - }, - siteConfig, - postTypeConfig, - attachmentConfig, - taxonomyConfig, - sidebarConfig, - widgetConfig, - widgetTypeConfig, - userConfig, - commentConfig, - menuConfig, - menuItemConfig, - menuLocationConfig, - globalStyleConfig, - themeConfig, - pluginConfig, -]; - -type PostTypeConfig = { - kind: 'postType'; - key: 'id'; - defaultContext: 'edit'; -}; - -type PostEntity< C extends Context = Context > = EntityType< - PostTypeConfig & { name: 'post' }, - Records.Post< C >, - C ->; -type PageEntity< C extends Context > = EntityType< - PostTypeConfig & { name: 'page' }, - Records.Page< C >, - C ->; -type WpTemplateEntity< C extends Context > = EntityType< - PostTypeConfig & { name: 'wp_template' }, - Records.WpTemplate< C >, - C ->; -type WpTemplatePartEntity< C extends Context > = EntityType< - PostTypeConfig & { name: 'wp_template_part' }, - Records.WpTemplatePart< C >, - C ->; - -export type CoreEntities< C extends Context > = - | SiteEntity< C > - | PostTypeEntity< C > - | AttachmentEntity< C > - | TaxonomyEntity< C > - | SidebarEntity< C > - | WidgetEntity< C > - | WidgetTypeEntity< C > - | UserEntity< C > - | CommentEntity< C > - | NavMenuEntity< C > - | NavMenuItemEntity< C > - | MenuLocationEntity< C > - | ThemeEntity< C > - | PluginEntity< C > - | PostEntity< C > - | PageEntity< C > - | WpTemplateEntity< C > - | WpTemplatePartEntity< C >; - -export const additionalEntityConfigLoaders = [ - { kind: 'postType', loadEntities: loadPostTypeEntities }, - { kind: 'taxonomy', loadEntities: loadTaxonomyEntities }, -]; - -/** - * Returns a function to be used to retrieve extra edits to apply before persisting a post type. - * - * @param {Object} persistedRecord Already persisted Post - * @param {Object} edits Edits. - * @return {Object} Updated edits. - */ -export const prePersistPostType = ( persistedRecord, edits ) => { - const newEdits = {} as Partial< Updatable< Post< 'edit' > > >; - - if ( persistedRecord?.status === 'auto-draft' ) { - // Saving an auto-draft should create a draft by default. - if ( ! edits.status && ! newEdits.status ) { - newEdits.status = 'draft'; - } - - // Fix the auto-draft default title. - if ( - ( ! edits.title || edits.title === 'Auto Draft' ) && - ! newEdits.title && - ( ! persistedRecord?.title || - persistedRecord?.title === 'Auto Draft' ) - ) { - newEdits.title = ''; - } - } - - return newEdits; -}; - -/** - * Returns the list of post type entities. - * - * @return {Promise} Entities promise - */ -async function loadPostTypeEntities() { - const postTypes = ( await apiFetch( { - path: '/wp/v2/types?context=view', - } ) ) as Record< string, Type< 'view' > >; - return map( postTypes, ( postType, name ) => { - const isTemplate = [ 'wp_template', 'wp_template_part' ].includes( - name - ); - const namespace = postType?.rest_namespace ?? 'wp/v2'; - return { - kind: 'postType', - baseURL: `/${ namespace }/${ postType.rest_base }`, - baseURLParams: { context: 'edit' }, - name, - label: postType.name, - transientEdits: { - blocks: true, - selection: true, - }, - mergedEdits: { meta: true }, - rawAttributes: POST_RAW_ATTRIBUTES, - getTitle: ( record ) => - record?.title?.rendered || - record?.title || - ( isTemplate - ? capitalCase( record.slug ?? '' ) - : String( record.id ) ), - __unstablePrePersist: isTemplate ? undefined : prePersistPostType, - __unstable_rest_base: postType.rest_base, - }; - } ); -} - -/** - * Returns the list of the taxonomies entities. - * - * @return {Promise} Entities promise - */ -async function loadTaxonomyEntities() { - const taxonomies = ( await apiFetch( { - path: '/wp/v2/taxonomies?context=view', - } ) ) as Record< string, Taxonomy< 'view' > >; - return map( taxonomies, ( taxonomy, name ) => { - const namespace = taxonomy?.rest_namespace ?? 'wp/v2'; - return { - kind: 'taxonomy', - baseURL: `/${ namespace }/${ taxonomy.rest_base }`, - baseURLParams: { context: 'edit' }, - name, - label: taxonomy.name, - }; - } ); -} - -/** - * Returns the entity's getter method name given its kind and name. - * - * @example - * ```js - * const nameSingular = getMethodName( 'root', 'theme', 'get' ); - * // nameSingular is getRootTheme - * - * const namePlural = getMethodName( 'root', 'theme', 'set' ); - * // namePlural is setRootThemes - * ``` - * - * @param {string} kind Entity kind. - * @param {string} name Entity name. - * @param {string} prefix Function prefix. - * @param {boolean} usePlural Whether to use the plural form or not. - * - * @return {string} Method name - */ -export const getMethodName = ( - kind, - name, - prefix = 'get', - usePlural = false -) => { - const entityConfig = find( rootEntitiesConfig, { kind, name } ); - const kindPrefix = kind === 'root' ? '' : pascalCase( kind ); - const nameSuffix = pascalCase( name ) + ( usePlural ? 's' : '' ); - const suffix = - usePlural && 'plural' in entityConfig! && entityConfig?.plural - ? pascalCase( entityConfig.plural ) - : nameSuffix; - return `${ prefix }${ kindPrefix }${ suffix }`; -}; - -/** - * Loads the kind entities into the store. - * - * @param {string} kind Kind - * - * @return {(thunkArgs: object) => Promise} Entities - */ -export const getOrLoadEntitiesConfig = - ( kind ) => - async ( { select, dispatch } ) => { - let configs = select.getEntitiesConfig( kind ); - if ( configs && configs.length !== 0 ) { - return configs; - } - - const loader = find( additionalEntityConfigLoaders, { kind } ); - if ( ! loader ) { - return []; - } - - configs = await loader.loadEntities(); - dispatch( addEntities( configs ) ); - - return configs; - }; diff --git a/packages/core-data/src/entity-types/attachment.ts b/packages/core-data/src/entity-types/attachment.ts index eb0280e88e9f1..703a08611c0e2 100644 --- a/packages/core-data/src/entity-types/attachment.ts +++ b/packages/core-data/src/entity-types/attachment.ts @@ -13,7 +13,6 @@ import type { } from './helpers'; import type { BaseEntityRecords as _BaseEntityRecords } from './base-entity-records'; -import type { DefaultContextOf } from './index'; declare module './base-entity-records' { export namespace BaseEntityRecords { @@ -142,6 +141,6 @@ declare module './base-entity-records' { } } -export type Attachment< - C extends Context = DefaultContextOf< 'root', 'media' > -> = OmitNevers< _BaseEntityRecords.Attachment< C > >; +export type Attachment< C extends Context = 'edit' > = OmitNevers< + _BaseEntityRecords.Attachment< C > +>; diff --git a/packages/core-data/src/entity-types/comment.ts b/packages/core-data/src/entity-types/comment.ts index 9f3228261e7d3..fee7f1faec784 100644 --- a/packages/core-data/src/entity-types/comment.ts +++ b/packages/core-data/src/entity-types/comment.ts @@ -9,7 +9,6 @@ import type { RenderedText, } from './helpers'; import type { BaseEntityRecords as _BaseEntityRecords } from './base-entity-records'; -import type { DefaultContextOf } from './index'; export type CommentStatus = 'hold' | 'approve' | 'spam' | 'trash' | '1' | '0'; @@ -92,6 +91,6 @@ declare module './base-entity-records' { } } -export type Comment< - C extends Context = DefaultContextOf< 'root', 'comment' > -> = OmitNevers< _BaseEntityRecords.Comment< C > >; +export type Comment< C extends Context = 'edit' > = OmitNevers< + _BaseEntityRecords.Comment< C > +>; diff --git a/packages/core-data/src/entity-types/entities.ts b/packages/core-data/src/entity-types/entities.ts deleted file mode 100644 index ccadeeda3f001..0000000000000 --- a/packages/core-data/src/entity-types/entities.ts +++ /dev/null @@ -1,130 +0,0 @@ -/** - * Internal dependencies - */ -import type { Context } from './helpers'; -import type { EntityRecord } from './index'; - -/** - * HTTP Query parameters sent with the API request to fetch the entity records. - */ -export type EntityQuery< - C extends Context, - Fields extends string[] | undefined = undefined -> = Record< string, any > & { - context?: C; - /** - * The requested fields. If specified, the REST API will remove from the response - * any fields not on that list. - */ - _fields?: Fields; -}; - -interface Edit {} - -/** - * Helper type that transforms "raw" entity configuration from entities.ts - * into a format that makes searching by root and kind easy and extensible. - * - * This is the foundation of return type inference in calls such as: - * `getEntityRecord( "root", "comment", 15 )`. - * - * @see EntityRecordOf - * @see getEntityRecord - */ -export type EntityType< - Config extends Pick< EntityConfig< Record, Ctx >, RequiredConfigKeys >, - Record extends EntityRecord< Ctx >, - Ctx extends Context -> = { - record: Record; - config: Omit< EntityConfig< Record, Ctx >, RequiredConfigKeys > & Config; - key: Config[ 'key' ] extends string ? Config[ 'key' ] : 'id'; - defaultContext: Config[ 'baseURLParams' ] extends { - context: infer InferredContext; - } - ? InferredContext - : 'view'; -}; - -type RequiredConfigKeys = 'name' | 'kind' | 'key' | 'baseURLParams'; - -export interface EntityConfig< - R extends EntityRecord< C >, - C extends Context -> { - /** Path in WP REST API from which to request records of this entity. */ - baseURL: string; - - /** Arguments to supply by default to API requests for records of this entity. */ - baseURLParams?: EntityQuery< Context >; - - /** - * Returns the title for a given record of this entity. - * - * Some entities have an associated title, such as the name of a - * particular template part ("full width") or of a menu ("main nav"). - */ - getTitle?: ( record: R ) => string; - - /** - * Indicates an alternate field in record that can be used for identification. - * - * e.g. a post has an id but may also be uniquely identified by its `slug` - */ - key?: string; - - /** - * Collection in which to classify records of this entity. - * - * 'root' is a special name given to the core entities provided by the editor. - * - * It may be the case that we request an entity record for which we have no - * valid config in memory. In these cases the editor will look for a loader - * function to requests more entity configs from the server for the given - * "kind." This is how WordPress defers loading of template entity configs. - */ - kind: string; - - /** Translated form of human-recognizable name or reference to records of this entity. */ - label: string; - - mergedEdits?: { - meta?: boolean; - }; - - /** Name given to records of this entity, e.g. 'media', 'postType', 'widget' */ - name: string; - - /** - * Manually provided plural form of the entity name. - * - * When not supplied the editor will attempt to auto-generate a plural form. - */ - plural?: string; - - /** - * Fields in record of this entity which may appear as a compound object with - * a source value (`raw`) as well as a processed value (`rendered`). - * - * e.g. a post's `content` in the edit context contains the raw value stored - * in the database as well as the rendered version with shortcodes replaced, - * content texturized, blocks transformed, etc… - */ - rawAttributes?: ( keyof R )[]; - - /** - * Which transient edit operations records of this entity support. - */ - transientEdits?: { - blocks?: boolean; - selection?: boolean; - }; - - // Unstable properties - - /** Returns additional changes before applying edits to a record of this entity. */ - __unstablePrePersist?: ( record: R, edits: Edit[] ) => Edit[]; - - /** Used in `canEdit()` */ - __unstable_rest_base?: string; -} diff --git a/packages/core-data/src/entity-types/index.ts b/packages/core-data/src/entity-types/index.ts index fe167f1b2dcaf..19d10a28ad698 100644 --- a/packages/core-data/src/entity-types/index.ts +++ b/packages/core-data/src/entity-types/index.ts @@ -20,9 +20,7 @@ import type { Widget } from './widget'; import type { WidgetType } from './widget-type'; import type { WpTemplate } from './wp-template'; import type { WpTemplatePart } from './wp-template-part'; -import type { CoreEntities } from '../entities'; -export type { EntityType } from './entities'; export type { BaseEntityRecords } from './base-entity-records'; export type { @@ -48,8 +46,6 @@ export type { WpTemplatePart, }; -export type UpdatableEntityRecord = Updatable< EntityRecord< 'edit' > >; - /** * An interface that may be extended to add types for new entities. Each entry * must be a union of entity definitions adhering to the EntityInterface type. @@ -60,105 +56,52 @@ export type UpdatableEntityRecord = Updatable< EntityRecord< 'edit' > >; * import type { Context } from '@wordpress/core-data'; * // ... * - * interface Order { + * interface Client { * id: number; - * clientId: number; + * name: string; * // ... * } * - * type OrderEntity = { - * kind: 'myPlugin'; - * name: 'order'; - * recordType: Order; + * interface Order< C extends Context > { + * id: number; + * name: string; + * // ... * } * * declare module '@wordpress/core-data' { - * export interface PerPackageEntities< C extends Context > { - * myPlugin: OrderEntity | ClientEntity + * export interface PerPackageEntityRecords< C extends Context > { + * myPlugin: Client | Order> * } * } * - * const c = getEntityRecord( 'myPlugin', 'order', 15 ); + * const c = getEntityRecord( 'myPlugin', 'order', 15 ); * // c is of the type Order * ``` */ -export interface PerPackageEntityConfig< C extends Context > { - core: CoreEntities< C >; +export interface PerPackageEntityRecords< C extends Context > { + core: + | Attachment< C > + | Comment< C > + | MenuLocation< C > + | NavMenu< C > + | NavMenuItem< C > + | Page< C > + | Plugin< C > + | Post< C > + | Settings< C > + | Sidebar< C > + | Taxonomy< C > + | Theme< C > + | User< C > + | Type< C > + | Widget< C > + | WidgetType< C > + | WpTemplate< C > + | WpTemplatePart< C >; } -/** - * A union of all the registered entities. - */ -type EntityConfig< C extends Context = any > = - PerPackageEntityConfig< C >[ keyof PerPackageEntityConfig< C > ]; - /** * A union of all known record types. */ -export type EntityRecord< C extends Context = any > = - EntityConfig< C >[ 'record' ]; - -/** - * An entity corresponding to a specified record type. - */ -export type EntityConfigOf< - RecordOrKind extends EntityRecord | Kind, - N extends Name | undefined = undefined -> = RecordOrKind extends EntityRecord - ? Extract< EntityConfig, { record: RecordOrKind } > - : Extract< EntityConfig, { config: { kind: RecordOrKind; name: N } } >; - -/** - * Name of the requested entity. - */ -export type NameOf< R extends EntityRecord > = - EntityConfigOf< R >[ 'config' ][ 'name' ]; - -/** - * Kind of the requested entity. - */ -export type KindOf< R extends EntityRecord > = - EntityConfigOf< R >[ 'config' ][ 'kind' ]; - -/** - * Primary key type of the requested entity, sourced from PerPackageEntities. - * - * For core entities, the key type is computed using the entity configuration in entities.js. - */ -export type KeyOf< - RecordOrKind extends EntityRecord | Kind, - N extends Name | undefined = undefined, - E extends EntityConfig = EntityConfigOf< RecordOrKind, N > -> = ( E[ 'key' ] extends keyof E[ 'record' ] - ? E[ 'record' ][ E[ 'key' ] ] - : never ) & - ( number | string ); - -/** - * Default context of the requested entity, sourced from PerPackageEntities. - * - * For core entities, the default context is extracted from the entity configuration - * in entities.js. - */ -export type DefaultContextOf< - RecordOrKind extends EntityRecord | Kind, - N extends Name | undefined = undefined -> = EntityConfigOf< RecordOrKind, N >[ 'defaultContext' ]; - -/** - * An entity record type associated with specified kind and name, sourced from PerPackageEntities. - */ -export type EntityRecordOf< - K extends Kind, - N extends Name, - C extends Context = DefaultContextOf< K, N > -> = Extract< EntityConfig< C >, { config: { kind: K; name: N } } >[ 'record' ]; - -/** - * A union of all known entity kinds. - */ -export type Kind = EntityConfig[ 'config' ][ 'kind' ]; -/** - * A union of all known entity names. - */ -export type Name = EntityConfig[ 'config' ][ 'name' ]; +export type EntityRecord< C extends Context = 'edit' > = + PerPackageEntityRecords< C >[ keyof PerPackageEntityRecords< C > ]; diff --git a/packages/core-data/src/entity-types/menu-location.ts b/packages/core-data/src/entity-types/menu-location.ts index 8e15908eee7b4..9bc3e5f7189b0 100644 --- a/packages/core-data/src/entity-types/menu-location.ts +++ b/packages/core-data/src/entity-types/menu-location.ts @@ -4,7 +4,6 @@ import type { Context, OmitNevers } from './helpers'; import type { BaseEntityRecords as _BaseEntityRecords } from './base-entity-records'; -import type { DefaultContextOf } from './index'; declare module './base-entity-records' { export namespace BaseEntityRecords { @@ -25,6 +24,6 @@ declare module './base-entity-records' { } } -export type MenuLocation< - C extends Context = DefaultContextOf< 'root', 'menuLocation' > -> = OmitNevers< _BaseEntityRecords.MenuLocation< C > >; +export type MenuLocation< C extends Context = 'edit' > = OmitNevers< + _BaseEntityRecords.MenuLocation< C > +>; diff --git a/packages/core-data/src/entity-types/nav-menu-item.ts b/packages/core-data/src/entity-types/nav-menu-item.ts index 6d0c29a1f6b56..26a4d07d1e075 100644 --- a/packages/core-data/src/entity-types/nav-menu-item.ts +++ b/packages/core-data/src/entity-types/nav-menu-item.ts @@ -9,7 +9,6 @@ import type { } from './helpers'; import type { BaseEntityRecords as _BaseEntityRecords } from './base-entity-records'; -import type { DefaultContextOf } from './index'; export type NavMenuItemType = | 'taxonomy' @@ -107,6 +106,6 @@ declare module './base-entity-records' { } } -export type NavMenuItem< - C extends Context = DefaultContextOf< 'root', 'menuItem' > -> = OmitNevers< _BaseEntityRecords.NavMenuItem< C > >; +export type NavMenuItem< C extends Context = 'edit' > = OmitNevers< + _BaseEntityRecords.NavMenuItem< C > +>; diff --git a/packages/core-data/src/entity-types/nav-menu.ts b/packages/core-data/src/entity-types/nav-menu.ts index 857cf674c9f8a..4626281ce1172 100644 --- a/packages/core-data/src/entity-types/nav-menu.ts +++ b/packages/core-data/src/entity-types/nav-menu.ts @@ -4,7 +4,6 @@ import type { Context, ContextualField, OmitNevers } from './helpers'; import type { BaseEntityRecords as _BaseEntityRecords } from './base-entity-records'; -import { DefaultContextOf } from './index'; declare module './base-entity-records' { export namespace BaseEntityRecords { @@ -49,5 +48,6 @@ declare module './base-entity-records' { } } -export type NavMenu< C extends Context = DefaultContextOf< 'root', 'menu' > > = - OmitNevers< _BaseEntityRecords.NavMenu< C > >; +export type NavMenu< C extends Context = 'edit' > = OmitNevers< + _BaseEntityRecords.NavMenu< C > +>; diff --git a/packages/core-data/src/entity-types/page.ts b/packages/core-data/src/entity-types/page.ts index cd2859a9bca72..4bdf583fe4880 100644 --- a/packages/core-data/src/entity-types/page.ts +++ b/packages/core-data/src/entity-types/page.ts @@ -12,7 +12,6 @@ import type { } from './helpers'; import type { BaseEntityRecords as _BaseEntityRecords } from './base-entity-records'; -import type { DefaultContextOf } from './index'; declare module './base-entity-records' { export namespace BaseEntityRecords { @@ -140,5 +139,6 @@ declare module './base-entity-records' { } } -export type Page< C extends Context = DefaultContextOf< 'postType', 'page' > > = - OmitNevers< _BaseEntityRecords.Page< C > >; +export type Page< C extends Context = 'edit' > = OmitNevers< + _BaseEntityRecords.Page< C > +>; diff --git a/packages/core-data/src/entity-types/plugin.ts b/packages/core-data/src/entity-types/plugin.ts index 569316a48ef41..61954ec853531 100644 --- a/packages/core-data/src/entity-types/plugin.ts +++ b/packages/core-data/src/entity-types/plugin.ts @@ -9,7 +9,6 @@ import type { } from './helpers'; import type { BaseEntityRecords as _BaseEntityRecords } from './base-entity-records'; -import type { DefaultContextOf } from './index'; declare module './base-entity-records' { export namespace BaseEntityRecords { @@ -75,5 +74,6 @@ declare module './base-entity-records' { } export type PluginStatus = 'active' | 'inactive'; -export type Plugin< C extends Context = DefaultContextOf< 'root', 'plugin' > > = - OmitNevers< _BaseEntityRecords.Plugin< C > >; +export type Plugin< C extends Context = 'edit' > = OmitNevers< + _BaseEntityRecords.Plugin< C > +>; diff --git a/packages/core-data/src/entity-types/post.ts b/packages/core-data/src/entity-types/post.ts index ebfc4bbd1bf60..bcde222281cf0 100644 --- a/packages/core-data/src/entity-types/post.ts +++ b/packages/core-data/src/entity-types/post.ts @@ -13,7 +13,6 @@ import type { } from './helpers'; import type { BaseEntityRecords as _BaseEntityRecords } from './base-entity-records'; -import type { DefaultContextOf } from './index'; declare module './base-entity-records' { export namespace BaseEntityRecords { @@ -149,5 +148,6 @@ declare module './base-entity-records' { } } -export type Post< C extends Context = DefaultContextOf< 'postType', 'post' > > = - OmitNevers< _BaseEntityRecords.Post< C > >; +export type Post< C extends Context = 'edit' > = OmitNevers< + _BaseEntityRecords.Post< C > +>; diff --git a/packages/core-data/src/entity-types/settings.ts b/packages/core-data/src/entity-types/settings.ts index 6181baa60a81f..23bd223571d39 100644 --- a/packages/core-data/src/entity-types/settings.ts +++ b/packages/core-data/src/entity-types/settings.ts @@ -9,7 +9,6 @@ import type { } from './helpers'; import type { BaseEntityRecords as _BaseEntityRecords } from './base-entity-records'; -import type { DefaultContextOf } from './index'; declare module './base-entity-records' { export namespace BaseEntityRecords { @@ -94,5 +93,6 @@ declare module './base-entity-records' { } } -export type Settings< C extends Context = DefaultContextOf< 'root', 'site' > > = - OmitNevers< _BaseEntityRecords.Settings< C > >; +export type Settings< C extends Context = 'view' > = OmitNevers< + _BaseEntityRecords.Settings< C > +>; diff --git a/packages/core-data/src/entity-types/sidebar.ts b/packages/core-data/src/entity-types/sidebar.ts index 8cf1bd12642bb..923e0279a9a3f 100644 --- a/packages/core-data/src/entity-types/sidebar.ts +++ b/packages/core-data/src/entity-types/sidebar.ts @@ -4,7 +4,6 @@ import type { Context, OmitNevers } from './helpers'; import type { BaseEntityRecords as _BaseEntityRecords } from './base-entity-records'; -import type { DefaultContextOf } from './index'; declare module './base-entity-records' { export namespace BaseEntityRecords { @@ -55,6 +54,6 @@ declare module './base-entity-records' { type SidebarStatus = 'active' | 'inactive'; -export type Sidebar< - C extends Context = DefaultContextOf< 'root', 'sidebar' > -> = OmitNevers< _BaseEntityRecords.Sidebar< C > >; +export type Sidebar< C extends Context = 'edit' > = OmitNevers< + _BaseEntityRecords.Sidebar< C > +>; diff --git a/packages/core-data/src/entity-types/taxonomy.ts b/packages/core-data/src/entity-types/taxonomy.ts index 267eb6fd43b59..5dd76758bc68d 100644 --- a/packages/core-data/src/entity-types/taxonomy.ts +++ b/packages/core-data/src/entity-types/taxonomy.ts @@ -4,7 +4,6 @@ import type { Context, ContextualField, OmitNevers } from './helpers'; import type { BaseEntityRecords as _BaseEntityRecords } from './base-entity-records'; -import type { DefaultContextOf } from './index'; declare module './base-entity-records' { export namespace BaseEntityRecords { @@ -88,6 +87,6 @@ declare module './base-entity-records' { } } -export type Taxonomy< - C extends Context = DefaultContextOf< 'postType', 'taxonomy' > -> = OmitNevers< _BaseEntityRecords.Taxonomy< C > >; +export type Taxonomy< C extends Context = 'edit' > = OmitNevers< + _BaseEntityRecords.Taxonomy< C > +>; diff --git a/packages/core-data/src/entity-types/theme.ts b/packages/core-data/src/entity-types/theme.ts index 26d70a92007d5..04904ae2501f0 100644 --- a/packages/core-data/src/entity-types/theme.ts +++ b/packages/core-data/src/entity-types/theme.ts @@ -4,7 +4,6 @@ import type { Context, PostFormat, RenderedText, OmitNevers } from './helpers'; import type { BaseEntityRecords as _BaseEntityRecords } from './base-entity-records'; -import type { DefaultContextOf } from './index'; declare module './base-entity-records' { export namespace BaseEntityRecords { @@ -222,5 +221,6 @@ declare module './base-entity-records' { } } -export type Theme< C extends Context = DefaultContextOf< 'root', 'theme' > > = - OmitNevers< _BaseEntityRecords.Theme< C > >; +export type Theme< C extends Context = 'edit' > = OmitNevers< + _BaseEntityRecords.Theme< C > +>; diff --git a/packages/core-data/src/entity-types/type.ts b/packages/core-data/src/entity-types/type.ts index b0df93cd06bc4..e6c0cce30d6ed 100644 --- a/packages/core-data/src/entity-types/type.ts +++ b/packages/core-data/src/entity-types/type.ts @@ -4,7 +4,6 @@ import type { Context, ContextualField, OmitNevers } from './helpers'; import type { BaseEntityRecords as _BaseEntityRecords } from './base-entity-records'; -import type { DefaultContextOf } from './index'; declare module './base-entity-records' { export namespace BaseEntityRecords { @@ -76,5 +75,6 @@ declare module './base-entity-records' { } } -export type Type< C extends Context = DefaultContextOf< 'root', 'postType' > > = - OmitNevers< _BaseEntityRecords.Type< C > >; +export type Type< C extends Context = 'edit' > = OmitNevers< + _BaseEntityRecords.Type< C > +>; diff --git a/packages/core-data/src/entity-types/user.ts b/packages/core-data/src/entity-types/user.ts index 92bbd0a1344f6..9de22efb3d794 100644 --- a/packages/core-data/src/entity-types/user.ts +++ b/packages/core-data/src/entity-types/user.ts @@ -9,7 +9,6 @@ import type { } from './helpers'; import type { BaseEntityRecords as _BaseEntityRecords } from './base-entity-records'; -import type { DefaultContextOf } from './index'; declare module './base-entity-records' { export namespace BaseEntityRecords { @@ -110,5 +109,6 @@ declare module './base-entity-records' { } } -export type User< C extends Context = DefaultContextOf< 'root', 'user' > > = - OmitNevers< _BaseEntityRecords.User< C > >; +export type User< C extends Context = 'edit' > = OmitNevers< + _BaseEntityRecords.User< C > +>; diff --git a/packages/core-data/src/entity-types/widget-type.ts b/packages/core-data/src/entity-types/widget-type.ts index 372a25d4cefcc..29c47d5a9635c 100644 --- a/packages/core-data/src/entity-types/widget-type.ts +++ b/packages/core-data/src/entity-types/widget-type.ts @@ -4,7 +4,6 @@ import type { Context, OmitNevers } from './helpers'; import type { BaseEntityRecords as _BaseEntityRecords } from './base-entity-records'; -import type { DefaultContextOf } from './index'; declare module './base-entity-records' { export namespace BaseEntityRecords { @@ -33,6 +32,6 @@ declare module './base-entity-records' { } } -export type WidgetType< - C extends Context = DefaultContextOf< 'root', 'widgetType' > -> = OmitNevers< _BaseEntityRecords.WidgetType< C > >; +export type WidgetType< C extends Context = 'edit' > = OmitNevers< + _BaseEntityRecords.WidgetType< C > +>; diff --git a/packages/core-data/src/entity-types/widget.ts b/packages/core-data/src/entity-types/widget.ts index b3bec1d6a12bc..8509914891281 100644 --- a/packages/core-data/src/entity-types/widget.ts +++ b/packages/core-data/src/entity-types/widget.ts @@ -4,7 +4,6 @@ import type { Context, ContextualField, OmitNevers } from './helpers'; import type { BaseEntityRecords as _BaseEntityRecords } from './base-entity-records'; -import type { DefaultContextOf } from './index'; declare module './base-entity-records' { export namespace BaseEntityRecords { @@ -60,5 +59,6 @@ declare module './base-entity-records' { } } -export type Widget< C extends Context = DefaultContextOf< 'root', 'widget' > > = - OmitNevers< _BaseEntityRecords.Widget< C > >; +export type Widget< C extends Context = 'edit' > = OmitNevers< + _BaseEntityRecords.Widget< C > +>; diff --git a/packages/core-data/src/entity-types/wp-template-part.ts b/packages/core-data/src/entity-types/wp-template-part.ts index bbe92b3a34a3e..466da11b437ef 100644 --- a/packages/core-data/src/entity-types/wp-template-part.ts +++ b/packages/core-data/src/entity-types/wp-template-part.ts @@ -10,7 +10,6 @@ import type { } from './helpers'; import type { BaseEntityRecords as _BaseEntityRecords } from './base-entity-records'; -import type { DefaultContextOf } from './index'; declare module './base-entity-records' { export namespace BaseEntityRecords { @@ -90,6 +89,6 @@ declare module './base-entity-records' { } } -export type WpTemplatePart< - C extends Context = DefaultContextOf< 'postType', 'wp_template_part' > -> = OmitNevers< _BaseEntityRecords.WpTemplatePart< C > >; +export type WpTemplatePart< C extends Context = 'edit' > = OmitNevers< + _BaseEntityRecords.WpTemplatePart< C > +>; diff --git a/packages/core-data/src/entity-types/wp-template.ts b/packages/core-data/src/entity-types/wp-template.ts index c134d5b9f167e..544476bbb7f36 100644 --- a/packages/core-data/src/entity-types/wp-template.ts +++ b/packages/core-data/src/entity-types/wp-template.ts @@ -10,7 +10,6 @@ import type { } from './helpers'; import type { BaseEntityRecords as _BaseEntityRecords } from './base-entity-records'; -import type { DefaultContextOf } from './index'; declare module './base-entity-records' { export namespace BaseEntityRecords { @@ -90,6 +89,6 @@ declare module './base-entity-records' { } } -export type WpTemplate< - C extends Context = DefaultContextOf< 'postType', 'wp_template' > -> = OmitNevers< _BaseEntityRecords.WpTemplate< C > >; +export type WpTemplate< C extends Context = 'edit' > = OmitNevers< + _BaseEntityRecords.WpTemplate< C > +>; diff --git a/packages/core-data/src/hooks/test/use-entity-record.js b/packages/core-data/src/hooks/test/use-entity-record.js index 524c7d8348c08..6bd20f3494b08 100644 --- a/packages/core-data/src/hooks/test/use-entity-record.js +++ b/packages/core-data/src/hooks/test/use-entity-record.js @@ -71,7 +71,38 @@ describe( 'useEntityRecord', () => { expect( data ).toEqual( { edit: expect.any( Function ), - editedRecord: {}, + editedRecord: { hello: 'world', id: 1 }, + hasEdits: false, + record: { hello: 'world', id: 1 }, + save: expect.any( Function ), + hasResolved: true, + isResolving: false, + status: 'SUCCESS', + } ); + } ); + + it( 'applies edits to the entity record', async () => { + // Provide response + triggerFetch.mockImplementation( () => TEST_RECORD ); + + let widget; + const TestComponent = () => { + widget = useEntityRecord( 'root', 'widget', 1 ); + return

      ; + }; + render( + + + + ); + + await act( async () => { + jest.advanceTimersByTime( 1 ); + } ); + + expect( widget ).toEqual( { + edit: expect.any( Function ), + editedRecord: { hello: 'world', id: 1 }, hasEdits: false, record: { hello: 'world', id: 1 }, save: expect.any( Function ), @@ -79,5 +110,14 @@ describe( 'useEntityRecord', () => { isResolving: false, status: 'SUCCESS', } ); + + await act( async () => { + widget.edit( { hello: 'foo' } ); + jest.advanceTimersByTime( 1 ); + } ); + + expect( widget.hasEdits ).toEqual( true ); + expect( widget.record ).toEqual( { hello: 'world', id: 1 } ); + expect( widget.editedRecord ).toEqual( { hello: 'foo', id: 1 } ); } ); } ); diff --git a/packages/core-data/src/hooks/test/use-resource-permissions.js b/packages/core-data/src/hooks/test/use-resource-permissions.js index 55b17f03d4f8d..e36bf61bd1d7f 100644 --- a/packages/core-data/src/hooks/test/use-resource-permissions.js +++ b/packages/core-data/src/hooks/test/use-resource-permissions.js @@ -55,6 +55,7 @@ describe( 'useResourcePermissions', () => { isResolving: false, hasResolved: false, canCreate: false, + canRead: false, } ); // Required to make sure no updates happen outside of act() @@ -67,6 +68,7 @@ describe( 'useResourcePermissions', () => { isResolving: false, hasResolved: true, canCreate: true, + canRead: false, } ); } ); @@ -86,6 +88,7 @@ describe( 'useResourcePermissions', () => { isResolving: false, hasResolved: false, canCreate: false, + canRead: false, canUpdate: false, canDelete: false, } ); @@ -100,6 +103,7 @@ describe( 'useResourcePermissions', () => { isResolving: false, hasResolved: true, canCreate: true, + canRead: false, canUpdate: false, canDelete: false, } ); diff --git a/packages/core-data/src/hooks/use-entity-record.ts b/packages/core-data/src/hooks/use-entity-record.ts index 4bd4c9293073e..4db789e9d0b91 100644 --- a/packages/core-data/src/hooks/use-entity-record.ts +++ b/packages/core-data/src/hooks/use-entity-record.ts @@ -56,6 +56,8 @@ export interface Options { /** * Resolves the specified entity record. * + * @since 6.1.0 Introduced in WordPress core. + * * @param kind Kind of the entity, e.g. `root` or a `postType`. See rootEntitiesConfig in ../entities.ts for a list of available kinds. * @param name Name of the entity, e.g. `plugin` or a `post`. See rootEntitiesConfig in ../entities.ts for a list of available names. * @param recordId ID of the requested entity record. @@ -163,8 +165,16 @@ export default function useEntityRecord< RecordType >( const { editedRecord, hasEdits } = useSelect( ( select ) => ( { - editedRecord: select( coreStore ).getEditedEntityRecord(), - hasEdits: select( coreStore ).hasEditsForEntityRecord(), + editedRecord: select( coreStore ).getEditedEntityRecord( + kind, + name, + recordId + ), + hasEdits: select( coreStore ).hasEditsForEntityRecord( + kind, + name, + recordId + ), } ), [ kind, name, recordId ] ); diff --git a/packages/core-data/src/hooks/use-entity-records.ts b/packages/core-data/src/hooks/use-entity-records.ts index 2dbc83bf66c3b..c9b64bd5b4144 100644 --- a/packages/core-data/src/hooks/use-entity-records.ts +++ b/packages/core-data/src/hooks/use-entity-records.ts @@ -24,6 +24,8 @@ const EMPTY_ARRAY = []; /** * Resolves the specified entity records. * + * @since 6.1.0 Introduced in WordPress core. + * * @param kind Kind of the entity, e.g. `root` or a `postType`. See rootEntitiesConfig in ../entities.ts for a list of available kinds. * @param name Name of the entity, e.g. `plugin` or a `post`. See rootEntitiesConfig in ../entities.ts for a list of available names. * @param queryArgs Optional HTTP query description for how to fetch the data, passed to the requested API endpoint. diff --git a/packages/core-data/src/hooks/use-query-select.ts b/packages/core-data/src/hooks/use-query-select.ts index a9a1ea9bd48b2..401c2e471dc3b 100644 --- a/packages/core-data/src/hooks/use-query-select.ts +++ b/packages/core-data/src/hooks/use-query-select.ts @@ -35,6 +35,9 @@ interface QuerySelectResponse< Data > { * Like useSelect, but the selectors return objects containing * both the original data AND the resolution info. * + * @since 6.1.0 Introduced in WordPress core. + * @private + * * @param {Function} mapQuerySelect see useSelect * @param {Array} deps see useSelect * @@ -71,7 +74,7 @@ interface QuerySelectResponse< Data > { * * @return {QuerySelectResponse} Queried data. */ -export default function __experimentalUseQuerySelect( mapQuerySelect, deps ) { +export default function useQuerySelect( mapQuerySelect, deps ) { return useSelect( ( select, registry ) => { const resolve = ( store ) => enrichSelectors( select( store ) ); return mapQuerySelect( resolve, registry ); diff --git a/packages/core-data/src/hooks/use-resource-permissions.ts b/packages/core-data/src/hooks/use-resource-permissions.ts index 9defa0d9b12cf..aef636bb53814 100644 --- a/packages/core-data/src/hooks/use-resource-permissions.ts +++ b/packages/core-data/src/hooks/use-resource-permissions.ts @@ -44,6 +44,8 @@ type ResourcePermissionsResolution< IdType > = [ /** * Resolves resource permissions. * + * @since 6.1.0 Introduced in WordPress core. + * * @param resource The resource in question, e.g. media. * @param id ID of a specific resource entry, if needed, e.g. 10. * @@ -116,20 +118,39 @@ export default function useResourcePermissions< IdType = void >( const { canUser } = resolve( coreStore ); const create = canUser( 'create', resource ); if ( ! id ) { + const read = canUser( 'read', resource ); + + const isResolving = create.isResolving || read.isResolving; + const hasResolved = create.hasResolved && read.hasResolved; + let status = Status.Idle; + if ( isResolving ) { + status = Status.Resolving; + } else if ( hasResolved ) { + status = Status.Success; + } + return { - status: create.status, - isResolving: create.isResolving, - hasResolved: create.hasResolved, + status, + isResolving, + hasResolved, canCreate: create.hasResolved && create.data, + canRead: read.hasResolved && read.data, }; } + const read = canUser( 'read', resource, id ); const update = canUser( 'update', resource, id ); const _delete = canUser( 'delete', resource, id ); const isResolving = - create.isResolving || update.isResolving || _delete.isResolving; + read.isResolving || + create.isResolving || + update.isResolving || + _delete.isResolving; const hasResolved = - create.hasResolved && update.hasResolved && _delete.hasResolved; + read.hasResolved && + create.hasResolved && + update.hasResolved && + _delete.hasResolved; let status = Status.Idle; if ( isResolving ) { @@ -141,6 +162,7 @@ export default function useResourcePermissions< IdType = void >( status, isResolving, hasResolved, + canRead: hasResolved && read.data, canCreate: hasResolved && create.data, canUpdate: hasResolved && update.data, canDelete: hasResolved && _delete.data, @@ -152,7 +174,7 @@ export default function useResourcePermissions< IdType = void >( export function __experimentalUseResourcePermissions( resource: string, - id?: IdType + id?: unknown ) { deprecated( `wp.data.__experimentalUseResourcePermissions`, { alternative: 'wp.data.useResourcePermissions', diff --git a/packages/core-data/src/index.js b/packages/core-data/src/index.js index 05cace07c992b..43fa4a0b3cd07 100644 --- a/packages/core-data/src/index.js +++ b/packages/core-data/src/index.js @@ -60,8 +60,6 @@ const storeConfig = () => ( { * Store definition for the code data namespace. * * @see https://github.com/WordPress/gutenberg/blob/HEAD/packages/data/README.md#createReduxStore - * - * @type {Object} */ export const store = createReduxStore( STORE_NAME, storeConfig() ); diff --git a/packages/core-data/src/selectors.ts b/packages/core-data/src/selectors.ts index 6de78fc47ee8c..79309d84fb98b 100644 --- a/packages/core-data/src/selectors.ts +++ b/packages/core-data/src/selectors.ts @@ -18,30 +18,19 @@ import { STORE_NAME } from './name'; import { getQueriedItems } from './queried-data'; import { DEFAULT_ENTITY_KEY } from './entities'; import { getNormalizedCommaSeparable, isRawAttribute } from './utils'; -import type { - Context, - DefaultContextOf, - EntityRecordOf, - KeyOf, - Kind, - KindOf, - Name, - NameOf, - User, - WpTemplate, -} from './entity-types'; +import type * as ET from './entity-types'; // This is an incomplete, high-level approximation of the State type. // It makes the selectors slightly more safe, but is intended to evolve // into a more detailed representation over time. // See https://github.com/WordPress/gutenberg/pull/40025#discussion_r865410589 for more context. -interface State { +export interface State { autosaves: Record< string | number, Array< unknown > >; blockPatterns: Array< unknown >; blockPatternCategories: Array< unknown >; currentGlobalStylesId: string; currentTheme: string; - currentUser: User< 'edit' >; + currentUser: ET.User< 'edit' >; embedPreviews: Record< string, { html: string } >; entities: EntitiesState; themeBaseGlobalStyles: Record< string, Object >; @@ -50,19 +39,21 @@ interface State { users: UserState; } +type EntityRecordKey = string | number; + interface EntitiesState { config: EntityConfig[]; - records: Record< Kind, Record< Name, EntityState< Kind, Name > > >; + records: Record< string, Record< string, EntityState< ET.EntityRecord > > >; } -interface EntityState< K extends Kind, N extends Name > { - edits: Record< KeyOf< K, N >, Partial< EntityRecordOf< K, N > > >; - saving: Record< KeyOf< K, N >, { pending: boolean } >; +interface EntityState< EntityRecord extends ET.EntityRecord > { + edits: Record< string, Partial< EntityRecord > >; + saving: Record< string, { pending: boolean } >; } interface EntityConfig { - name: Name; - kind: Kind; + name: string; + kind: string; } interface UndoState extends Array< Object > { @@ -71,31 +62,16 @@ interface UndoState extends Array< Object > { } interface UserState { - queries: Record< string, GenericRecordKey[] >; - byId: Record< GenericRecordKey, User< 'edit' > >; + queries: Record< string, EntityRecordKey[] >; + byId: Record< EntityRecordKey, ET.User< 'edit' > >; } -type GenericRecordKey = number | string; -type EntityRecord = any; type Optional< T > = T | undefined; /** * HTTP Query parameters sent with the API request to fetch the entity records. */ -export type EntityQuery< - C extends Context, - WithFields extends boolean = true -> = Omit< Record< string, any >, '_fields' > & { - context?: C; -} & ( WithFields extends true - ? { - /** - * The requested fields. If specified, the REST API will remove from the response - * any fields not on that list. - */ - _fields: string[]; - } - : {} ); +type GetRecordsHttpQuery = Record< string, any >; /** * Shared reference to an empty object for cases where it is important to avoid @@ -116,7 +92,7 @@ const EMPTY_OBJECT = {}; * @return Whether a request is in progress for an embed preview. */ export const isRequestingEmbedPreview = createRegistrySelector( - ( select ) => + ( select: any ) => ( state: State, url: string ): boolean => { return select( STORE_NAME ).isResolving( 'getEmbedPreview', [ url, @@ -136,8 +112,8 @@ export const isRequestingEmbedPreview = createRegistrySelector( */ export function getAuthors( state: State, - query?: EntityQuery< any > -): User< 'edit' >[] { + query?: GetRecordsHttpQuery +): ET.User[] { deprecated( "select( 'core' ).getAuthors()", { since: '5.9', alternative: "select( 'core' ).getUsers({ who: 'authors' })", @@ -157,7 +133,7 @@ export function getAuthors( * * @return Current user object. */ -export function getCurrentUser( state: State ): User< 'edit' > { +export function getCurrentUser( state: State ): ET.User< 'edit' > { return state.currentUser; } @@ -170,7 +146,7 @@ export function getCurrentUser( state: State ): User< 'edit' > { * @return Users list. */ export const getUserQueryResults = createSelector( - ( state: State, queryID: string ): User< 'edit' >[] => { + ( state: State, queryID: string ): ET.User< 'edit' >[] => { const queryResults = state.users.queries[ queryID ]; return map( queryResults, ( id ) => state.users.byId[ id ] ); @@ -190,7 +166,7 @@ export const getUserQueryResults = createSelector( * * @return Array of entities with config matching kind. */ -export function getEntitiesByKind( state: State, kind: Kind ): Array< any > { +export function getEntitiesByKind( state: State, kind: string ): Array< any > { deprecated( "wp.data.select( 'core' ).getEntitiesByKind()", { since: '6.0', alternative: "wp.data.select( 'core' ).getEntitiesConfig()", @@ -206,7 +182,7 @@ export function getEntitiesByKind( state: State, kind: Kind ): Array< any > { * * @return Array of entities with config matching kind. */ -export function getEntitiesConfig( state: State, kind: Kind ): Array< any > { +export function getEntitiesConfig( state: State, kind: string ): Array< any > { return filter( state.entities.config, { kind } ); } @@ -220,7 +196,7 @@ export function getEntitiesConfig( state: State, kind: Kind ): Array< any > { * * @return Entity config */ -export function getEntity( state: State, kind: Kind, name: Name ): any { +export function getEntity( state: State, kind: string, name: string ): any { deprecated( "wp.data.select( 'core' ).getEntity()", { since: '6.0', alternative: "wp.data.select( 'core' ).getEntityConfig()", @@ -237,56 +213,14 @@ export function getEntity( state: State, kind: Kind, name: Name ): any { * * @return Entity config */ -export function getEntityConfig( state: State, kind: Kind, name: Name ): any { +export function getEntityConfig( + state: State, + kind: string, + name: string +): any { return find( state.entities.config, { kind, name } ); } -/** - * GetEntityRecord is declared as an *interface*, but it actually describes - * the specifies the getEntityRecord *function* signature. It may seem unusual, - * but it's just how TypeScript implements function overloading. - * - * More accurately, GetEntityRecord distinguishes between two different signatures - * the getEntityRecord selector has: - * - * 1. When query._fields is not given, the returned type is EntityRecordOf< K, N, C > - * 2. When query._fields is given, the returned type is Partial> - * - * Unfortunately, due to a TypeScript limitation (https://github.com/microsoft/TypeScript/issues/23132) - * we can't use a single function signature with a return type such as: - * - * Fields extends undefined - * ? EntityRecordOf< K, N, C > - * : Partial< EntityRecordOf< K, N, C > > - */ -interface GetEntityRecord { - < - R extends EntityRecordOf< K, N >, - C extends Context = DefaultContextOf< R >, - K extends Kind = KindOf< R >, - N extends Name = NameOf< R > - >( - state: State, - kind: K, - name: N, - key: KeyOf< K, N >, - query: EntityQuery< C, true > - ): Partial< EntityRecordOf< K, N, C > > | null | undefined; - - < - R extends EntityRecordOf< K, N >, - C extends Context = DefaultContextOf< R >, - K extends Kind = KindOf< R >, - N extends Name = NameOf< R > - >( - state: State, - kind: K, - name: N, - key: KeyOf< K, N >, - query?: EntityQuery< C, false > - ): EntityRecordOf< K, N, C > | null | undefined; -} - /** * Returns the Entity's record object by key. Returns `null` if the value is not * yet received, undefined if the value entity is known to not exist, or the @@ -301,19 +235,18 @@ interface GetEntityRecord { * * @return Record. */ -export const getEntityRecord: GetEntityRecord = createSelector( +export const getEntityRecord = createSelector( < - R extends EntityRecordOf< K, N >, - C extends Context = DefaultContextOf< R >, - K extends Kind = KindOf< R >, - N extends Name = NameOf< R > + EntityRecord extends + | ET.EntityRecord< any > + | Partial< ET.EntityRecord< any > > >( state: State, - kind: K, - name: N, - key: KeyOf< R >, - query - ) => { + kind: string, + name: string, + key: EntityRecordKey, + query?: GetRecordsHttpQuery + ): EntityRecord | undefined => { const queriedState = get( state.entities.records, [ kind, name, @@ -342,7 +275,7 @@ export const getEntityRecord: GetEntityRecord = createSelector( const value = get( item, field ); set( filteredItem, field, value ); } - return filteredItem; + return filteredItem as EntityRecord; } return item; @@ -381,10 +314,9 @@ export const getEntityRecord: GetEntityRecord = createSelector( * @return Record. */ export function __experimentalGetEntityRecordNoResolver< - K extends Kind, - N extends Name ->( state: State, kind: K, name: N, key: KeyOf< K, N > ) { - return getEntityRecord( state, kind, name, key ); + EntityRecord extends ET.EntityRecord< any > +>( state: State, kind: string, name: string, key: EntityRecordKey ) { + return getEntityRecord< EntityRecord >( state, kind, name, key ); } /** @@ -399,13 +331,18 @@ export function __experimentalGetEntityRecordNoResolver< * @return Object with the entity's raw attributes. */ export const getRawEntityRecord = createSelector( - < K extends Kind, N extends Name >( + < EntityRecord extends ET.EntityRecord< any > >( state: State, - kind: K, - name: N, - key: KeyOf< K, N > + kind: string, + name: string, + key: EntityRecordKey ): EntityRecord | undefined => { - const record = getEntityRecord( state, kind, name, key ); + const record = getEntityRecord< EntityRecord >( + state, + kind, + name, + key + ); return ( record && Object.keys( record ).reduce( ( accumulator, _key ) => { @@ -424,15 +361,15 @@ export const getRawEntityRecord = createSelector( accumulator[ _key ] = record[ _key ]; } return accumulator; - }, {} ) + }, {} as any ) ); }, ( state: State, - kind: Kind, - name: Name, - recordId: GenericRecordKey, - query?: EntityQuery< any > + kind: string, + name: string, + recordId: EntityRecordKey, + query?: GetRecordsHttpQuery ) => { const context = query?.context ?? 'default'; return [ @@ -468,59 +405,15 @@ export const getRawEntityRecord = createSelector( * * @return Whether entity records have been received. */ -export function hasEntityRecords< - R extends EntityRecordOf< K, N >, - C extends Context = DefaultContextOf< R >, - K extends Kind = KindOf< R >, - N extends Name = NameOf< R > ->( state: State, kind: K, name: N, query?: EntityQuery< C > ): boolean { +export function hasEntityRecords( + state: State, + kind: string, + name: string, + query?: GetRecordsHttpQuery +): boolean { return Array.isArray( getEntityRecords( state, kind, name, query ) ); } -/** - * GetEntityRecord is declared as an *interface*, but it actually describes - * the specifies the getEntityRecord *function* signature. It may seem unusual, - * but it's just how TypeScript implements function overloading. - * - * More accurately, GetEntityRecord distinguishes between two different signatures - * the getEntityRecord selector has: - * - * 1. When query._fields is not given, the returned type is EntityRecordOf< K, N, C >[] - * 2. When query._fields is given, the returned type is Partial>[] - * - * Unfortunately, due to a TypeScript limitation (https://github.com/microsoft/TypeScript/issues/23132) - * we can't use a single function signature with a return type such as: - * - * Fields extends undefined - * ? EntityRecordOf< K, N, C >[] - * : Partial< EntityRecordOf< K, N, C > >[] - */ -interface GetEntityRecords { - < - R extends EntityRecordOf< K, N >, - C extends Context = DefaultContextOf< R >, - K extends Kind = KindOf< R >, - N extends Name = NameOf< R > - >( - state: State, - kind: K, - name: N, - query: EntityQuery< C, true > - ): Partial< EntityRecordOf< K, N, C > >[] | null | undefined; - - < - R extends EntityRecordOf< K, N >, - C extends Context = DefaultContextOf< R >, - K extends Kind = KindOf< R >, - N extends Name = NameOf< R > - >( - state: State, - kind: K, - name: N, - query?: EntityQuery< C, false > - ): EntityRecordOf< K, N, C >[] | null | undefined; -} - /** * Returns the Entity's records. * @@ -532,17 +425,16 @@ interface GetEntityRecords { * * @return Records. */ -export const getEntityRecords: GetEntityRecords = < - R extends EntityRecordOf< K, N >, - C extends Context = DefaultContextOf< R >, - K extends Kind = KindOf< R >, - N extends Name = NameOf< R > +export const getEntityRecords = < + EntityRecord extends + | ET.EntityRecord< any > + | Partial< ET.EntityRecord< any > > >( state: State, - kind: K, - name: N, - query -) => { + kind: string, + name: string, + query?: GetRecordsHttpQuery +): EntityRecord[] | null => { // Queried data state is prepopulated for all known entities. If this is not // assigned for the given parameters, then it is known to not exist. const queriedState = get( state.entities.records, [ @@ -558,9 +450,9 @@ export const getEntityRecords: GetEntityRecords = < type DirtyEntityRecord = { title: string; - key: GenericRecordKey; - name: Name; - kind: Kind; + key: EntityRecordKey; + name: string; + kind: string; }; /** * Returns the list of dirty entity records. @@ -575,64 +467,44 @@ export const __experimentalGetDirtyEntityRecords = createSelector( entities: { records }, } = state; const dirtyRecords: DirtyEntityRecord[] = []; - ( Object.keys( records ) as Kind[] ).forEach( - < K extends Kind >( kind: K ) => { - ( Object.keys( records[ kind ] ) as Name[] ).forEach( - < N extends Name >( name: N ) => { - const primaryKeys = ( - Object.keys( - records[ kind ][ name ].edits - ) as KeyOf< K, N >[] - ).filter( - ( primaryKey ) => - // The entity record must exist (not be deleted), - // and it must have edits. - getEntityRecord( - state, - kind, - name, - primaryKey - ) && - hasEditsForEntityRecord( - state, - kind, - name, - primaryKey - ) - ); + Object.keys( records ).forEach( ( kind ) => { + Object.keys( records[ kind ] ).forEach( ( name ) => { + const primaryKeys = ( + Object.keys( records[ kind ][ name ].edits ) as string[] + ).filter( + ( primaryKey ) => + // The entity record must exist (not be deleted), + // and it must have edits. + getEntityRecord( state, kind, name, primaryKey ) && + hasEditsForEntityRecord( state, kind, name, primaryKey ) + ); - if ( primaryKeys.length ) { - const entityConfig = getEntityConfig( - state, - kind, - name - ); - primaryKeys.forEach( ( primaryKey ) => { - const entityRecord = getEditedEntityRecord( - state, - kind, - name, - primaryKey - ); - dirtyRecords.push( { - // We avoid using primaryKey because it's transformed into a string - // when it's used as an object key. - key: entityRecord[ + if ( primaryKeys.length ) { + const entityConfig = getEntityConfig( state, kind, name ); + primaryKeys.forEach( ( primaryKey ) => { + const entityRecord = getEditedEntityRecord( + state, + kind, + name, + primaryKey + ); + dirtyRecords.push( { + // We avoid using primaryKey because it's transformed into a string + // when it's used as an object key. + key: entityRecord + ? entityRecord[ entityConfig.key || DEFAULT_ENTITY_KEY - ], - title: - entityConfig?.getTitle?.( - entityRecord - ) || '', - name, - kind, - } ); - } ); - } - } - ); - } - ); + ] + : undefined, + title: + entityConfig?.getTitle?.( entityRecord ) || '', + name, + kind, + } ); + } ); + } + } ); + } ); return dirtyRecords; }, @@ -652,55 +524,40 @@ export const __experimentalGetEntitiesBeingSaved = createSelector( entities: { records }, } = state; const recordsBeingSaved: DirtyEntityRecord[] = []; - ( Object.keys( records ) as Kind[] ).forEach( - < K extends Kind >( kind: K ) => { - ( Object.keys( records[ kind ] ) as Name[] ).forEach( - < N extends Name >( name: N ) => { - const primaryKeys = ( - Object.keys( - records[ kind ][ name ].saving - ) as KeyOf< K, N >[] - ).filter( ( primaryKey ) => - isSavingEntityRecord( - state, - kind, - name, - primaryKey - ) - ); + Object.keys( records ).forEach( ( kind ) => { + Object.keys( records[ kind ] ).forEach( ( name ) => { + const primaryKeys = ( + Object.keys( records[ kind ][ name ].saving ) as string[] + ).filter( ( primaryKey ) => + isSavingEntityRecord( state, kind, name, primaryKey ) + ); - if ( primaryKeys.length ) { - const entityConfig = getEntityConfig( - state, - kind, - name - ); - primaryKeys.forEach( ( primaryKey ) => { - const entityRecord = getEditedEntityRecord( - state, - kind, - name, - primaryKey - ); - recordsBeingSaved.push( { - // We avoid using primaryKey because it's transformed into a string - // when it's used as an object key. - key: entityRecord[ + if ( primaryKeys.length ) { + const entityConfig = getEntityConfig( state, kind, name ); + primaryKeys.forEach( ( primaryKey ) => { + const entityRecord = getEditedEntityRecord( + state, + kind, + name, + primaryKey + ); + recordsBeingSaved.push( { + // We avoid using primaryKey because it's transformed into a string + // when it's used as an object key. + key: entityRecord + ? entityRecord[ entityConfig.key || DEFAULT_ENTITY_KEY - ], - title: - entityConfig?.getTitle?.( - entityRecord - ) || '', - name, - kind, - } ); - } ); - } - } - ); - } - ); + ] + : undefined, + title: + entityConfig?.getTitle?.( entityRecord ) || '', + name, + kind, + } ); + } ); + } + } ); + } ); return recordsBeingSaved; }, ( state ) => [ state.entities.records ] @@ -716,11 +573,11 @@ export const __experimentalGetEntitiesBeingSaved = createSelector( * * @return The entity record's edits. */ -export function getEntityRecordEdits< K extends Kind, N extends Name >( +export function getEntityRecordEdits( state: State, - kind: K, - name: N, - recordId: KeyOf< K, N > + kind: string, + name: string, + recordId: EntityRecordKey ): Optional< any > { return get( state.entities.records, [ kind, @@ -745,11 +602,11 @@ export function getEntityRecordEdits< K extends Kind, N extends Name >( * @return The entity record's non transient edits. */ export const getEntityRecordNonTransientEdits = createSelector( - < K extends Kind, N extends Name >( + ( state: State, - kind: K, - name: N, - recordId: KeyOf< K, N > + kind: string, + name: string, + recordId: EntityRecordKey ): Optional< any > => { const { transientEdits } = getEntityConfig( state, kind, name ) || {}; const edits = getEntityRecordEdits( state, kind, name, recordId ) || {}; @@ -763,7 +620,7 @@ export const getEntityRecordNonTransientEdits = createSelector( return acc; }, {} ); }, - ( state: State, kind: Kind, name: Name, recordId: GenericRecordKey ) => [ + ( state: State, kind: string, name: string, recordId: EntityRecordKey ) => [ state.entities.config, get( state.entities.records, [ kind, name, 'edits', recordId ] ), ] @@ -780,11 +637,11 @@ export const getEntityRecordNonTransientEdits = createSelector( * * @return Whether the entity record has edits or not. */ -export function hasEditsForEntityRecord< K extends Kind, N extends Name >( +export function hasEditsForEntityRecord( state: State, - kind: K, - name: N, - recordId: KeyOf< K, N > + kind: string, + name: string, + recordId: EntityRecordKey ): boolean { return ( isSavingEntityRecord( state, kind, name, recordId ) || @@ -805,21 +662,21 @@ export function hasEditsForEntityRecord< K extends Kind, N extends Name >( * @return The entity record, merged with its edits. */ export const getEditedEntityRecord = createSelector( - < K extends Kind, N extends Name >( + < EntityRecord extends ET.EntityRecord< any > >( state: State, - kind: K, - name: N, - recordId: KeyOf< K, N > - ): EntityRecord | undefined => ( { + kind: string, + name: string, + recordId: EntityRecordKey + ): ET.Updatable< EntityRecord > | undefined => ( { ...getRawEntityRecord( state, kind, name, recordId ), ...getEntityRecordEdits( state, kind, name, recordId ), } ), ( state: State, - kind: Kind, - name: Name, - recordId: GenericRecordKey, - query?: EntityQuery< any > + kind: string, + name: string, + recordId: EntityRecordKey, + query?: GetRecordsHttpQuery ) => { const context = query?.context ?? 'default'; return [ @@ -857,9 +714,9 @@ export const getEditedEntityRecord = createSelector( */ export function isAutosavingEntityRecord( state: State, - kind: Kind, - name: Name, - recordId: GenericRecordKey + kind: string, + name: string, + recordId: EntityRecordKey ): boolean { const { pending, isAutosave } = get( state.entities.records, @@ -879,15 +736,15 @@ export function isAutosavingEntityRecord( * * @return Whether the entity record is saving or not. */ -export function isSavingEntityRecord< K extends Kind, N extends Name >( +export function isSavingEntityRecord( state: State, - kind: K, - name: N, - recordId: KeyOf< K, N > + kind: string, + name: string, + recordId: EntityRecordKey ): boolean { return get( state.entities.records, - [ kind, name, 'saving', recordId as GenericRecordKey, 'pending' ], + [ kind, name, 'saving', recordId as EntityRecordKey, 'pending' ], false ); } @@ -904,9 +761,9 @@ export function isSavingEntityRecord< K extends Kind, N extends Name >( */ export function isDeletingEntityRecord( state: State, - kind: Kind, - name: Name, - recordId: GenericRecordKey + kind: string, + name: string, + recordId: EntityRecordKey ): boolean { return get( state.entities.records, @@ -927,9 +784,9 @@ export function isDeletingEntityRecord( */ export function getLastEntitySaveError( state: State, - kind: Kind, - name: Name, - recordId: GenericRecordKey + kind: string, + name: string, + recordId: EntityRecordKey ): any { return get( state.entities.records, [ kind, @@ -952,9 +809,9 @@ export function getLastEntitySaveError( */ export function getLastEntityDeleteError( state: State, - kind: Kind, - name: Name, - recordId: GenericRecordKey + kind: string, + name: string, + recordId: EntityRecordKey ): any { return get( state.entities.records, [ kind, @@ -1115,7 +972,7 @@ export function canUser( state: State, action: string, resource: string, - id?: GenericRecordKey + id?: EntityRecordKey ): boolean | undefined { const key = [ action, resource, id ].filter( Boolean ).join( '/' ); return get( state, [ 'userPermissions', key ] ); @@ -1138,9 +995,9 @@ export function canUser( */ export function canUserEditEntityRecord( state: State, - kind: Kind, - name: Name, - recordId: GenericRecordKey + kind: string, + name: string, + recordId: EntityRecordKey ): boolean | undefined { const entityConfig = getEntityConfig( state, kind, name ); if ( ! entityConfig ) { @@ -1166,7 +1023,7 @@ export function canUserEditEntityRecord( export function getAutosaves( state: State, postType: string, - postId: GenericRecordKey + postId: EntityRecordKey ): Array< any > | undefined { return state.autosaves[ postId ]; } @@ -1181,18 +1038,18 @@ export function getAutosaves( * * @return The autosave for the post and author. */ -export function getAutosave( +export function getAutosave< EntityRecord extends ET.EntityRecord< any > >( state: State, postType: string, - postId: GenericRecordKey, - authorId: GenericRecordKey + postId: EntityRecordKey, + authorId: EntityRecordKey ): EntityRecord | undefined { if ( authorId === undefined ) { return; } const autosaves = state.autosaves[ postId ]; - return find( autosaves, { author: authorId } ); + return find( autosaves, { author: authorId } ) as EntityRecord | undefined; } /** @@ -1209,7 +1066,7 @@ export const hasFetchedAutosaves = createRegistrySelector( ( state: State, postType: string, - postId: GenericRecordKey + postId: EntityRecordKey ): boolean => { return select( STORE_NAME ).hasFinishedResolution( 'getAutosaves', [ postType, @@ -1257,21 +1114,25 @@ export const getReferenceByDistinctEdits = createSelector( export function __experimentalGetTemplateForLink( state: State, link: string -): WpTemplate< 'edit' > | null { - const records = getEntityRecords( state, 'postType', 'wp_template', { - 'find-template': link, - } ); +): Optional< ET.Updatable< ET.WpTemplate > > | null { + const records = getEntityRecords< ET.WpTemplate >( + state, + 'postType', + 'wp_template', + { + 'find-template': link, + } + ); - const template = records?.length ? records[ 0 ] : null; - if ( template ) { - return getEditedEntityRecord( + if ( records?.length ) { + return getEditedEntityRecord< ET.WpTemplate >( state, 'postType', 'wp_template', - template.id + records[ 0 ].id ); } - return template; + return null; } /** diff --git a/packages/core-data/tsconfig.json b/packages/core-data/tsconfig.json new file mode 100644 index 0000000000000..7ff37851b7ab9 --- /dev/null +++ b/packages/core-data/tsconfig.json @@ -0,0 +1,21 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "rootDir": "src", + "declarationDir": "build-types", + "noUnusedParameters": false, + "checkJs": false, + "noImplicitAny": false + }, + "references": [ + { "path": "../api-fetch" }, + { "path": "../data" }, + { "path": "../deprecated" }, + { "path": "../element" }, + { "path": "../html-entities" }, + { "path": "../i18n" }, + { "path": "../is-shallow-equal" }, + { "path": "../url" } + ], + "include": [ "src/**/*" ] +} diff --git a/packages/create-block-tutorial-template/CHANGELOG.md b/packages/create-block-tutorial-template/CHANGELOG.md index ccfcc5b877d66..e99298248b396 100644 --- a/packages/create-block-tutorial-template/CHANGELOG.md +++ b/packages/create-block-tutorial-template/CHANGELOG.md @@ -2,6 +2,12 @@ ## Unreleased +## 2.5.0 (2022-08-24) + +### Enhancement + +- Add support to the `dynamic` variant ([#41289](https://github.com/WordPress/gutenberg/pull/41289), [#43481](https://github.com/WordPress/gutenberg/pull/43481)). + ## 2.3.0 (2022-06-01) ### Enhancement diff --git a/packages/create-block-tutorial-template/block-templates/index.js.mustache b/packages/create-block-tutorial-template/block-templates/index.js.mustache index ba504dcf80f19..fa35fbf272922 100644 --- a/packages/create-block-tutorial-template/block-templates/index.js.mustache +++ b/packages/create-block-tutorial-template/block-templates/index.js.mustache @@ -44,6 +44,7 @@ registerBlockType( metadata.name, { */ edit: Edit, {{#isStaticVariant}} + /** * @see ./save.js */ diff --git a/packages/create-block-tutorial-template/block-templates/template.php.mustache b/packages/create-block-tutorial-template/block-templates/template.php.mustache index 38a471b67c0e2..bd08be1004a07 100644 --- a/packages/create-block-tutorial-template/block-templates/template.php.mustache +++ b/packages/create-block-tutorial-template/block-templates/template.php.mustache @@ -1,5 +1,5 @@ {{#isDynamicVariant}}

      > - +

      {{/isDynamicVariant}} diff --git a/packages/create-block-tutorial-template/package.json b/packages/create-block-tutorial-template/package.json index e476405bd87b1..91a0ef7504e91 100644 --- a/packages/create-block-tutorial-template/package.json +++ b/packages/create-block-tutorial-template/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/create-block-tutorial-template", - "version": "2.4.1-next.d6164808d3.0", + "version": "2.5.0", "description": "Template for @wordpress/create-block used in the official WordPress tutorial.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/create-block-tutorial-template/plugin-templates/$slug.php.mustache b/packages/create-block-tutorial-template/plugin-templates/$slug.php.mustache index 99249aed24890..0211437450c15 100644 --- a/packages/create-block-tutorial-template/plugin-templates/$slug.php.mustache +++ b/packages/create-block-tutorial-template/plugin-templates/$slug.php.mustache @@ -37,25 +37,24 @@ * * @see https://developer.wordpress.org/reference/functions/register_block_type/ */ -{{#isStaticVariant}} function {{namespaceSnakeCase}}_{{slugSnakeCase}}_block_init() { + {{#isStaticVariant}} register_block_type( __DIR__ . '/build' ); -} -add_action( 'init', '{{namespaceSnakeCase}}_{{slugSnakeCase}}_block_init' ); -{{/isStaticVariant}} -{{#isDynamicVariant}} -function {{namespaceSnakeCase}}_{{slugSnakeCase}}_block_init() { + {{/isStaticVariant}} + {{#isDynamicVariant}} register_block_type( __DIR__ . '/build', array( 'render_callback' => '{{namespaceSnakeCase}}_{{slugSnakeCase}}_render_callback', ) ); + {{/isDynamicVariant}} } add_action( 'init', '{{namespaceSnakeCase}}_{{slugSnakeCase}}_block_init' ); +{{#isDynamicVariant}} /** - * Render callback function + * Render callback function. * * @param array $attributes The block attributes. * @param string $content The block content. @@ -63,7 +62,7 @@ add_action( 'init', '{{namespaceSnakeCase}}_{{slugSnakeCase}}_block_init' ); * * @return string The rendered output. */ -function {{namespaceSnakeCase}}_{{slugSnakeCase}}_render_callback( $atts, $content, $block) { +function {{namespaceSnakeCase}}_{{slugSnakeCase}}_render_callback( $atts, $content, $block ) { ob_start(); require plugin_dir_path( __FILE__ ) . 'build/template.php'; return ob_get_clean(); diff --git a/packages/create-block/CHANGELOG.md b/packages/create-block/CHANGELOG.md index 27b29290bbfbe..ee56c67353b48 100644 --- a/packages/create-block/CHANGELOG.md +++ b/packages/create-block/CHANGELOG.md @@ -2,7 +2,7 @@ ## Unreleased -## 4.0.0-next.0 (2022-08-23) +## 4.0.0 (2022-08-24) ### Breaking Change @@ -11,7 +11,7 @@ ### New Feature - Add `--no-plugin` flag to allow scaffolding of a block in an existing plugin ([#41642](https://github.com/WordPress/gutenberg/pull/41642)) -- Introduce the `--variant` flag to allow selection of a variant as defined in the template ([#41289](https://github.com/WordPress/gutenberg/pull/41289)). +- Introduce the `--variant` flag to allow selection of a variant as defined in the template ([#41289](https://github.com/WordPress/gutenberg/pull/41289), [#43481](https://github.com/WordPress/gutenberg/pull/43481)). ## 3.6.0 (2022-07-13) diff --git a/packages/create-block/lib/index.js b/packages/create-block/lib/index.js index 5df49217ef211..785e593a379fe 100644 --- a/packages/create-block/lib/index.js +++ b/packages/create-block/lib/index.js @@ -77,10 +77,22 @@ program await checkSystemRequirements( engines ); try { const pluginTemplate = await getPluginTemplate( templateName ); - const defaultValues = getDefaultValues( - pluginTemplate, - variant + const availableVariants = Object.keys( + pluginTemplate.variants ); + if ( variant && ! availableVariants.includes( variant ) ) { + if ( ! availableVariants.length ) { + throw new CLIError( + `"${ variant }" variant was selected. This template does not have any variants!` + ); + } + throw new CLIError( + `"${ variant }" is not a valid variant for this template. Available variants are: ${ availableVariants.join( + ', ' + ) }.` + ); + } + const optionsValues = Object.fromEntries( Object.entries( { plugin, @@ -90,11 +102,14 @@ program title, wpScripts, wpEnv, - variant, } ).filter( ( [ , value ] ) => value !== undefined ) ); if ( slug ) { + const defaultValues = getDefaultValues( + pluginTemplate, + variant + ); const answers = { ...defaultValues, slug, @@ -111,19 +126,24 @@ program : "Let's add a new block to your existing WordPress plugin:" ); - const filterOptionsProvided = ( { name } ) => - ! Object.keys( optionsValues ).includes( name ); + if ( ! variant && availableVariants.length > 1 ) { + const result = await inquirer.prompt( { + type: 'list', + name: 'variant', + message: + 'The template variant to use for this block:', + choices: availableVariants, + } ); + variant = result.variant; + } - // Get the variant prompt first. This will help get the default values - const variantPrompt = - Object.keys( pluginTemplate.variants )?.length > 1 - ? getPrompts( pluginTemplate, [ 'variant' ] ) - : false; - - const variantSelection = variantPrompt - ? await inquirer.prompt( variantPrompt ) - : false; + const defaultValues = getDefaultValues( + pluginTemplate, + variant + ); + const filterOptionsProvided = ( { name } ) => + ! Object.keys( optionsValues ).includes( name ); const blockPrompts = getPrompts( pluginTemplate, [ @@ -134,9 +154,8 @@ program 'dashicon', 'category', ], - variantSelection.variant + variant ).filter( filterOptionsProvided ); - const blockAnswers = await inquirer.prompt( blockPrompts ); const pluginAnswers = plugin @@ -163,7 +182,8 @@ program 'licenseURI', 'domainPath', 'updateURI', - ] + ], + variant ).filter( filterOptionsProvided ); const result = await inquirer.prompt( pluginPrompts @@ -175,8 +195,8 @@ program await scaffold( pluginTemplate, { ...defaultValues, ...optionsValues, + variant, ...blockAnswers, - ...variantSelection, ...pluginAnswers, } ); } diff --git a/packages/create-block/lib/prompts.js b/packages/create-block/lib/prompts.js index 9a6b9b1449537..12da9f892b80e 100644 --- a/packages/create-block/lib/prompts.js +++ b/packages/create-block/lib/prompts.js @@ -134,16 +134,8 @@ const updateURI = { message: 'A custom update URI for the plugin (optional):', }; -const variant = { - type: 'list', - name: 'variant', - message: 'The template variant to use for this block:', - choices: [], -}; - module.exports = { slug, - variant, namespace, title, description, diff --git a/packages/create-block/lib/scaffold.js b/packages/create-block/lib/scaffold.js index 2636db0a3d464..9e18dcc3e3d2f 100644 --- a/packages/create-block/lib/scaffold.js +++ b/packages/create-block/lib/scaffold.js @@ -12,10 +12,9 @@ const initWPScripts = require( './init-wp-scripts' ); const initWPEnv = require( './init-wp-env' ); const { code, info, success, error } = require( './log' ); const { writeOutputAsset, writeOutputTemplate } = require( './output' ); -const { getTemplateVariantVars } = require( './templates' ); module.exports = async ( - { blockOutputTemplates, pluginOutputTemplates, outputAssets, variants }, + { blockOutputTemplates, pluginOutputTemplates, outputAssets }, { $schema, apiVersion, @@ -44,7 +43,7 @@ module.exports = async ( editorScript, editorStyle, style, - variant, + variantVars, } ) => { slug = slug.toLowerCase(); @@ -100,8 +99,7 @@ module.exports = async ( editorScript, editorStyle, style, - ...getTemplateVariantVars( variants, variant ), - ...variants[ variant ], + ...variantVars, }; if ( plugin ) { diff --git a/packages/create-block/lib/templates.js b/packages/create-block/lib/templates.js index 41933286df71a..eb0e5e52631c7 100644 --- a/packages/create-block/lib/templates.js +++ b/packages/create-block/lib/templates.js @@ -111,9 +111,9 @@ const externalTemplateExists = async ( templateName ) => { const configToTemplate = async ( { pluginTemplatesPath, blockTemplatesPath, - defaultValues = {}, assetsPath, - variants, + defaultValues = {}, + variants = {}, ...deprecated } ) => { if ( defaultValues === null || typeof defaultValues !== 'object' ) { @@ -140,9 +140,9 @@ const configToTemplate = async ( { blockOutputTemplates: blockTemplatesPath ? await getOutputTemplates( blockTemplatesPath ) : {}, - defaultValues, - outputAssets: assetsPath ? await getOutputAssets( assetsPath ) : {}, pluginOutputTemplates: await getOutputTemplates( pluginTemplatesPath ), + outputAssets: assetsPath ? await getOutputAssets( assetsPath ) : {}, + defaultValues, variants, }; }; @@ -231,25 +231,14 @@ const getDefaultValues = ( pluginTemplate, variant ) => { editorStyle: 'file:./index.css', style: 'file:./style-index.css', ...pluginTemplate.defaultValues, - ...pluginTemplate.variants[ variant ], + ...pluginTemplate.variants?.[ variant ], + variantVars: getVariantVars( pluginTemplate.variants, variant ), }; }; const getPrompts = ( pluginTemplate, keys, variant ) => { - const defaultValues = getDefaultValues( pluginTemplate ); - const variantData = pluginTemplate.variants[ variant ] ?? false; + const defaultValues = getDefaultValues( pluginTemplate, variant ); return keys.map( ( promptName ) => { - if ( promptName === 'variant' ) { - prompts[ promptName ].choices = Object.keys( - pluginTemplate.variants - ); - } - if ( variantData && variantData[ promptName ] ) { - return { - ...prompts[ promptName ], - default: variantData[ promptName ], - }; - } return { ...prompts[ promptName ], default: defaultValues[ promptName ], @@ -257,28 +246,21 @@ const getPrompts = ( pluginTemplate, keys, variant ) => { } ); }; -const getTemplateVariantVars = ( variants, variant ) => { +const getVariantVars = ( variants, variant ) => { const variantVars = {}; - if ( variants ) { - const variantNames = Object.keys( variants ); - const chosenVariant = variant ?? variantNames[ 0 ]; // If no variant is passed, use the first in the array as the default + const variantNames = Object.keys( variants ); + if ( variantNames.length === 0 ) { + return variantVars; + } - if ( variantNames.includes( chosenVariant ) ) { - for ( const variantName of variantNames ) { - const key = - variantName.charAt( 0 ).toUpperCase() + - variantName.slice( 1 ); - variantVars[ `is${ key }Variant` ] = - chosenVariant === variantName ?? false; - } - } else { - throw new CLIError( - `"${ chosenVariant }" is not a valid variant for this template. Available variants are: ${ variantNames.join( - ', ' - ) }` - ); - } + const currentVariant = variant ?? variantNames[ 0 ]; + for ( const variantName of variantNames ) { + const key = + variantName.charAt( 0 ).toUpperCase() + variantName.slice( 1 ); + variantVars[ `is${ key }Variant` ] = + currentVariant === variantName ?? false; } + return variantVars; }; @@ -286,5 +268,4 @@ module.exports = { getPluginTemplate, getDefaultValues, getPrompts, - getTemplateVariantVars, }; diff --git a/packages/create-block/lib/templates/block/index.js.mustache b/packages/create-block/lib/templates/block/index.js.mustache index 7ce98bf84dff5..cbf5f7f7864fb 100644 --- a/packages/create-block/lib/templates/block/index.js.mustache +++ b/packages/create-block/lib/templates/block/index.js.mustache @@ -34,6 +34,7 @@ registerBlockType( metadata.name, { */ edit: Edit, {{#isStaticVariant}} + /** * @see ./save.js */ diff --git a/packages/create-block/lib/templates/es5/$slug.php.mustache b/packages/create-block/lib/templates/es5/$slug.php.mustache index 2a9135db105fa..d35db3dea0da4 100644 --- a/packages/create-block/lib/templates/es5/$slug.php.mustache +++ b/packages/create-block/lib/templates/es5/$slug.php.mustache @@ -68,32 +68,24 @@ function {{namespaceSnakeCase}}_{{slugSnakeCase}}_block_init() { array(), filemtime( "$dir/$style_css" ) ); - {{^isDynamicVariant}} + register_block_type( '{{namespace}}/{{slug}}', array( 'editor_script' => '{{namespace}}-{{slug}}-block-editor', 'editor_style' => '{{namespace}}-{{slug}}-block-editor', 'style' => '{{namespace}}-{{slug}}-block', - ) - ); - {{/isDynamicVariant}} - {{#isDynamicVariant}} - register_block_type( - '{{namespace}}/{{slug}}', - array( - 'editor_script' => '{{namespace}}-{{slug}}-block-editor', - 'editor_style' => '{{namespace}}-{{slug}}-block-editor', - 'style' => '{{namespace}}-{{slug}}-block', + {{#isDynamicVariant}} 'render_callback' => '{{namespaceSnakeCase}}_{{slugSnakeCase}}_render_callback', + {{/isDynamicVariant}} ) ); - {{/isDynamicVariant}} } add_action( 'init', '{{namespaceSnakeCase}}_{{slugSnakeCase}}_block_init' ); {{#isDynamicVariant}} + /** - * Render callback function + * Render callback function. * * @param array $attributes The block attributes. * @param string $content The block content. @@ -101,7 +93,7 @@ add_action( 'init', '{{namespaceSnakeCase}}_{{slugSnakeCase}}_block_init' ); * * @return string The rendered output. */ -function {{namespaceSnakeCase}}_{{slugSnakeCase}}_render_callback( $atts, $content, $block) { +function {{namespaceSnakeCase}}_{{slugSnakeCase}}_render_callback( $attributes, $content, $block ) { ob_start(); require plugin_dir_path( __FILE__ ) . '/template.php'; return ob_get_clean(); diff --git a/packages/create-block/lib/templates/es5/index.js.mustache b/packages/create-block/lib/templates/es5/index.js.mustache index a81ad9f625fe5..333bd5790b20b 100644 --- a/packages/create-block/lib/templates/es5/index.js.mustache +++ b/packages/create-block/lib/templates/es5/index.js.mustache @@ -94,7 +94,9 @@ useBlockProps(), __( '{{title}} – hello from the editor!', '{{textdomain}}' ) ); - }{{#isStaticVariant}}, + }, + {{#isStaticVariant}} + /** * The save function defines the way in which the different attributes should be combined * into the final markup, which is then serialized by the block editor into `post_content`. diff --git a/packages/create-block/lib/templates/plugin/$slug.php.mustache b/packages/create-block/lib/templates/plugin/$slug.php.mustache index 89780f870bd80..f10318db7ee55 100644 --- a/packages/create-block/lib/templates/plugin/$slug.php.mustache +++ b/packages/create-block/lib/templates/plugin/$slug.php.mustache @@ -37,25 +37,24 @@ * * @see https://developer.wordpress.org/reference/functions/register_block_type/ */ -{{#isStaticVariant}} function {{namespaceSnakeCase}}_{{slugSnakeCase}}_block_init() { + {{#isStaticVariant}} register_block_type( __DIR__ . '/build' ); -} -add_action( 'init', '{{namespaceSnakeCase}}_{{slugSnakeCase}}_block_init' ); -{{/isStaticVariant}} -{{#isDynamicVariant}} -function {{namespaceSnakeCase}}_{{slugSnakeCase}}_block_init() { + {{/isStaticVariant}} + {{#isDynamicVariant}} register_block_type( __DIR__ . '/build', array( 'render_callback' => '{{namespaceSnakeCase}}_{{slugSnakeCase}}_render_callback', ) ); + {{/isDynamicVariant}} } add_action( 'init', '{{namespaceSnakeCase}}_{{slugSnakeCase}}_block_init' ); +{{#isDynamicVariant}} /** - * Render callback function + * Render callback function. * * @param array $attributes The block attributes. * @param string $content The block content. @@ -63,7 +62,7 @@ add_action( 'init', '{{namespaceSnakeCase}}_{{slugSnakeCase}}_block_init' ); * * @return string The rendered output. */ -function {{namespaceSnakeCase}}_{{slugSnakeCase}}_render_callback( $atts, $content, $block) { +function {{namespaceSnakeCase}}_{{slugSnakeCase}}_render_callback( $attributes, $content, $block ) { ob_start(); require plugin_dir_path( __FILE__ ) . 'build/template.php'; return ob_get_clean(); diff --git a/packages/create-block/package.json b/packages/create-block/package.json index 8b6cda013be7d..befe18b22eedf 100644 --- a/packages/create-block/package.json +++ b/packages/create-block/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/create-block", - "version": "4.0.1-next.d6164808d3.0", + "version": "4.0.0", "description": "Generates PHP, JS and CSS code for registering a block for a WordPress plugin.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/custom-templated-path-webpack-plugin/package.json b/packages/custom-templated-path-webpack-plugin/package.json index 61483821cb181..e80255fd2b91f 100644 --- a/packages/custom-templated-path-webpack-plugin/package.json +++ b/packages/custom-templated-path-webpack-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/custom-templated-path-webpack-plugin", - "version": "2.1.3-next.d6164808d3.0", + "version": "2.1.2", "description": "Webpack plugin for creating custom path template tags.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/customize-widgets/CHANGELOG.md b/packages/customize-widgets/CHANGELOG.md index 704828483d04d..dd5d888701ed1 100644 --- a/packages/customize-widgets/CHANGELOG.md +++ b/packages/customize-widgets/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 3.13.0 (2022-08-24) + ## 3.12.0 (2022-08-10) ## 3.11.0 (2022-07-27) diff --git a/packages/customize-widgets/package.json b/packages/customize-widgets/package.json index dc80a7c9d0ffb..8fe956324058c 100644 --- a/packages/customize-widgets/package.json +++ b/packages/customize-widgets/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/customize-widgets", - "version": "3.12.1-next.d6164808d3.0", + "version": "3.13.0", "description": "Widgets blocks in Customizer Module for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/data-controls/CHANGELOG.md b/packages/data-controls/CHANGELOG.md index 8d4fb48663ef1..7b0f3fccc62d7 100644 --- a/packages/data-controls/CHANGELOG.md +++ b/packages/data-controls/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 2.16.0 (2022-08-24) + ## 2.15.0 (2022-08-10) ## 2.14.0 (2022-07-27) diff --git a/packages/data-controls/package.json b/packages/data-controls/package.json index 7d682a8bd270f..d65613a9eecae 100644 --- a/packages/data-controls/package.json +++ b/packages/data-controls/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/data-controls", - "version": "2.15.1-next.d6164808d3.0", + "version": "2.16.0", "description": "A set of common controls for the @wordpress/data api.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/data/CHANGELOG.md b/packages/data/CHANGELOG.md index ff22aaf946f33..4c5c51043e3e6 100644 --- a/packages/data/CHANGELOG.md +++ b/packages/data/CHANGELOG.md @@ -2,12 +2,16 @@ ## Unreleased -## 7.0.0-next.0 (2022-08-23) +## 7.0.0 (2022-08-24) ### Breaking Changes – Add TypeScript types to the built package (via "types": "build-types" in the package.json) +### Bug Fix + +- Packages: Replace `is-plain-obj` with `is-plain-object` ([#43511](https://github.com/WordPress/gutenberg/pull/43511)). + ## 6.15.0 (2022-08-10) ## 6.14.0 (2022-07-27) diff --git a/packages/data/README.md b/packages/data/README.md index eab6dfcb2a2d7..d45a71d3fa603 100644 --- a/packages/data/README.md +++ b/packages/data/README.md @@ -342,6 +342,10 @@ const store = createReduxStore( 'my-shop', { register( store ); ``` +_Type_ + +- `import('./types').combineReducers` + _Parameters_ - _reducers_ `Object`: An object whose values correspond to different reducing functions that need to be combined into one. @@ -745,11 +749,11 @@ const SaleButton = ( { children } ) => { _Parameters_ -- _storeNameOrDescriptor_ `[string|StoreDescriptor]`: Optionally provide the name of the store or its descriptor from which to retrieve action creators. If not provided, the registry.dispatch function is returned instead. +- _storeNameOrDescriptor_ `[StoreNameOrDescriptor]`: Optionally provide the name of the store or its descriptor from which to retrieve action creators. If not provided, the registry.dispatch function is returned instead. _Returns_ -- `Function`: A custom react hook. +- `UseDispatchReturn`: A custom react hook. ### useRegistry diff --git a/packages/data/package.json b/packages/data/package.json index 80fcd2d656346..b6613e8b635d2 100644 --- a/packages/data/package.json +++ b/packages/data/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/data", - "version": "7.0.1-next.d6164808d3.0", + "version": "7.0.0", "description": "Data module for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -36,7 +36,7 @@ "@wordpress/priority-queue": "file:../priority-queue", "@wordpress/redux-routine": "file:../redux-routine", "equivalent-key-map": "^0.2.2", - "is-plain-obj": "^4.1.0", + "is-plain-object": "^5.0.0", "is-promise": "^4.0.0", "lodash": "^4.17.21", "redux": "^4.1.2", diff --git a/packages/data/src/components/use-dispatch/use-dispatch.js b/packages/data/src/components/use-dispatch/use-dispatch.js index f60b7f9091245..306939307be8a 100644 --- a/packages/data/src/components/use-dispatch/use-dispatch.js +++ b/packages/data/src/components/use-dispatch/use-dispatch.js @@ -3,7 +3,14 @@ */ import useRegistry from '../registry-provider/use-registry'; -/** @typedef {import('../../types').StoreDescriptor} StoreDescriptor */ +/** + * @typedef {import('../../types').StoreDescriptor} StoreDescriptor + * @template StoreConfig + */ +/** + * @typedef {import('../../types').UseDispatchReturn} UseDispatchReturn + * @template StoreNameOrDescriptor + */ /** * A custom react hook returning the current registry dispatch actions creators. @@ -11,11 +18,12 @@ import useRegistry from '../registry-provider/use-registry'; * Note: The component using this hook must be within the context of a * RegistryProvider. * - * @param {string|StoreDescriptor} [storeNameOrDescriptor] Optionally provide the name of the - * store or its descriptor from which to - * retrieve action creators. If not - * provided, the registry.dispatch - * function is returned instead. + * @template {undefined | string | StoreDescriptor} [StoreNameOrDescriptor=undefined] + * @param {StoreNameOrDescriptor} [storeNameOrDescriptor] Optionally provide the name of the + * store or its descriptor from which to + * retrieve action creators. If not + * provided, the registry.dispatch + * function is returned instead. * * @example * This illustrates a pattern where you may need to retrieve dynamic data from @@ -48,7 +56,7 @@ import useRegistry from '../registry-provider/use-registry'; * // * // Start Sale! * ``` - * @return {Function} A custom react hook. + * @return {UseDispatchReturn} A custom react hook. */ const useDispatch = ( storeNameOrDescriptor ) => { const { dispatch } = useRegistry(); diff --git a/packages/data/src/index.js b/packages/data/src/index.js index d67b691c7a42f..91675d48c0354 100644 --- a/packages/data/src/index.js +++ b/packages/data/src/index.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import combineReducers from 'turbo-combine-reducers'; +import turboCombineReducers from 'turbo-combine-reducers'; /** * Internal dependencies @@ -43,6 +43,7 @@ export { plugins }; * The combineReducers helper function turns an object whose values are different * reducing functions into a single reducing function you can pass to registerReducer. * + * @type {import('./types').combineReducers} * @param {Object} reducers An object whose values correspond to different reducing * functions that need to be combined into one. * @@ -77,7 +78,7 @@ export { plugins }; * @return {Function} A reducer that invokes every reducer inside the reducers * object, and constructs a state object with the same shape. */ -export { combineReducers }; +export const combineReducers = turboCombineReducers; /** * Given a store descriptor, returns an object of the store's selectors. diff --git a/packages/data/src/plugins/persistence/index.js b/packages/data/src/plugins/persistence/index.js index a9eb1d21bd1e9..a013f2417d969 100644 --- a/packages/data/src/plugins/persistence/index.js +++ b/packages/data/src/plugins/persistence/index.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import isPlainObject from 'is-plain-obj'; +import { isPlainObject } from 'is-plain-object'; import { merge } from 'lodash'; /** diff --git a/packages/data/src/redux-store/metadata/reducer.ts b/packages/data/src/redux-store/metadata/reducer.ts index fd9aeaa343115..43f27bfd09f4d 100644 --- a/packages/data/src/redux-store/metadata/reducer.ts +++ b/packages/data/src/redux-store/metadata/reducer.ts @@ -1,7 +1,7 @@ /** * External dependencies */ -import { omit, has } from 'lodash'; +import { omit } from 'lodash'; import EquivalentKeyMap from 'equivalent-key-map'; import type { Reducer } from 'redux'; @@ -126,7 +126,7 @@ const isResolved = ( state: Record< string, State > = {}, action: Action ) => { case 'INVALIDATE_RESOLUTION_FOR_STORE': return {}; case 'INVALIDATE_RESOLUTION_FOR_STORE_SELECTOR': - return has( state, [ action.selectorName ] ) + return action.selectorName in state ? omit( state, [ action.selectorName ] ) : state; case 'START_RESOLUTION': diff --git a/packages/data/src/types.ts b/packages/data/src/types.ts index ed65772988ed8..0e1b9d7618957 100644 --- a/packages/data/src/types.ts +++ b/packages/data/src/types.ts @@ -1,3 +1,9 @@ +/** + * External dependencies + */ +// eslint-disable-next-line no-restricted-imports +import type { combineReducers as reduxCombineReducers } from 'redux'; + type MapOf< T > = { [ name: string ]: T }; export type ActionCreator = Function | Generator; @@ -44,7 +50,23 @@ export type UseSelectReturn< F extends MapSelect | StoreDescriptor< any > > = ? CurriedSelectorsOf< F > : never; -export type MapSelect = ( select: SelectFunction ) => any; +export type UseDispatchReturn< StoreNameOrDescriptor > = + StoreNameOrDescriptor extends StoreDescriptor< any > + ? ActionCreatorsOf< ConfigOf< StoreNameOrDescriptor > > + : StoreNameOrDescriptor extends undefined + ? DispatchFunction + : any; + +export type DispatchFunction = < StoreNameOrDescriptor >( + store: StoreNameOrDescriptor +) => StoreNameOrDescriptor extends StoreDescriptor< any > + ? ActionCreatorsOf< ConfigOf< StoreNameOrDescriptor > > + : any; + +export type MapSelect = ( + select: SelectFunction, + registry: DataRegistry +) => any; export type SelectFunction = < S >( store: S ) => CurriedSelectorsOf< S >; @@ -85,9 +107,11 @@ export interface DataEmitter { // Type Helpers. -type ActionCreatorsOf< Config extends AnyConfig > = +export type ConfigOf< S > = S extends StoreDescriptor< infer C > ? C : never; + +export type ActionCreatorsOf< Config extends AnyConfig > = Config extends ReduxStoreConfig< any, infer ActionCreators, any > - ? { [ name in keyof ActionCreators ]: Function | Generator } + ? ActionCreators : never; type SelectorsOf< Config extends AnyConfig > = Config extends ReduxStoreConfig< @@ -97,3 +121,5 @@ type SelectorsOf< Config extends AnyConfig > = Config extends ReduxStoreConfig< > ? { [ name in keyof Selectors ]: Function } : never; + +export type combineReducers = typeof reduxCombineReducers; diff --git a/packages/data/tsconfig.json b/packages/data/tsconfig.json index c333776d38da6..c604c1785853c 100644 --- a/packages/data/tsconfig.json +++ b/packages/data/tsconfig.json @@ -14,7 +14,5 @@ { "path": "../priority-queue" }, { "path": "../redux-routine" } ], - "include": [ - "src/**/*" - ] + "include": [ "src/**/*" ] } diff --git a/packages/date/CHANGELOG.md b/packages/date/CHANGELOG.md index 06d9cff0e09fb..4b29aae66ca3b 100644 --- a/packages/date/CHANGELOG.md +++ b/packages/date/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 4.16.0 (2022-08-24) + ## 4.15.0 (2022-08-10) ## 4.14.0 (2022-07-27) diff --git a/packages/date/package.json b/packages/date/package.json index 2f42d181074e8..675addc69d0c6 100644 --- a/packages/date/package.json +++ b/packages/date/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/date", - "version": "4.15.1-next.d6164808d3.0", + "version": "4.16.0", "description": "Date module for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/date/src/index.js b/packages/date/src/index.js index 2a67fc35d59a2..520d3468fd0c5 100644 --- a/packages/date/src/index.js +++ b/packages/date/src/index.js @@ -580,7 +580,8 @@ function buildMoment( dateValue, timezone = '' ) { const dateMoment = momentLib( dateValue ); if ( timezone && ! isUTCOffset( timezone ) ) { - return dateMoment.tz( String( timezone ) ); + // The ! isUTCOffset() check guarantees that timezone is a string. + return dateMoment.tz( /** @type {string} */ ( timezone ) ); } if ( timezone && isUTCOffset( timezone ) ) { diff --git a/packages/dependency-extraction-webpack-plugin/CHANGELOG.md b/packages/dependency-extraction-webpack-plugin/CHANGELOG.md index eb3415147d9c5..e1624aceb5ade 100644 --- a/packages/dependency-extraction-webpack-plugin/CHANGELOG.md +++ b/packages/dependency-extraction-webpack-plugin/CHANGELOG.md @@ -2,7 +2,7 @@ ## Unreleased -## 4.0.0-next.0 (2022-08-23) +## 4.0.0 (2022-08-24) ### Breaking Change diff --git a/packages/dependency-extraction-webpack-plugin/package.json b/packages/dependency-extraction-webpack-plugin/package.json index aef10b2a83354..c32e7e1092a72 100644 --- a/packages/dependency-extraction-webpack-plugin/package.json +++ b/packages/dependency-extraction-webpack-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/dependency-extraction-webpack-plugin", - "version": "4.0.1-next.d6164808d3.0", + "version": "4.0.0", "description": "Extract WordPress script dependencies from webpack bundles.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/deprecated/CHANGELOG.md b/packages/deprecated/CHANGELOG.md index 69053f3b07fd8..370cf81b39e0c 100644 --- a/packages/deprecated/CHANGELOG.md +++ b/packages/deprecated/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 3.16.0 (2022-08-24) + ## 3.15.0 (2022-08-10) ## 3.14.0 (2022-07-27) diff --git a/packages/deprecated/package.json b/packages/deprecated/package.json index 9a8c973b448ed..fae32eac4e8fe 100644 --- a/packages/deprecated/package.json +++ b/packages/deprecated/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/deprecated", - "version": "3.15.1-next.d6164808d3.0", + "version": "3.16.0", "description": "Deprecation utility for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/docgen/lib/markdown/formatter.js b/packages/docgen/lib/markdown/formatter.js index 281a995b519f2..8e45086e0d7e9 100644 --- a/packages/docgen/lib/markdown/formatter.js +++ b/packages/docgen/lib/markdown/formatter.js @@ -35,6 +35,24 @@ const formatExamples = ( tags, docs ) => { } }; +const formatSince = ( tags, docs ) => { + if ( tags && tags.length > 0 ) { + docs.push( '\n' ); + docs.push( '\n' ); + docs.push( '*Changelog*' ); + docs.push( '\n' ); + docs.push( '\n' ); + docs.push( + ...tags.map( + ( tag ) => + `\n${ cleanSpaces( + `\`${ tag.name }\` ${ tag.description }` + ) }` + ) + ); + } +}; + const formatDeprecated = ( tags, docs ) => { if ( tags && tags.length > 0 ) { docs.push( '\n' ); @@ -187,6 +205,7 @@ module.exports = ( }, docs ); + formatSince( getSymbolTagsByName( symbol, 'since' ), docs ); docs.push( '\n' ); docs.push( '\n' ); } ); diff --git a/packages/docgen/package.json b/packages/docgen/package.json index 8aa48962d2717..53ab1f3c40785 100644 --- a/packages/docgen/package.json +++ b/packages/docgen/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/docgen", - "version": "1.24.1-next.d6164808d3.0", + "version": "1.25.0", "description": "Autogenerate public API documentation from exports and JSDoc comments.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/docgen/test/get-jsdoc-from-token.js b/packages/docgen/test/get-jsdoc-from-token.js index a54160b56ec6e..6a6d396e26d4e 100644 --- a/packages/docgen/test/get-jsdoc-from-token.js +++ b/packages/docgen/test/get-jsdoc-from-token.js @@ -9,7 +9,7 @@ describe( 'JSDoc', () => { getJSDocFromToken( { leadingComments: [ { - value: '*\n * A function that adds two parameters.\n *\n * @deprecated Use native addition instead.\n * @since v2\n *\n * @see addition\n * @link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Arithmetic_Operators\n *\n * @param {number} firstParam The first param to add.\n * @param {number} secondParam The second param to add.\n *\n * @example\n *\n * ```js\n * const addResult = sum( 1, 3 );\n * console.log( addResult ); // will yield 4\n * ```\n *\n * @return {number} The result of adding the two params.\n ', + value: '*\n * A function that adds two parameters.\n *\n * @deprecated Use native addition instead.\n * @since v2 Introduced.\n *\n * @see addition\n * @link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Arithmetic_Operators\n *\n * @param {number} firstParam The first param to add.\n * @param {number} secondParam The second param to add.\n *\n * @example\n *\n * ```js\n * const addResult = sum( 1, 3 );\n * console.log( addResult ); // will yield 4\n * ```\n *\n * @return {number} The result of adding the two params.\n ', }, ], } ) @@ -24,6 +24,7 @@ describe( 'JSDoc', () => { { tag: 'since', name: 'v2', + description: 'Introduced.\n', }, { tag: 'see', diff --git a/packages/dom-ready/CHANGELOG.md b/packages/dom-ready/CHANGELOG.md index 2011edceef17c..b8efa5f4d4b82 100644 --- a/packages/dom-ready/CHANGELOG.md +++ b/packages/dom-ready/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 3.16.0 (2022-08-24) + ## 3.15.0 (2022-08-10) ## 3.14.0 (2022-07-27) diff --git a/packages/dom-ready/package.json b/packages/dom-ready/package.json index ce6860b4d2b7d..c17236d7d637c 100644 --- a/packages/dom-ready/package.json +++ b/packages/dom-ready/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/dom-ready", - "version": "3.15.1-next.d6164808d3.0", + "version": "3.16.0", "description": "Execute callback after the DOM is loaded.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/dom/CHANGELOG.md b/packages/dom/CHANGELOG.md index 6a59bf33f7f7a..4244419ccf3bb 100644 --- a/packages/dom/CHANGELOG.md +++ b/packages/dom/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 3.16.0 (2022-08-24) + ## 3.15.0 (2022-08-10) ## 3.14.0 (2022-07-27) diff --git a/packages/dom/package.json b/packages/dom/package.json index f57dd487a4d26..1637faa057812 100644 --- a/packages/dom/package.json +++ b/packages/dom/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/dom", - "version": "3.15.1-next.d6164808d3.0", + "version": "3.16.0", "description": "DOM utilities module for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -29,7 +29,7 @@ "sideEffects": false, "dependencies": { "@babel/runtime": "^7.16.0", - "@wordpress/deprecated": "^3.15.1-next.d6164808d3.0" + "@wordpress/deprecated": "^3.8.0" }, "publishConfig": { "access": "public" diff --git a/packages/e2e-test-utils-playwright/src/editor/open-document-settings-sidebar.ts b/packages/e2e-test-utils-playwright/src/editor/open-document-settings-sidebar.ts index 11bf6774a6da1..0cb27c33c6c88 100644 --- a/packages/e2e-test-utils-playwright/src/editor/open-document-settings-sidebar.ts +++ b/packages/e2e-test-utils-playwright/src/editor/open-document-settings-sidebar.ts @@ -2,7 +2,7 @@ * Internal dependencies */ import type { Editor } from './index'; -const { expect } = require( '../test' ); +import { expect } from '../test'; /** * Clicks on the button in the header which opens Document Settings sidebar when it is closed. @@ -10,15 +10,21 @@ const { expect } = require( '../test' ); * @param {Editor} this */ export async function openDocumentSettingsSidebar( this: Editor ) { - const editorSettings = this.page.locator( - 'role=region[name="Editor settings"i]' + const editorSettingsButton = this.page.locator( + 'role=region[name="Editor top bar"i] >> role=button[name="Settings"i]' ); - if ( ! ( await editorSettings.isVisible() ) ) { - await this.page.click( - 'role=region[name="Editor top bar"i] >> role=button[name="Settings"i]' - ); + const isEditorSettingsOpened = + ( await editorSettingsButton.getAttribute( 'aria-expanded' ) ) === + 'true'; - await expect( editorSettings ).toBeVisible(); + if ( ! isEditorSettingsOpened ) { + await editorSettingsButton.click(); + + await expect( + this.page.locator( + 'role=region[name="Editor settings"i] >> role=button[name^="Close settings"i]' + ) + ).toBeVisible(); } } diff --git a/packages/e2e-test-utils-playwright/src/request-utils/index.ts b/packages/e2e-test-utils-playwright/src/request-utils/index.ts index 322aa48e850f8..bd6ed7035561e 100644 --- a/packages/e2e-test-utils-playwright/src/request-utils/index.ts +++ b/packages/e2e-test-utils-playwright/src/request-utils/index.ts @@ -13,6 +13,7 @@ import { WP_ADMIN_USER, WP_BASE_URL } from '../config'; import type { User } from './login'; import { login } from './login'; import { listMedia, uploadMedia, deleteMedia, deleteAllMedia } from './media'; +import { createUser, deleteAllUsers } from './users'; import { setupRest, rest, getMaxBatchSize, batchRest } from './rest'; import { getPluginsMap, activatePlugin, deactivatePlugin } from './plugins'; import { deleteAllTemplates } from './templates'; @@ -133,6 +134,8 @@ class RequestUtils { uploadMedia = uploadMedia.bind( this ); deleteMedia = deleteMedia.bind( this ); deleteAllMedia = deleteAllMedia.bind( this ); + createUser = createUser.bind( this ); + deleteAllUsers = deleteAllUsers.bind( this ); } export type { StorageState }; diff --git a/packages/e2e-test-utils-playwright/src/request-utils/users.ts b/packages/e2e-test-utils-playwright/src/request-utils/users.ts new file mode 100644 index 0000000000000..c790ec9e3a989 --- /dev/null +++ b/packages/e2e-test-utils-playwright/src/request-utils/users.ts @@ -0,0 +1,121 @@ +/** + * Internal dependencies + */ +import type { RequestUtils } from './index'; + +export interface User { + id: number; + email: string; +} + +export interface UserData { + username: string; + email: string; + firstName?: string; + lastName?: string; + password?: string; + roles?: string[]; +} + +export interface UserRequestData { + username: string; + email: string; + first_name?: string; + last_name?: string; + password?: string; + roles?: string[]; +} + +/** + * List all users. + * + * @see https://developer.wordpress.org/rest-api/reference/users/#list-users + * @param this + */ +async function listUsers( this: RequestUtils ) { + const response = await this.rest< User[] >( { + method: 'GET', + path: '/wp/v2/users', + params: { + per_page: 100, + }, + } ); + + return response; +} + +/** + * Add a test user. + * + * @see https://developer.wordpress.org/rest-api/reference/users/#create-a-user + * @param this + * @param user User data to create. + */ +async function createUser( this: RequestUtils, user: UserData ) { + const userData: UserRequestData = { + username: user.username, + email: user.email, + }; + + if ( user.firstName ) { + userData.first_name = user.firstName; + } + + if ( user.lastName ) { + userData.last_name = user.lastName; + } + + if ( user.password ) { + userData.password = user.password; + } + + if ( user.roles ) { + userData.roles = user.roles; + } + + const response = await this.rest< User >( { + method: 'POST', + path: '/wp/v2/users', + data: userData, + } ); + + return response; +} + +/** + * Delete a user. + * + * @see https://developer.wordpress.org/rest-api/reference/users/#delete-a-user + * @param this + * @param userId The ID of the user. + */ +async function deleteUser( this: RequestUtils, userId: number ) { + const response = await this.rest( { + method: 'DELETE', + path: `/wp/v2/users/${ userId }`, + params: { force: true, reassign: 1 }, + } ); + + return response; +} + +/** + * Delete all users except main root user. + * + * @param this + */ +async function deleteAllUsers( this: RequestUtils ) { + const users = await listUsers.bind( this )(); + + // The users endpoint doesn't support batch request yet. + const responses = await Promise.all( + users + // Do not delete root user. + .filter( ( user: User ) => user.id !== 1 ) + .map( ( user: User ) => deleteUser.bind( this )( user.id ) ) + ); + + return responses; +} + +export { createUser, deleteAllUsers }; diff --git a/packages/e2e-test-utils/CHANGELOG.md b/packages/e2e-test-utils/CHANGELOG.md index cef8dcf1e84f6..aa4cb2afa429c 100644 --- a/packages/e2e-test-utils/CHANGELOG.md +++ b/packages/e2e-test-utils/CHANGELOG.md @@ -2,7 +2,7 @@ ## Unreleased -## 8.0.0-next.0 (2022-08-23) +## 8.0.0 (2022-08-24) ### Breaking Change diff --git a/packages/e2e-test-utils/package.json b/packages/e2e-test-utils/package.json index 21c3765f20572..b90e7697a1990 100644 --- a/packages/e2e-test-utils/package.json +++ b/packages/e2e-test-utils/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/e2e-test-utils", - "version": "8.0.1-next.d6164808d3.0", + "version": "8.0.0", "description": "End-To-End (E2E) test utils for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/e2e-tests/CHANGELOG.md b/packages/e2e-tests/CHANGELOG.md index 47d361b22d2fa..fd9b5eb41775d 100644 --- a/packages/e2e-tests/CHANGELOG.md +++ b/packages/e2e-tests/CHANGELOG.md @@ -2,7 +2,7 @@ ## Unreleased -## 5.0.0-next.0 (2022-08-23) +## 5.0.0 (2022-08-24) ### Breaking Change diff --git a/packages/e2e-tests/package.json b/packages/e2e-tests/package.json index a64013b89854c..94f524ea44c02 100644 --- a/packages/e2e-tests/package.json +++ b/packages/e2e-tests/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/e2e-tests", - "version": "5.0.1-next.d6164808d3.0", + "version": "5.0.0", "description": "End-To-End (E2E) tests for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/e2e-tests/specs/editor/blocks/navigation.test.js b/packages/e2e-tests/specs/editor/blocks/navigation.test.js index f95b806495957..db8f750281569 100644 --- a/packages/e2e-tests/specs/editor/blocks/navigation.test.js +++ b/packages/e2e-tests/specs/editor/blocks/navigation.test.js @@ -278,8 +278,9 @@ async function waitForBlock( blockName ) { } // Disable reason - these tests are to be re-written. +// Skipped temporarily due to issues with GH actions: https://wordpress.slack.com/archives/C02QB2JS7/p1661331673166269. // eslint-disable-next-line jest/no-disabled-tests -describe( 'Navigation', () => { +describe.skip( 'Navigation', () => { const contributorUsername = `contributoruser_${ ++uniqueId }`; let contributorPassword; @@ -1681,10 +1682,7 @@ Expected mock function not to be called but it was called with: ["POST", "http:/ ); } ); - // Skip reason: running it in interactive works but selecting and - // checking for focus consistently fails in the test. - // eslint-disable-next-line jest/no-disabled-tests - it.skip( 'should always focus select menu button after item selection', async () => { + it( 'should always focus select menu button after item selection', async () => { // Create some navigation menus to work with. await createNavigationMenu( { title: 'First navigation', @@ -1712,7 +1710,7 @@ Expected mock function not to be called but it was called with: ["POST", "http:/ const theOption = await page.waitForXPath( "//button[@aria-checked='false'][contains(., 'First navigation')]" ); - theOption.click(); + await theOption.click(); // Once the options are closed, does select menu button receive focus? const selectMenuDropdown2 = await page.$( diff --git a/packages/e2e-tests/specs/editor/plugins/__snapshots__/cpt-locking.test.js.snap b/packages/e2e-tests/specs/editor/plugins/__snapshots__/cpt-locking.test.js.snap index fcacbe069610c..f12d66f3f2f33 100644 --- a/packages/e2e-tests/specs/editor/plugins/__snapshots__/cpt-locking.test.js.snap +++ b/packages/e2e-tests/specs/editor/plugins/__snapshots__/cpt-locking.test.js.snap @@ -84,7 +84,9 @@ exports[`cpt locking template_lock false should allow blocks to be inserted 1`] -
      • List content
      +
        +
      • List content
      • +
      " `; diff --git a/packages/e2e-tests/specs/editor/plugins/inner-blocks-allowed-blocks.test.js b/packages/e2e-tests/specs/editor/plugins/inner-blocks-allowed-blocks.test.js index 3cbb2312ad946..e5e1854cc1708 100644 --- a/packages/e2e-tests/specs/editor/plugins/inner-blocks-allowed-blocks.test.js +++ b/packages/e2e-tests/specs/editor/plugins/inner-blocks-allowed-blocks.test.js @@ -75,6 +75,9 @@ describe( 'Allowed Blocks Setting on InnerBlocks', () => { await page.$x( `//button//span[contains(text(), 'List')]` ) )[ 0 ]; await insertButton.click(); + // Select the list wrapper so the image is inserable. + await page.keyboard.press( 'ArrowUp' ); + await page.keyboard.press( 'ArrowUp' ); await insertBlock( 'Image' ); await closeGlobalBlockInserter(); await page.waitForSelector( '.product[data-number-of-children="2"]' ); diff --git a/packages/e2e-tests/specs/editor/plugins/register-block-type-hooks.test.js b/packages/e2e-tests/specs/editor/plugins/register-block-type-hooks.test.js deleted file mode 100644 index d0f2457ec2005..0000000000000 --- a/packages/e2e-tests/specs/editor/plugins/register-block-type-hooks.test.js +++ /dev/null @@ -1,32 +0,0 @@ -/** - * WordPress dependencies - */ -import { - activatePlugin, - createNewPost, - deactivatePlugin, - openGlobalBlockInserter, -} from '@wordpress/e2e-test-utils'; - -describe( 'Register block type hooks', () => { - beforeEach( async () => { - await activatePlugin( 'gutenberg-test-register-block-type-hooks' ); - await createNewPost(); - } ); - - afterEach( async () => { - await deactivatePlugin( 'gutenberg-test-register-block-type-hooks' ); - } ); - - it( 'has a custom category for Paragraph block', async () => { - await openGlobalBlockInserter(); - - const widgetsCategory = await page.waitForSelector( - '.block-editor-block-types-list[aria-label="Widgets"]' - ); - - expect( - await widgetsCategory.$( '.editor-block-list-item-paragraph' ) - ).toBeDefined(); - } ); -} ); diff --git a/packages/e2e-tests/specs/editor/various/__snapshots__/block-deletion.test.js.snap b/packages/e2e-tests/specs/editor/various/__snapshots__/block-deletion.test.js.snap index a433a325d2a92..8b0dcaec4067f 100644 --- a/packages/e2e-tests/specs/editor/various/__snapshots__/block-deletion.test.js.snap +++ b/packages/e2e-tests/specs/editor/various/__snapshots__/block-deletion.test.js.snap @@ -24,7 +24,9 @@ exports[`block deletion - deleting the third and fourth blocks using backspace w -
      • caret was here
      +
        +
      • caret was here
      • +
      " `; diff --git a/packages/e2e-tests/specs/editor/various/__snapshots__/duplicating-blocks.test.js.snap b/packages/e2e-tests/specs/editor/various/__snapshots__/duplicating-blocks.test.js.snap deleted file mode 100644 index bb8192de22cb9..0000000000000 --- a/packages/e2e-tests/specs/editor/various/__snapshots__/duplicating-blocks.test.js.snap +++ /dev/null @@ -1,21 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Duplicating blocks should duplicate blocks using the block settings menu 1`] = ` -" -

      Clone me

      - - - -

      Clone me

      -" -`; - -exports[`Duplicating blocks should duplicate blocks using the keyboard shortcut 1`] = ` -" -

      Clone me

      - - - -

      Clone me

      -" -`; diff --git a/packages/e2e-tests/specs/editor/various/__snapshots__/keep-styles-on-block-transforms.test.js.snap b/packages/e2e-tests/specs/editor/various/__snapshots__/keep-styles-on-block-transforms.test.js.snap index c8ec4b42cb346..126fda3cc96e3 100644 --- a/packages/e2e-tests/specs/editor/various/__snapshots__/keep-styles-on-block-transforms.test.js.snap +++ b/packages/e2e-tests/specs/editor/various/__snapshots__/keep-styles-on-block-transforms.test.js.snap @@ -6,12 +6,6 @@ exports[`Keep styles on block transforms Should keep colors during a transform 1 " `; -exports[`Keep styles on block transforms Should keep the font size during a transform from multiple blocks into a single one 1`] = ` -" -
      • Line 1 to be made large
      • Line 2 to be made large
      • Line 3 to be made large
      -" -`; - exports[`Keep styles on block transforms Should keep the font size during a transform from multiple blocks into multiple blocks 1`] = ` "

      Line 1 to be made large

      diff --git a/packages/e2e-tests/specs/editor/various/__snapshots__/multi-block-selection.test.js.snap b/packages/e2e-tests/specs/editor/various/__snapshots__/multi-block-selection.test.js.snap index de86d82627bf0..51e7f452082d8 100644 --- a/packages/e2e-tests/specs/editor/various/__snapshots__/multi-block-selection.test.js.snap +++ b/packages/e2e-tests/specs/editor/various/__snapshots__/multi-block-selection.test.js.snap @@ -152,7 +152,9 @@ exports[`Multi-block selection should multi-select from within the list block 1` -
      • 1
      +
        +
      • 1
      • +
      " `; @@ -248,6 +250,28 @@ exports[`Multi-block selection should preserve dragged selection on move 1`] = ` " `; +exports[`Multi-block selection should properly select multiple blocks if selected nested blocks belong to different parent 1`] = ` +" +
      +

      first

      + + + +

      group

      +
      + + + +
      +

      second

      + + + +

      group

      +
      +" +`; + exports[`Multi-block selection should return original focus after failed multi selection attempt 1`] = ` "

      2

      diff --git a/packages/e2e-tests/specs/editor/various/__snapshots__/rich-text.test.js.snap b/packages/e2e-tests/specs/editor/various/__snapshots__/rich-text.test.js.snap index 92baa3662c13e..074158c55f096 100644 --- a/packages/e2e-tests/specs/editor/various/__snapshots__/rich-text.test.js.snap +++ b/packages/e2e-tests/specs/editor/various/__snapshots__/rich-text.test.js.snap @@ -102,12 +102,24 @@ exports[`RichText should only mutate text data on input 1`] = ` exports[`RichText should paste list contents into paragraph 1`] = ` " -
      • 1
        • 2
      +
        +
      • 1 +
          +
        • 2
        • +
        +
      • +
      - -

      1
      2

      -" + +
        +
      • 1 +
          +
        • 2
        • +
        +
      • +
      +" `; exports[`RichText should paste paragraph contents into list 1`] = ` @@ -116,7 +128,9 @@ exports[`RichText should paste paragraph contents into list 1`] = ` -
      • 1
      • 2
      +
        +
      • 1
        2
      • +
      " `; diff --git a/packages/e2e-tests/specs/editor/various/__snapshots__/writing-flow.test.js.snap b/packages/e2e-tests/specs/editor/various/__snapshots__/writing-flow.test.js.snap index 37328170c07e1..e0425d7d46d74 100644 --- a/packages/e2e-tests/specs/editor/various/__snapshots__/writing-flow.test.js.snap +++ b/packages/e2e-tests/specs/editor/various/__snapshots__/writing-flow.test.js.snap @@ -24,16 +24,6 @@ exports[`Writing Flow Should navigate inner blocks with arrow keys 1`] = ` " `; -exports[`Writing Flow should allow selecting entire list with longer last item 1`] = ` -" -

      a

      - - - -
      -" -`; - exports[`Writing Flow should create valid paragraph blocks when rapidly pressing Enter 1`] = ` "

      @@ -208,7 +198,9 @@ exports[`Writing Flow should navigate empty paragraphs 1`] = ` exports[`Writing Flow should not create extra line breaks in multiline value 1`] = ` " -
      +
        +
      • +
      " `; diff --git a/packages/e2e-tests/specs/editor/various/block-switcher.test.js b/packages/e2e-tests/specs/editor/various/block-switcher.test.js index 1b82f1f23ad02..d4029557e5120 100644 --- a/packages/e2e-tests/specs/editor/various/block-switcher.test.js +++ b/packages/e2e-tests/specs/editor/various/block-switcher.test.js @@ -18,6 +18,8 @@ describe( 'Block Switcher', () => { // Insert a list block. await insertBlock( 'List' ); await page.keyboard.type( 'List content' ); + await page.keyboard.press( 'ArrowUp' ); + await page.keyboard.press( 'ArrowUp' ); await pressKeyWithModifier( 'alt', 'F10' ); // Verify the block switcher exists. @@ -31,7 +33,6 @@ describe( 'Block Switcher', () => { 'Heading', 'Quote', 'Columns', - 'Table of Contents', ] ) ); } ); @@ -45,6 +46,8 @@ describe( 'Block Switcher', () => { // Insert a list block. await insertBlock( 'List' ); await page.keyboard.type( 'List content' ); + await page.keyboard.press( 'ArrowUp' ); + await page.keyboard.press( 'ArrowUp' ); await pressKeyWithModifier( 'alt', 'F10' ); // Verify the block switcher exists. @@ -56,7 +59,7 @@ describe( 'Block Switcher', () => { 'Group', 'Paragraph', 'Heading', - 'Table of Contents', + 'Columns', ] ) ); } ); @@ -71,13 +74,14 @@ describe( 'Block Switcher', () => { 'core/group', 'core/heading', 'core/columns', - 'core/table-of-contents', ].map( ( block ) => wp.blocks.unregisterBlockType( block ) ); } ); // Insert a list block. await insertBlock( 'List' ); await page.keyboard.type( 'List content' ); + await page.keyboard.press( 'ArrowUp' ); + await page.keyboard.press( 'ArrowUp' ); await pressKeyWithModifier( 'alt', 'F10' ); // Verify the block switcher exists. @@ -91,6 +95,8 @@ describe( 'Block Switcher', () => { it( 'Should show Columns block only if selected blocks are between limits (1-6)', async () => { await insertBlock( 'List' ); await page.keyboard.type( 'List content' ); + await page.keyboard.press( 'ArrowUp' ); + await page.keyboard.press( 'ArrowUp' ); await insertBlock( 'Heading' ); await page.keyboard.type( 'I am a header' ); await page.keyboard.down( 'Shift' ); @@ -103,6 +109,8 @@ describe( 'Block Switcher', () => { it( 'Should NOT show Columns transform only if selected blocks are more than max limit(6)', async () => { await insertBlock( 'List' ); await page.keyboard.type( 'List content' ); + await page.keyboard.press( 'ArrowUp' ); + await page.keyboard.press( 'ArrowUp' ); await insertBlock( 'Heading' ); await page.keyboard.type( 'I am a header' ); await page.keyboard.press( 'Enter' ); diff --git a/packages/e2e-tests/specs/editor/various/duplicating-blocks.test.js b/packages/e2e-tests/specs/editor/various/duplicating-blocks.test.js deleted file mode 100644 index 6916fdd6852aa..0000000000000 --- a/packages/e2e-tests/specs/editor/various/duplicating-blocks.test.js +++ /dev/null @@ -1,47 +0,0 @@ -/** - * WordPress dependencies - */ -import { - clickMenuItem, - createNewPost, - insertBlock, - getEditedPostContent, - clickBlockToolbarButton, - pressKeyWithModifier, -} from '@wordpress/e2e-test-utils'; - -describe( 'Duplicating blocks', () => { - beforeEach( async () => { - await createNewPost(); - } ); - - it( 'should duplicate blocks using the block settings menu', async () => { - await insertBlock( 'Paragraph' ); - await page.keyboard.type( 'Clone me' ); - - // Select the test we just typed - // This doesn't do anything but we previously had a duplicationi bug - // When the selection was not collapsed. - await pressKeyWithModifier( 'primary', 'a' ); - - await clickBlockToolbarButton( 'Options' ); - await clickMenuItem( 'Duplicate' ); - - expect( await getEditedPostContent() ).toMatchSnapshot(); - } ); - - it( 'should duplicate blocks using the keyboard shortcut', async () => { - await insertBlock( 'Paragraph' ); - await page.keyboard.type( 'Clone me' ); - - // Select the test we just typed - // This doesn't do anything but we previously had a duplicationi bug - // When the selection was not collapsed. - await pressKeyWithModifier( 'primary', 'a' ); - - // Duplicate using the keyboard shortccut. - await pressKeyWithModifier( 'primaryShift', 'd' ); - - expect( await getEditedPostContent() ).toMatchSnapshot(); - } ); -} ); diff --git a/packages/e2e-tests/specs/editor/various/inserting-blocks.test.js b/packages/e2e-tests/specs/editor/various/inserting-blocks.test.js index f03bd39064196..9056b5b0a62b9 100644 --- a/packages/e2e-tests/specs/editor/various/inserting-blocks.test.js +++ b/packages/e2e-tests/specs/editor/various/inserting-blocks.test.js @@ -279,6 +279,8 @@ describe( 'Inserting blocks', () => { await insertBlock( 'Group' ); await insertBlock( 'Paragraph' ); await page.keyboard.type( 'Paragraph after group' ); + // Click the Group first to make the appender inside it clickable. + await page.click( '[data-type="core/group"]' ); await page.click( '[data-type="core/group"] [aria-label="Add block"]' ); const browseAll = await page.waitForXPath( '//button[text()="Browse all"]' @@ -295,6 +297,8 @@ describe( 'Inserting blocks', () => { await insertBlock( 'Group' ); await insertBlock( 'Paragraph' ); await page.keyboard.type( 'Text' ); + // Click the Group first to make the appender inside it clickable. + await page.click( '[data-type="core/group"]' ); await page.click( '[data-type="core/group"] [aria-label="Add block"]' ); await page.waitForSelector( INSERTER_SEARCH_SELECTOR ); await page.focus( INSERTER_SEARCH_SELECTOR ); diff --git a/packages/e2e-tests/specs/editor/various/keep-styles-on-block-transforms.test.js b/packages/e2e-tests/specs/editor/various/keep-styles-on-block-transforms.test.js index 1443b49e5131b..56b2ea6263d92 100644 --- a/packages/e2e-tests/specs/editor/various/keep-styles-on-block-transforms.test.js +++ b/packages/e2e-tests/specs/editor/various/keep-styles-on-block-transforms.test.js @@ -34,23 +34,6 @@ describe( 'Keep styles on block transforms', () => { expect( await getEditedPostContent() ).toMatchSnapshot(); } ); - it( 'Should keep the font size during a transform from multiple blocks into a single one', async () => { - // Create a paragraph block with some content. - await clickBlockAppender(); - await page.keyboard.type( 'Line 1 to be made large' ); - await page.keyboard.press( 'Enter' ); - await page.keyboard.type( 'Line 2 to be made large' ); - await page.keyboard.press( 'Enter' ); - await page.keyboard.type( 'Line 3 to be made large' ); - await pressKeyWithModifier( 'shift', 'ArrowUp' ); - await pressKeyWithModifier( 'shift', 'ArrowUp' ); - await page.click( - '[role="radiogroup"][aria-label="Font size"] [aria-label="Large"]' - ); - await transformBlockTo( 'List' ); - expect( await getEditedPostContent() ).toMatchSnapshot(); - } ); - it( 'Should keep the font size during a transform from multiple blocks into multiple blocks', async () => { // Create a paragraph block with some content. await clickBlockAppender(); diff --git a/packages/e2e-tests/specs/editor/various/multi-block-selection.test.js b/packages/e2e-tests/specs/editor/various/multi-block-selection.test.js index 33d7295a4ab46..a5954796bff9e 100644 --- a/packages/e2e-tests/specs/editor/various/multi-block-selection.test.js +++ b/packages/e2e-tests/specs/editor/various/multi-block-selection.test.js @@ -331,6 +331,9 @@ describe( 'Multi-block selection', () => { await page.keyboard.up( 'Shift' ); await transformBlockTo( 'Group' ); + // Confirm setup. + expect( await getEditedPostContent() ).toMatchSnapshot(); + // Click the first paragraph in the first Group block while pressing `shift` key. const firstParagraph = await page.waitForXPath( "//p[text()='first']" ); await page.keyboard.down( 'Shift' ); @@ -676,6 +679,10 @@ describe( 'Multi-block selection', () => { await pressKeyWithModifier( 'primary', 'a' ); + await page.waitForSelector( '[data-type="core/column"].is-selected' ); + + await pressKeyWithModifier( 'primary', 'a' ); + await page.waitForSelector( '[data-type="core/column"].is-multi-selected' ); @@ -699,6 +706,7 @@ describe( 'Multi-block selection', () => { // Confirm correct setup: a paragraph and a list. expect( await getEditedPostContent() ).toMatchSnapshot(); + await pressKeyWithModifier( 'primary', 'a' ); await pressKeyWithModifier( 'primary', 'a' ); await pressKeyWithModifier( 'primary', 'a' ); diff --git a/packages/e2e-tests/specs/editor/various/rich-text.test.js b/packages/e2e-tests/specs/editor/various/rich-text.test.js index 4d8b899eafbad..f4d8e0d391721 100644 --- a/packages/e2e-tests/specs/editor/various/rich-text.test.js +++ b/packages/e2e-tests/specs/editor/various/rich-text.test.js @@ -473,15 +473,18 @@ describe( 'RichText', () => { await page.keyboard.press( 'Enter' ); await page.keyboard.type( ' 2' ); - // Select all and copy. + // Select all text. + await pressKeyWithModifier( 'primary', 'a' ); + // Select the nested list. + await pressKeyWithModifier( 'primary', 'a' ); + // Select the parent list item. + await pressKeyWithModifier( 'primary', 'a' ); + // Select all the parent list item text. + await pressKeyWithModifier( 'primary', 'a' ); + // Select the entire list. await pressKeyWithModifier( 'primary', 'a' ); await pressKeyWithModifier( 'primary', 'c' ); - // Collapse the selection to the end. - await page.keyboard.press( 'ArrowRight' ); - - // Create a paragraph. - await page.keyboard.press( 'Enter' ); await page.keyboard.press( 'Enter' ); // Paste paragraph contents. diff --git a/packages/e2e-tests/specs/editor/various/splitting-merging.test.js b/packages/e2e-tests/specs/editor/various/splitting-merging.test.js index 8ff33dfcc233b..c3c47706af9c2 100644 --- a/packages/e2e-tests/specs/editor/various/splitting-merging.test.js +++ b/packages/e2e-tests/specs/editor/various/splitting-merging.test.js @@ -235,7 +235,7 @@ describe( 'splitting and merging blocks', () => { await page.keyboard.type( 'item 1' ); await page.keyboard.press( 'Enter' ); await page.keyboard.type( 'item 2' ); - await pressKeyTimes( 'ArrowUp', 2 ); + await pressKeyTimes( 'ArrowUp', 5 ); await page.keyboard.press( 'Delete' ); // Carret should be in the first block and at the proper position. await page.keyboard.type( '-' ); @@ -257,13 +257,18 @@ describe( 'splitting and merging blocks', () => { await page.keyboard.press( 'Enter' ); await page.keyboard.type( 'item 2' ); await page.keyboard.press( 'ArrowUp' ); + await page.keyboard.press( 'ArrowUp' ); await pressKeyTimes( 'ArrowLeft', 6 ); await page.keyboard.press( 'Backspace' ); // Carret should be in the first block and at the proper position. await page.keyboard.type( '-' ); expect( await getEditedPostContent() ).toMatchInlineSnapshot( ` " -

      hi-item 1

      +

      hi

      + + + +

      -item 1

      diff --git a/packages/e2e-tests/specs/editor/various/toolbar-roving-tabindex.test.js b/packages/e2e-tests/specs/editor/various/toolbar-roving-tabindex.test.js index 87869b3354409..d131ddbbe0742 100644 --- a/packages/e2e-tests/specs/editor/various/toolbar-roving-tabindex.test.js +++ b/packages/e2e-tests/specs/editor/various/toolbar-roving-tabindex.test.js @@ -86,7 +86,8 @@ describe( 'Toolbar roving tabindex', () => { it( 'ensures list block toolbar uses roving tabindex', async () => { await insertBlock( 'List' ); await page.keyboard.type( 'List' ); - await testBlockToolbarKeyboardNavigation( 'Block: List', 'List' ); + await testBlockToolbarKeyboardNavigation( 'List text', 'Select List' ); + await page.click( `[aria-label="Select List"]` ); await wrapCurrentBlockWithGroup( 'List' ); await testGroupKeyboardNavigation( 'Block: List', 'List' ); } ); diff --git a/packages/e2e-tests/specs/editor/various/writing-flow.test.js b/packages/e2e-tests/specs/editor/various/writing-flow.test.js index 322f64f0fcffe..2a918c4d2a1db 100644 --- a/packages/e2e-tests/specs/editor/various/writing-flow.test.js +++ b/packages/e2e-tests/specs/editor/various/writing-flow.test.js @@ -570,21 +570,25 @@ describe( 'Writing Flow', () => { expect( await getEditedPostContent() ).toMatchSnapshot(); } ); - it( 'should allow selecting entire list with longer last item', async () => { + it( 'should extend selection into paragraph for list with longer last item', async () => { await page.keyboard.press( 'Enter' ); await page.keyboard.type( 'a' ); await page.keyboard.press( 'Enter' ); await page.keyboard.type( '* b' ); await page.keyboard.press( 'Enter' ); await page.keyboard.type( 'cd' ); + + // Selects part of the first list item, although invisible. await pressKeyWithModifier( 'shift', 'ArrowUp' ); + await page.evaluate( () => new Promise( window.requestIdleCallback ) ); + // Extends selection into the first paragraph await pressKeyWithModifier( 'shift', 'ArrowUp' ); + await page.evaluate( () => new Promise( window.requestIdleCallback ) ); - // Ensure multi selection is not triggered and selection stays within - // the list. + // Mixed selection, so all content will be removed. await page.keyboard.press( 'Backspace' ); - expect( await getEditedPostContent() ).toMatchSnapshot(); + expect( await getEditedPostContent() ).toBe( '' ); } ); it( 'should not have a dead zone between blocks (lower)', async () => { diff --git a/packages/edit-post/CHANGELOG.md b/packages/edit-post/CHANGELOG.md index aae0955e4d97f..26ca30b5a833e 100644 --- a/packages/edit-post/CHANGELOG.md +++ b/packages/edit-post/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 6.13.0 (2022-08-24) + ## 6.12.0 (2022-08-10) ## 6.11.0 (2022-07-27) diff --git a/packages/edit-post/package.json b/packages/edit-post/package.json index e6db4e4eb4834..f74f2abb8aa75 100644 --- a/packages/edit-post/package.json +++ b/packages/edit-post/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/edit-post", - "version": "6.12.1-next.d6164808d3.0", + "version": "6.13.0", "description": "Edit Post module for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/edit-site/CHANGELOG.md b/packages/edit-site/CHANGELOG.md index 1d1d07818d88d..e388f985917bb 100644 --- a/packages/edit-site/CHANGELOG.md +++ b/packages/edit-site/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 4.13.0 (2022-08-24) + ## 4.12.0 (2022-08-10) ## 4.11.0 (2022-07-27) diff --git a/packages/edit-site/package.json b/packages/edit-site/package.json index 928e2d6009410..3de873024ba28 100644 --- a/packages/edit-site/package.json +++ b/packages/edit-site/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/edit-site", - "version": "4.12.1-next.d6164808d3.0", + "version": "4.13.0", "description": "Edit Site Page module for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/edit-site/src/components/add-new-template/new-template.js b/packages/edit-site/src/components/add-new-template/new-template.js index 0fdbf8a3c3d1f..ea7a1a840024e 100644 --- a/packages/edit-site/src/components/add-new-template/new-template.js +++ b/packages/edit-site/src/components/add-new-template/new-template.js @@ -41,6 +41,7 @@ import { useTaxonomiesMenuItems, usePostTypeMenuItems, useAuthorMenuItem, + usePostTypeArchiveMenuItems, } from './utils'; import AddCustomGenericTemplateModal from './add-custom-generic-template-modal'; import { useHistory } from '../routes'; @@ -301,6 +302,7 @@ function useMissingTemplates( } ); const missingTemplates = [ ...enhancedMissingDefaultTemplateTypes, + ...usePostTypeArchiveMenuItems(), ...postTypesMenuItems, ...taxonomiesMenuItems, ]; diff --git a/packages/edit-site/src/components/add-new-template/utils.js b/packages/edit-site/src/components/add-new-template/utils.js index 607848291bdb1..4063509a6ddb5 100644 --- a/packages/edit-site/src/components/add-new-template/utils.js +++ b/packages/edit-site/src/components/add-new-template/utils.js @@ -12,7 +12,7 @@ import { store as editorStore } from '@wordpress/editor'; import { decodeEntities } from '@wordpress/html-entities'; import { useMemo, useCallback } from '@wordpress/element'; import { __, sprintf } from '@wordpress/i18n'; -import { blockMeta, post } from '@wordpress/icons'; +import { blockMeta, post, archive } from '@wordpress/icons'; /** * @typedef IHasNameAndId @@ -86,10 +86,90 @@ const usePublicTaxonomies = () => { }, [ taxonomies ] ); }; +function usePostTypeNeedsUniqueIdentifier( publicPostTypes ) { + const postTypeLabels = useMemo( () => + publicPostTypes?.reduce( ( accumulator, { labels } ) => { + const singularName = labels.singular_name.toLowerCase(); + accumulator[ singularName ] = + ( accumulator[ singularName ] || 0 ) + 1; + return accumulator; + }, {} ) + ); + return useCallback( + ( { labels, slug } ) => { + const singularName = labels.singular_name.toLowerCase(); + return postTypeLabels[ singularName ] > 1 && singularName !== slug; + }, + [ postTypeLabels ] + ); +} + +export function usePostTypeArchiveMenuItems() { + const publicPostTypes = usePublicPostTypes(); + const postTypesWithArchives = useMemo( + () => publicPostTypes?.filter( ( postType ) => postType.has_archive ), + [ publicPostTypes ] + ); + const existingTemplates = useExistingTemplates(); + const needsUniqueIdentifier = usePostTypeNeedsUniqueIdentifier( + postTypesWithArchives + ); + return useMemo( + () => + postTypesWithArchives + ?.filter( + ( postType ) => + ! ( existingTemplates || [] ).some( + ( existingTemplate ) => + existingTemplate.slug === + 'archive-' + postType.slug + ) + ) + .map( ( postType ) => { + let title; + if ( needsUniqueIdentifier( postType ) ) { + title = sprintf( + // translators: %1s: Name of the post type e.g: "Post"; %2s: Slug of the post type e.g: "book". + __( 'Archive: %1$s (%2$s)' ), + postType.labels.singular_name, + postType.slug + ); + } else { + title = sprintf( + // translators: %s: Name of the post type e.g: "Post". + __( 'Archive: %s' ), + postType.labels.singular_name + ); + } + return { + slug: 'archive-' + postType.slug, + description: sprintf( + // translators: %s: Name of the post type e.g: "Post". + __( + 'Displays an archive with the latests posts of type: %s.' + ), + postType.labels.singular_name + ), + title, + // `icon` is the `menu_icon` property of a post type. We + // only handle `dashicons` for now, even if the `menu_icon` + // also supports urls and svg as values. + icon: postType.icon?.startsWith( 'dashicons-' ) + ? postType.icon.slice( 10 ) + : archive, + templatePrefix: 'archive', + }; + } ) || [], + [ postTypesWithArchives, existingTemplates, needsUniqueIdentifier ] + ); +} + export const usePostTypeMenuItems = ( onClickMenuItem ) => { const publicPostTypes = usePublicPostTypes(); const existingTemplates = useExistingTemplates(); const defaultTemplateTypes = useDefaultTemplateTypes(); + const needsUniqueIdentifier = + usePostTypeNeedsUniqueIdentifier( publicPostTypes ); // `page`is a special case in template hierarchy. const templatePrefixes = useMemo( () => @@ -103,21 +183,6 @@ export const usePostTypeMenuItems = ( onClickMenuItem ) => { }, {} ), [ publicPostTypes ] ); - // We need to keep track of naming conflicts. If a conflict - // occurs, we need to add slug. - const postTypeLabels = publicPostTypes?.reduce( - ( accumulator, { labels } ) => { - const singularName = labels.singular_name.toLowerCase(); - accumulator[ singularName ] = - ( accumulator[ singularName ] || 0 ) + 1; - return accumulator; - }, - {} - ); - const needsUniqueIdentifier = ( labels, slug ) => { - const singularName = labels.singular_name.toLowerCase(); - return postTypeLabels[ singularName ] > 1 && singularName !== slug; - }; const postTypesInfo = useEntitiesInfo( 'postType', templatePrefixes ); const existingTemplateSlugs = ( existingTemplates || [] ).map( ( { slug } ) => slug @@ -134,10 +199,7 @@ export const usePostTypeMenuItems = ( onClickMenuItem ) => { ); const hasGeneralTemplate = existingTemplateSlugs?.includes( generalTemplateSlug ); - const _needsUniqueIdentifier = needsUniqueIdentifier( - labels, - slug - ); + const _needsUniqueIdentifier = needsUniqueIdentifier( postType ); let menuItemTitle = sprintf( // translators: %s: Name of the post type e.g: "Post". __( 'Single item: %s' ), diff --git a/packages/edit-site/src/components/global-styles/dimensions-panel.js b/packages/edit-site/src/components/global-styles/dimensions-panel.js index cca4fdd029828..583a43f341149 100644 --- a/packages/edit-site/src/components/global-styles/dimensions-panel.js +++ b/packages/edit-site/src/components/global-styles/dimensions-panel.js @@ -121,20 +121,21 @@ function splitStyleValue( value ) { function splitGapValue( value ) { // Check for shorthand value (a string value). if ( value && typeof value === 'string' ) { - // Convert to value for individual sides for BoxControl. + // If the value is a string, treat it as a single side (top) for the spacing controls. return { top: value, - right: value, - bottom: value, - left: value, }; } - return { - ...value, - right: value?.left, - bottom: value?.top, - }; + if ( value ) { + return { + ...value, + right: value?.left, + bottom: value?.top, + }; + } + + return value; } // Props for managing `layout.contentSize`. @@ -238,21 +239,26 @@ function useMarginProps( name ) { function useBlockGapProps( name ) { const [ gapValue, setGapValue ] = useStyle( 'spacing.blockGap', name ); const gapValues = splitGapValue( gapValue ); - const setGapValues = ( nextBoxGapValue ) => { - if ( ! nextBoxGapValue ) { - setGapValue( null ); - } - setGapValue( { - top: nextBoxGapValue?.top, - left: nextBoxGapValue?.left, - } ); - }; const gapSides = useCustomSides( name, 'blockGap' ); const isAxialGap = gapSides && gapSides.some( ( side ) => AXIAL_SIDES.includes( side ) ); const resetGapValue = () => setGapValue( undefined ); const [ userSetGapValue ] = useStyle( 'spacing.blockGap', name, 'user' ); const hasGapValue = () => !! userSetGapValue; + const setGapValues = ( nextBoxGapValue ) => { + if ( ! nextBoxGapValue ) { + setGapValue( null ); + } + // If axial gap is not enabled, treat the 'top' value as the shorthand gap value. + if ( ! isAxialGap && nextBoxGapValue?.hasOwnProperty( 'top' ) ) { + setGapValue( nextBoxGapValue.top ); + } else { + setGapValue( { + top: nextBoxGapValue?.top, + left: nextBoxGapValue?.left, + } ); + } + }; return { gapValue, gapValues, @@ -469,27 +475,42 @@ export default function DimensionsPanel( { name } ) { label={ __( 'Block spacing' ) } onDeselect={ resetGapValue } isShownByDefault={ true } + className={ classnames( { + 'tools-panel-item-spacing': showSpacingPresetsControl, + } ) } > - { isAxialGap ? ( - + ) : ( + + ) ) } + { showSpacingPresetsControl && ( + - ) : ( - ) } ) } diff --git a/packages/edit-site/src/components/global-styles/hooks.js b/packages/edit-site/src/components/global-styles/hooks.js index 1da9a7293835f..662aeed77519b 100644 --- a/packages/edit-site/src/components/global-styles/hooks.js +++ b/packages/edit-site/src/components/global-styles/hooks.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { get, cloneDeep, set, isEqual, has } from 'lodash'; +import { get, cloneDeep, set, isEqual } from 'lodash'; /** * WordPress dependencies @@ -210,10 +210,8 @@ export function getSupportedGlobalStylesPanels( name ) { // unset, we still enable it. if ( STYLE_PROPERTY[ styleName ].requiresOptOut ) { if ( - has( - blockType.supports, - STYLE_PROPERTY[ styleName ].support[ 0 ] - ) && + STYLE_PROPERTY[ styleName ].support[ 0 ] in + blockType.supports && get( blockType.supports, STYLE_PROPERTY[ styleName ].support diff --git a/packages/edit-site/src/components/global-styles/preview.js b/packages/edit-site/src/components/global-styles/preview.js index b06777c656ea9..a424da638da2b 100644 --- a/packages/edit-site/src/components/global-styles/preview.js +++ b/packages/edit-site/src/components/global-styles/preview.js @@ -79,13 +79,13 @@ const StylesPreview = ( { label, isFocused } ) => { ) .slice( 0, 2 ); - // Reset leaked styles from WP common.css. + // Reset leaked styles from WP common.css and remove main content layout padding. const editorStyles = useMemo( () => { if ( styles ) { return [ ...styles, { - css: 'body{min-width: 0;}', + css: 'body{min-width: 0;padding: 0;}', isGlobalStyles: true, }, ]; diff --git a/packages/edit-site/src/components/global-styles/utils.js b/packages/edit-site/src/components/global-styles/utils.js index 81d3935575273..4c9d7bae19f7a 100644 --- a/packages/edit-site/src/components/global-styles/utils.js +++ b/packages/edit-site/src/components/global-styles/utils.js @@ -79,7 +79,7 @@ export const PRESET_METADATA = [ }, { path: [ 'spacing', 'spacingSizes' ], - valueKey: 'spacingSizes', + valueKey: 'size', cssVarInfix: 'spacing', valueFunc: ( { size } ) => size, classes: [], diff --git a/packages/edit-widgets/CHANGELOG.md b/packages/edit-widgets/CHANGELOG.md index 2e3e6a42c18e6..922875209c20c 100644 --- a/packages/edit-widgets/CHANGELOG.md +++ b/packages/edit-widgets/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 4.13.0 (2022-08-24) + ## 4.12.0 (2022-08-10) ## 4.11.0 (2022-07-27) diff --git a/packages/edit-widgets/package.json b/packages/edit-widgets/package.json index 55c3ff6a103bf..32cdf9ea5af97 100644 --- a/packages/edit-widgets/package.json +++ b/packages/edit-widgets/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/edit-widgets", - "version": "4.12.1-next.d6164808d3.0", + "version": "4.13.0", "description": "Widgets Page module for WordPress..", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/editor/CHANGELOG.md b/packages/editor/CHANGELOG.md index f1d777ffdf072..6482bde1bcc9c 100644 --- a/packages/editor/CHANGELOG.md +++ b/packages/editor/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 12.15.0 (2022-08-24) + ## 12.14.0 (2022-08-10) ## 12.13.0 (2022-07-27) diff --git a/packages/editor/package.json b/packages/editor/package.json index afee0596bef70..d9bcc4d2c0a69 100644 --- a/packages/editor/package.json +++ b/packages/editor/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/editor", - "version": "12.14.1-next.d6164808d3.0", + "version": "12.15.0", "description": "Enhanced block editor for WordPress posts.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/editor/src/components/post-featured-image/index.js b/packages/editor/src/components/post-featured-image/index.js index 800177adfccd0..dd50d9bd58805 100644 --- a/packages/editor/src/components/post-featured-image/index.js +++ b/packages/editor/src/components/post-featured-image/index.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { has, get } from 'lodash'; +import { get } from 'lodash'; /** * WordPress dependencies @@ -59,7 +59,7 @@ function getMediaDetails( media, postId ) { media.id, postId ); - if ( has( media, [ 'media_details', 'sizes', defaultSize ] ) ) { + if ( defaultSize in ( media?.media_details?.sizes ?? {} ) ) { return { mediaWidth: media.media_details.sizes[ defaultSize ].width, mediaHeight: media.media_details.sizes[ defaultSize ].height, @@ -74,7 +74,7 @@ function getMediaDetails( media, postId ) { media.id, postId ); - if ( has( media, [ 'media_details', 'sizes', fallbackSize ] ) ) { + if ( fallbackSize in ( media?.media_details?.sizes ?? {} ) ) { return { mediaWidth: media.media_details.sizes[ fallbackSize ].width, mediaHeight: media.media_details.sizes[ fallbackSize ].height, diff --git a/packages/editor/src/components/post-url/label.js b/packages/editor/src/components/post-url/label.js index c7498acbf394f..5c233dfd54946 100644 --- a/packages/editor/src/components/post-url/label.js +++ b/packages/editor/src/components/post-url/label.js @@ -15,7 +15,7 @@ export default function PostURLLabel() { export function usePostURLLabel() { const postLink = useSelect( - ( select ) => select( editorStore ).getCurrentPost().link, + ( select ) => select( editorStore ).getPermalink(), [] ); return filterURLForDisplay( safeDecodeURIComponent( postLink ) ); diff --git a/packages/editor/src/components/theme-support-check/test/index.js b/packages/editor/src/components/theme-support-check/test/index.js index 6edd77333f519..acdbcfe253552 100644 --- a/packages/editor/src/components/theme-support-check/test/index.js +++ b/packages/editor/src/components/theme-support-check/test/index.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { shallow } from 'enzyme'; +import { render, screen } from '@testing-library/react'; /** * Internal dependencies @@ -10,10 +10,8 @@ import { ThemeSupportCheck } from '../index'; describe( 'ThemeSupportCheck', () => { it( "should not render if there's no support check provided", () => { - const wrapper = shallow( - foobar - ); - expect( wrapper.type() ).toBe( null ); + render( foobar ); + expect( screen.queryByText( 'foobar' ) ).not.toBeInTheDocument(); } ); it( 'should render if post-thumbnails are supported', () => { @@ -21,7 +19,7 @@ describe( 'ThemeSupportCheck', () => { 'post-thumbnails': true, }; const supportKeys = 'post-thumbnails'; - const wrapper = shallow( + render( { foobar ); - expect( wrapper.type() ).not.toBe( null ); + expect( screen.getByText( 'foobar' ) ).toBeVisible(); } ); it( 'should render if post-thumbnails are supported for the post type', () => { @@ -37,7 +35,7 @@ describe( 'ThemeSupportCheck', () => { 'post-thumbnails': [ 'post' ], }; const supportKeys = 'post-thumbnails'; - const wrapper = shallow( + render( { foobar ); - expect( wrapper.type() ).not.toBe( null ); + expect( screen.getByText( 'foobar' ) ).toBeVisible(); } ); it( "should not render if post-thumbnails aren't supported for the post type", () => { @@ -54,7 +52,7 @@ describe( 'ThemeSupportCheck', () => { 'post-thumbnails': [ 'post' ], }; const supportKeys = 'post-thumbnails'; - const wrapper = shallow( + render( { foobar ); - expect( wrapper.type() ).toBe( null ); + expect( screen.queryByText( 'foobar' ) ).not.toBeInTheDocument(); } ); it( 'should not render if post-thumbnails is limited and false is passed for postType', () => { @@ -71,7 +69,7 @@ describe( 'ThemeSupportCheck', () => { 'post-thumbnails': [ 'post' ], }; const supportKeys = 'post-thumbnails'; - const wrapper = shallow( + render( { foobar ); - expect( wrapper.type() ).toBe( null ); + expect( screen.queryByText( 'foobar' ) ).not.toBeInTheDocument(); } ); it( "should not render if theme doesn't support post-thumbnails", () => { @@ -88,7 +86,7 @@ describe( 'ThemeSupportCheck', () => { 'post-thumbnails': false, }; const supportKeys = 'post-thumbnails'; - const wrapper = shallow( + render( { foobar ); - expect( wrapper.type() ).toBe( null ); + expect( screen.queryByText( 'foobar' ) ).not.toBeInTheDocument(); } ); } ); diff --git a/packages/element/CHANGELOG.md b/packages/element/CHANGELOG.md index f8c0e0b4164b2..0ce6ed7b92b84 100644 --- a/packages/element/CHANGELOG.md +++ b/packages/element/CHANGELOG.md @@ -2,6 +2,12 @@ ## Unreleased +## 4.14.0 (2022-08-24) + +### Bug Fix + +- Packages: Replace `is-plain-obj` with `is-plain-object` ([#43511](https://github.com/WordPress/gutenberg/pull/43511)). + ## 4.13.0 (2022-08-10) ## 4.12.0 (2022-07-27) diff --git a/packages/element/package.json b/packages/element/package.json index 4a14216d70b3a..6c58e7dae8756 100644 --- a/packages/element/package.json +++ b/packages/element/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/element", - "version": "4.13.1-next.d6164808d3.0", + "version": "4.14.0", "description": "Element React module for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -33,7 +33,7 @@ "@types/react-dom": "^17.0.11", "@wordpress/escape-html": "file:../escape-html", "change-case": "^4.1.2", - "is-plain-obj": "^4.1.0", + "is-plain-object": "^5.0.0", "react": "^17.0.2", "react-dom": "^17.0.2" }, diff --git a/packages/element/src/serialize.js b/packages/element/src/serialize.js index 79cbc92822181..d18a6f00fa941 100644 --- a/packages/element/src/serialize.js +++ b/packages/element/src/serialize.js @@ -28,7 +28,7 @@ /** * External dependencies */ -import isPlainObject from 'is-plain-obj'; +import { isPlainObject } from 'is-plain-object'; import { paramCase as kebabCase } from 'change-case'; /** diff --git a/packages/element/src/test/platform.js b/packages/element/src/test/platform.js index fc98f4e19b22a..5b6cd8e623424 100644 --- a/packages/element/src/test/platform.js +++ b/packages/element/src/test/platform.js @@ -1,7 +1,3 @@ -/** - * External dependencies - */ -import { shallow } from 'enzyme'; /** * Internal dependencies */ @@ -10,10 +6,10 @@ import Platform from '../platform'; describe( 'Platform', () => { it( 'is chooses the right thing', () => { const element = Platform.select( { - web: shallow(
      ), - native: shallow( ), + web:
      , + native: