diff --git a/changelog.txt b/changelog.txt
index bd7375a67e9d8..8159928bd84fb 100644
--- a/changelog.txt
+++ b/changelog.txt
@@ -1,5 +1,26 @@
== Changelog ==
+= 17.0.2 =
+
+
+
+## Changelog
+
+### Bug Fixes
+
+#### Typography
+- Fix another fatal error in WP_Fonts_Resolver::Get_settings(). ([56067](https://github.com/WordPress/gutenberg/pull/56067))
+
+
+
+
+## Contributors
+
+The following contributors merged PRs in this release:
+
+@arthur791004
+
+
= 17.0.1 =
## Changelog
diff --git a/docs/reference-guides/core-blocks.md b/docs/reference-guides/core-blocks.md
index e65853a6fbdf7..0ae5979b79704 100644
--- a/docs/reference-guides/core-blocks.md
+++ b/docs/reference-guides/core-blocks.md
@@ -771,7 +771,7 @@ Give quoted text visual emphasis. "In quoting others, we cite ourselves." — Ju
- **Name:** core/quote
- **Category:** text
-- **Supports:** anchor, color (background, gradients, heading, link, text), typography (fontSize, lineHeight), ~~html~~
+- **Supports:** anchor, color (background, gradients, heading, link, text), layout (~~allowEditing~~), spacing (blockGap), typography (fontSize, lineHeight), ~~html~~
- **Attributes:** align, citation, value
## Read More
diff --git a/lib/experimental/fonts-api/class-wp-fonts-resolver.php b/lib/experimental/fonts-api/class-wp-fonts-resolver.php
index f2299c7c36831..efa66839cd39c 100644
--- a/lib/experimental/fonts-api/class-wp-fonts-resolver.php
+++ b/lib/experimental/fonts-api/class-wp-fonts-resolver.php
@@ -200,11 +200,6 @@ private static function get_settings() {
if ( $set_theme_structure ) {
$set_theme_structure = false;
$settings = static::set_tyopgraphy_settings_array_structure( $settings );
-
- // Initialize the font families from settings if set and is an array, otherwise default to an empty array.
- if ( ! isset( $settings['typography']['fontFamilies']['theme'] ) || ! is_array( $settings['typography']['fontFamilies']['theme'] ) ) {
- $settings['typography']['fontFamilies']['theme'] = array();
- }
}
// Initialize the font families from variation if set and is an array, otherwise default to an empty array.
@@ -219,7 +214,12 @@ private static function get_settings() {
);
// Make sure there are no duplicates.
- $settings['typography']['fontFamilies'] = array_unique( $settings['typography']['fontFamilies'] );
+ $settings['typography']['fontFamilies'] = array_unique( $settings['typography']['fontFamilies'], SORT_REGULAR );
+
+ // The font families from settings might become null after running the `array_unique`.
+ if ( ! isset( $settings['typography']['fontFamilies']['theme'] ) || ! is_array( $settings['typography']['fontFamilies']['theme'] ) ) {
+ $settings['typography']['fontFamilies']['theme'] = array();
+ }
}
return $settings;
diff --git a/package-lock.json b/package-lock.json
index 8b64b911fae9f..9439981c29d7d 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "gutenberg",
- "version": "17.0.1",
+ "version": "17.0.2",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "gutenberg",
- "version": "17.0.1",
+ "version": "17.0.2",
"hasInstallScript": true,
"license": "GPL-2.0-or-later",
"dependencies": {
@@ -95,7 +95,7 @@
"@octokit/rest": "16.26.0",
"@octokit/types": "6.34.0",
"@octokit/webhooks-types": "5.6.0",
- "@playwright/test": "1.32.0",
+ "@playwright/test": "1.39.0",
"@pmmmwh/react-refresh-webpack-plugin": "0.5.11",
"@storybook/addon-a11y": "7.2.2",
"@storybook/addon-actions": "7.2.2",
@@ -7136,22 +7136,18 @@
}
},
"node_modules/@playwright/test": {
- "version": "1.32.0",
- "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.32.0.tgz",
- "integrity": "sha512-zOdGloaF0jeec7hqoLqM5S3L2rR4WxMJs6lgiAeR70JlH7Ml54ZPoIIf3X7cvnKde3Q9jJ/gaxkFh8fYI9s1rg==",
+ "version": "1.39.0",
+ "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.39.0.tgz",
+ "integrity": "sha512-3u1iFqgzl7zr004bGPYiN/5EZpRUSFddQBra8Rqll5N0/vfpqlP9I9EXqAoGacuAbX6c9Ulg/Cjqglp5VkK6UQ==",
"dev": true,
"dependencies": {
- "@types/node": "*",
- "playwright-core": "1.32.0"
+ "playwright": "1.39.0"
},
"bin": {
"playwright": "cli.js"
},
"engines": {
- "node": ">=14"
- },
- "optionalDependencies": {
- "fsevents": "2.3.2"
+ "node": ">=16"
}
},
"node_modules/@pmmmwh/react-refresh-webpack-plugin": {
@@ -43229,16 +43225,34 @@
"integrity": "sha512-TuvHS8AOIZNAlE77WUDiR4rySV/VMptyMfcfeoMgs4P8apaZM3JrnbzBiixKUv+XR6i+BXrQh8WAnjaSPFO65Q==",
"dev": true
},
- "node_modules/playwright-core": {
- "version": "1.32.0",
- "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.32.0.tgz",
- "integrity": "sha512-Z9Ij17X5Z3bjpp6XKujGBp9Gv4eViESac9aDmwgQFUEJBW0K80T21m/Z+XJQlu4cNsvPygw33b6V1Va6Bda5zQ==",
+ "node_modules/playwright": {
+ "version": "1.39.0",
+ "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.39.0.tgz",
+ "integrity": "sha512-naE5QT11uC/Oiq0BwZ50gDmy8c8WLPRTEWuSSFVG2egBka/1qMoSqYQcROMT9zLwJ86oPofcTH2jBY/5wWOgIw==",
"dev": true,
+ "dependencies": {
+ "playwright-core": "1.39.0"
+ },
"bin": {
"playwright": "cli.js"
},
"engines": {
- "node": ">=14"
+ "node": ">=16"
+ },
+ "optionalDependencies": {
+ "fsevents": "2.3.2"
+ }
+ },
+ "node_modules/playwright/node_modules/playwright-core": {
+ "version": "1.39.0",
+ "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.39.0.tgz",
+ "integrity": "sha512-+k4pdZgs1qiM+OUkSjx96YiKsXsmb59evFoqv8SKO067qBA+Z2s/dCzJij/ZhdQcs2zlTAgRKfeiiLm8PQ2qvw==",
+ "dev": true,
+ "bin": {
+ "playwright-core": "cli.js"
+ },
+ "engines": {
+ "node": ">=16"
}
},
"node_modules/please-upgrade-node": {
@@ -52344,6 +52358,12 @@
"node": ">= 8"
}
},
+ "node_modules/web-vitals": {
+ "version": "3.5.0",
+ "resolved": "https://registry.npmjs.org/web-vitals/-/web-vitals-3.5.0.tgz",
+ "integrity": "sha512-f5YnCHVG9Y6uLCePD4tY8bO/Ge15NPEQWtvm3tPzDKygloiqtb4SVqRHBcrIAqo2ztqX5XueqDn97zHF0LdT6w==",
+ "dev": true
+ },
"node_modules/webdriver": {
"version": "8.16.20",
"resolved": "https://registry.npmjs.org/webdriver/-/webdriver-8.16.20.tgz",
@@ -55196,7 +55216,8 @@
"form-data": "^4.0.0",
"get-port": "^5.1.1",
"lighthouse": "^10.4.0",
- "mime": "^3.0.0"
+ "mime": "^3.0.0",
+ "web-vitals": "^3.5.0"
},
"engines": {
"node": ">=12"
@@ -56443,7 +56464,7 @@
"minimist": "^1.2.0",
"npm-package-json-lint": "^6.4.0",
"npm-packlist": "^3.0.0",
- "playwright-core": "1.32.0",
+ "playwright-core": "1.39.0",
"postcss": "^8.4.5",
"postcss-loader": "^6.2.1",
"prettier": "npm:wp-prettier@3.0.3",
@@ -56470,11 +56491,23 @@
"npm": ">=6.14.4"
},
"peerDependencies": {
- "@playwright/test": "^1.32.0",
+ "@playwright/test": "^1.39.0",
"react": "^18.0.0",
"react-dom": "^18.0.0"
}
},
+ "packages/scripts/node_modules/playwright-core": {
+ "version": "1.39.0",
+ "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.39.0.tgz",
+ "integrity": "sha512-+k4pdZgs1qiM+OUkSjx96YiKsXsmb59evFoqv8SKO067qBA+Z2s/dCzJij/ZhdQcs2zlTAgRKfeiiLm8PQ2qvw==",
+ "dev": true,
+ "bin": {
+ "playwright-core": "cli.js"
+ },
+ "engines": {
+ "node": ">=16"
+ }
+ },
"packages/server-side-render": {
"name": "@wordpress/server-side-render",
"version": "4.22.0",
@@ -61619,14 +61652,12 @@
}
},
"@playwright/test": {
- "version": "1.32.0",
- "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.32.0.tgz",
- "integrity": "sha512-zOdGloaF0jeec7hqoLqM5S3L2rR4WxMJs6lgiAeR70JlH7Ml54ZPoIIf3X7cvnKde3Q9jJ/gaxkFh8fYI9s1rg==",
+ "version": "1.39.0",
+ "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.39.0.tgz",
+ "integrity": "sha512-3u1iFqgzl7zr004bGPYiN/5EZpRUSFddQBra8Rqll5N0/vfpqlP9I9EXqAoGacuAbX6c9Ulg/Cjqglp5VkK6UQ==",
"dev": true,
"requires": {
- "@types/node": "*",
- "fsevents": "2.3.2",
- "playwright-core": "1.32.0"
+ "playwright": "1.39.0"
}
},
"@pmmmwh/react-refresh-webpack-plugin": {
@@ -70293,7 +70324,8 @@
"form-data": "^4.0.0",
"get-port": "^5.1.1",
"lighthouse": "^10.4.0",
- "mime": "^3.0.0"
+ "mime": "^3.0.0",
+ "web-vitals": "^3.5.0"
}
},
"@wordpress/e2e-tests": {
@@ -71116,7 +71148,7 @@
"minimist": "^1.2.0",
"npm-package-json-lint": "^6.4.0",
"npm-packlist": "^3.0.0",
- "playwright-core": "1.32.0",
+ "playwright-core": "1.39.0",
"postcss": "^8.4.5",
"postcss-loader": "^6.2.1",
"prettier": "npm:wp-prettier@3.0.3",
@@ -71134,6 +71166,14 @@
"webpack-bundle-analyzer": "^4.9.1",
"webpack-cli": "^5.1.4",
"webpack-dev-server": "^4.15.1"
+ },
+ "dependencies": {
+ "playwright-core": {
+ "version": "1.39.0",
+ "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.39.0.tgz",
+ "integrity": "sha512-+k4pdZgs1qiM+OUkSjx96YiKsXsmb59evFoqv8SKO067qBA+Z2s/dCzJij/ZhdQcs2zlTAgRKfeiiLm8PQ2qvw==",
+ "dev": true
+ }
}
},
"@wordpress/server-side-render": {
@@ -90185,11 +90225,23 @@
"integrity": "sha512-TuvHS8AOIZNAlE77WUDiR4rySV/VMptyMfcfeoMgs4P8apaZM3JrnbzBiixKUv+XR6i+BXrQh8WAnjaSPFO65Q==",
"dev": true
},
- "playwright-core": {
- "version": "1.32.0",
- "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.32.0.tgz",
- "integrity": "sha512-Z9Ij17X5Z3bjpp6XKujGBp9Gv4eViESac9aDmwgQFUEJBW0K80T21m/Z+XJQlu4cNsvPygw33b6V1Va6Bda5zQ==",
- "dev": true
+ "playwright": {
+ "version": "1.39.0",
+ "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.39.0.tgz",
+ "integrity": "sha512-naE5QT11uC/Oiq0BwZ50gDmy8c8WLPRTEWuSSFVG2egBka/1qMoSqYQcROMT9zLwJ86oPofcTH2jBY/5wWOgIw==",
+ "dev": true,
+ "requires": {
+ "fsevents": "2.3.2",
+ "playwright-core": "1.39.0"
+ },
+ "dependencies": {
+ "playwright-core": {
+ "version": "1.39.0",
+ "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.39.0.tgz",
+ "integrity": "sha512-+k4pdZgs1qiM+OUkSjx96YiKsXsmb59evFoqv8SKO067qBA+Z2s/dCzJij/ZhdQcs2zlTAgRKfeiiLm8PQ2qvw==",
+ "dev": true
+ }
+ }
},
"please-upgrade-node": {
"version": "3.2.0",
@@ -97126,6 +97178,12 @@
"integrity": "sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==",
"dev": true
},
+ "web-vitals": {
+ "version": "3.5.0",
+ "resolved": "https://registry.npmjs.org/web-vitals/-/web-vitals-3.5.0.tgz",
+ "integrity": "sha512-f5YnCHVG9Y6uLCePD4tY8bO/Ge15NPEQWtvm3tPzDKygloiqtb4SVqRHBcrIAqo2ztqX5XueqDn97zHF0LdT6w==",
+ "dev": true
+ },
"webdriver": {
"version": "8.16.20",
"resolved": "https://registry.npmjs.org/webdriver/-/webdriver-8.16.20.tgz",
diff --git a/package.json b/package.json
index 266cbcc802d14..9f456f359a741 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "gutenberg",
- "version": "17.0.1",
+ "version": "17.0.2",
"private": true,
"description": "A new WordPress editor experience.",
"author": "The WordPress Contributors",
@@ -107,7 +107,7 @@
"@octokit/rest": "16.26.0",
"@octokit/types": "6.34.0",
"@octokit/webhooks-types": "5.6.0",
- "@playwright/test": "1.32.0",
+ "@playwright/test": "1.39.0",
"@pmmmwh/react-refresh-webpack-plugin": "0.5.11",
"@storybook/addon-a11y": "7.2.2",
"@storybook/addon-actions": "7.2.2",
diff --git a/packages/block-editor/src/components/block-list/index.js b/packages/block-editor/src/components/block-list/index.js
index a9d5f15f12f81..ab57605563e1d 100644
--- a/packages/block-editor/src/components/block-list/index.js
+++ b/packages/block-editor/src/components/block-list/index.js
@@ -17,12 +17,7 @@ import {
useMergeRefs,
useDebounce,
} from '@wordpress/compose';
-import {
- createContext,
- useState,
- useMemo,
- useCallback,
-} from '@wordpress/element';
+import { createContext, useMemo, useCallback } from '@wordpress/element';
/**
* Internal dependencies
@@ -40,13 +35,10 @@ import {
} from '../block-edit/context';
import { useTypingObserver } from '../observe-typing';
-const elementContext = createContext();
-
export const IntersectionObserver = createContext();
const pendingBlockVisibilityUpdatesPerRegistry = new WeakMap();
function Root( { className, ...settings } ) {
- const [ element, setElement ] = useState();
const isLargeViewport = useViewportMatch( 'medium' );
const { isOutlineMode, isFocusMode, editorMode } = useSelect(
( select ) => {
@@ -115,13 +107,9 @@ function Root( { className, ...settings } ) {
settings
);
return (
-
-
-
- { /* Ensure element and layout styles are always at the end of the document */ }
-
-
-
+
+
+
);
}
@@ -133,8 +121,6 @@ export default function BlockList( settings ) {
);
}
-BlockList.__unstableElementContext = elementContext;
-
function Items( {
placeholder,
rootClientId,
diff --git a/packages/block-editor/src/components/block-styles/index.js b/packages/block-editor/src/components/block-styles/index.js
index f598b35f890f1..03a6f8939c181 100644
--- a/packages/block-editor/src/components/block-styles/index.js
+++ b/packages/block-editor/src/components/block-styles/index.js
@@ -13,7 +13,6 @@ import {
__experimentalTruncate as Truncate,
Popover,
} from '@wordpress/components';
-import deprecated from '@wordpress/deprecated';
/**
* Internal dependencies
@@ -120,12 +119,3 @@ function BlockStyles( { clientId, onSwitch = noop, onHoverClassName = noop } ) {
}
export default BlockStyles;
-
-BlockStyles.Slot = () => {
- deprecated( 'BlockStyles.Slot', {
- version: '6.4',
- since: '6.2',
- } );
-
- return null;
-};
diff --git a/packages/block-editor/src/components/inserter/block-patterns-tab/patterns-filter.js b/packages/block-editor/src/components/inserter/block-patterns-tab/patterns-filter.js
index 73fef4aed68e2..04a0f27d162dd 100644
--- a/packages/block-editor/src/components/inserter/block-patterns-tab/patterns-filter.js
+++ b/packages/block-editor/src/components/inserter/block-patterns-tab/patterns-filter.js
@@ -9,7 +9,7 @@ import {
MenuItemsChoice,
ExternalLink,
} from '@wordpress/components';
-import { __ } from '@wordpress/i18n';
+import { __, _x } from '@wordpress/i18n';
import { Icon } from '@wordpress/icons';
import { useMemo, createInterpolateElement } from '@wordpress/element';
@@ -55,15 +55,24 @@ export function PatternsFilter( {
const patternSyncMenuOptions = useMemo(
() => [
- { value: SYNC_TYPES.all, label: __( 'All' ) },
+ {
+ value: SYNC_TYPES.all,
+ label: _x( 'All', 'Option that shows all patterns' ),
+ },
{
value: SYNC_TYPES.full,
- label: __( 'Synced' ),
+ label: _x(
+ 'Synced',
+ 'Option that shows all synchronized patterns'
+ ),
disabled: shouldDisableSyncFilter,
},
{
value: SYNC_TYPES.unsynced,
- label: __( 'Not synced' ),
+ label: _x(
+ 'Not synced',
+ 'Option that shows all patterns that are not synchronized'
+ ),
disabled: shouldDisableSyncFilter,
},
],
diff --git a/packages/block-editor/src/components/link-control/README.md b/packages/block-editor/src/components/link-control/README.md
index edab6b4ad488c..5c31c4c14371c 100644
--- a/packages/block-editor/src/components/link-control/README.md
+++ b/packages/block-editor/src/components/link-control/README.md
@@ -275,14 +275,14 @@ If passed, children are rendered after the input.
```jsx
-
+
-
+
```
diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js
index c25ed5cd1187a..a208fa20d242f 100644
--- a/packages/block-editor/src/components/link-control/index.js
+++ b/packages/block-editor/src/components/link-control/index.js
@@ -6,7 +6,13 @@ import classnames from 'classnames';
/**
* WordPress dependencies
*/
-import { Button, Spinner, Notice, TextControl } from '@wordpress/components';
+import {
+ Button,
+ Spinner,
+ Notice,
+ TextControl,
+ __experimentalHStack as HStack,
+} from '@wordpress/components';
import { __ } from '@wordpress/i18n';
import { useRef, useState, useEffect } from '@wordpress/element';
import { focus } from '@wordpress/dom';
@@ -467,7 +473,13 @@ function LinkControl( {
) }
{ showActions && (
-
+
+
-
-
+
) }
{ renderControlBottom && renderControlBottom() }
diff --git a/packages/block-editor/src/components/link-control/style.scss b/packages/block-editor/src/components/link-control/style.scss
index fae3f03786dfe..7b6bbff0700a3 100644
--- a/packages/block-editor/src/components/link-control/style.scss
+++ b/packages/block-editor/src/components/link-control/style.scss
@@ -96,12 +96,7 @@ $preview-image-height: 140px;
}
.block-editor-link-control__search-actions {
- display: flex;
- flex-direction: row-reverse; // put "Cancel" on the left but retain DOM order.
- justify-content: flex-start;
- gap: $grid-unit-10;
padding: $grid-unit-10 $grid-unit-20 $grid-unit-20;
- order: 20;
}
.block-editor-link-control__search-results-wrapper {
diff --git a/packages/block-editor/src/components/media-replace-flow/style.scss b/packages/block-editor/src/components/media-replace-flow/style.scss
index dd3b0563c3ca8..61df542cf5840 100644
--- a/packages/block-editor/src/components/media-replace-flow/style.scss
+++ b/packages/block-editor/src/components/media-replace-flow/style.scss
@@ -17,6 +17,7 @@
&.has-siblings {
border-top: $border-width solid $gray-900;
margin-top: $grid-unit-10;
+ padding-bottom: $grid-unit-10;
}
.block-editor-media-replace-flow__image-url-label {
@@ -55,8 +56,7 @@
}
.block-editor-link-control__search-actions {
- top: 0; // cancel default top positioning
- right: 4px;
+ padding: $grid-unit-10 0 0;
}
}
}
diff --git a/packages/block-editor/src/components/rich-text/index.native.js b/packages/block-editor/src/components/rich-text/index.native.js
index 32bd1afd3d540..aab10e9ab6547 100644
--- a/packages/block-editor/src/components/rich-text/index.native.js
+++ b/packages/block-editor/src/components/rich-text/index.native.js
@@ -17,7 +17,6 @@ import {
} from '@wordpress/blocks';
import { useInstanceId, useMergeRefs } from '@wordpress/compose';
import {
- __experimentalRichText as RichText,
__unstableCreateElement,
isEmpty,
insert,
@@ -46,6 +45,7 @@ import {
} from './utils';
import EmbedHandlerPicker from './embed-handler-picker';
import { Content } from './content';
+import RichText from './native';
const classes = 'block-editor-rich-text__editable';
@@ -688,6 +688,8 @@ ForwardedRichTextContainer.Content.defaultProps = {
value: '',
};
+ForwardedRichTextContainer.Raw = RichText;
+
/**
* @see https://github.com/WordPress/gutenberg/blob/HEAD/packages/block-editor/src/components/rich-text/README.md
*/
diff --git a/packages/rich-text/src/component/format-edit.js b/packages/block-editor/src/components/rich-text/native/format-edit.js
similarity index 86%
rename from packages/rich-text/src/component/format-edit.js
rename to packages/block-editor/src/components/rich-text/native/format-edit.js
index 1867c1ef2e4f7..75b077ab321d4 100644
--- a/packages/rich-text/src/component/format-edit.js
+++ b/packages/block-editor/src/components/rich-text/native/format-edit.js
@@ -1,8 +1,7 @@
/**
- * Internal dependencies
+ * WordPress dependencies
*/
-import { getActiveFormat } from '../get-active-format';
-import { getActiveObject } from '../get-active-object';
+import { getActiveFormat, getActiveObject } from '@wordpress/rich-text';
export default function FormatEdit( {
formatTypes,
diff --git a/packages/rich-text/src/get-format-colors.native.js b/packages/block-editor/src/components/rich-text/native/get-format-colors.native.js
similarity index 92%
rename from packages/rich-text/src/get-format-colors.native.js
rename to packages/block-editor/src/components/rich-text/native/get-format-colors.native.js
index f9b3a9187ca2b..a54d3e10f78a0 100644
--- a/packages/rich-text/src/get-format-colors.native.js
+++ b/packages/block-editor/src/components/rich-text/native/get-format-colors.native.js
@@ -1,7 +1,7 @@
/**
- * WordPress dependencies
+ * Internal dependencies
*/
-import { getColorObjectByAttributeValues } from '@wordpress/block-editor';
+import { getColorObjectByAttributeValues } from '../../../components/colors';
const FORMAT_TYPE = 'core/text-color';
const REGEX_TO_MATCH = /^has-(.*)-color$/;
diff --git a/packages/block-editor/src/components/rich-text/native/index.js b/packages/block-editor/src/components/rich-text/native/index.js
new file mode 100644
index 0000000000000..2d1ec238274a0
--- /dev/null
+++ b/packages/block-editor/src/components/rich-text/native/index.js
@@ -0,0 +1 @@
+export default () => {};
diff --git a/packages/rich-text/src/component/index.native.js b/packages/block-editor/src/components/rich-text/native/index.native.js
similarity index 98%
rename from packages/rich-text/src/component/index.native.js
rename to packages/block-editor/src/components/rich-text/native/index.native.js
index 47b47d58c5988..2381b9809eca8 100644
--- a/packages/rich-text/src/component/index.native.js
+++ b/packages/block-editor/src/components/rich-text/native/index.native.js
@@ -29,23 +29,25 @@ import { BACKSPACE, DELETE, ENTER } from '@wordpress/keycodes';
import { isURL } from '@wordpress/url';
import { atSymbol, plus } from '@wordpress/icons';
import { __ } from '@wordpress/i18n';
+import {
+ applyFormat,
+ getActiveFormat,
+ getActiveFormats,
+ insert,
+ getTextContent,
+ isEmpty,
+ create,
+ toHTMLString,
+ isCollapsed,
+ remove,
+} from '@wordpress/rich-text';
/**
* Internal dependencies
*/
import { useFormatTypes } from './use-format-types';
import FormatEdit from './format-edit';
-import { applyFormat } from '../apply-format';
-import { getActiveFormat } from '../get-active-format';
-import { getActiveFormats } from '../get-active-formats';
-import { insert } from '../insert';
-import { getTextContent } from '../get-text-content';
-import { isEmpty } from '../is-empty';
-import { create } from '../create';
-import { toHTMLString } from '../to-html-string';
-import { isCollapsed } from '../is-collapsed';
-import { remove } from '../remove';
-import { getFormatColors } from '../get-format-colors';
+import { getFormatColors } from './get-format-colors';
import styles from './style.scss';
import ToolbarButtonWithOptions from './toolbar-button-with-options';
diff --git a/packages/rich-text/src/component/style.native.scss b/packages/block-editor/src/components/rich-text/native/style.native.scss
similarity index 100%
rename from packages/rich-text/src/component/style.native.scss
rename to packages/block-editor/src/components/rich-text/native/style.native.scss
diff --git a/packages/rich-text/src/test/__snapshots__/index.native.js.snap b/packages/block-editor/src/components/rich-text/native/test/__snapshots__/index.native.js.snap
similarity index 100%
rename from packages/rich-text/src/test/__snapshots__/index.native.js.snap
rename to packages/block-editor/src/components/rich-text/native/test/__snapshots__/index.native.js.snap
diff --git a/packages/rich-text/src/test/index.native.js b/packages/block-editor/src/components/rich-text/native/test/index.native.js
similarity index 98%
rename from packages/rich-text/src/test/index.native.js
rename to packages/block-editor/src/components/rich-text/native/test/index.native.js
index e0ce7ff78d6cc..64bfb3b183c6b 100644
--- a/packages/rich-text/src/test/index.native.js
+++ b/packages/block-editor/src/components/rich-text/native/test/index.native.js
@@ -8,7 +8,7 @@ import { getEditorHtml, render, initializeEditor } from 'test/helpers';
* WordPress dependencies
*/
import { select } from '@wordpress/data';
-import { store as blockEditorStore } from '@wordpress/block-editor';
+import { store as richTextStore } from '@wordpress/rich-text';
import { coreBlocks } from '@wordpress/block-library';
import {
getBlockTypes,
@@ -19,8 +19,8 @@ import {
/**
* Internal dependencies
*/
-import { store as richTextStore } from '../store';
-import RichText from '../component/index.native';
+import { store as blockEditorStore } from '../../../../store';
+import RichText from '../index.native';
/**
* Mock `useSelect` with various global application settings, e.g., styles.
diff --git a/packages/rich-text/src/test/performance/rich-text.native.js b/packages/block-editor/src/components/rich-text/native/test/performance/rich-text.native.js
similarity index 94%
rename from packages/rich-text/src/test/performance/rich-text.native.js
rename to packages/block-editor/src/components/rich-text/native/test/performance/rich-text.native.js
index aaaf42c90137f..7be9981d04bce 100644
--- a/packages/rich-text/src/test/performance/rich-text.native.js
+++ b/packages/block-editor/src/components/rich-text/native/test/performance/rich-text.native.js
@@ -11,7 +11,7 @@ import {
/**
* Internal dependencies
*/
-import RichText from '../../component/index.native';
+import RichText from '../../index.native';
describe( 'RichText Performance', () => {
const onCreateUndoLevel = jest.fn();
diff --git a/packages/rich-text/src/component/toolbar-button-with-options.native.js b/packages/block-editor/src/components/rich-text/native/toolbar-button-with-options.native.js
similarity index 100%
rename from packages/rich-text/src/component/toolbar-button-with-options.native.js
rename to packages/block-editor/src/components/rich-text/native/toolbar-button-with-options.native.js
diff --git a/packages/rich-text/src/component/use-format-types.js b/packages/block-editor/src/components/rich-text/native/use-format-types.js
similarity index 97%
rename from packages/rich-text/src/component/use-format-types.js
rename to packages/block-editor/src/components/rich-text/native/use-format-types.js
index 637be62b4361e..ff65d7421ae5c 100644
--- a/packages/rich-text/src/component/use-format-types.js
+++ b/packages/block-editor/src/components/rich-text/native/use-format-types.js
@@ -3,10 +3,7 @@
*/
import { useMemo } from '@wordpress/element';
import { useSelect, useDispatch } from '@wordpress/data';
-/**
- * Internal dependencies
- */
-import { store as richTextStore } from '../store';
+import { store as richTextStore } from '@wordpress/rich-text';
function formatTypesSelector( select ) {
return select( richTextStore ).getFormatTypes();
diff --git a/packages/block-editor/src/hooks/duotone.js b/packages/block-editor/src/hooks/duotone.js
index 5470ea5789f3a..5442e394e68c7 100644
--- a/packages/block-editor/src/hooks/duotone.js
+++ b/packages/block-editor/src/hooks/duotone.js
@@ -16,7 +16,6 @@ import {
import { createHigherOrderComponent, useInstanceId } from '@wordpress/compose';
import { addFilter } from '@wordpress/hooks';
import { useMemo, useEffect } from '@wordpress/element';
-import { useDispatch } from '@wordpress/data';
/**
* Internal dependencies
@@ -34,12 +33,10 @@ import {
} from '../components/duotone/utils';
import { getBlockCSSSelector } from '../components/global-styles/get-block-css-selector';
import { scopeSelector } from '../components/global-styles/utils';
-import { useBlockSettings } from './utils';
+import { useBlockSettings, useStyleOverride } from './utils';
import { default as StylesFiltersPanel } from '../components/global-styles/filters-panel';
import { useBlockEditingMode } from '../components/block-editing-mode';
import { __unstableUseBlockElement as useBlockElement } from '../components/block-list/use-block-props/use-block-refs';
-import { store as blockEditorStore } from '../store';
-import { unlock } from '../lock-unlock';
const EMPTY_ARRAY = [];
@@ -291,8 +288,27 @@ function DuotoneStyles( {
const isValidFilter = Array.isArray( colors ) || colors === 'unset';
- const { setStyleOverride, deleteStyleOverride } = unlock(
- useDispatch( blockEditorStore )
+ useStyleOverride(
+ isValidFilter
+ ? {
+ css:
+ colors !== 'unset'
+ ? getDuotoneStylesheet( selector, filterId )
+ : getDuotoneUnsetStylesheet( selector ),
+ __unstableType: 'presets',
+ }
+ : undefined
+ );
+ useStyleOverride(
+ isValidFilter
+ ? {
+ assets:
+ colors !== 'unset'
+ ? getDuotoneFilter( filterId, colors )
+ : '',
+ __unstableType: 'svgs',
+ }
+ : undefined
);
const blockElement = useBlockElement( clientId );
@@ -300,19 +316,6 @@ function DuotoneStyles( {
useEffect( () => {
if ( ! isValidFilter ) return;
- setStyleOverride( filterId, {
- css:
- colors !== 'unset'
- ? getDuotoneStylesheet( selector, filterId )
- : getDuotoneUnsetStylesheet( selector ),
- __unstableType: 'presets',
- } );
- setStyleOverride( `duotone-${ filterId }`, {
- assets:
- colors !== 'unset' ? getDuotoneFilter( filterId, colors ) : '',
- __unstableType: 'svgs',
- } );
-
// Safari does not always update the duotone filter when the duotone colors
// are changed. When using Safari, force the block element to be repainted by
// the browser to ensure any changes are reflected visually. This logic matches
@@ -329,20 +332,7 @@ function DuotoneStyles( {
blockElement.offsetHeight;
blockElement.style.display = display;
}
-
- return () => {
- deleteStyleOverride( filterId );
- deleteStyleOverride( `duotone-${ filterId }` );
- };
- }, [
- isValidFilter,
- blockElement,
- colors,
- selector,
- filterId,
- setStyleOverride,
- deleteStyleOverride,
- ] );
+ }, [ isValidFilter, blockElement ] );
return null;
}
diff --git a/packages/block-editor/src/hooks/layout.js b/packages/block-editor/src/hooks/layout.js
index e6113484f8a2c..f4730702e9adb 100644
--- a/packages/block-editor/src/hooks/layout.js
+++ b/packages/block-editor/src/hooks/layout.js
@@ -9,7 +9,7 @@ import classnames from 'classnames';
import { createHigherOrderComponent, useInstanceId } from '@wordpress/compose';
import { addFilter } from '@wordpress/hooks';
import { getBlockSupport, hasBlockSupport } from '@wordpress/blocks';
-import { useSelect, useDispatch } from '@wordpress/data';
+import { useSelect } from '@wordpress/data';
import {
Button,
ButtonGroup,
@@ -17,7 +17,6 @@ import {
PanelBody,
} from '@wordpress/components';
import { __ } from '@wordpress/i18n';
-import { useEffect } from '@wordpress/element';
/**
* Internal dependencies
@@ -29,8 +28,7 @@ import { getLayoutType, getLayoutTypes } from '../layouts';
import { useBlockEditingMode } from '../components/block-editing-mode';
import { LAYOUT_DEFINITIONS } from '../layouts/definitions';
import { kebabCase } from '../utils/object';
-import { useBlockSettings } from './utils';
-import { unlock } from '../lock-unlock';
+import { useBlockSettings, useStyleOverride } from './utils';
const layoutBlockSupportKey = 'layout';
@@ -381,17 +379,7 @@ function BlockWithLayoutStyles( { block: BlockListBlock, props } ) {
layoutClasses
);
- const { setStyleOverride, deleteStyleOverride } = unlock(
- useDispatch( blockEditorStore )
- );
-
- useEffect( () => {
- if ( ! css ) return;
- setStyleOverride( selector, { css } );
- return () => {
- deleteStyleOverride( selector );
- };
- }, [ selector, css, setStyleOverride, deleteStyleOverride ] );
+ useStyleOverride( { css } );
return (
{
- if ( ! css ) return;
- setStyleOverride( selector, { css } );
- return () => {
- deleteStyleOverride( selector );
- };
- }, [ selector, css, setStyleOverride, deleteStyleOverride ] );
+ useStyleOverride( { css } );
return ;
}
diff --git a/packages/block-editor/src/hooks/position.js b/packages/block-editor/src/hooks/position.js
index bdd8095e9b250..710dbfaf5ace0 100644
--- a/packages/block-editor/src/hooks/position.js
+++ b/packages/block-editor/src/hooks/position.js
@@ -14,22 +14,16 @@ import {
} from '@wordpress/components';
import { createHigherOrderComponent, useInstanceId } from '@wordpress/compose';
import { useSelect } from '@wordpress/data';
-import {
- useContext,
- useMemo,
- createPortal,
- Platform,
-} from '@wordpress/element';
+import { useMemo, Platform } from '@wordpress/element';
import { addFilter } from '@wordpress/hooks';
/**
* Internal dependencies
*/
-import BlockList from '../components/block-list';
import { useSettings } from '../components/use-settings';
import InspectorControls from '../components/inspector-controls';
import useBlockDisplayInformation from '../components/use-block-display-information';
-import { cleanEmptyObject } from './utils';
+import { cleanEmptyObject, useStyleOverride } from './utils';
import { unlock } from '../lock-unlock';
import { store as blockEditorStore } from '../store';
@@ -368,7 +362,6 @@ export const withPositionStyles = createHigherOrderComponent(
hasPositionBlockSupport && ! isPositionDisabled;
const id = useInstanceId( BlockListBlock );
- const element = useContext( BlockList.__unstableElementContext );
// Higher specificity to override defaults in editor UI.
const positionSelector = `.wp-container-${ id }.wp-container-${ id }`;
@@ -392,15 +385,9 @@ export const withPositionStyles = createHigherOrderComponent(
!! attributes?.style?.position?.type,
} );
- return (
- <>
- { allowPositionStyles &&
- element &&
- !! css &&
- createPortal( , element ) }
-
- >
- );
+ useStyleOverride( { css } );
+
+ return ;
},
'withPositionStyles'
);
diff --git a/packages/block-editor/src/hooks/style.js b/packages/block-editor/src/hooks/style.js
index 356042e3017ca..d74e10b0208f1 100644
--- a/packages/block-editor/src/hooks/style.js
+++ b/packages/block-editor/src/hooks/style.js
@@ -6,7 +6,7 @@ import classnames from 'classnames';
/**
* WordPress dependencies
*/
-import { useContext, useMemo, createPortal } from '@wordpress/element';
+import { useMemo } from '@wordpress/element';
import { addFilter } from '@wordpress/hooks';
import {
getBlockSupport,
@@ -19,7 +19,6 @@ import { getCSSRules, compileCSS } from '@wordpress/style-engine';
/**
* Internal dependencies
*/
-import BlockList from '../components/block-list';
import { BACKGROUND_SUPPORT_KEY, BackgroundImagePanel } from './background';
import { BORDER_SUPPORT_KEY, BorderPanel } from './border';
import { COLOR_SUPPORT_KEY, ColorEdit } from './color';
@@ -34,7 +33,7 @@ import {
DimensionsPanel,
} from './dimensions';
import useDisplayBlockControls from '../components/use-display-block-controls';
-import { shouldSkipSerialization } from './utils';
+import { shouldSkipSerialization, useStyleOverride } from './utils';
import { scopeSelector } from '../components/global-styles/utils';
import { useBlockEditingMode } from '../components/block-editing-mode';
@@ -484,33 +483,20 @@ const withElementsStyles = createHigherOrderComponent(
: undefined;
}, [ baseElementSelector, blockElementStyles, props.name ] );
- const element = useContext( BlockList.__unstableElementContext );
+ useStyleOverride( { css: styles } );
return (
- <>
- { styles &&
- element &&
- createPortal(
- ,
- element
- ) }
-
-
- >
+
);
},
'withElementsStyles'
diff --git a/packages/block-editor/src/hooks/utils.js b/packages/block-editor/src/hooks/utils.js
index e7c71ea5551f8..5985b821d00a8 100644
--- a/packages/block-editor/src/hooks/utils.js
+++ b/packages/block-editor/src/hooks/utils.js
@@ -2,7 +2,8 @@
* WordPress dependencies
*/
import { getBlockSupport } from '@wordpress/blocks';
-import { useMemo } from '@wordpress/element';
+import { useMemo, useEffect, useId } from '@wordpress/element';
+import { useDispatch } from '@wordpress/data';
/**
* Internal dependencies
@@ -10,6 +11,8 @@ import { useMemo } from '@wordpress/element';
import { useSettings } from '../components';
import { useSettingsForBlockElement } from '../components/global-styles/hooks';
import { getValueFromObjectPath, setImmutably } from '../utils/object';
+import { store as blockEditorStore } from '../store';
+import { unlock } from '../lock-unlock';
/**
* Removed falsy values from nested object.
@@ -115,6 +118,35 @@ export function shouldSkipSerialization( blockType, featureSet, feature ) {
return skipSerialization;
}
+export function useStyleOverride( { id, css, assets, __unstableType } = {} ) {
+ const { setStyleOverride, deleteStyleOverride } = unlock(
+ useDispatch( blockEditorStore )
+ );
+ const fallbackId = useId();
+ useEffect( () => {
+ // Unmount if there is CSS and assets are empty.
+ if ( ! css && ! assets ) return;
+ const _id = id || fallbackId;
+ setStyleOverride( _id, {
+ id,
+ css,
+ assets,
+ __unstableType,
+ } );
+ return () => {
+ deleteStyleOverride( _id );
+ };
+ }, [
+ id,
+ css,
+ assets,
+ __unstableType,
+ fallbackId,
+ setStyleOverride,
+ deleteStyleOverride,
+ ] );
+}
+
/**
* Based on the block and its context, returns an object of all the block settings.
* This object can be passed as a prop to all the Styles UI components
diff --git a/packages/block-editor/src/private-apis.js b/packages/block-editor/src/private-apis.js
index cf4e251874282..b1d499ae09946 100644
--- a/packages/block-editor/src/private-apis.js
+++ b/packages/block-editor/src/private-apis.js
@@ -12,7 +12,7 @@ import { PrivateListView } from './components/list-view';
import BlockInfo from './components/block-info-slot-fill';
import BlockContextualToolbar from './components/block-tools/block-contextual-toolbar';
import { useShouldContextualToolbarShow } from './utils/use-should-contextual-toolbar-show';
-import { cleanEmptyObject } from './hooks/utils';
+import { cleanEmptyObject, useStyleOverride } from './hooks/utils';
import BlockQuickNavigation from './components/block-quick-navigation';
import { LayoutStyle } from './components/block-list/layout';
import { BlockRemovalWarningModal } from './components/block-removal-warning-modal';
@@ -45,6 +45,7 @@ lock( privateApis, {
BlockContextualToolbar,
useShouldContextualToolbarShow,
cleanEmptyObject,
+ useStyleOverride,
BlockQuickNavigation,
LayoutStyle,
BlockRemovalWarningModal,
diff --git a/packages/block-library/src/gallery/gap-styles.js b/packages/block-library/src/gallery/gap-styles.js
index 7bd0932d62f5b..4c2002d4b7ddc 100644
--- a/packages/block-library/src/gallery/gap-styles.js
+++ b/packages/block-library/src/gallery/gap-styles.js
@@ -2,13 +2,18 @@
* WordPress dependencies
*/
import {
- BlockList,
__experimentalGetGapCSSValue as getGapCSSValue,
+ privateApis as blockEditorPrivateApis,
} from '@wordpress/block-editor';
-import { useContext, createPortal } from '@wordpress/element';
+
+/**
+ * Internal dependencies
+ */
+import { unlock } from '../lock-unlock';
+
+const { useStyleOverride } = unlock( blockEditorPrivateApis );
export default function GapStyles( { blockGap, clientId } ) {
- const styleElement = useContext( BlockList.__unstableElementContext );
// --gallery-block--gutter-size is deprecated. --wp--style--gallery-gap-default should be used by themes that want to set a default
// gap on the gallery.
const fallbackValue = `var( --wp--style--gallery-gap-default, var( --gallery-block--gutter-size, var( --wp--style--block-gap, 0.5em ) ) )`;
@@ -35,11 +40,7 @@ export default function GapStyles( { blockGap, clientId } ) {
gap: ${ gapValue }
}`;
- const GapStyle = () => {
- return ;
- };
+ useStyleOverride( { css: gap } );
- return gap && styleElement
- ? createPortal( , styleElement )
- : null;
+ return null;
}
diff --git a/packages/block-library/src/navigation-link/index.php b/packages/block-library/src/navigation-link/index.php
index 5333ab6ea3dc9..1165ce94b5921 100644
--- a/packages/block-library/src/navigation-link/index.php
+++ b/packages/block-library/src/navigation-link/index.php
@@ -322,6 +322,25 @@ function build_variation_for_navigation_link( $entity, $kind ) {
return $variation;
}
+/**
+ * Register a variation for a post type / taxonomy for the navigation link block
+ *
+ * @param array $variation Variation array from build_variation_for_navigation_link.
+ * @return void
+ */
+function register_block_core_navigation_link_variation( $variation ) {
+ // Directly set the variations on the registered block type
+ // because there's no server side registration for variations (see #47170).
+ $navigation_block_type = WP_Block_Type_Registry::get_instance()->get_registered( 'core/navigation-link' );
+ // If the block is not registered yet, bail early.
+ // Variation will be registered in register_block_core_navigation_link then.
+ if ( ! $navigation_block_type ) {
+ return;
+ }
+
+ $navigation_block_type->variations[] = $variation;
+}
+
/**
* Register the navigation link block.
*
@@ -329,6 +348,9 @@ function build_variation_for_navigation_link( $entity, $kind ) {
* @throws WP_Error An WP_Error exception parsing the block definition.
*/
function register_block_core_navigation_link() {
+ // This will only handle post types and taxonomies registered until this point (init on priority 9).
+ // See action hooks below for other post types and taxonomies.
+ // See https://github.com/WordPress/gutenberg/issues/53826 for details.
$post_types = get_post_types( array( 'show_in_nav_menus' => true ), 'objects' );
$taxonomies = get_taxonomies( array( 'show_in_nav_menus' => true ), 'objects' );
@@ -369,3 +391,38 @@ function register_block_core_navigation_link() {
);
}
add_action( 'init', 'register_block_core_navigation_link' );
+// Register actions for all post types and taxonomies, to add variations when they are registered.
+// All post types/taxonomies registered before register_block_core_navigation_link, will be handled by that function.
+add_action( 'registered_post_type', 'register_block_core_navigation_link_post_type_variation', 10, 2 );
+add_action( 'registered_taxonomy', 'register_block_core_navigation_link_taxonomy_variation', 10, 3 );
+
+/**
+ * Register custom post type variations for navigation link on post type registration
+ * Handles all post types registered after the block is registered in register_navigation_link_post_type_variations
+ *
+ * @param string $post_type The post type name passed from registered_post_type filter.
+ * @param WP_Post_Type $post_type_object The post type object passed from registered_post_type.
+ * @return void
+ */
+function register_block_core_navigation_link_post_type_variation( $post_type, $post_type_object ) {
+ if ( $post_type_object->show_in_nav_menus ) {
+ $variation = build_variation_for_navigation_link( $post_type_object, 'post-type' );
+ register_block_core_navigation_link_variation( $variation );
+ }
+}
+
+/**
+ * Register a custom taxonomy variation for navigation link on taxonomy registration
+ * Handles all taxonomies registered after the block is registered in register_navigation_link_post_type_variations
+ *
+ * @param string $taxonomy Taxonomy slug.
+ * @param array|string $object_type Object type or array of object types.
+ * @param array $args Array of taxonomy registration arguments.
+ * @return void
+ */
+function register_block_core_navigation_link_taxonomy_variation( $taxonomy, $object_type, $args ) {
+ if ( isset( $args['show_in_nav_menus'] ) && $args['show_in_nav_menus'] ) {
+ $variation = build_variation_for_navigation_link( (object) $args, 'post-type' );
+ register_block_core_navigation_link_variation( $variation );
+ }
+}
diff --git a/packages/block-library/src/quote/block.json b/packages/block-library/src/quote/block.json
index eff4649230a58..d0bb9005f8e63 100644
--- a/packages/block-library/src/quote/block.json
+++ b/packages/block-library/src/quote/block.json
@@ -54,6 +54,12 @@
"background": true,
"text": true
}
+ },
+ "layout": {
+ "allowEditing": false
+ },
+ "spacing": {
+ "blockGap": true
}
},
"styles": [
diff --git a/packages/block-library/src/quote/style.scss b/packages/block-library/src/quote/style.scss
index e453e407a0875..8688294bc5594 100644
--- a/packages/block-library/src/quote/style.scss
+++ b/packages/block-library/src/quote/style.scss
@@ -18,5 +18,9 @@
font-size: 1.125em;
text-align: right;
}
+
+ }
+ > cite {
+ display: block;
}
}
diff --git a/packages/block-library/src/read-more/style.scss b/packages/block-library/src/read-more/style.scss
index f0e0291eb6ffc..3a15de37adf68 100644
--- a/packages/block-library/src/read-more/style.scss
+++ b/packages/block-library/src/read-more/style.scss
@@ -1,7 +1,7 @@
.wp-block-read-more {
display: block;
width: fit-content;
- &:not([style*="text-decoration"]) {
+ &:where(:not([style*="text-decoration"])) {
text-decoration: none;
&:focus,
diff --git a/packages/e2e-test-utils-playwright/package.json b/packages/e2e-test-utils-playwright/package.json
index 775e1da771331..f8d9b7cd2f017 100644
--- a/packages/e2e-test-utils-playwright/package.json
+++ b/packages/e2e-test-utils-playwright/package.json
@@ -37,7 +37,8 @@
"form-data": "^4.0.0",
"get-port": "^5.1.1",
"lighthouse": "^10.4.0",
- "mime": "^3.0.0"
+ "mime": "^3.0.0",
+ "web-vitals": "^3.5.0"
},
"peerDependencies": {
"@playwright/test": ">=1"
diff --git a/packages/e2e-test-utils-playwright/src/metrics/index.ts b/packages/e2e-test-utils-playwright/src/metrics/index.ts
index 68343f6d7c482..fac05d9004bd8 100644
--- a/packages/e2e-test-utils-playwright/src/metrics/index.ts
+++ b/packages/e2e-test-utils-playwright/src/metrics/index.ts
@@ -2,6 +2,11 @@
* External dependencies
*/
import type { Page, Browser } from '@playwright/test';
+import { join } from 'path';
+// resolution-mode support in TypeScript 5.3 will resolve this.
+// See https://devblogs.microsoft.com/typescript/announcing-typescript-5-3-beta/
+// @ts-expect-error
+import type { Metric } from 'web-vitals';
type EventType =
| 'click'
@@ -32,11 +37,22 @@ type MetricsConstructorProps = {
page: Page;
};
+interface WebVitalsMeasurements {
+ CLS?: number;
+ FCP?: number;
+ FID?: number;
+ INP?: number;
+ LCP?: number;
+ TTFB?: number;
+}
+
export class Metrics {
browser: Browser;
page: Page;
trace: Trace;
+ webVitals: WebVitalsMeasurements = {};
+
constructor( { page }: MetricsConstructorProps ) {
this.page = page;
this.browser = page.context().browser()!;
@@ -273,4 +289,95 @@ export class Metrics {
)
.map( ( item ) => ( item.dur ? item.dur / 1000 : 0 ) );
}
+
+ /**
+ * Initializes the web-vitals library upon next page navigation.
+ *
+ * Defaults to automatically triggering the navigation,
+ * but it can also be done manually.
+ *
+ * @example
+ * ```js
+ * await metrics.initWebVitals();
+ * console.log( await metrics.getWebVitals() );
+ * ```
+ *
+ * @example
+ * ```js
+ * await metrics.initWebVitals( false );
+ * await page.goto( '/some-other-page' );
+ * console.log( await metrics.getWebVitals() );
+ * ```
+ *
+ * @param reload Whether to force navigation by reloading the current page.
+ */
+ async initWebVitals( reload = true ) {
+ await this.page.addInitScript( {
+ path: join(
+ __dirname,
+ '../../../../node_modules/web-vitals/dist/web-vitals.umd.cjs'
+ ),
+ } );
+
+ await this.page.exposeFunction(
+ '__reportVitals__',
+ ( data: string ) => {
+ const measurement: Metric = JSON.parse( data );
+ this.webVitals[ measurement.name ] = measurement.value;
+ }
+ );
+
+ await this.page.addInitScript( () => {
+ const reportVitals = ( measurement: unknown ) =>
+ window.__reportVitals__( JSON.stringify( measurement ) );
+
+ window.addEventListener( 'DOMContentLoaded', () => {
+ // @ts-ignore
+ window.webVitals.onCLS( reportVitals );
+ // @ts-ignore
+ window.webVitals.onFCP( reportVitals );
+ // @ts-ignore
+ window.webVitals.onFID( reportVitals );
+ // @ts-ignore
+ window.webVitals.onINP( reportVitals );
+ // @ts-ignore
+ window.webVitals.onLCP( reportVitals );
+ // @ts-ignore
+ window.webVitals.onTTFB( reportVitals );
+ } );
+ } );
+
+ if ( reload ) {
+ // By reloading the page the script will be applied.
+ await this.page.reload();
+ }
+ }
+
+ /**
+ * Returns web vitals as collected by the web-vitals library.
+ *
+ * If the web-vitals library hasn't been loaded on the current page yet,
+ * it will be initialized with a page reload.
+ *
+ * Reloads the page to force web-vitals to report all collected metrics.
+ *
+ * @return {WebVitalsMeasurements} Web vitals measurements.
+ */
+ async getWebVitals() {
+ // Reset values.
+ this.webVitals = {};
+
+ const hasScript = await this.page.evaluate(
+ () => typeof window.webVitals !== 'undefined'
+ );
+
+ if ( ! hasScript ) {
+ await this.initWebVitals();
+ }
+
+ // Trigger navigation so the web-vitals library reports values on unload.
+ await this.page.reload();
+
+ return this.webVitals;
+ }
}
diff --git a/packages/e2e-test-utils-playwright/src/page-utils/drag-files.ts b/packages/e2e-test-utils-playwright/src/page-utils/drag-files.ts
index fa43fc76d27c3..d0af0b499f286 100644
--- a/packages/e2e-test-utils-playwright/src/page-utils/drag-files.ts
+++ b/packages/e2e-test-utils-playwright/src/page-utils/drag-files.ts
@@ -9,7 +9,7 @@ import { getType } from 'mime';
* Internal dependencies
*/
import type { PageUtils } from './index';
-import type { ElementHandle, Locator } from '@playwright/test';
+import type { Locator } from '@playwright/test';
type FileObject = {
name: string;
@@ -118,25 +118,29 @@ async function dragFiles(
/**
* Drop the files at the current position.
- *
- * @param locator
*/
- drop: async ( locator: Locator | ElementHandle | null ) => {
- if ( ! locator ) {
- const topMostElement = await this.page.evaluateHandle(
- ( { x, y } ) => {
- return document.elementFromPoint( x, y );
- },
- position
- );
- locator = topMostElement.asElement();
- }
+ drop: async () => {
+ const topMostElement = await this.page.evaluateHandle(
+ ( { x, y } ) => {
+ const element = document.elementFromPoint( x, y );
+ if ( element instanceof HTMLIFrameElement ) {
+ const offsetBox = element.getBoundingClientRect();
+ return element.contentDocument!.elementFromPoint(
+ x - offsetBox.x,
+ y - offsetBox.y
+ );
+ }
+ return element;
+ },
+ position
+ );
+ const elementHandle = topMostElement.asElement();
- if ( ! locator ) {
+ if ( ! elementHandle ) {
throw new Error( 'Element not found.' );
}
- const dataTransfer = await locator.evaluateHandle(
+ const dataTransfer = await elementHandle.evaluateHandle(
async ( _node, _fileObjects ) => {
const dt = new DataTransfer();
const fileInstances = await Promise.all(
@@ -159,7 +163,7 @@ async function dragFiles(
fileObjects
);
- await locator.dispatchEvent( 'drop', { dataTransfer } );
+ await elementHandle.dispatchEvent( 'drop', { dataTransfer } );
await cdpSession.detach();
},
diff --git a/packages/e2e-test-utils-playwright/src/types.ts b/packages/e2e-test-utils-playwright/src/types.ts
index 62e38033b73e8..3dd6b0683aa33 100644
--- a/packages/e2e-test-utils-playwright/src/types.ts
+++ b/packages/e2e-test-utils-playwright/src/types.ts
@@ -1,29 +1,12 @@
+/**
+ * External dependencies
+ */
declare global {
interface Window {
// Silence the warning for `window.wp` in Playwright's evaluate functions.
wp: any;
- }
-
- // Experimental API that is subject to change.
- // See https://developer.mozilla.org/en-US/docs/Web/API/LayoutShiftAttribution
- interface LayoutShiftAttribution {
- readonly node: Node;
- readonly previousRect: DOMRectReadOnly;
- readonly currentRect: DOMRectReadOnly;
- readonly toJSON: () => string;
- }
-
- // Experimental API that is subject to change.
- // See https://developer.mozilla.org/en-US/docs/Web/API/LayoutShift
- interface LayoutShift extends PerformanceEntry {
- readonly duration: number;
- readonly entryType: 'layout-shift';
- readonly name: 'layout-shift';
- readonly startTime: DOMHighResTimeStamp;
- readonly value: number;
- readonly hadRecentInput: boolean;
- readonly lastInputTime: DOMHighResTimeStamp;
- readonly sources: LayoutShiftAttribution[];
+ // Helper function added by Metrics fixture for web-vitals.js.
+ __reportVitals__: ( data: string ) => void;
}
}
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
deleted file mode 100644
index 5c6867e75ad7a..0000000000000
--- a/packages/e2e-tests/specs/editor/plugins/__snapshots__/cpt-locking.test.js.snap
+++ /dev/null
@@ -1,147 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`cpt locking template_lock all should insert line breaks when using enter and shift-enter 1`] = `
-"
-
-
-
-
-First line
Second line
Third line
-
-
-
-
-
-
-
-
-
-
-"
-`;
-
-exports[`cpt locking template_lock all should not error when deleting the cotents of a paragraph 1`] = `
-"
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-"
-`;
-
-exports[`cpt locking template_lock all unlocked group should allow blocks to be moved 1`] = `
-"
-
-"
-`;
-
-exports[`cpt locking template_lock all unlocked group should allow blocks to be removed 1`] = `
-"
-
-"
-`;
-
-exports[`cpt locking template_lock false should allow blocks to be inserted 1`] = `
-"
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-"
-`;
-
-exports[`cpt locking template_lock false should allow blocks to be moved 1`] = `
-"
-p1
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-"
-`;
-
-exports[`cpt locking template_lock false should allow blocks to be removed 1`] = `
-"
-
-
-
-
-
-
-
-
-
-
-
-"
-`;
-
-exports[`cpt locking template_lock insert should allow blocks to be moved 1`] = `
-"
-p1
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-"
-`;
diff --git a/packages/e2e-tests/specs/editor/plugins/cpt-locking.test.js b/packages/e2e-tests/specs/editor/plugins/cpt-locking.test.js
deleted file mode 100644
index 447be0793fafb..0000000000000
--- a/packages/e2e-tests/specs/editor/plugins/cpt-locking.test.js
+++ /dev/null
@@ -1,251 +0,0 @@
-/**
- * WordPress dependencies
- */
-import {
- activatePlugin,
- clickBlockToolbarButton,
- clickMenuItem,
- createNewPost,
- deactivatePlugin,
- getEditedPostContent,
- insertBlock,
- pressKeyTimes,
- pressKeyWithModifier,
- setPostContent,
- canvas,
-} from '@wordpress/e2e-test-utils';
-
-describe( 'cpt locking', () => {
- beforeAll( async () => {
- await activatePlugin( 'gutenberg-test-plugin-cpt-locking' );
- } );
-
- afterAll( async () => {
- await deactivatePlugin( 'gutenberg-test-plugin-cpt-locking' );
- } );
-
- const shouldDisableTheInserter = async () => {
- expect(
- await page.evaluate( () => {
- const inserter = document.querySelector(
- '.edit-post-header [aria-label="Add block"], .edit-post-header [aria-label="Toggle block inserter"]'
- );
- return inserter.getAttribute( 'disabled' );
- } )
- ).not.toBeNull();
- };
-
- const shouldNotAllowBlocksToBeRemoved = async () => {
- await canvas().type(
- '.block-editor-rich-text__editable[data-type="core/paragraph"]',
- 'p1'
- );
- await clickBlockToolbarButton( 'Options' );
- expect(
- await page.$x( '//button/span[contains(text(), "Delete")]' )
- ).toHaveLength( 0 );
- };
-
- const shouldAllowBlocksToBeMoved = async () => {
- await canvas().click(
- 'div > .block-editor-rich-text__editable[data-type="core/paragraph"]'
- );
- expect( await page.$( 'button[aria-label="Move up"]' ) ).not.toBeNull();
- await page.click( 'button[aria-label="Move up"]' );
- await canvas().type(
- 'div > .block-editor-rich-text__editable[data-type="core/paragraph"]',
- 'p1'
- );
- expect( await getEditedPostContent() ).toMatchSnapshot();
- };
-
- describe( 'template_lock all', () => {
- beforeEach( async () => {
- await createNewPost( { postType: 'locked-all-post' } );
- } );
-
- it( 'should disable the inserter', shouldDisableTheInserter );
-
- it(
- 'should not allow blocks to be removed',
- shouldNotAllowBlocksToBeRemoved
- );
-
- it( 'should not allow blocks to be moved', async () => {
- await canvas().click(
- '.block-editor-rich-text__editable[data-type="core/paragraph"]'
- );
- expect( await page.$( 'button[aria-label="Move up"]' ) ).toBeNull();
- } );
-
- it( 'should not error when deleting the cotents of a paragraph', async () => {
- await canvas().click(
- '.block-editor-block-list__block[data-type="core/paragraph"]'
- );
- const textToType = 'Paragraph';
- await page.keyboard.type( 'Paragraph' );
- await pressKeyTimes( 'Backspace', textToType.length + 1 );
- expect( await getEditedPostContent() ).toMatchSnapshot();
- } );
-
- it( 'should insert line breaks when using enter and shift-enter', async () => {
- await canvas().click(
- '.block-editor-block-list__block[data-type="core/paragraph"]'
- );
- await page.keyboard.type( 'First line' );
- await pressKeyTimes( 'Enter', 1 );
- await page.keyboard.type( 'Second line' );
- await pressKeyWithModifier( 'shift', 'Enter' );
- await page.keyboard.type( 'Third line' );
- expect( await getEditedPostContent() ).toMatchSnapshot();
- } );
-
- it( 'should show invalid template notice if the blocks do not match the templte', async () => {
- const content = await getEditedPostContent();
- const [ , contentWithoutImage ] =
- content.split( '' );
- await setPostContent( contentWithoutImage );
- const noticeContent = await page.waitForSelector(
- '.editor-template-validation-notice .components-notice__content'
- );
- expect(
- await page.evaluate(
- ( _noticeContent ) => _noticeContent.firstChild.nodeValue,
- noticeContent
- )
- ).toEqual(
- 'The content of your post doesn’t match the template assigned to your post type.'
- );
- } );
-
- it( 'should not allow blocks to be inserted in inner blocks', async () => {
- await canvas().click(
- 'button[aria-label="Two columns; equal split"]'
- );
- await page.evaluate(
- () => new Promise( window.requestIdleCallback )
- );
- expect(
- await canvas().$(
- '.wp-block-column .block-editor-button-block-appender'
- )
- ).toBeNull();
-
- expect(
- await page.evaluate( () => {
- const inserter = document.querySelector(
- '.edit-post-header [aria-label="Add block"], .edit-post-header [aria-label="Toggle block inserter"]'
- );
- return inserter.getAttribute( 'disabled' );
- } )
- ).not.toBeNull();
- } );
- } );
-
- describe( 'template_lock insert', () => {
- beforeEach( async () => {
- await createNewPost( { postType: 'locked-insert-post' } );
- } );
-
- it( 'should disable the inserter', shouldDisableTheInserter );
-
- it(
- 'should not allow blocks to be removed',
- shouldNotAllowBlocksToBeRemoved
- );
-
- it( 'should allow blocks to be moved', shouldAllowBlocksToBeMoved );
- } );
-
- describe( 'template_lock false', () => {
- beforeEach( async () => {
- await createNewPost( { postType: 'not-locked-post' } );
- } );
-
- it( 'should allow blocks to be inserted', async () => {
- expect(
- // "Add block" selector is required to make sure performance comparison
- // doesn't fail on older branches where we still had "Add block" as label.
- await page.$(
- '.edit-post-header [aria-label="Add block"], .edit-post-header [aria-label="Toggle block inserter"]'
- )
- ).not.toBeNull();
- await insertBlock( 'List' );
- await page.keyboard.type( 'List content' );
- expect( await getEditedPostContent() ).toMatchSnapshot();
- } );
-
- it( 'should allow blocks to be removed', async () => {
- await canvas().type(
- '.block-editor-rich-text__editable[data-type="core/paragraph"]',
- 'p1'
- );
- await clickBlockToolbarButton( 'Options' );
- await clickMenuItem( 'Delete' );
- expect( await getEditedPostContent() ).toMatchSnapshot();
- } );
-
- it( 'should allow blocks to be moved', shouldAllowBlocksToBeMoved );
- } );
-
- describe( 'template_lock all unlocked group', () => {
- beforeEach( async () => {
- await createNewPost( {
- postType: 'l-post-ul-group',
- } );
- } );
-
- it( 'should allow blocks to be removed', async () => {
- await canvas().type(
- 'div > .block-editor-rich-text__editable[data-type="core/paragraph"]',
- 'p1'
- );
- await clickBlockToolbarButton( 'Options' );
- await clickMenuItem( 'Delete' );
-
- expect( await getEditedPostContent() ).toMatchSnapshot();
- } );
-
- it( 'should allow blocks to be moved', shouldAllowBlocksToBeMoved );
- } );
-
- describe( 'template_lock all locked group', () => {
- beforeEach( async () => {
- await createNewPost( {
- postType: 'l-post-l-group',
- } );
- } );
-
- it(
- 'should not allow blocks to be removed',
- shouldNotAllowBlocksToBeRemoved
- );
-
- it( 'should not allow blocks to be moved', async () => {
- await canvas().click(
- '.block-editor-rich-text__editable[data-type="core/paragraph"]'
- );
- expect( await page.$( 'button[aria-label="Move up"]' ) ).toBeNull();
- } );
- } );
-
- describe( 'template_lock all inherited group', () => {
- beforeEach( async () => {
- await createNewPost( {
- postType: 'l-post-i-group',
- } );
- } );
-
- it(
- 'should not allow blocks to be removed',
- shouldNotAllowBlocksToBeRemoved
- );
-
- it( 'should not allow blocks to be moved', async () => {
- await canvas().click(
- '.block-editor-rich-text__editable[data-type="core/paragraph"]'
- );
- expect( await page.$( 'button[aria-label="Move up"]' ) ).toBeNull();
- } );
- } );
-} );
diff --git a/packages/edit-post/src/components/visual-editor/style.scss b/packages/edit-post/src/components/visual-editor/style.scss
index fa61cc9889cf9..237bbf25f2c79 100644
--- a/packages/edit-post/src/components/visual-editor/style.scss
+++ b/packages/edit-post/src/components/visual-editor/style.scss
@@ -21,7 +21,6 @@
font-size: $default-font-size;
padding: 6px 12px;
- &.is-tertiary,
&.has-icon {
padding: 6px;
}
diff --git a/packages/edit-site/src/components/dataviews/in-filter.js b/packages/edit-site/src/components/dataviews/in-filter.js
index 06e6360e9d5a8..9642169335aaf 100644
--- a/packages/edit-site/src/components/dataviews/in-filter.js
+++ b/packages/edit-site/src/components/dataviews/in-filter.js
@@ -5,6 +5,7 @@ import {
__experimentalInputControlPrefixWrapper as InputControlPrefixWrapper,
SelectControl,
} from '@wordpress/components';
+import { __, sprintf } from '@wordpress/i18n';
export const OPERATOR_IN = 'in';
@@ -30,7 +31,11 @@ export default ( { filter, view, onChangeView } ) => {
htmlFor={ id }
className="dataviews__select-control-prefix"
>
- { filter.name + ':' }
+ { sprintf(
+ /* translators: filter name. */
+ __( '%s:' ),
+ filter.name
+ ) }
}
options={ filter.elements }
diff --git a/packages/edit-site/src/components/page-patterns/patterns-list.js b/packages/edit-site/src/components/page-patterns/patterns-list.js
index bbcb7e7910211..eb56fdded9060 100644
--- a/packages/edit-site/src/components/page-patterns/patterns-list.js
+++ b/packages/edit-site/src/components/page-patterns/patterns-list.js
@@ -12,7 +12,7 @@ import {
__experimentalHeading as Heading,
__experimentalText as Text,
} from '@wordpress/components';
-import { __, isRTL } from '@wordpress/i18n';
+import { __, _x, isRTL } from '@wordpress/i18n';
import { chevronLeft, chevronRight } from '@wordpress/icons';
import { privateApis as routerPrivateApis } from '@wordpress/router';
import { useAsyncList, useViewportMatch } from '@wordpress/compose';
@@ -33,9 +33,15 @@ import Pagination from './pagination';
const { useLocation, useHistory } = unlock( routerPrivateApis );
const SYNC_FILTERS = {
- all: __( 'All' ),
- [ PATTERN_SYNC_TYPES.full ]: __( 'Synced' ),
- [ PATTERN_SYNC_TYPES.unsynced ]: __( 'Not synced' ),
+ all: _x( 'All', 'Option that shows all patterns' ),
+ [ PATTERN_SYNC_TYPES.full ]: _x(
+ 'Synced',
+ 'Option that shows all synchronized patterns'
+ ),
+ [ PATTERN_SYNC_TYPES.unsynced ]: _x(
+ 'Not synced',
+ 'Option that shows all patterns that are not synchronized'
+ ),
};
const SYNC_DESCRIPTIONS = {
diff --git a/packages/edit-site/src/components/sidebar-edit-mode/page-panels/reset-default-template.js b/packages/edit-site/src/components/sidebar-edit-mode/page-panels/reset-default-template.js
index 95fd2a4814d82..0f29292274546 100644
--- a/packages/edit-site/src/components/sidebar-edit-mode/page-panels/reset-default-template.js
+++ b/packages/edit-site/src/components/sidebar-edit-mode/page-panels/reset-default-template.js
@@ -3,7 +3,8 @@
*/
import { MenuGroup, MenuItem } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
-import { useEntityRecord } from '@wordpress/core-data';
+import { useDispatch } from '@wordpress/data';
+import { store as coreStore } from '@wordpress/core-data';
/**
* Internal dependencies
@@ -18,7 +19,7 @@ export default function ResetDefaultTemplate( { onClick } ) {
const currentTemplateSlug = useCurrentTemplateSlug();
const isPostsPage = useIsPostsPage();
const { postType, postId } = useEditedPostContext();
- const entity = useEntityRecord( 'postType', postType, postId );
+ const { editEntityRecord } = useDispatch( coreStore );
// The default template in a post is indicated by an empty string.
if ( ! currentTemplateSlug || isPostsPage ) {
return null;
@@ -27,7 +28,13 @@ export default function ResetDefaultTemplate( { onClick } ) {